SembleをAstroブログ(1595 MD + 152 ソース)で試したらhybridがコードを見失った
目次
MinishLab/semble を M1 Mac のこのブログ(Astro、記事 1595 mdとソース 152ファイル)で実機検証した。
ウォーム状態の BM25 クエリは 0.84 秒で getPublishedArticles や featuredArticles の定義を最上位に返した。
一方で 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 file | 45,692 | baseline |
| Semble | 566 | 98%減 |
ただし、この比較は「grepのあとに一致ファイルを丸ごと読む」エージェント挙動をモデル化したものだ。
人間が rg -n、sed -n、rg -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回目以降のウォーム状態でのクエリは次のとおり。
| クエリ | モード | 正解 | 結果 | ウォーム時間 |
|---|---|---|---|---|
seasonalBanner | hybrid | src/lib/seasonal-banner.ts | 外し(CLIP記事/C# Union記事) | 4.99s |
seasonalBanner | bm25 | 同上 | 最上位で正解(score 13.8) | 0.87s |
getPublishedArticles | hybrid | src/lib/articles.ts | 最上位で正解 | – |
getPublishedArticles | bm25 | 同上 | 2位(1位は呼び出し側rss.xml.ts) | 0.84s |
featuredArticles | bm25 | src/lib/featured.ts | 最上位(score 11.8) | 0.84s |
| 「kindle book hero image rule」 | hybrid | CLAUDE.mdのルール | kindle記事は出るがCLAUDE.mdは出ず | 4.80s |
| 「タグ スラッシュ ビルドエラー」 | hybrid | CLAUDE.mdの該当節 | 完全に外す | 4.94s |
| 「specialbanner」 | bm25 | CLAUDE.md + seasonal-banner.ts | 両方hit | – |
シンボル検索の不思議な挙動は、ここで一番はっきり出た。
seasonalBanner は src/lib/seasonal-banner.ts:10 に export 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.toml に uvx --from "semble[mcp]" semble を登録する。
MCPツールは search と find_related の2つで、ローカルパスだけでなくgit URLも渡せる。
READMEはbash統合をかなり大きく扱っている。
Claude CodeやCodex CLIのサブエージェントでは、MCPスキーマがトップレベル側で遅延ロードされる作りになっており、サブエージェントからMCPツールを直接呼べない。
そのため AGENTS.md や CLAUDE.md に semble search と semble find-related の使い方を書き、bashツールとして呼ばせる導線が用意されている。
CTXでClaude Codeに動くメモリを足す記事 と並べると役割の違いが見える。
CTXはUserPromptSubmit hookで、git log、コード、Markdown、過去チャットをプロンプト直前に差し込む側だ。
Sembleはエージェントが探索中に呼ぶ検索コマンドで、コードを読みに行くファイル数を絞る側にある。
Sembleの位置
トークン削減系の道具にはいくつか層がある。
Compresr Context Gateway はLLM APIの前にプロキシを置いて会話履歴やツール出力を圧縮する。
YourMemory や OCR-Memory は長期履歴の取り出し方を扱う。
Sembleはその手前で、エージェントが grep してからファイルを読みに行く入り口を絞る。
自分のリポでは -m bm25 を default にしてシンボル検索はこれで通し、自然文で記事や仕様を探すときだけ hybrid を明示で呼ぶ、というところから始める。
1595 mdが乗っているうちは、hybrid を default にするとシンボル検索が壊れる。