Skip to content

セマンティック検索結果にスニペット(本文抜粋)が含まれない #179

@Kewton

Description

@Kewton

概要

search --semantic の結果にスニペット(本文抜粋)が含まれず、見出しのみが返る。セマンティック検索で判断に関連する文書が見つかっても、中身が空では判断を取り出せない。

再現手順

commandindexdev search --semantic "認証の仕組み" --format llm

Note: commandindexdev は開発用バイナリ名。commandindex でも同様に再現可能。

実際の結果

<!-- estimated tokens: ~0 -->
## dev-reports/feature/1/technical-spec.md
### 認証フロー

## data/logs/claude/mycodebranchdesk-feature-331-worktree-2026-02-21.md
### 認証あり時: {"authEnabled":true}

見出しのみ。本文スニペットなし。estimated tokens: ~0

期待される結果

<!-- estimated tokens: ~500 -->
## dev-reports/feature/1/technical-spec.md
### 認証フロー

トークンベースの認証を採用。サーバー起動時にランダムトークンを生成し、
環境変数CM_AUTH_TOKENで外部から指定も可能。Edge Runtimeのmiddleware.tsで
全APIリクエストを検証する。

## dev-reports/design/issue-96-npm-cli-design-policy.md
### 認証トークンのセキュリティ

CLI側ではCM_AUTH_TOKEN環境変数からトークンを取得し、HTTPヘッダーに付与。
平文保存のリスクはあるが、ローカルネットワーク前提のため許容。

根本原因分析

enrich_with_metadata() (search.rs) はtantivyからbody本文を取得し SemanticSearchResult.body に格納しているが、以下の2つの原因でスニペットが表示されない:

  1. heading不一致によるfallbackパス: embedding側の section_heading とtantivy側のheadingが一致しない場合、body: String::new() のfallback結果が使われる(空文字)
  2. SnippetConfig/LlmFormatOptionsが未接続: run_semantic_search()SnippetConfig / LlmFormatOptions パラメータが渡されておらず、format関数に伝播されない

format別の現状

format 現状の挙動 問題点
human truncate_body(&..., 2, 120) ハードコード SnippetConfig未参照
llm write_body() でbody全文出力 bodyが空の場合はestimated tokens: ~0、LlmFormatOptions未適用
json bodyフィールドをそのまま出力 bodyが空の場合は空文字

影響範囲

変更対象ファイル

ファイル 変更内容
src/main.rs run_semantic_search()呼び出し時にsnippet_config/llm_optionsを渡す
src/cli/search.rs run_semantic_search()シグネチャ変更、enrich_with_metadata()のfallback改善
src/output/mod.rs format_semantic_results()シグネチャにSnippetConfig/LlmFormatOptions追加
src/output/human.rs format_semantic_human()にSnippetConfig追加、ハードコード値を置換
src/output/llm.rs format_semantic_llm()にLlmFormatOptions追加、max_body_linesトランケーション適用
src/output/json.rs bodyフィールドが正しく出力されることを確認
tests/output_format.rs format_semantic_results()の新シグネチャに合わせたテスト更新

影響なし

  • CLIレベルの破壊的変更なし(既存オプションがセマンティック検索でも有効になるだけ)
  • 外部クレートの追加不要
  • BM25検索・ハイブリッド検索のコードパスに変更なし
  • パフォーマンスへの影響なし(truncate_bodyは軽量な文字列操作のみ)

影響(ユーザー視点)

#168(issue/before-changeへのスニペット付与)と類似の症状。ただし技術的アプローチは異なる:

セマンティック検索は「キーワードを知らなくても意味で判断を引ける」唯一の手段だが、見つかった結果に中身がなければ判断を取り出せない。

対象バリュー

  • 判断再利用: スニペットがあれば、検索結果だけで「過去にどう判断したか」が読める
  • 文脈先回り: AIエージェントがsemanticで見つけた文書をfile.readせずに判断理由を把握できる

改善案

  1. run_semantic_search() にSnippetConfig/LlmFormatOptionsを追加: main.rsからsemantic分岐で既存のsnippet_options/llm_optionsを渡す
  2. format_semantic_results() シグネチャ変更: SnippetConfigとLlmFormatOptionsを受け取り、各フォーマッタに伝播
  3. format関数の修正:
    • format_semantic_human(): SnippetConfigを引数追加、ハードコード値(2行/120文字)を置換
    • format_semantic_llm(): LlmFormatOptionsを追加しmax_body_linesによるtruncate_body_for_llm適用
    • format_semantic_json(): bodyフィールドはそのまま(全文出力)
  4. heading不一致時のfallbackパス改善: sections.first() が存在すればそのbodyを使用
  5. --snippet-lines/--snippet-charsオプションをセマンティック検索でも有効にする

受け入れ基準

  1. search --semantic <query> --format llm でヒットセクションのbody本文が出力され、estimated tokensが0でないこと
  2. search --semantic <query> --format human でSnippetConfigに従った行数・文字数でスニペットが表示されること
  3. search --semantic <query> --format json でbodyフィールドに本文が含まれること
  4. --snippet-lines / --snippet-chars オプションがセマンティック検索でも機能すること
  5. 既存のBM25検索・ハイブリッド検索の動作に影響がないこと

テスト方針

  • format_semantic_human() にSnippetConfigを渡した場合のユニットテスト
  • format_semantic_llm() でbody付きSemanticSearchResultが正しく出力されるユニットテスト
  • format_semantic_llm() でLlmFormatOptions.max_body_linesが適用されるテスト
  • enrich_with_metadata のheading不一致fallbackテスト
  • 既存テスト(tests/output_format.rs)の新シグネチャ対応
  • BM25/ハイブリッド検索の既存テストが全パスすること

テスト環境

  • commandindex 0.1.0
  • embeddingモデル: qllama/bge-m3:q8_0
  • CommandMateリポジトリ(2910ファイル、77832セクションembedding済み)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions