GrooveSeek

Semantic search over a Markdown knowledge base, served over MCP.

View the Project on GitHub alphabet-h/grooveseek

2. クエリを token 単位の OR phrase にコンパイルする

背景と問題

kb-mcp のハイブリッド検索は 2 つの検索器を Reciprocal Rank Fusion で融合する — 埋め込みに対する sqlite-vec の KNN と、trigram tokenizer の上に載る SQLite FTS5 である。 v0.16.0 より前、FTS 側はユーザのクエリを単一の quoted phrase で包んだものを受け取って いた。trigram tokenizer の下では quoted phrase は連続部分文字列の照合なので、この構成は クエリ全体を逐語で探していたことになる。

キーワードならこれで一応動く。しかし文では何にも当たらない — 質問文がそのままの形で 書かれている文書は存在しないからである。dogfood のナレッジベース (650 文書 / 9,419 chunk) で実測したところ、自然文の golden query 10 件は全件が FTS 候補 0 件、main golden 26 件 のうち融合すべき入力が 2 つ揃っていたのは 16 件だけだった。残りではハイブリッドは ハイブリッドではない — RRF の入力は片方だけで、システムは「ハイブリッド」を名乗りながら ベクトル単独検索として動いていた。

この欠陥は外からは見えない。エラーにならず、候補数はユーザに出ず、結果は返る — 片方の 検索器から出た、質の落ちた結果が。15 リリース生き延びたうえ、テストは構造的にこれを 検出できなかった。すべての融合テストが「FTS で当たる chunk」をクエリ埋め込みと同じ位置に 置いていたため、ベクトル半身だけで assert が満たされていたからである。

本決定が答えるのは、「部分文字列でしか照合できない tokenizer」と「単語が空白で区切られない 日本語中心のコーパス」という前提の下で、クエリをどう FTS5 の MATCH 式に変換するか、である。

決定要因

検討した選択肢

  1. 現状維持 — クエリ 1 本を逐語 phrase にする
  2. 形態素解析 — 辞書付き tokenizer (lindera / vibrato) で日本語を分割し、形態素ごとに phrase を出す
  3. 字種境界で分割 — まず separator で切り、次に漢字 / ひらがな / カタカナ / その他の 語構成文字の遷移で切る
  4. クエリを quote せずに FTS5 へ渡し、FTS5 自身の式パーサに解釈させる

決定

採用: 3 — 字種境界で分割。分割の利得の大半を、そのコストを払わずに得られること、 そしてクエリ文字列の純粋関数であり網羅的にテストできることが理由である。

再ランキングの評価について"再ランキング" OR "ランキング" OR "の評価" OR "について" にコンパイルされる。 日本語では字種の遷移が語境界の粗いが実効的な代理になる — 複合語は漢字の連なり、 外来語はカタカナの連なり、文法的な助詞はひらがな、という対応があるためである。

dogfood コーパスで、同一の scratch copy を使って前後を実測した (bge-m3、reranker なし):

  before after
融合の入力が 2 つ揃う golden query 16/26 26/26
MRR (main / binary golden) 0.955 / 0.939 0.962 / 0.955
recall@10 0.954 0.965
recall@5 0.926 0.906

他を採らなかった理由:

インタフェースの変更

2 つが、ユーザおよび保存データとの契約になった:

帰結

確認方法

参考