GrooveSeek

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

View the Project on GitHub alphabet-h/grooveseek

11. ハイブリッド検索の両脚から語を除外する

背景と問題

ADR-0002 は、ハイブリッド の全文検索側がクエリを token 単位の OR phrase にコンパイルするようにし、 recall を広げた。しかしこれには、呼び出し側が特定の語を除外して絞り込む手段が 残っていない。path_globstags_any / tags_all はドキュメントのメタ データにしか効かず、クエリ構文からは chunk の本文に一切届かない。SQLite FTS5 には NOT 演算子があるが、ハイブリッドのベクトル側は語について何も知らない — 全文検索側だけで除外しても、Reciprocal Rank Fusion がベクトル側から同じ chunk を再び拾い上げてしまい、「除外した」と報告しながら実際には除外できて いない検索になる。

本決定が答えるのは、「片方しか語を理解しない 2 つの検索器のハイブリッド」で 語の除外をどう両方に届けるか、そして除外だけで探すものが何も残らないクエリを どう扱うか、である。

決定要因

検討した選択肢

  1. 全文検索側だけで NOT 負の phrase を FTS5 式に組み込み、ベクトル側は 触らない
  2. 両脚への hard filter。 FTS5 式は (positives) NOT (negatives) を持ち、 ベクトル側は「負の式単独で FTS5 がマッチする chunk id 集合」に入る候補を 落とす
  3. soft demotion。 融合後、除外語を含む chunk を落とさず順位だけ下げる

決定

採用: 2 — 両脚への hard filter。判定は両脚が共有する 1 回の FTS5 評価に 委ねる。

選択肢 1 は ADR-0002 が生んだ欠陥を鏡写しに再生産する — 「除外した」と報告する ハイブリッド検索が、融合が汲み上げてくる先では実際には除外していない。選択肢 3 は呼び出し側に説明できる契約にならない — 「除外の効果を確認したい」に対して 「たいてい消えている」は答えにならない。選択肢 2 のコストは、検索 1 回あたり 負の式の rowid だけの走査 1 回で、ranking も LIMIT も無い: 5,000 chunk の 全行にマッチする負の式 (= 除外語が全行にある最悪ケース) に対し 934.5µs (best of 5) と実測した。同じ検索に伴う ranking 付き FTS query の 3.5855ms に 対する比であり (measured: cargo test -p grooveseek --release --lib the_exclusion_id_scan_stays_cheaper_than_the_ranked_fts_query -- --ignored --nocapture)、 併走する query のコストの 4 分の 1 強にとどまる。

インタフェースの変更

帰結

確認方法

参考