Semantic search over a Markdown knowledge base, served over MCP.
kb-mcp eval は golden query セットに対して retrieval を測る。測定対象の
ナレッジベースが、その持ち主がノートを書く場所でもある場合 — 個人やチームの KB
では普通のこと — golden query の文面を逐語で引用したノートは、その query に対する
最強の一致になる。上位を占め、ラベル付けされた正解を押し下げる。
参照コーパスで実際に観測された: ある query の文面を引用した当日書かれたノートが 1 位を取り、expected の文書を 4 位まで落とした。集計 recall は保たれたので何も 失敗せず、原因が分かったのは人がたまたま per-query の行を読んでいたからである。 評価について書くほど評価は通りにくくなるのに、それを知らせるものが無い。
厄介なのは、この観測には原因が 2 つあり、見分けがつかないことだ。query を逐語で
含む文書は、テストについて書いたノートか、そうでなければ query の出典である。
後者ならその文書はその query の expected に入るべきで、間違っているのは golden
ファイルの方になる。区別できるのは golden を書いた本人だけである。
eval の exit code には既に意味がある (--fail-on-regression)。何を報告するに
せよ、それと exit code を奪い合ってはならない。eval が 1 run で既に払っているものの範囲に収める。各案を参照コーパス (662 文書 / 26 golden query、既知の混入は上記のノート 1 件のみ) で測った。指標は「健全なコーパスで何件発火するか」である。
expected でない hit を報告する。top_k の hit の本文が query 文面を
逐語で含んでいたら報告する。案 4。
案 1 は引くべき閾値が存在しない。上位 hit はどれも query と高類似であり — それが retrieval というものである — 条件は「検索結果を報告する」にほぼ等しい。
案 3 は 8 件を検出し、全部が偽陽性だった。理由は調整の問題ではなく構造的である:
golden query はしばしばトピック名そのもの (cross-encoder / torch.compile /
Qwen3.5-Omni) であり、トピック名はそのトピックを解説する文書に逐語で出てくる。
逐語一致 1 件は「そのトピックについての文書」の形である。
案 2 は 0 件だった。top_k に絞っても偽陽性は減らない (構造上それらは上位
hit である) 一方、規則だけが弱くなる。同じコーパスでの実測では、唯一実際に混入
していたノートは、引用している 2 つの query のうち片方の top_k には入り、
もう片方では 1〜10 位すべてが単一文書の chunk で埋まっていて入らなかった。
案 4 は ちょうど 1 件を検出し、それは実際に golden セットを記述していたノート だった — golden query の設計を説明しながら query 文字列をバッククォートで引用して いる文書である。複数の golden query を引用するのは「テストについてのノート」の 形であり、案 3 を使い物にならなくした母集団ときれいに分離できる。
所見は示唆する原因ではなく測った事実で名付け (golden-queries-quoted)、
メッセージには 2 つの原因を両方書く。これは kb-mcp doctor と同じ規律である:
観測を報告し、直し方を挙げ、何も変更しない。
eval run は索引済み chunk 本文を 1 パス走査する。検索と同じ read
スナップショットの中で走るので、指標を出したのと同じ index について報告する。
約 9.4k chunk の 1 パスは、run が既に行っている embedding と検索に比べれば無視できる。--format json には findings として出る。
exit code は変えない。引用が混入なのかラベル漏れなのかは kb-mcp が決められる
ことではないので、ビルドを落とす根拠にはならない。findings は JSON に常に存在し、何も無ければ空配列になる。消費側が
「検査して 0 件」と「検査以前の出力」を区別できるようにするため。rank_in_top_k
を key 不在ではなく null にするのも同じ理由 — top_k に居ないことは
欠測ではなく測った結果である。EvalRun に載るので run 履歴にも書かれる。そして ConfigFingerprint
の外に意図的に置く。記録済み corpus と同じ理由で、fingerprint に入るものは
変化したときに diff を無効化するため、何かを報告した run だけが baseline を
失うことになってしまう。expected に挙がっている文書は、その query については数えない。
それが正解だからその文面を含んでいる。免除は entry が heading を指していても
文書全体に及ぶ。ラベル付けされた章だけに狭めると、「そのトピックについての
文書の別の章にトピック名が出ている」を数え始めることになり — それは 1 件規則を
使い物にならなくした母集団そのものである — さらに、所見も 2 件閾値も文書単位
なのに免除だけ章単位になって規則の粒度が揃わなくなる。代償は、同じ query に
答える文書の別の章にある本物の引用が報告されないこと。cross-encoder reranking と
how does cross-encoder reranking work?)、長い方を 1 回引用しただけで両方に
一致するので、一致した needle をそのまま数えると 1 回の引用が 2 件閾値を
満たしてしまう。同じ文書の他の一致の部分文字列になっている一致は落とす。
出現位置を持たない以上、「短い方が別の場所で本当に引用されていた」場合も
一緒に落ちるが、少なく数える側に倒すのは 2 件閾値そのものと同じ取引である。