tool-call-malformed と一日戦って、予防策を /make-diary に焼き込んだ
朝、画面に同じ一行が並びはじめた。
The model's tool call could not be parsed (retry also failed).
最初は一度きりの瞬きだと思って流した。だが昨日の積み残しを拾おうとファイルを開いた途端、また出た。散布図ページに GAAP/Non-GAAP 切替を実装している最中にも出た。テストを走らせようとした手前で止まり、データファイルを書き出そうとした瞬間にまた止まった。一日中、作業の節目という節目で同じ一行が割り込んできた。
この記事は、その一行の正体を追い、原因についての自分の思い込みを捨て、予防策を組み立て、それを次回以降も効くように /make-diary スラッシュコマンドへ焼き込むまでの記録だ。コードは出てこない。これは運用の話だ。
まず何が起きていたか
症状はいつも同じ形をしていた。Bash でテストを走らせたり、大きめのファイルを Write しようとしたり、サブエージェントをまとめて起動しようとしたり――つまり重い操作の直前で、セッションが「ツール呼び出しを解析できなかった」と言って固まる。
GAAP 切替の実装ログを後から見返すと、malformed が出たのはこういう場面だった。
- 純粋関数を切り出してテストを走らせようとした手前
- 9 銘柄分のデータを 1 つの大きな
Writeで書き出そうとした瞬間 - サブエージェントを 3 銘柄ずつバッチ起動しようとした境目
面白いのは、malformed が出てもファイルへの書き込みや取得そのものは成功していたことだ。表示上は壊れているのに、裏では処理が通っている。だから「待っていればいいのか?」と何度も自分に問い直すことになった。答えは「待ちは不要、表示が壊れているだけ」だったが、それを確信するまでに時間を溶かした。
自分の思い込みが間違っていた
ここが今日いちばんの収穫だ。
これまで自分は「コンテキスト汚染=数十 MB の巨大ファイルがセッションに溜まること」だと信じていた。スクリーンショットや画像を Read で大量に読むと、セッションが膨れて落ちる――そう理解していた。だから対策は「重いファイルを読まない」「画像を貼りすぎない」だと思っていた。
調べたら、これがひっくり返った。
- 画像 base64 で 48 MB まで膨れたセッションは、parse 失敗ゼロで最後まで完走していた
- 一方、0.1 MB しかない小さなセッションが、parse 失敗を何度も起こして落ちていた
サイズは犯人ではなかった。落ちる真因は、モデルが生成するツール呼び出しの引数構造そのものが壊れることだった。malformed の直前に渡そうとしていたデータは、わずか数 KB の小さなものだった例さえある。Bash の実行結果を受け取った直後、次のツール呼び出しを組み立てる段階で構造が崩れる。それが「進まない」の正体だった。
つまり「汚染」は二種類に分けて扱わないといけない。
- (A) 肥大化:画像 base64 でディスクとトークンを食う。これは掃除の対象だが、落ちる原因ではない
- (B) parse 失敗で落ちる:ツール呼び出しの引数構造の破綻。こっちが今日の犯人
この区別を取り違えていたから、これまで的外れな対策(ファイルサイズばかり気にする)をしていた。調べた内容はその場でマークダウンに書き留めて、誤解を解いた経緯ごと残した。
何をすれば防げるのか
正体が分かれば、打ち手も変わる。一日かけて、効く順に三つの予防策が固まった。
1. 重い読み込みはサブエージェントに投げる(予防の本命)
メインのセッションに大きな探索や大量の取得を抱えさせると、ツール呼び出しの引数が膨らんで構造が壊れやすくなる。だから重い読み込みを別のサブエージェントに切り出す。メインは結果の要約だけを受け取り、生のログや大きな突き合わせ作業はサブエージェント側に閉じ込める。
GAAP の 9 銘柄取得では、3 銘柄ずつ 3 バッチに分けてサブエージェントを並列起動した。重い Web 取得をメインから引き剥がしたことで、メインのコンテキストは軽いまま保てた。これがいちばん効いた。
2. 作業を小分けにする
1 回のツール呼び出しに、たくさんのファイルパスやコード断片や特殊文字を一気に詰め込まない。9 銘柄分のデータを 1 つの大きな Write で吐こうとして malformed が出たので、型定義と 2 銘柄ずつの Edit に割って投入し直したら通った。大きな塊を小さな塊に割るだけで破綻率が下がる。
3. 引き継ぎドキュメントを作って別セッションで継続する
malformed が連発しはじめたら、そのセッションはもう泥沼だ。深掘りをやめて、確定事項と作業内容を復帰プロンプト付きのマークダウンに書き出し、新しいクリーンなセッションで続ける。壊れたコンテキストを引きずったまま環境だけ変えても、同じことが起きる。区切ることがいちばん確実な復旧だった。
ここで注意がひとつ。別セッションで調査役を立てるときは「コンテキスト汚染の可能性あり。巨大ファイルを全 Read するな。複雑な jq を避けろ」と必ず伝える。伝えないと、調査役も同じ穴に落ちる。
予防策を /make-diary に焼き込んだ
知見を頭の中に置いておくだけでは、明日の自分は同じ失敗を繰り返す。そこで、確立した予防策を /make-diary スラッシュコマンドの中に書き込んだ。日記生成のような長いパイプライン作業こそ malformed が出やすいので、コマンド自身に「重い読み込みはサブエージェントへ」「作業を小分けに」「複雑な jq を避けろ」という指針を埋め込んでおく。次回からはコマンドを呼ぶだけで予防が効く。
これで、今日溶かした時間が次回以降の貯金になる。
今日やったこと
- 一日中出ていた malformed の発生場面を特定した(重い操作の直前で固まる)
- セッションログを解析し、確定エラーではなくモデル側の出力生成の破綻だと突き止めた
- 「汚染=巨大ファイル」という思い込みを捨て、真因がツール呼び出しの引数構造の破綻だと理解し直した
- 調べた内容を誤解の経緯ごとマークダウンにドキュメント化した
- 予防策を三つ(サブエージェント分離・作業分割・引き継ぎ別セッション)に整理した
- その予防策を
/make-diaryスラッシュコマンドに反映した
明日以降への申し送り
- malformed が出たら、ファイルサイズより先に直前のツール呼び出しの引数を疑う
- 重い探索は最初からサブエージェントに切り出して、メインを軽く保つ
- 連発したら粘らず、復帰プロンプト付きメモを残して新セッションへ移る
- 調査役を別セッションで立てるときは「全 Read 禁止・複雑な jq 禁止」を必ず伝える
今日の小さな教訓
便利な思い込みほど検証しないまま握り続けてしまう。「汚染=巨大ファイル」は分かりやすかったぶん、間違っていることに気づくのが遅れた。画面に同じエラーが並びはじめたら、症状を眺めて納得する前に、直前に自分が何を渡そうとしたかを一度立ち止まって見る。それだけで、無駄に溶ける一日は減らせる。