BM25でCodexのトークン消費を30%削減する — 7,000ファイル規模で実証したコード検索RAG実践
BM25でCodexのトークン消費を30%削減する — 7,000ファイル規模で実証したコード検索RAG実践
AIコーディングエージェントの月次請求額を見て青ざめた経験はないでしょうか。原因の多くは「エージェントがリポジトリを探し回ること」そのものにあります。本記事では、40年前から使われている古典的アルゴリズムBM25を検索前段に挟むだけで、Codexのトークン消費が**1,812,066 → 1,283,250トークン(29.2%減)**まで落ちた実測結果と、その再現方法を段階的に解説します。
なぜCodexはトークンを浪費するのか
AIエージェントの「逐次ファイル探索」という構造的問題
Codexをはじめとする多くのAIコーディングエージェントは、コードを探す際に以下のループを繰り返します。
ls → grep → read → ls → grep → read → ...
1ファイルを読むたびにコンテキストウィンドウへトークンが積み上がります。リポジトリが小さければ問題になりませんが、ファイル数が増えるにつれてこの消費量は非線形に膨らんでいきます。7,536ファイル・68.8MBのリポジトリで1タスクあたり181万トークンという数字は、その典型例です。動作時間の中央値は50.8秒。遅さ自体もコストです。
発想の転換 — 「探索」を「検索」に置き換える
解決策はシンプルです。エージェントに自力で探させるのではなく、最初から絞り込んだ候補を渡すことです。本記事の結論を先に示しておきましょう。
- トークン消費:29.2%削減(181万 → 128万)
- 動作時間:41%短縮(50.8秒 → 30.0秒)
- 回答品質:同等以上を維持
この結果を実現したのがBM25によるコード検索RAGパイプラインです。
BM25とは何か — TF-IDFの発展形を30秒で理解する
スコアリング式と2つのパラメータ
BM25(Best Match 25)はTF-IDFの発展版である確率的検索モデルです。スコアリング式は以下のとおりです。
score(D, Q) = Σ IDF(qᵢ) · [f(qᵢ, D) · (k₁ + 1)] / [f(qᵢ, D) + k₁ · (1 - b + b · |D| / avgdl)]
ポイントは2つのパラメータです。
k₁(単語頻度の飽和):同じ単語が100回出現しても、スコアは100倍にはなりません。頻度が増えるほど効きが弱まり、上限値に収束します。b(文書長の正規化):長いファイルが単純に有利にならないよう、平均文書長(avgdl)を基準に正規化します。
TF-IDFとの違い
| 特性 | TF-IDF | BM25 |
|---|---|---|
| 単語頻度の扱い | 線形に加算 | 飽和して逓減(k₁) |
| 文書長の正規化 | 弱い | bパラメータで制御 |
| コード検索適性 | 普通 | 高い(識別子の反復に強い) |
なぜコード検索と相性が良いのか
コードには2つの特徴があります。第一に、userIdやauthenticateといった同一の識別子が何十回も登場します。飽和効果により、頻出語が過剰にスコアを押し上げる問題を自動で抑制できます。第二に、数行のユーティリティ関数から数百行のクラス定義まで、ファイル長のばらつきが極めて大きい。長さの正規化が効果的に機能する理由です。
実測結果 — 181万トークンが128万トークンになった
実験環境とインデックス構築コスト
実験は以下の環境で行われました。
- リポジトリ規模:7,536ファイル / 68.8MB
- チャンク分割:15,000断片(1ファイルあたり平均約2断片)
- インデックス:SQLite FTS5 / 164MB / 構築時間27秒
「重い仕組みではない」という点を最初に確認しておくことが重要です。外部サービス不要、構築27秒ならCIで毎回作り直しても十分許容範囲内です。
トークン削減率と速度の改善
| 指標 | 通常探索 | BM25探索 | 改善率 |
|---|---|---|---|
| トークン消費 | 1,812,066 | 1,283,250 | -29.2% |
| 動作時間(中央値) | 50.8秒 | 30.0秒 | -41.0% |
難度別に見た削減率 — 難しいタスクでも効くのか
「簡単なタスクだから減っただけ」という疑問に対して、実測データが答えを出しています。
| タスク難度 | トークン削減率 |
|---|---|
| 低難度 | 39.8% |
| 中難度 | 31.9% |
| 高難度 | 28.5% |
高難度タスクでも28.5%削減できています。難しいタスクほどエージェントが多くのファイルを読み込もうとする傾向がありますが、BM25が候補を絞ることで「意味のある探索」だけに集中させられることが確認できます。
回答品質は落ちなかったのか
12問の難度別テストで、BM25探索は通常探索と同等以上の品質を維持しました。削減されたトークンの正体は「無駄なファイル読み込み」であって、回答に必要な情報ではありません。
コード検索に効くBM25の実装テクニック
識別子の分割 — キャメルケース・スネークケースを構成語へ
最も重要な前処理です。コード特有の命名規則に対応しないと、検索クエリと一生マッチしません。
import re
def split_identifier(token: str) -> list[str]:
"""キャメルケース・スネークケース・ケバブケースを分割する"""
# スネークケース・ケバブケース
parts = re.split(r'[_\-]', token)
result = []
for part in parts:
# キャメルケースをさらに分割
words = re.sub(r'([a-z])([A-Z])', r'\1 \2', part).split()
result.extend([w.lower() for w in words if w])
return result
# 使用例
print(split_identifier("getUserProfile"))
# → ['get', 'user', 'profile']
print(split_identifier("auth_token_expired"))
# → ['auth', 'token', 'expired']getUserProfileを分割せずにインデックスしていると、「user profile」というクエリでは永遠にヒットしません。
ファイル名・ディレクトリ名への重み付け
パス情報は強力なシグナルです。auth/配下を探すときやUserRepository.tsというファイルを探すとき、ファイル名・ディレクトリ名に高いウェイトを与えることで精度が大きく向上します。
def build_document(file_path: str, content: str) -> dict:
"""検索用ドキュメントを構築する"""
path_parts = file_path.replace('/', ' ').replace('.', ' ')
identifiers = extract_identifiers(content)
return {
# ファイルパスは3倍のウェイト
"searchable_text": f"{path_parts} {path_parts} {path_parts} {identifiers} {content}",
"file_path": file_path,
"content": content,
}SQLite FTS5という現実的な選択肢
外部依存なし、追加サービスなし。SQLite FTS5はPythonの標準ライブラリだけで完結します。
import sqlite3
def build_index(documents: list[dict], db_path: str = "code_index.db"):
"""BM25インデックスをSQLite FTS5で構築する"""
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE VIRTUAL TABLE IF NOT EXISTS code_search
USING fts5(
file_path,
searchable_text,
content_snippet,
tokenize='unicode61'
)
""")
conn.executemany(
"INSERT INTO code_search VALUES (?, ?, ?)",
[(doc["file_path"], doc["searchable_text"], doc["content"][:500])
for doc in documents]
)
conn.commit()
return conn
def search(conn: sqlite3.Connection, query: str, top_k: int = 10) -> list[dict]:
"""BM25スコアで上位k件を返す"""
# FTS5はBM25をネイティブサポート
results = conn.execute("""
SELECT file_path, content_snippet,
rank -- BM25スコア(負数、小さいほど良い)
FROM code_search
WHERE searchable_text MATCH ?
ORDER BY rank
LIMIT ?
""", (query, top_k)).fetchall()
return [{"path": r[0], "snippet": r[1], "score": r[2]} for r in results]7,536ファイル・15,000断片のインデックスが27秒で構築できます。
最新研究「BM25 Wins at Scale」が裏付けたこと
大規模コーパスでBM25がエージェント探索を20ポイント逆転
2026年7月に発表された論文「BM25 Wins at Scale: A Scaling Study of RAG Paradigms」(arxiv:2607.26497)が、この知見を理論的に裏付けました。コーパスが約1,000万トークンを超えるとBM25がエージェント探索を20ポイント超で逆転するという衝撃的な結果です。
| 手法 | 最大コーパス精度 | クエリあたりトークン |
|---|---|---|
| ファイルシステムエージェント | 30.7% | 895,000 |
| Agent + BM25 | 69.4% | 101,000(-89%) |
| DenseRAG(ベクトル検索) | 29.9% | 効率的だが精度が低い |
精度で約2.3倍、トークン効率で約9倍という圧倒的な差です。
ベクトル検索が大規模コードで伸び悩む理由
興味深いのはDenseRAG(ベクトル検索)の精度が29.9%と、エージェント探索の30.7%とほぼ同等にとどまっている点です。コードには「意味の近さ」より「完全一致」が重要な要素が多く存在します。
- 関数名:
getUserByIdとfetchUserByIdentifierは意味は近いが別物 - エラーコード:
ECONNREFUSEDはベクトル空間で「似たコード」と混同される - バージョン文字列:
>=3.11.0を意味的に検索しても意味がない
BM25はこれらすべてで正確にマッチします。
2026年のRAGパイプライン設計にどう組み込むか
業界標準になったハイブリッド検索の構成
現時点でのベストプラクティスは、BM25とベクトル検索を組み合わせるハイブリッド構成です。
クエリ
├─ BM25(キーワード検索)─┐
│ ├─ RRF(Reciprocal Rank Fusion)
└─ ベクトル検索 ──────────┘
│
クロスエンコーダ再ランク
│
LLM
各段の役割は明確です。BM25が完全一致・識別子マッチを担い、ベクトル検索が意味的な関連性をカバー、RRFが両者のスコアを統合、クロスエンコーダが最終的な関連度を精緻化します。
BM25だけで十分なケース・ベクトルを足すべきケース
| ユースケース | 推奨構成 |
|---|---|
| コードベース検索(識別子・関数名中心) | BM25のみで十分 |
| コード + 仕様書・設計ドキュメント混在 | ハイブリッド推奨 |
| 自然言語コメント・README中心 | ベクトル検索優位 |
グラフRAGを選ばなかった理由
グラフRAG(HippoRAG2・LightRAGなど)は理論的に魅力的ですが、実用上の障壁があります。HippoRAG2でインデックス構築に3日、LightRAGで4年相当のトークンコストという報告があります。「効果は認めるが運用に乗らない」というのが現実的な判断です。まずBM25で30%削減を確実に取り、グラフRAGは将来の選択肢として残しておく戦略が堅実です。
段階的な導入ステップ
- トークン消費の計測:現状の消費量をベースラインとして記録する
- インデックス構築:SQLite FTS5で15分以内に完成
- 識別子分割トークナイザの実装:キャメルケース・スネークケースへの対応
- エージェントへの検索ツール登録:MCPサーバまたはカスタムツールとして提供
- ビフォーアフター計測:削減率を定量評価して投資対効果を確認
よくある疑問(FAQ)
Codex標準の検索機能とは何が違う?
Codexの標準検索はシンプルなキーワードマッチです。文書長の正規化も頻度の飽和処理もないため、長いファイルや頻出語を含むファイルが不当に上位に来やすく、候補の絞り込み精度が低くなります。BM25はこの2点を数学的に補正しています。
導入・運用コストはどれくらい?
インデックスサイズ164MB、構築時間27秒、外部サービス不要。月次トークン費用の29.2%削減と比較すれば、導入コストは初日で回収できる計算になります。
Claude CodeやCursorでも同じことができる?
できます。MCPサーバとして検索エンドポイントを実装し、エージェントのツールリストに登録する形が一般的です。BM25はエージェント非依存の汎用テクニックであるため、どのAIコーディングツールにも適用できます。
インデックスの更新はどうする?
差分が小さい場合は変更ファイルのみを再インデックスする差分更新が効率的です。ただし構築が27秒で完了するため、git hookやCIパイプライン上でフル再構築するシンプルな運用も十分現実的です。
まとめ — 古典的アルゴリズムがAIエージェント時代に効く
本記事の要点を3点にまとめます。
- BM25の前段配置でCodexのトークンを29.2%削減、41%高速化 — 1,812,066 → 1,283,250トークン、50.8秒 → 30.0秒
- 品質は維持され、高難度タスクでも28.5%削減 — 削減されるのは無駄な探索であって、必要な情報ではない
- SQLiteで完結する軽量実装、大規模リポジトリほど効果大 — 論文「BM25 Wins at Scale」が1,000万トークン超での逆転を実証
今すぐ試せる第一歩は、自分のリポジトリで現在のトークン消費を計測することです。数値が出れば、BM25導入後の改善率が定量的に見えてきます。次に識別子分割の有無でA/B比較を行い、検索精度への影響を確認してください。
40年前のアルゴリズムが、AIエージェント時代のコスト問題を解決する。技術の歴史は面白いものです。
参考リンク
関連記事
Webサービスのシャットダウン実装完全ガイド|新規停止から410 Goneまで、5フェーズで安全に閉じる手順
Webサービスのシャットダウン実装完全ガイド|新規停止から410 Goneまで、5フェーズで安全に閉じる手順 はじめに:サービスを「止める」のは、作るより難しい 「ドメインを解約したから終わり」と思っていませんか? 実際の開発現場では、ドメインを落としただけではサービスは完全に終わっていません。Stripeのサブスクリプションが生き続け、ユーザーへの課金が継続したまま——そういった事故は珍しくない...
Mojo 1.0が正式リリース|Pythonのように書き、C++のように動くAI向け言語の全貌
Mojo 1.0が正式リリース|Pythonのように書き、C++のように動くAI向け言語の全貌 メタディスクリプション: Chris Lattner率いるModularのAI特化言語「Mojo」がついに1.0に到達。Pythonとの互換性、圧倒的なパフォーマンス、1.0での変更点、非同期・パターンマッチングを含む今後のロードマップまで、AI/MLエンジニア目線で解説します。 --- AIエンジニア...
Amazon EKS の HPA が最大40倍高速に|Provisioned Control Plane で変わるスケーリング設計
Amazon EKS の HPA が最大40倍高速に|Provisioned Control Plane で変わるスケーリング設計 フラッシュセールが始まった瞬間、ダッシュボードのエラーレートが急上昇する。「HPA は設定済みのはずなのに、なぜ Pod がまだ増え始めていないのか」——本番運用をしているエンジニアなら、一度は経験したことのある焦りです。 2026年7月、AWS はそのボトルネックに...
MIXIのAI研修2026を全解剖|新卒エンジニアに「何をどこまで」教えているのか
MIXIのAI研修2026を全解剖|新卒エンジニアに「何をどこまで」教えているのか MIXIは2025年7月27日、2026年度新卒エンジニア向け技術研修12科目の資料・動画・実習リポジトリをすべて無料公開しました。中でも最大の話題を呼んでいるのがAI研修です。前年の1日から2日間へと大幅拡充され、Day1のスライドが339ブクマ、Day2が424ブクマと業界に大きな反響を生んでいます。 本記事で...