結論

パラメータ 32B 以下のローカル LLM でコーディングエージェントを動かすと、tool_call のパス解決で頻繁に失敗する。ファイルパスの typo、拡張子の取り違え、ディレクトリ階層の省略など、モデルが生成するパスが実在しないケースが多い。これを LLM 側のプロンプト改善で吸収しようとしても限界がある。

そこで、パス解決の失敗を MCP サーバー側で決定論的にリカバリする pathfinder を Rust で自作した。ColBERT(Late Interaction)モデルによるセマンティック再ランキングを組み込み、INT8 量子化で 16MB・CPU 推論のみ・10ms 未満のレイテンシで動作する。GPU の VRAM を一切消費しないため、推論サーバーのリソースを圧迫しない。

約 4,000 ファイルのモノレポで filesystem MCP サーバーと比較した結果、パス解決の精度は僅差だったが、コンテキスト消費量を約 10,000 トークン抑制できた。filesystem MCP は list_directory の祖先チェックでファイル数に比例した O(n) のコンテキスト膨張が起きるのに対し、pathfinder は候補を絞り込んでから返すためコンテキスト効率がよい。

ついでに ModernBERT 推論を Rust で実装したので、開発してきた複数プロジェクトのコードを横断的に俯瞰できる CLI ツールとしても仕上げた。

Zed Editor の MCP Servers 画面: ctree, pathfinder, serena が並んでいる
Zed Editor の MCP Servers 画面 — ctree, pathfinder, serena が独立した MCP ツールとして動作する

動機: ローカル LLM の tool_call はなぜ壊れるか

Claude や GPT-4 クラスのモデルでは tool_call のパス解決はほとんど問題にならない。しかし、ローカルで動かす 7B〜32B クラスのモデルでは事情が違う。

典型的な失敗パターン:

失敗パターン
ディレクトリ名の typosrc/componets/Button.tsxcomponents
拡張子の取り違えconfig.yaml → 実際は config.yml
階層の省略utils/helper.go → 実際は internal/pkg/utils/helper.go
類似ファイル名の混同auth/login.ts vs auth/login.test.ts

これらの失敗が起きるたびに LLM はエラーメッセージを受け取り、リトライを試みる。そのたびにコンテキストが消費され、コンテキストウィンドウの圧迫がさらなる精度低下を招く悪循環になる。

プロンプトで「正確なパスを使え」と指示しても、モデルのパラメータサイズに起因する限界は越えられない。これはモデル側ではなくツール側で吸収すべき問題だと判断した。


アーキテクチャ

pathfinder は Rust 製の MCP サーバーで、stdin/stdout の JSON-RPC 2.0 で LLM クライアント(Claude Code、Zed Editor など)と通信する。

三段階のパス解決

LLM が生成した不正確なパスに対して、以下の三段階で正しいパスを推定する。

  1. レキシカルスコアリング — ないしょ。1ms 未満
  2. クエリ履歴の相関 — 直近の解決結果をリングバッファで保持し、作業コンテキストに沿った候補を優先
  3. ColBERT セマンティック再ランキング — 上位候補に対して ColBERT の MaxSim スコアで再順位付け。約 5-8ms

レキシカルスコアリングだけで高い確信度が得られる場合は、ニューラル再ランキングをスキップする。これにより、大半のクエリは 1ms 未満で返る。

ColBERT モデル

セマンティック再ランキングには ColBERT(Late Interaction)アーキテクチャのモデルを使っている。

項目
モデルlateon-code-edge(コード特化)/ mxbai-edge-colbert(汎用)
パラメータ数約 17M
量子化INT8(ONNX Runtime)
モデルサイズ約 16MB
埋め込み次元128
推論CPU のみ(GPU 不要)

ONNX Runtime 経由で推論し、HuggingFace Tokenizers でテキストをエンコードする。Python 依存はない。

MCP ツール

pathfinder が公開する主要ツール:

  • path_resolve — 失敗したパスを受け取り、最も可能性の高い実在パスを返す。intent_text(「Go config loader」のような短い目的記述)を受け取ることで精度が向上する
  • tool_retry_with_resolve — パスを解決した上で、元の操作(read_file、list_dir 等)を自動リトライする
  • candidate_list — 上位候補をリストで返す。曖昧なケースのブラウジング用
  • roots_list / reindex_paths / server_version — 管理系ツール

tool_retry_with_resolve が実用上の主力で、LLM が read_file で ENOENT を受けた際に、パス解決とリトライを 1 回の tool_call で完結させる。

パス解決の実例

LLM がディレクトリ名を typo した場合:

  path_resolve:
  check_path: "content/ja/docs/tech/infrastrcture/podman-quadlet-systemd-ubuntu.md"
                                      ^^^^^^^^^^^ typo
  →  resolved: "content/ja/docs/tech/infrastructure/podman-quadlet-systemd-ubuntu.md"
  
  $ pathfinder --help
pathfinder — semantic path finder & MCP resolution server

USAGE
    pathfinder [OPTIONS]          Interactive semantic directory finder (default).
    pathfinder --mcp [OPTIONS]    Start as an MCP server.

FINDER OPTIONS
    --include-builds    Include build/artifact dirs (target, dist, …).

MCP OPTIONS
    --root <PATH>       Add a project root directory to watch and index.
                        May be specified multiple times.  Defaults to $PWD.

GENERAL OPTIONS
    -h, --help          Print this help message and exit.
    -V, --version       Print version, model, and PCA config to stderr and exit.

MCP TOOLS
  1. path_resolve            Resolve a failed file path to the best match.
  2. tool_retry_with_resolve Resolve + retry the operation in one call.
  3. roots_list              Return configured root directories.
  4. reindex_paths           Force a full index rebuild.

ENVIRONMENT VARIABLES
    PF_MCP_INFERENCE    Inference mode: "general" (default) or "code".
    Models (both INT8 quantized):
      general → mxbai-edge-colbert (17M, 48-dim)
      code    → lateon-code-edge (17M, 48-dim)
  

CLI: vim 操作でプロジェクトを横断的に俯瞰する

MCP サーバーとは別に、同じ ColBERT 推論エンジンを使った対話的な CLI ツールも作った。開発してきた複数プロジェクト(domain 層、infra 層、presentation 層)のコードを横断的に俯瞰する用途を意識している。

操作体系

シェルで pf(コード特化)または pfg(汎用)を起動すると、セマンティック検索のインターフェースが開く。

  • vim キーバインド で候補リストをナビゲート
  • ディレクトリ階層を ネストの最深部まで掘り下げ、最深部でファイル一覧が表示される
  • 右キー(→) で選択ファイルを less で開く
  • 結果を選択すると、そのディレクトリに cd する
  pf          # コード特化モード(lateon-code-edge)
pfg         # 汎用モード(mxbai-edge-colbert)
  

ModernBERT の推論を Rust でネイティブ実装しているため、検索のレスポンスは体感的にほぼ即時で返る。

pathfinder CLI: pf コマンドでセマンティック検索し、ディレクトリを選択して cd する
pathfinder CLI — セマンティック検索でプロジェクトを横断的に俯瞰し、選択結果のディレクトリに cd する

ベンチマーク: filesystem MCP との比較

約 4,000 ファイルのモノレポアプリケーションサンプルで、filesystem MCP サーバーとの比較検証を行った。

パス解決精度

pathfinder 単体の精度テスト(70 テストケース、12 カテゴリ):

カテゴリ内容結果
正しいパスそのまま返る8/8
ディレクトリ typocomponetscomponents10/10
ファイル名 typo文字の入れ替え・欠落10/10
拡張子の取り違え.yaml.yml7/7
階層の省略中間ディレクトリが欠落5/5
intent ベースクエリ目的記述からの推定4/5
リトライ操作resolve + retry の一体動作3/3
紛らわしいパスペア類似名ファイルの区別6/6
深いネスト8 階層以上4/4
言語横断クエリ「Go の設定ファイル」等4/6
テスト/設定ファイルtest, config の区別4/4
合計67/70 (95.7%)

コンテキスト消費量

filesystem MCP との精度差は僅差だったが、コンテキスト消費量に約 10,000 トークンの差が出た。

filesystem MCP は list_directory で祖先ディレクトリを順にチェックしていくため、ファイル数に比例してレスポンスのトークン数が増える。4,000 ファイル規模でこの差が出ているので、ファイル数が増えるほど差は拡大する。

pathfinder は候補をスコアリングで絞り込んでから返すため、ファイル数に対するコンテキスト消費が抑えられる。ローカル LLM の限られたコンテキストウィンドウ(8K〜32K)では、この差が実用上の精度に直結する。


リソースフットプリント

項目
バイナリサイズシングルバイナリ(Rust)
モデルサイズ約 16MB(INT8)
メモリオーバーヘッド50MB 未満
GPU VRAM 消費ゼロ
典型的レイテンシ10ms 未満(レキシカルのみなら 1ms 未満)

GPU を一切使わないため、vLLM や llama.cpp が VRAM を最大限使っている状態でも pathfinder の動作に影響しない。これはローカル LLM 環境では重要な特性だ。


注意事項

  • レキシカルスコアリングの内部ロジックはないしょ。ctree と合わせて自社のオープンソース LLM パイプライン基盤に組み込んで運用しているため
  • 現在のテストスイートは 70 ケースで、言語横断クエリ(intent_text でプログラミング言語を指定するケース)で 2 件の未解決失敗がある
  • ベンチマークは約 4,000 ファイルのモノレポで実施。数万ファイル規模での検証は今後の課題
  • ColBERT モデルの選択(コード特化 vs 汎用)は PF_MCP_INFERENCE 環境変数で切り替え可能
  • MCP プロトコル(2024-11-05)に準拠。Claude Code、Zed Editor 等の MCP クライアントで利用可能