CodeRouter 直せる壊れ方と、直してはいけない壊れ方 — 壊れやすいモデルを 6 つ集めて限界を測った第24話(改訂版)
TL;DR: 前話で作った「壊れた tool call の修復ベンチ」を、今度は壊れやすいと評判のモデル群に横断適用しました。llama、mistral、phi4-mini、小さい qwen ── 6 モデル × 直結/経由 × 100 リクエスト。結果、修復で 0%→100% になるモデルがさらに 2 つ増え、phi4-mini は 0%→80%。途中で「古い CLI で計測していた」という恥ずかしい事故をやらかしたのに、それが修復器 3 世代の効果を 1 段ずつ切り分ける A/B データに化けました。見つかった新しい壊れ方は、Gemma・Mistral・phi4 という血縁のない 3 家系が独立に吐く同じ構文 ── `echo(message: 'demo')` みたいな関数呼び出し風テキスト。1 機構で対応する修復器 R4 を実装し、AI レビュアーに 2 回殴られてバグを直し、オフライン回収率は 78%→100%、偽陽性ゼロ維持。そして修復では原理的に救えない gemma4 の空応答 20% は、フォールバックで 80%→100% に。全部同日に v2.7.2 / v2.7.3 としてリリースしました。第 24 話です。
あらすじ — 24 話目です
連作のタイムスタンプ。初コミットは 2026-04-19、前話(第 23 話・修復の定量ベンチ)から 1 日。本記事の作業日は 2026-07-05、初コミットから 77 日目です。

前話の敵は「効いているつもりの自分の機能」でした。今回の敵は**「まだ見ていない壊れ方」**です。
きっかけ — 壊れやすいモデルを、わざわざ集めた
前話のベンチで修復器の弱点(malformed 14.3%)を直し、3 モデルのライブ実測で「0→100」を 1 例出しました。でも 1 例は 1 例です。qwen2.5-coder:7b という特定のモデルに効いただけかもしれない。
そこで発想を逆にしました。直したいから集めるのではなく、壊したいから集める。 コミュニティで「tool call が壊れる」と評判のモデルを調べて、llama3.2:3b、llama3.1:8b、mistral:7b、qwen2.5-coder:1.5b、phi4-mini、gemma 系 ── 合計 17GB 超をダウンロードして、全部に同じ 100 リクエストを撃ちました。
白状すると、この時点の私は「llama 系は JSON が崩れて、mistral はコマンド風テキストを吐くはず」と予想していました。両方外れます。
前提 — 何が、どこを通っているのか
計測の話に入る前に、伝達経路を 1 枚にしておきます。X でも「エージェントとルーターとモデルの間で何が起きているのか」という質問をよくもらうので。

行きは単純です。Claude Code(エージェント)が Anthropic 形式でリクエストを出し、CodeRouter が OpenAI 形式に変換して Ollama に渡し、ローカル LLM が推論する。
本番は帰り道です。モデルの応答が CodeRouter を通るとき、3 つに分岐します。
A. 正式な tool_calls が入っている → そのまま形式変換して通す。強いモデルは常にこの経路で、だから「経由しても劣化ゼロ」になる
B. 本文にテキストとして tool call が書かれている → 修復層が正式な tool_use に再構成する。この記事で測っているのはここの能力です。許可リストに無いツール名は何があっても組み立てない(偽陽性 0% を回帰テストで固定)
C. 応答が空 → 直すテキストが無いので修復は不可能。同じリクエストを次のモデルに再送する(フォールバック ── 後半で出てきます)
どの経路を通っても、Claude Code に届くのは正式な tool_use だけ。エージェント側は、後ろで修復が起きたことすら知りません。
ついでに、よく聞かれる分業の話。「プロンプトやハーネスで tool call の書き方を矯正するのと重複しませんか?」── 重複はしません。修復は「構造化 call が無いとき」しか発動しないので、矯正が効いて正式な形で出れば素通りです。ただしルーター前提なら、形式の矯正プロンプトは外すのをおすすめします。コンテキストを食う上に、プロンプト内の JSON 例を「本文テキストとして真似する」誘発リスクがある ── まさに B の壊れ方の一種で、実測でも頻出でした。残すべきは「いつ呼ぶか・どのツールか・引数に何を入れるか」の指示。そこは意味の層で、ルーターには直せません(この線引きが後半の主役になります)。
形式の矯正はルーターに(出口側・決定的)、判断の指示はハーネスに(入口側・確率的)。
事故が証拠になった話
計測の途中で、phi4-mini の結果に説明のつかない穴がありました。修復できるはずの形が素通しされている。調べたら原因は実にしょうもない ── serve していた CLI が古かった。`uv tool install` した coderouter は v2.7.0 のままで、修復器の強化(v2.7.1)が入っていなかったんです。
昔の私なら全部捨てて計測し直したと思います。今回はラベルを付け直しました。「これは v2.7.0(旧修復器)の実測」。そして v2.7.1 で同じ計測をもう 1 周。すると何が起きたか ──

phi4-mini の行を見てください。20% → 40% → 80%。修復器の強化 1 段ごとに、効果が 1 段ずつ数字に刻まれている。 事故で 2 世代分の計測が揃ったから書けた表です。「事故は、ラベリングさえ正しければ証拠になる」── これは今回一番の拾い物でした。
ついでに正直な報告。llama 系は temperature=0 では全く壊れませんでした。両経路 100%。「壊れやすい」の評判はこの条件では再現せず、予想は外れ。でも负の結果も結果です。壊すには温度やマルチターンという別の軸が要る、と分かったので次の計測が設計できる。
壊れ方には家系がある
R4 前に残っていた「直せない 20%」の中身を並べたとき、鳥肌が立ちました。
Gemma 系: `echo(message="probe")` ── Python 風
Mistral: `echo(message: 'demo')` ── コロン区切り
phi4: `write_note({"path": ...})` ── JSON を括弧に入れた JS 風
血縁のない 3 つのモデル家系が、同じ「ツール名 + 括弧 + 引数」という構文の方言を吐いている。これは個体の癖ではなく、学習データに無限にある関数呼び出しが滲み出た種としてのイディオムです。
だから対応も「mistral 対応」「phi4 対応」と 3 個作るのではなく、1 つの形クラスとして書く。方言 3 種(`=` / `:` / `{JSON}`)を 1 つのパーサで受ける修復器 R4 を実装しました。今後知らないモデルを使っても、高確率でこの族のどれかに落ちてくれるはずです。
AI レビュアーに 2 回殴られた
実装は AI サブエージェント(Opus)、レビューは別のサブエージェント(Sonnet)に敵対的にやらせました。「ベンチに無い攻撃面を探せ。反例を実際に構築して撃て」と。
1 巡目で 5 発被弾。「For example なら抑止できるが、The calling convention is as follows: だと素通しで修復される」── 例示を見抜くガードが固定フレーズの辞書頼みで、言い換えに弱かった。反例は全部ベンチに焼き込んで修正。
2 巡目はもっと痛かった。空行 1 本で抑止が無効化するバグを 2 件、実弾付きで指摘されました。「Here is a sample payload:\n{...}」は抑止できるのに、「Here is a sample payload:\n\n{...}」だと修復してしまう。ベンチの「修復してはいけないケース」が全部改行 1 本で書かれていたせいで、ベンチが green のままバグが隠れていた。
ベンチは弱点を照らすが、ベンチ自身の死角は照らさない。死角は敵対者に探させるしかない。
最終的にオフライン回収率 78%→100%、偽陽性は反例 8 件を足した上でゼロ維持。テストは 1,553 本に増えました。
直してはいけない壊れ方
R4 を積んでライブを再計測したら、mistral は 80%→100%(0→100 の 3 例目)。phi4-mini は 40%→80%。
残った phi4 の 20% が、この記事の題名の答えです。失敗した 20 件は全部同じ形でした:
write_note({"path":"notes/日本語.txt","text":"line1\ndefine("quotes", 1234) and a comma..."})形は R4 で直せる形です。でも中身を見ると、JSON が引用符のネストで壊れているだけでなく、書けと指示した文字列そのものが別の文字列に化けている。`with "quotes"` と頼んだのに `define("quotes", 1234)` と書いてある。
形を直したら、このツールは間違った引数で実行されます。ファイルに違う内容が書き込まれる。だから R4 は「引数が完全にパースできないものは直さない」と決めてあり、この 20 件は設計どおり落ちている。
形を直せることと、意味を直せることは別。 修復器は形の層であって意味の層ではない。意味が壊れたものを形だけ整えて実行するのは、修復ではなく事故の転送だ。
phi4 の結果表は綺麗でした。形が直せるプロンプトは 20/20 成功、意味が壊れるプロンプトは 20/20 失敗。中間がない。境界を引いた場所に、数字がそのまま線を描いた。
直せないものは、修復の外で救う
もう 1 つの「直せない 20%」が gemma4:26b の空応答です。200 OK なのに中身が空。直すテキストが存在しないので、修復器の世代をいくら上げても 80% から動きません。
これは前話からの宿題で、答えは修復の外にありました。空応答を検知した瞬間、同じリクエストを次のモデルに流す per-request フォールバック。gemma4 → qwen3-coder:30b の連鎖で実測したら、空応答 20/20 が全部回収されて 80%→100%。
これで 3 日がかりの絵が完成しました。
壊れた形は修復で救う ── 0→100 が 3 モデル、phi4 も 0→80
健全なモデルは劣化させない ── 強いモデルは経由しても 100→100
修復できないもの(空応答・意味崩壊)はフォールバックで救う ── gemma4 80→100
同日に v2.7.2 と v2.7.3 としてリリース。今回も依存追加ゼロ、全部デフォルト off の opt-in、後方互換です(これは意地)。
検証
数字の出典は全部リポジトリに置きました。オフライン 84 件コーパス(回収率 100%・偽陽性 0/25)、ライブ実測の一次データ(モデル×版数×経路の JSON/md)、CI ゲート。テストは 1,553 本、体感ではなくテキストで確認できます。
メタ教訓
連作のメタ教訓(抜粋):
(2 話) diagnostic ツール自身も diagnostic され続ける必要がある
(6 話) 自分の検証フローに自分が騙される
(10 話)「対応している」の中身を分解しないと、正しい道具を選べない
(12 話) 自分のプロダクトの価値が「X の不在」に依存しているなら、X は遅かれ早かれ埋まる
(22 話) テストは通るが想定が間違っている場所は、読み直さないと見つからない
(23 話) 自分のコア機能ほど、体感でなく数字で疑え。総合値ではなく最悪のセルを名指しせよ
第 24 話の教訓:
直せるかどうかの前に、直すべきかどうかの線を引け。形を直せることと意味を直せることは別で、意味が壊れたものを形だけ整えるのは事故の転送になる。そして「直すべきでない」と引いた線は、ベンチの negative として焼き込め ── 線は文書ではなく回帰ゲートに書いた時だけ、守られ続ける。
次の話
llama を壊す条件探し(デフォルト温度・マルチターン・低 quant)と、ツール非対応モデルの事前検知。壊れ方のコレクションはまだ増える予定です。「うちのモデルはこんな壊れ方をする」という実例も引き続き募集しています ── コーパスは公開してあるので、issue や PR で送ってもらえれば追加します。
いいなと思ったら応援しよう!
この記事が参考になったら、ぜひ CodeRouter に スターをつけていただけると嬉しいです! 開発の励みになり、今後の改善(特に16GB環境向けのさらなる最適化や新修復機能)の原動力になります☕
🔗 https://github.com/zephel01/CodeRouter (MITライセンス・Python 3.12+・軽量ランタイム)
> スターを押すだけでも本当に励みになります!
> 使ってみての感想・壊れ方報告・要望も大歓迎です(IssueやXでメンションいただけるとすぐ対応します)。
uvx --from coderouter-cli coderouter serve --port 8088
ANTHROPIC_BASE_URL=http://localhost:8088 ANTHROPIC_AUTH_TOKEN=dummy claudeいいなと思ったら応援しよう!
サーバー代とコーヒー代になります☕ 役に立ったら応援よろしくお願いします!