← ブログに戻る

Knowledge Graphの抽出順を逆にしたらAPIコストが3倍になった話

コードのKnowledge Graphを30万行のモノレポに対して構築していて、ある日のClaude API明細を見て手が止まりました。前週の3倍。コード行数は1割も増えていません。差分は実装の中身ではなく、抽出フェーズの順番だけです。Pass2(LLMセマンティック抽出)を先に走らせて、その出力に対してPass1(機械抽出)で構造を後付けする設計に変えていました。

結論を先に書きます。Pass1機械抽出を先に走らせ、Pass2 LLM抽出は差分にだけ当てる。この順序を守るとAPIコストはほぼ線形に収まり、逆にすると関数の数に比例してLLM呼び出しが膨れます。30万行で3倍違ったのは、この線が破れた結果でした。

Pass1先行とPass2先行の月額APIコスト比較。Pass2先行はファイル数に比例してコストが膨らむ

なぜ「Pass2 first」を試したくなったか

そもそもなぜ順番を逆にしたかというと、Pass2のほうが「賢い」からです。Tree-sitterのAST抽出はシグネチャと静的呼び出ししか取れません。動的呼び出し、設計意図、ドメイン概念タグ、要約。Knowledge Graphとしての価値はPass2側にあって、Pass1は土台にすぎないという感覚が強かったわけです。

それなら最初からLLMに全関数を読ませて、出てきた構造をPass1で検証すればいい、という流れで設計を組み直しました。テストデータの1万行リポジトリでは普通に動きました。トークン代も許容範囲。これを30万行のモノレポに展開した瞬間に、明細が3倍になりました。

理由はあとから書くと自明ですが、当時の私は3日くらい原因を見失っていました。

軸の比較表 — Pass1とPass2は別物

順序の話の前に、Pass1とPass2の性質差を一度整理しておきます。これがブレた状態だと、順番の議論が空中戦になります。

Pass 1 機械抽出Pass 2 LLMセマンティック抽出
入力ASTのみAST + ソースコード + 周辺コンテキスト
出力ノードとエッジ(構造)ノード、エッジ、概念タグ、要約、信頼度
決定論完全に決定論的確率的(温度を下げてもゼロではない)
コストローカルCPU、API課金なしLLM API課金、リクエスト遅延あり
精度の天井Tree-sitterの正確性 ≒ 100%モデルとプロンプト次第

Pass1は速くて安く、決定論的で、AST上で取れる範囲では精度がほぼ100%です。Pass2はその上に意味のレイヤを乗せます。動的呼び出しの推測、設計意図、ドメインタグ、関数要約。Pass2のエッジには信頼度0.0-0.9を付けて、Pass1の信頼度1.0と区別します。

「賢いのはPass2」というのは正しい。ただしPass2は単体では走らせるべきものではない、というのが3倍コストの教訓です。

Pass2 first が破綻する理由

Pass2を先に走らせると、何が起きるか。LLMに全ファイルを渡して関数や呼び出し関係を抽出させようとすると、Pass1なら無料で取れる情報まで全部LLMに「もう一度発見」させることになります。

具体的には次のような重複が発生します。

  • 関数定義の境界(ASTで決定論的に取れる)を、LLMに毎回コードを読ませて推定させる
  • import関係(from x import yはASTで100%取れる)を、LLMに文字列マッチで再抽出させる
  • 継承関係(class B(A):はASTで明示)を、LLMに「このクラスは何を継承していますか」と問い合わせる

30万行のモノレポでは関数数だけで2万を超えます。Pass2先行だとそれぞれの関数に対してフルコンテキストでLLMが呼ばれ、しかも出力が確率的なので、同じ関数の境界が呼び出しのたびに微妙にずれる。差分検出が効かないのでキャッシュも効きません。トークン代がファイル数にほぼ線形で膨らんで、月額が3倍になりました。

逆にPass1先行なら、関数の骨格はASTから決定論的に取れるので一度作ったら不変です。Pass2はそのグラフの上で「設計意図だけ」「ドメインタグだけ」を狭い入力で取りに行けばよく、コストは差分(変更されたファイルや関数)にしか発生しません。

「Pass1で骨格、Pass2で意味」という二段戦略

整理するとこうなります。

  • Pass1(コールド): 全リポジトリに対して1回走らせる。AST抽出だけ。コストはCPUのみ。これがKnowledge Graphのベースライン
  • Pass2(ホット): 変更されたファイル、最新コミット、PRブランチで触られた関数だけに対して走らせる。設計意図とドメインタグを取りに行く

Pass2の入力も絞ります。対象関数の本体、所属クラスのdocstring、呼び出し元と呼び出し先のシグネチャ各1-2件。これでPass2の1呼び出しあたりの入力トークンは200-800に収まります。Pass1で構築済みのグラフがあるからこそ、この周辺情報を瞬時に取り出せる、という関係です。

私の30万行リポジトリでこの構成に直したあと、APIコストは月額の8割が消えました。Pass1がカバーする領域はLLM不要なので当然です。残った2割が、本当にPass2が必要だった「意味と推論」の部分でした。

Tree-sitterで取れるものを甘く見ない

ここでPass1の射程をもう一度確認しておきます。Tree-sitterは2026年時点で公式に150言語以上をサポートしていて、JavaScript、TypeScript、Python、Go、Rust、Java、C#、Ruby、Swift、Kotlin、Scala、Elixirあたりはどれも実用品質のクエリが書けます。最近Zig、Carbon、Mojoのような新興言語も追加されていて、Pass1の射程は思っているより広い。

最小構成のクエリ例を1つ置いておきます。Python関数の名前と引数を取るやつです。

(function_definition
  name: (identifier) @name
  parameters: (parameters) @params
  body: (block) @body)

これを tree-sitter query で全ファイルに当てると、関数定義のシンボル、パラメータ、本体が決定論的に取れます。LLMに「この関数の名前は?」と聞く理由はゼロです。同じ要領で、import、クラス定義、メソッド定義、継承関係まで一気に取れます。

ただし1つだけ罠があります。Python等の型注釈はASTに乗っていても、それが「正しい型」とは限らない。Pass1のconfidence 1.0は「ASTにそう書かれている」ことの保証で、「型が正しい」保証ではありません。型検証はmypyなど別レイヤに任せる、という線引きをしておくと事故が減ります。

既存OSSの派閥

ついでに、Knowledge Graph系OSSが「Pass1寄り」か「Pass2寄り」か、私の観測範囲で並べておきます。

  • Microsoft GraphRAG: 入口がLLMによるエンティティ抽出寄り。コスト管理が運用上の最重要課題と本家ドキュメントにも書かれている
  • nano-graphrag: GraphRAGの軽量実装。LLM抽出が主軸だが、入力を絞る設計が初期から強い
  • LightRAG: 構造抽出とセマンティック抽出を分離する設計思想。私の見立てではPass1/Pass2を分けようとしているOSSに近い
  • Graphify: コード特化。EXTRACTED(コード由来・1.0) / INFERRED(LLM推測) / AMBIGUOUS の3層分類を持っていて、Pass1/Pass2の区別を運用に組み込んでいる

「LLMファースト」と「機械抽出ファースト」のどちらの設計思想で書かれているかを見ると、それぞれのOSSがどんなドキュメント規模を想定しているかが透けて見えます。1,000ドキュメント以下ならLLMファーストでも吸収できますが、数万を超えると機械抽出ファーストでないとコストが回らない、というのが私の経験則です。

2026年6月時点のコスト感

具体的な数字を置いておきます。本記事執筆時点(2026年6月)のClaude Sonnet API価格で、30万行モノレポを全件Pass2した場合の概算は次のとおりです。

  • 関数数: 約20,000
  • Pass2あたり入力トークン: 平均500(本体 + 周辺情報)
  • 出力トークン: 平均200
  • 全件Pass2のAPIコスト: 数百ドル/月

これを「Pass1先行 + Pass2は変更差分だけ」に直すと、Pass2対象は1日あたり数十関数に収まり、月額APIコストは1桁ドルに落ちます。ファイル数に対する線形性が破れていれば、抽出順を疑うのが先。私はこの線が破れた瞬間に気付けず、3日溶かしました。

結論として残ったルール

3倍コストの事件から私が残したルールは3つです。

  1. Pass1先行は譲らない。AST抽出は無料で決定論的、これが土台
  2. Pass2はホット領域だけ。変更コミット、PRブランチ、レビュー対象関数
  3. Pass2の入力は周辺情報で絞る。1呼び出し200-800トークンに収める

Knowledge Graphが「LLMを賢く使う技術」だと思っているうちは、Pass2先行のほうが直感的に正しく見えます。ただ実運用では、LLMを賢く使うために、LLMを使わないで済む部分を先に確定させる、という順序のほうが圧倒的に効きます。賢い順序のほうが、結果として安い。


ここで書いたPass1/Pass2の二段抽出設計、Tree-sitterクエリの最小構成、Pass2の入力絞り込み、そしてホット/コールド戦略の実装パターンは、ナレッジグラフ実践ガイドに体系立てて書きました。本記事のコスト3倍事件は本書のPass1/Pass2章の余白で得た教訓そのもので、設計判断のフレームを欲しい方はそちらをどうぞ。