はじめに

pathfinder を実装する過程で,複数の改善サイクルを経験した。単純な「パス候補を返す」という基本機能から,モデル精度の検証,動的パラメータ調整,ファイルシステム統合 へと段階的に改善されていったプロセスを記録する。

改善のサイクル

改善 Phase 1-2:基礎設定とTopK動的調整

初期課題

  • Prefill phase(ファイルシステム索引化時)でのメモリロード遅延
  • 相対パス vs 絶対パスの混乱がLLMの失敗の主因
  • TopK値の固定がすべてのシナリオに対応できない

対応

1. .gitignore サポート

  # .gitignore の内容に基づき自動的に無視対象を決定
# 例:
__pycache__
node_modules
dist
build
target
.git
  

この設定により,ファイルシステム索引のサイズを削減し,検索精度を向上。MCP の INCLUDE_DIRS に例外ディレクトリを明示的に指定できる仕様に統一。

2. TopK の動的調整

固定TopK(例: 10)から,ファイルシステムサイズに応じた動的値に変更:

  TopK = ceil(total_files * ratio)  # ratio: 0.05 や 0.1 など
  

例:ファイル総数 3,000 個で TopK = 150,小規模プロジェクト(300ファイル)なら TopK = 15。

これにより,検索スペースが大きいほど候補を増やし,小規模なら絞込む という適応的な動作を実現。

3. 初回再ランキングのホットロード

ReRanking(ONNX 推論)の初回実行は遅延が大きい。対策:

  // 起動時に定数パスで事前に再ランキングを実行
let _ = rerank("alpha_path", "beta_path", &embedder)?;
  

これにより,実際のクエリ時には ONNX エンジンが warm state になっており,応答レイテンシが安定化。

改善 Phase 3-4:ファイルシステム統合と設定簡素化

課題

  • IGNORE_CASE 環境変数による設定が複雑
  • .gitignore の ネガティブパターン (例: !mxbai-colbert 例外指定)に対応していない
  • Sampling環境が小規模

対応

1. .gitignore ネガティブパターンへの対応

  /models/*       # models配下はすべて無視
!mxbai-colbert  # ただしこのディレクトリは含める
  

パーサを改修し,ネガティブパターンを正確に解析。複雑な exclude/include ロジックを実装。

2. IGNORE_DIRS 廃止,INCLUDE_DIRS へ統一

設計方針を反転:

  • デフォルト動作:.gitignore に従う
  • 例外:INCLUDE_DIRS env で明示的に含める

これにより,ほぼすべてのプロジェクトが自動的に .gitignore 準拠になる。

3. Sampling環境の拡張

ベンチマーク用に,3,000+ ファイルの複雑なディレクトリツリーを構築:

  sampling/
├── cmd/
│   ├── server/
│   │   └── main.go
├── internal/
│   ├── domain/
│   │   ├── knowledge/
│   │   ├── llm/
│   │   └── pipeline/
│   ├── infra/
│   │   ├── llamacpp/
│   │   ├── lmstudio/
│   │   ├── nats/
│   │   ├── openaihttp/
│   │   ├── postgres/
│   │   ├── reranker/
│   │   └── vllm/
├── devstack/
│   ├── dagster/
│   └── trino/
└── packages/
    └── resolver-go/
  

このツリーにより,実務的な monorepo シナリオをシミュレート。

改善 Phase 5-6:モデル精度(定量化 vs 浮動小数点)の検証

課題

  • INT8 定量化で精度が落ちるか不明
  • レイテンシ vs 精度のトレードオフが定量評価されていない
  • MonoRepo 環境での精度悪化の可能性

対応

1. 複数モデル・複数量子化での並行ベンチ

  モデル候補:
├── mxbai-edge-colbert-v0-32m (公式)
│   ├── onnx/model_int8.onnx    ← 高速,精度?
│   ├── onnx/model_fp16.onnx    ← 中速,中精度
│   └── onnx/model_fp32.onnx    ← 低速,高精度
├── lightonai-mxbai-edge       ← オルタナティブ
└── ort-comm-colbert-sm-v1     ← 軽量版
  

各モデルで同一ベンチを実行、比較。

実測結果(monorepo全体ベンチマーク:typo・パス前置詞欠落・拡張子間違い・曖昧なベアファイル名など全カテゴリ混合55ケース,3,261ファイル):

構成精度平均レイテンシ
answerai-colbert-small INT8(デフォルト)87.3%(48/55)9.07ms
answerai-colbert-small FP1687.3%(48/55)12.61ms
answerai-colbert-small FP3290.9%(50/55)1,080ms
mxbai-colbert-edge INT887.3%(48/55)8.95ms
mxbai-edge-colbert INT887.3%(48/55)9.12ms

重要な発見:3つのColBERTモデルすべてが同一精度を示した。7件の失敗はモデルではなくスコアリングで、ニューラルモデルの改善では解決できない問題だった。

FP32は+3.6pp(87.3% → 90.9%)の精度向上を得たが、レイテンシが119倍(9ms → 1,080ms)。歩留まりが良いのはINT8で、それをベースにした。

2. アンサンブル検討→廃止

複数 ColBERT モデルのアンサンブルを試行:

  let scores_m1 = rerank_with_model(query, candidates, &model1)?;
let scores_m2 = rerank_with_model(query, candidates, &model2)?;
let final_scores = (scores_m1 + scores_m2) / 2.0;
  

3モデルのアンサンブルでも精度は87.3%のまま変わらず、レイテンシだけが3倍(33.2ms)に増加。

改善 Phase 7:履歴相関による精度向上

1. クエリ履歴の保持

メモリ上に最近の N 件(初期値:5件)のクエリ履歴を保持:

  struct QueryHistory {
    queries: Vec<QueryRecord>,
}

struct QueryRecord {
    query: String,
    parent_dir: String,
    timestamp: u64,
}
  

各新規クエリ時に,過去 5 件のクエリの親ディレクトリを確認。同一親の場合,その親で再度フィルタリング。

2. 親ディレクトリ相関スコアリング

  // candidate_dir が最近のクエリ親と同じ → スコア +20
// 似た構造(depth, name_pattern が近い) → スコア +10

fn compute_history_affinity(
    candidate_dir: &str,
    history: &QueryHistory,
) -> f32 {
    // 過去5件の parent_dir との一致度
    ...
}
  

相関ベンチマーク結果(曖昧なベアファイル名のみの部分集合:20シナリオ,連続クエリ):

モード精度内訳
履歴あり(primed)85.0%(17/20)dir_affinity: 13/15, pkg_affinity: 3/4, explicit: 1/1
履歴なし(baseline)35.0%(7/20)dir_affinity: 4/15, pkg_affinity: 2/4, explicit: 1/1
差分+50.0pp11件改善,1件回帰

+50ppの改善。履歴なしでは35%しか解決できないベアファイル名の曖昧性を、5件のリングバッファで85%まで引き上げた。

設計上の重要な決定

Skip Threshold(早期終了判定)

再ランキングが必要な場合を判定するスコア閾値:

  score >= 50.0 → 確信度高い,即座に返す
30.0 < score < 50.0 → marginal,再ランキング検討
score <= 30.0 → 低信頼度,常に再ランキング
  

当初は閾値の動的調整も検討したが、INT8構成では再ランキング込みでも約9msと十分高速であった。そのため、過度な最適化よりも設計の単純性を優先し、高めの固定値(50)で一旦運用することにした。

再ランキングのコスト

INT8での再ランキング込みで平均9msだが,以下の観点から 常時有効化を推奨

  1. レイテンシ増加が許容範囲
  2. 精度向上が顕著
  3. 「全て再ランキング」は実装が単純

LLM への指示刷り込み

ツール呼び出し設定時に,以下の指示を埋め込む:

このプロジェクトにおいて,ファイルの読み書き・検索操作には必ず tool_retry_with_resolve を使用してください。パス指定の正確性は自動復旧されます。

毎回のツール実行後,成功時でも「resolve による補正が入った」ことを LLM に通知することで,Path Resolution への依存を強化。

技術的な実装詳細

失敗パターン分析

monorepoベンチマーク55ケースの内訳:

カテゴリ正解全体正解率
クロスパッケージ混同77100%
ファイル名typo55100%
ネスト深度間違い44100%
ディレクトリ間違い44100%
拡張子間違い4757%
typo+パッケージ間違い複合1250%
その他(曖昧なベアファイル名)91275%

拡張子間違い(例: matcher.pymatcher.rs)と曖昧なベアファイル名client.go が8箇所に存在)が残る課題。前者はスコアリング設計、後者は履歴相関で対処。

モデル量子化の結論

INT8とFP16は精度が同一(87.3%)で、レイテンシのみが異なる(9ms vs 13ms)。FP32は+3.6ppだが119倍遅い。INT8がデフォルトとして最適。精度のボトルネックはモデル精度ではなくスコアリングアルゴリズムの設計にある。

成果物

CLIヘルプ出力

  [email protected] ~/Development/loftllc-web % pathfinder-mcp -h
pathfinder-mcp — deterministic path resolution MCP server

USAGE
    pathfinder [OPTIONS]

OPTIONS
    --root <PATH>     Add a project root directory to watch and index.
                      May be specified multiple times.  Defaults to $PWD.
    -h, --help        Print this help message and exit.

DESCRIPTION
    An MCP (Model Context Protocol) server that resolves ENOENT / NotFound
    path errors for AI coding agents.  It builds an in-memory path index of
    configured root directories and uses fuzzy matching combined with ColBERT
    MaxSim re-ranking (when an ONNX model is available) to resolve incorrect
    paths to their most likely existing counterparts.

    Communication is via JSON-RPC over stdin/stdout.  Evaluation metrics are
    written to stderr as JSON lines (redirect with 2>metrics.jsonl).

    The server runs a stdin reader thread with periodic orphan detection and
    exits automatically when the parent MCP client process disappears.

ENVIRONMENT VARIABLES
    RESOLVE_MODEL_PRECISION  Model precision: "int8" (default), "fp16", or "fp32".
    RESOLVE_MODEL_DIR        Model directory containing model_*.onnx + tokenizer.json.
    RESOLVE_TOPK             Minimum topk value (default 10).
    INCLUDE_DIRS             Comma-separated directory names to force-include.

MCP TOOLS
    path_resolve             Resolve a failed file path to the best match.
    tool_retry_with_resolve  Resolve and automatically retry the operation.
    roots_list               Return configured root directories.
    reindex_paths            Force a full rebuild of the path index.

MCP CLIENT CONFIGURATION (Claude Code)
    "pathfinder-mcp": {
      "command": "pathfinder",
      "args": ["--root", "${workspaceFolder}"]
    }
  

定量サマリ

項目
Rustソースコード2,117行(サーバ1,951 + 推論166)
Pythonベンチマーク1,587行(6スクリプト)
依存クレート8
MCPツール4
スコアリング特徴量レキシカル12 + 履歴2 + ニューラル1
テスト用ファイル3,261ファイル / 628ディレクトリ
評価モデルColBERT 3バリアント
ベンチマークスイート4(basic, small-project, monorepo, correlation)

結論

pathfinder の最適化は 単一の「完璧な」スコア関数を求める のではなく,段階的な改善と検証を通じた実用的なバランス探索 だった。

改善イテレーションを通じて得られた核心的な知見:

  1. 履歴相関の破壊的効果: わずか5件のリングバッファで+50ppの精度向上。ベアファイル名の曖昧性解消に対して、モデル改善よりもコンテキスト活用が圧倒的に有効
  2. INT8で十分(今回の用途では): FP32の119倍のレイテンシコストに対して+3.6ppの改善。インタラクティブなパス解決という用途ではINT8の9msが最適解