二つの数字が、私をこのベンチマークに引き戻した。tree-sitter で作ったコードグラフを MCP で提供する codebase-memory-mcp の README は、トークンを 99% 減らせると約束している。私自身の以前の実行では、それを包む薄いラッパーとして私が書いた cbm-lean によって、コーディングエージェントは素の grep より haiku で 26%、sonnet で 18% 安くなった。一つ目の数字は一度も再現できず、二つ目は私の設定の欠陥から出たものだった。これは、その欠陥を直して公開リポジトリでやり直した実行だ。
設定
各質問は三つの条件で実行した。
- cbm-lean と、システムプロンプト内の振り分けルール。構造に関する質問はグラフへ、正確な文字列は Grep へ。
- 素の codebase-memory-mcp サーバーと、同じルール。
- Read、Grep、Glob のみ。
各リポジトリに十二の質問を用意した。六つは構造に関するもの。ある関数を誰が呼ぶか、その関数が何を呼ぶか、HTTP エンドポイントからデータベースへの書き込みまでの呼び出しチェーン、モジュールの構成。残りの六つは正確さを問うもの。設定値、環境変数が読まれる場所、エラーメッセージ、クラスが定義されている行。各質問には正解に含まれるべき部分文字列の一覧があり、私はそのすべてをコードと手作業で照合した。
リポジトリは、小さな三つ(FastAPI のフルスタックテンプレート、ltx-2-mlx、avoid-ai-writing。グラフのノード数は 1.6k〜5k)と、ノード数 25k の Next.js モノレポである dub。Haiku 4.5 はすべてで、sonnet 5 は dub で実行した。合計 252 回、API 価格で $14.69。
コストは tokenEquiv で測る。入力トークン、プラス 1.25 × キャッシュ書き込み、プラス 0.1 × キャッシュ読み込み。プロンプトキャッシュが有効だと、課金された入力をそのまま見ると誤解を招く。たまたま温まったキャッシュで走った条件が、その条件とは関係のない理由で安く見えてしまうからだ。
結果
実行あたりの tokenEquiv の中央値。括弧内は正解数。
| 条件 | 実行回数 | cbm-lean | 素のグラフ | grep |
|---|---|---|---|---|
| 小さなリポジトリ、haiku 4.5 | 108 | 30,321 (28/36) | 43,846 (32/36) | 31,159 (33/36) |
| dub、haiku 4.5 | 72 | 18,791 (22/24) | 20,687 (22/24) | 14,955 (20/24) |
| dub、sonnet 5 | 72 | 17,227 (22/24) | 23,085 (22/24) | 13,348 (22/24) |
三つの結果が揺るがない。
- トークン数では、グラフは一度も勝たなかった。grep と比べて、小さなリポジトリ(p = 0.13)でも、haiku での dub(p = 0.18)でも測定可能な差はなく、sonnet での dub では 12 問中 11 問で grep のほうが安かった(p = 0.034)。正確さは同じで、どの条件も 24 問中 22 問だった。
- ラッパーは、すべての設定で素のサーバーに勝った(p ≤ 0.021)。素のサーバーは検索ヒット一件あたり約 1.3 KB を返し、その大半は識別子のダンプとハッシュベクトルだ。cbm-lean はヒットを約 250 バイトまで削る。
- グラフがはっきり勝った唯一の点は、大きなリポジトリでの小さなモデルの正確さだった。dub の構造に関する質問に、haiku はグラフありで 12 回中 12 回、grep では 12 回中 8 回正解した。トークンの中央値は 23.5k 対 16.0k だった。
p 値は、質問ごとの中央値に対するウィルコクソンの符号付き順位検定によるものだ。
それぞれの条件がどこで間違えたか
- グラフから取った行番号。エラーメッセージの行を問われたとき、グラフ条件はグラフの出力から答えを読み取り、login.py:33 と答えた。raise は 34 行目にあり、33 はその上の if だ。グラフ自身が 15〜88 行目に保存しているクラスについて config.py:74 と答え、59 行目で定義されたクラスについて client.ts:129 と答えた。振り分けルールは、正確さを問う質問では grep を使うよう指示していたが、haiku はいつも従うわけではなかった。
- 重複した名前。FastAPI テンプレートには create_user という名前のハンドラが二つある。管理者用エンドポイントを問われると、グラフを使う二つの条件はどちらも、6 回中 5 回で認証なしのほうをたどった。grep は毎回、スーパーユーザーのチェックの奥にあるほうを見つけた。
- 早すぎる打ち切り。dub で、grep を使う haiku は、呼び出しチェーンの末尾にある実際のデータベース書き込みを二度見落とした。
- 私自身の採点。sonnet のすべての実行が、どの条件でも、dub のアーキテクチャの答えから packages/tinybird を外していた。このフォルダには Tinybird のデータファイルがあり、package.json はない。だから、正しかったのはおそらくモデルのほうで、私のチェックが間違っていた。sonnet の六つの取りこぼしはすべてこの一問で、これを除いてもどの比較も変わらない。
グラフをつなぐことと、使うことは違う
最も役に立った結果は、本番の実行ではなく、パイロットから出た。Claude Code 2.1.283 はデフォルトで、MCP ツールのスキーマを ToolSearch という手順の後ろに遅延させる。モデルにはツール名が見えるが、何かを呼ぶ前にスキーマを取得しなければならない。ヘッドレスの haiku はそれをしなかった。六回のパイロット実行で、振り分けルールの有無にかかわらず、グラフは一度も呼ばれなかった。小さなリポジトリの十二問での結果は次のとおり。
| 設定 | グラフを呼んだ | 正解 | tokenEquiv の中央値 |
|---|---|---|---|
| デフォルトの読み込み、ルールあり | 12 問中 2 問 | 12 問中 11 問 | 41,008 |
| スキーマ読み込み済み、ルールなし | 12 問中 4 問 | 12 問中 9 問 | 62,008 |
| スキーマ読み込み済み、ルールあり | 36 問中 22 問 | 36 問中 28 問 | 30,321 |
スキーマを読み込んだ状態(ENABLE_TOOL_SEARCH=false)では、同じ呼び出し元の質問が、三ターンのうちに二回のグラフ呼び出しを経た。ルールなしでグラフをつないだ設定は、すべての中で最も高くついた。どのセッションもスキーマの分を払うのに、モデルはほとんどそれを使わないままだ。
MCP サーバーを公開していて、デフォルト設定でテストしているなら、まずモデルがそもそもそれを呼んでいるかを確かめてほしい。
以前の数字が間違っていた理由
最初のベンチマークは、私のプロジェクトフォルダの中で動いていた。そこの CLAUDE.md は、構造に関する質問にはグラフを使うようエージェントに指示していた。Claude Code は親ディレクトリの CLAUDE.md ファイルも読み込むので、その指示は、グラフのない条件も含めてすべての条件に届いていた。対照群に処置群の指示が与えられていたわけで、つまりその比較は、ツールと同じくらいルールを測っていた。
やり直しの実行では、上に CLAUDE.md があるツリーの外にリポジトリを置き、ユーザー設定とフックを読み込まず(--setting-sources project)、ルールはグラフを持つ条件にだけ与える。ほかの対照策も、それぞれ以前のラウンドで出た間違った結論を正したものだ。Bash と Agent はすべての条件で禁止している。範囲を限った Bash の許可はパイプでつないだコマンドに一致せず、サブエージェントはトークンを消費しながらターンを隠すからだ。条件の順番は繰り返しごとに入れ替えるので、いつも温まったキャッシュで走る条件はない。自分の条件にないツールを呼んだ実行は破棄する。そして答えは採点する。間違った答えで節約したトークンは、節約ではないからだ。
グラフをエージェントにつなごうとしているチームに伝えたいこと
- ノード数 25k までのリポジトリで Claude のモデルを使うなら、コードグラフによるトークン節約は期待しないこと。まず自分たちの質問で測ってほしい。
- それでもつなぐなら、出力を削り、スキーマを読み込み、エージェントに振り分けルールを与えること。後の二つがなければ、グラフはどのプロンプトでもただの重荷だ。
- モデルがグラフの結果から読み取った行番号は疑い、grep で確かめること。
- グラフが役に立つのは、小さなモデルが大きなコードベースで構造に関する質問、つまりアーキテクチャや呼び出しチェーンに答えるときだ。
限界
モデルは二つ、リポジトリは四つでノード数は 25k まで、繰り返しは二回か三回、質問はそれぞれ十二。Opus はなし、長い対話セッションもなし、25k ノードを超えるものもなし。同じ質問でも繰り返しごとの実行結果が大きくばらついたので、中央値を報告している。
再現
すべて code-graph-vs-grep にある。固定したコミット付きの質問、ベンチマークのドライバ、すべての答えを含む生の実行記録、そして cbm-lean 本体だ。
bash bench/setup.sh
node bench/run.mjs --reps 3
python3 bench/stats.py results/*.json