jev-mcp を更新した。

前回は、TypeSafe AIの判断モデルJevをCodexから呼び出すMCPサーバーを作った、というところまで書いた。4種類の判断ツールを接続しただけでは、実際にコーディング作業で使い続けられるかは分からない。

今回は、判断結果を評価するための小さなJSONL、評価CLI、呼び出し状況を確認するためのテレメトリ、失敗時の扱いを追加した。Codex向けのSkillにも、用途別の判断レシピを増やしている。

前回の記事は/blog/2026-09-20-2/。

判断モデルを使うなら評価データも必要になる

Jevに渡すのは、たとえば「このテスト失敗は変更と関係があるか」「次に調べるべき箇所はどれか」「この変更のリスクはどの程度か」といった小さな判断になる。

一回呼び出してそれらしい回答が返っただけでは、モデルやプロンプト、MCP側の変換を変えたときに何が変わったのか分からない。そこで、まず回帰確認用の小さな評価セットを置いた。

eval/ja-coding-minimal.jsonlには、架空の日本語コーディング判断を12件入れている。

質問の型件数
Noul4テスト失敗と変更の関連性
Choice4最初に調べるサブシステム
Score4回帰・安全性リスク

明確に判断できるものだけでなく、情報が足りない場合、候補が競合する場合、unknownを選ぶべき場合も含めた。Choiceでは正解を一つに固定できない例もあるので、許容する候補の集合を期待値にしている。

これは日本語のコーディング判断全般の性能を測るベンチマークではない。少数の架空ケースなので、明らかな退行を見つけて失敗例を読むための出発点として置いている。

APIを呼ばずに評価JSONLを確認する

評価ファイルは、まずローカルだけで形式を検査できる。

cargo run --locked -- eval validate eval/ja-coding-minimal.jsonl

eval validateはAPIキーを必要としない。JSONLの各行を読み、IDの重複や空行、質問と期待値の型の不一致、NoulとScoreの数値範囲、Choiceで期待した候補が実際の選択肢に含まれるかを確認する。

モデルへ送る前に評価データそのものの間違いを落とせるようにした。テストでも、妥当なファイルが通ることと、不正なファイルがネットワークなしで失敗することを確認している。

実モデルの結果はJSONLで残す

実際にJevを呼ぶ場合は、別のサブコマンドを使う。

TYPESAFE_API_KEY="your_typesafe_api_key" \
  cargo run --locked -- eval run eval/ja-coding-minimal.jsonl \
  > eval/baselines/ja-coding-YYYY-MM-DD.jsonl

この経路は実APIを呼ぶので、利用料金が発生する。各ケースごとに、pass、fail、error、測定値、解決されたモデル名、所要時間、入出力トークン数をJSONLで出す。最後には合計件数、成功・失敗・エラー数、平均時間、トークン数のsummaryを1行追加する。

一件の通信が失敗しても、残りのケースは続けて実行する。評価の途中でAPIエラーがあったことと、期待値から外れたことは別なので、どちらも同じfailにはしない。

まだリポジトリには実APIで測ったベースライン結果を入れていない。まず同じfixtureを固定して結果を保存し、モデル名やSkillの指示を変えたときに比較できる状態にしていく。

MCPのstdoutに混ぜない呼び出し統計

MCPはstdioで通信するので、標準出力へデバッグログを出すとプロトコルを壊してしまう。とはいえ、別のAPIを途中で呼ぶ以上、遅延や再試行、トークン数は確認したい。

そこで、JEV_TELEMETRY=1を設定したときだけ、1回のツール呼び出しにつき1行のJSONを標準エラーへ出すようにした。

記録するのはツール名、質問数、所要時間、試行回数、要求・解決されたモデル、入出力トークン数、成功またはエラー種別だけ。stateinstructionscriteria、確率分布、APIキーは記録しない。

Jevに渡す差分やログには、コードや障害情報が入る可能性がある。ログを取るために同じ内容をローカルへ増やさないよう、入力内容を持たない統計に限定した。

失敗を回答として扱わない

外部APIの失敗を「Noだった」「低スコアだった」と見なしてしまうと、判断結果と通信失敗が混ざる。MCPツールのエラーは、validationauthenticationrate_limittimeoutnetworkhttpinvalid_responseという種別を持つ構造化エラーとして返すようにした。

例えば入力検証で失敗した場合はAPIへ送らない。認証エラー、429や529の過負荷、タイムアウト、ネットワーク障害も分ける。HTTPエラーの本文は、送ったstateや認証情報を含む可能性があるので、そのままMCPの結果には出さない。

再試行の対象も限定している。429と529は最大2回再試行するが、リクエストと待機を合わせた全体の上限は60秒にしている。同じ入力で失敗した呼び出しを、Codex側が延々と繰り返さないための区別でもある。

Skillに判断のレシピを増やした

同梱しているjev-decisions Skillには、どの判断にJevを使うかを用途別に整理したreferenceを追加した。

  • 障害仮説の優先順位付けと次の診断箇所
  • 原因・再現・回帰・影響に関する証拠の十分性
  • 実行履歴からの停滞や繰り返しの確認
  • 長い作業履歴から残す観測の選択
  • 操作の意図、対象、影響、実行結果の助言的な評価

Skillは、これらを自動で実行するものではない。現在の作業で必要なレシピだけを読み、Codexが集めた証拠と、次の行動を変えられる質問だけをJevへ渡すためのものになっている。

同じ証拠に対する独立した判断はjev.batchへまとめる。一方で、前の回答を見て追加調査が必要になるものは、一度結果を確認してから次のstateを作る。この区別は前回から変えていない。

Skillの本文とreferenceは英語にしたが、Skillへの依頼やJevに渡す材料を英語へ統一する必要はない。今回の評価fixtureも日本語のままにしている。

テストで確認できる範囲

現在の実装では、モックHTTPサーバーを使って、入力検証、型の合わない回答、過大な応答、再試行、タイムアウト、構造化エラー、テレメトリを確認している。評価CLIのJSONL検証、日本語fixtureの12件構成、Skill reference内のJSON例もテスト対象にした。

ローカルでcargo test --locked --offlineを実行し、ユニットテスト17件と統合テスト3件が成功した。これは通信形式やローカルの評価処理を確認した結果であって、Jevの実判断の品質を測った結果ではない。

実モデルの結果は、料金と利用条件を確認したうえでベースラインとして別途保存する必要がある。今の段階では、判断を呼び出す経路だけでなく、何を期待し、何が失敗したかを後から追えるところまでを作った。

Jevを使うかどうか、あるいはどの結果を採用するかはCodex側に残る。モデルの回答が高い確率を返しても、原因の確定やテストの代わりにはならない。この前提を崩さずに、判断を切り出す意味がある場面を少しずつ増やしていく。