技術 約6分で読めます

SembleをAstroブログ(1595 MD + 152 ソース)で試したらhybridがコードを見失った

いけさん目次

MinishLab/semble を M1 Mac のこのブログ(Astro、記事 1595 mdとソース 152ファイル)で実機検証した。
ウォーム状態の BM25 クエリは 0.84 秒で getPublishedArticlesfeaturedArticles の定義を最上位に返した。
一方で hybrid モードは seasonalBanner のような短いシンボルを見失い、CLIPの記事やC#の Union 型解説を上位に出した。
記事コーパスが大きいリポジトリだと、コード検索とドキュメント検索で挙動が大きく変わる。

grepの代わりにチャンクを返す

Sembleは、コードを構文に沿ってチャンク化し、BM25と静的エンベディングを組み合わせて検索する。
エンベディングには minishlab/potion-code-16M を使う。
処理はすべてCPU上で完結する。静的エンベディングとBM25の結果をRRFで統合し、その後にコード向けのリランクをかける。クエリのたびに大きなTransformerモデルを動かす設計にはなっていない。

リランクでは、自然文っぽいクエリとシンボルっぽいクエリで重みを変える。
getUserById_private のようなクエリでは字面の一致を強め、How is authentication handled? のようなクエリでは意味検索とのバランスを取る。
定義箇所を参照箇所より上げる、同じファイルに複数hitがある場合にファイル単位で持ち上げる、testやlegacyや.d.tsを下げる、という調整も入っている。

ここは Chroma Context-1の記事 で見た検索エージェントとは違う。
Context-1はモデル自身が検索、読み込み、削除を繰り返す。
Sembleはそこまでエージェントループを持たず、最初の検索結果を小さくして、モデルに渡るコード量を減らす。

READMEのベンチはgrepにかなり厳しい

READMEは 19言語・63リポジトリ・約1,250クエリで作ったベンチを公開している。
クエリはsemantic、architecture、symbolの3カテゴリに分かれる。
全体のNDCG@10はSembleが0.854、CodeRankEmbed Hybridが0.862。
137MパラメータのCodeRankEmbed Hybridに対して品質は99%、インデックス構築は218倍速く、検索は11倍速いと書いている。

トークン効率の表に、Sembleの狙いがそのまま出ている。
「最初の関連hitまでに消費したtokens、見つからなければ32k」として平均すると、ripgrep + read fileは45,692トークン、Sembleは566トークン。
これが98%削減の根拠になっている。

方法期待トークン/クエリ備考
ripgrep + read file45,692baseline
Semble56698%減

ただし、この比較は「grepのあとに一致ファイルを丸ごと読む」エージェント挙動をモデル化したものだ。
人間が rg -nsed -nrg -C 3 を組み合わせて必要な範囲だけ読む場合とは違う。
Sembleが置き換える対象はgrepそのものというより、エージェントがgrep結果から雑に全文読み込みへ進む癖だ。

正解データの生成と検証にClaude Sonnet 4.6を使っているので、LLM-as-judgeのラベルである点には注意がいる。
codanna は19言語中6言語に対応していないため除外、claude-context は有料OpenAI APIキーとvector DBが必要なため除外されている。
「ローカルCPUだけで、APIキーなし」が要件になると、選べる候補は一気に減る。Sembleはその数少ない選択肢のひとつだ。

1595 mdのAstroブログで実機検証

uvx --from "semble[mcp]" semble で動かした。
対象は素のこのリポジトリで、内訳はマークダウン記事 1595、.astro 109、.ts 43、.js 89。
記事ディレクトリだけで 9MB あるので、コード検索ツールに対しては「ドキュメントコーパスが圧倒的に多い」異常な構成になる。

最初のクエリはインデックス構築込みで 33.73 秒(user 15.42 秒)。
2回目以降のウォーム状態でのクエリは次のとおり。

クエリモード正解結果ウォーム時間
seasonalBannerhybridsrc/lib/seasonal-banner.ts外し(CLIP記事/C# Union記事)4.99s
seasonalBannerbm25同上最上位で正解(score 13.8)0.87s
getPublishedArticleshybridsrc/lib/articles.ts最上位で正解
getPublishedArticlesbm25同上2位(1位は呼び出し側rss.xml.ts)0.84s
featuredArticlesbm25src/lib/featured.ts最上位(score 11.8)0.84s
「kindle book hero image rule」hybridCLAUDE.mdのルールkindle記事は出るがCLAUDE.mdは出ず4.80s
「タグ スラッシュ ビルドエラー」hybridCLAUDE.mdの該当節完全に外す4.94s
「specialbanner」bm25CLAUDE.md + seasonal-banner.ts両方hit

シンボル検索の不思議な挙動は、ここで一番はっきり出た。
seasonalBannersrc/lib/seasonal-banner.ts:10export const seasonalBanner = { として存在し、src/pages/index.astro でも参照されている。
それでも hybrid は CLIP/bge-m3 の記事や C#15 の Union 型解説を上位に置いた。
セマンティック側が「seasonal」「banner」という英単語に近い文章を大量に拾い、コードファイルを押し下げたと読める。

bm25 モードに切り替えると 0.87 秒で src/lib/seasonal-banner.ts:1-16 をスコア 13.8 で最上位に返した。
逆に getPublishedArticles のように一意性が高い識別子は hybrid でも定義位置を最上位に置けた。
Sembleの「定義箇所を参照箇所より上げる」リランクは、ここでは働いていた。

自然文クエリで CLAUDE.md を引き当てさせるのは思ったより難しかった。
「タグ スラッシュ ビルドエラー」は CLAUDE.md にそのまま「スラッシュ(/)禁止。ビルドエラーになる」と書いてあるのに、上位には Gradio や Astro 6 のビルド系記事が来てしまう。
CLAUDE.md自体はインデックスされている(specialbanner でちゃんと出る)ので、取り出し側ではなく順位付け側の問題だ。
記事 1595 mdというコーパス密度の前では、単一ファイルに散らばったガードレールが埋もれやすい。

semble savings のログでは 11 クエリで約 128.8k トークン、92% 節約と出た。
ただしこれは Semble が「同じヒットを得るのに該当ファイルを全文読んだら何トークンか」を引き算する自前計測なので、実際のエージェント運用での節約量より楽観的に出る。

MCPだけではサブエージェントに届かない

SembleはMCPサーバとして使える。
Claude Codeなら claude mcp add、Codexなら ~/.codex/config.tomluvx --from "semble[mcp]" semble を登録する。
MCPツールは searchfind_related の2つで、ローカルパスだけでなくgit URLも渡せる。

READMEはbash統合をかなり大きく扱っている。
Claude CodeやCodex CLIのサブエージェントでは、MCPスキーマがトップレベル側で遅延ロードされる作りになっており、サブエージェントからMCPツールを直接呼べない。
そのため AGENTS.mdCLAUDE.mdsemble searchsemble find-related の使い方を書き、bashツールとして呼ばせる導線が用意されている。

CTXでClaude Codeに動くメモリを足す記事 と並べると役割の違いが見える。
CTXはUserPromptSubmit hookで、git log、コード、Markdown、過去チャットをプロンプト直前に差し込む側だ。
Sembleはエージェントが探索中に呼ぶ検索コマンドで、コードを読みに行くファイル数を絞る側にある。

Sembleの位置

トークン削減系の道具にはいくつか層がある。
Compresr Context Gateway はLLM APIの前にプロキシを置いて会話履歴やツール出力を圧縮する。
YourMemoryOCR-Memory は長期履歴の取り出し方を扱う。
Sembleはその手前で、エージェントが grep してからファイルを読みに行く入り口を絞る。

自分のリポでは -m bm25 を default にしてシンボル検索はこれで通し、自然文で記事や仕様を探すときだけ hybrid を明示で呼ぶ、というところから始める。
1595 mdが乗っているうちは、hybrid を default にするとシンボル検索が壊れる。

参考