並列ツール呼び出しが一部失敗したときのis_errorの返し方
並列で呼ばれたツールの一部を実行しなかったときも、is_error: trueのtool_resultを必ず返す必要があります。書き方と例外パターンをまとめます。
実行しなかった呼び出しにもtool_resultを返す
Claudeが1回の応答で複数のツールを呼んだとき、実行の順序や方式を決めるのはこちら側です。Messages APIは実行順を規定しません。並列に実行してもよいし、順番どおりに1つずつ実行してもよく、ツールの性質に合わせて選べます。
この自由度には裏返しがあります。どちらの方式を選んでも、呼ばれたtool_useブロックの数だけ、必ずtool_resultを返すことです。順番に実行していて途中の呼び出しが失敗し、それ以降をあえて実行しなかった場合も例外ではありません。実行しなかった呼び出しには、is_error: trueと簡単な説明を添えたtool_resultを返します。
{
"type": "tool_result",
"tool_use_id": "toolu_02",
"is_error": true,
"content": "Not executed: the preceding write_file call failed."
}読み取りは並列、書き込みや送信は順番に、といった判断はこちらのコードが持ちます。is_error: trueは、その判断の結果をClaudeへ伝え返す仕組みです。
「tool_use ids were found without tool_result blocks immediately after」というエラーが出たときは、返し方の次の3点を順に疑います。
tool_resultの返し方で崩れやすい3点
数と対応
呼ばれた
tool_useの数だけ返し、それぞれを対応するtool_use_idで紐づけます。実行したかどうかは関係ありません。並びと位置
すべての
tool_resultを同じ1つのuserメッセージに入れ、テキストはその後ろに置きます。テキストを先に置くと400になります。直後のメッセージ
tool_resultは、tool_useを含むassistantメッセージのすぐ次のuserメッセージに入れます。間に別のメッセージは挟めません。
SDKのTool Runnerは、複数のツール呼び出しを含む応答の処理と結果の整形を肩代わりします。並び順や失敗時の扱いを自分で握りたい場合に、ここで見ている手書きの形が必要になります。
is_errorは「実行しなかった」と「実行して失敗した」の両方に使う
is_errorは、実行しなかった呼び出し専用のフィールドではありません。ツールを実行したものの結果がエラーだったことを伝える汎用のフィールドです。ネットワークエラーのように、実行はしたが正常な結果が得られなかったケースが基本用途で、実行しなかった呼び出しへのis_error: trueは、それを転用した形になります。
どちらのケースでもフィールドは同じなので、contentの文言で区別します。実行して失敗した場合は実際のエラーメッセージを返し、実行しなかった場合は「なぜ実行しなかったか」を短く書きます。Claudeはcontentの文面を読んで次の対応を決めます。公式も、"failed"のような汎用の文言でなく、何が起きて次に何を試すべきかを書くことを勧めています。
実行しなかった場合と実行して失敗した場合のほかに、Claudeが渡した入力が不正で実行できないケースにも同じ形が使えます。必須パラメータが欠けていたときにError: Missing required 'location' parameterのようなtool_resultをis_error: trueで返すと、Claudeは修正を加えて呼び直します。公式は、不正なツール呼び出しではClaudeが2〜3回修正して再試行してから、ユーザーに謝罪すると説明しています。
一方、Web検索のようなサーバーツールのエラーはClaudeが自分で処理するため、is_errorの結果を返す必要はありません。この記事の話はクライアントツール、つまり自分のコードで実行するツールに限ります。
実装では、実行しなかった呼び出しと実行して失敗した呼び出しを、tool_resultを組み立てる直前まで別のリストで持っておくと文言を調整しやすくなります。
順次と並列で、一部失敗の中身は違う
一部失敗が起きる状況は、選んだ実行方式で変わります。
実行方式ごとの一部失敗
順次実行
先行する呼び出しが失敗したら、後続はあえて実行しません。後続には「実行しなかった」理由をis_error: trueで返します。
並列実行
依存元が終わる前に依存先が動き、依存先が失敗します。この呼び出しには、実際に出たエラーメッセージをそのままis_error: trueで返します。
たとえば同じ応答で3つのツールが呼ばれ、2つ目がファイル書き込み、3つ目がその結果を読む依存関係にあるとします。3つとも並列で実行すると、3つ目が走る時点で2つ目の書き込みはまだ終わっていません。返すtool_resultは次のようになります。
[
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "OK: read succeeded" },
{ "type": "tool_result", "tool_use_id": "toolu_02", "content": "OK: write succeeded" },
{
"type": "tool_result",
"tool_use_id": "toolu_03",
"is_error": true,
"content": "File not found: the write in toolu_02 had not completed yet."
}
]1つだけ失敗しても、結果を別のメッセージに分けたり、成功分だけを先に返したりする必要はありません。
順次実行で止める側の実装は、失敗した時点で以降のブロックに定型の文言を割り当てるだけで済みます。次のPythonは、その最小の形です。
def run_sequentially(tool_uses, run_tool):
results, failed = [], False
for tu in tool_uses:
base = {"type": "tool_result", "tool_use_id": tu.id}
if failed:
results.append({**base, "is_error": True,
"content": "Not executed: an earlier call in this turn failed."})
continue
try:
results.append({**base, "content": run_tool(tu)})
except Exception as e:
failed = True
results.append({**base, "is_error": True,
"content": f"{type(e).__name__}: {e}"})
return results返り値をそのまま1つのuserメッセージのcontentに渡せば、数と位置の条件は満たされます。
computer useとbrowser useは順次実行が必須
computer useとbrowser useは、通常のクライアントツールより厳格です。これらのツールセットのメンバーを1つの応答にまとめて返してくる「バッチアクション」では、実行順を選べません。
バッチアクションの返し方
- 1
記載順に1つずつ実行する
並列には実行しません。クリックしてから入力する、のように後続が先行の結果に依存するためです。
- 2
失敗したブロックに失敗の内容を返す
is_error: trueと、何が起きたかを書いた文字列を返します。 - 3
残りのブロックに定型文を返す
スキップした呼び出しには、ツールごとに決まった文言を
is_error: trueで返します。 - 4
全部を1つのuserメッセージで返す
成功したブロックには通常の結果を返します。1つでも未回答のブロックが残ると、
invalid_request_errorで拒否されます。
スキップ時の文言は、通常のクライアントツールのように自由に書けません。ツールごとに次のとおり決まっています。
| ツール | toolset_name | スキップした呼び出しに返す文言 |
|---|---|---|
| computer use | toolset_name"computer" | スキップした呼び出しに返す文言Not executed: an earlier computer action in this turn failed. |
| browser use | toolset_name"browser" | スキップした呼び出しに返す文言Not executed: an earlier action in this turn failed. |
2つの文言は「computer」の1語だけ違います。コピーして使い回すと、片方のツールに他方の文言を返すことになります。
結果の中身にも、通常のクライアントツールにない制約があります。メンバーブロックに答えるtool_resultには、対応するtool_useと同じtoolset_nameを書き戻します。省くと拒否されます。contentに入れられるのはtextとimageだけで、browser useはそれにbrowser_stateブロックを1つ加えられます。タブ管理系のメンバーの結果はbrowser_stateブロックだけです。
バッチの扱いを間違えやすいのは、最初のブロックしか読まないループです。応答のcontentにあるtool_useは、先頭の1つで止めず、すべて順に回して処理します。
Claudeはバッチの最後に、screenshotなどの観察の呼び出しを置くのが通例です。browser useではread_pageやget_page_textのこともあります。computer useで最後がscreenshotでない場合は、最後の結果にimageブロックとして現在の画面を足しておくと、Claudeが画面を撮り直す往復を省けます。computer useでは、毎回のバッチをscreenshotで終えるよう、システムプロンプトで指示する手もあります。
computer useでは、人間の確認を挟む運用にも注意点があります。バッチは複数の操作を1ターンで終えてしまえるので、確認は各ブロックを実行する前に入れます。
依存関係のある呼び出しを減らすシステムプロンプトの一文
そもそも依存関係のある呼び出しが同じ応答に入らなければ、この種の一部失敗は起きにくくなります。公式が示す対策は、システムプロンプトに次の一文を足すことです。
Only batch tool calls that are independent of each other.これは並列ツール呼び出しを増やすシステムプロンプトの書き方で扱った並列化を促す指示とは逆方向です。並列化そのものは保ちつつ、依存関係のある呼び出しがまとめて返ってくる頻度だけを下げます。二つの指示は目的が違うだけで、矛盾しません。
一文を足しても、依存関係のある呼び出しがゼロになるわけではありません。プロンプトによる予防と、実行時のis_errorによる後処理は、片方で済ませずに両方用意しておくのが安全です。依存で失敗した呼び出しにもtool_resultを返している限り、想定外の組み合わせが起きてもリクエストは拒否されません。拒否されるのは、返し忘れや位置の誤りなど、返し方の条件が崩れたときです。
Claude Fable 5.1は、従来のモデルより並列呼び出しが減ることがあります。目立つのは、次の読み取りが暗黙のままになる長いエージェントループです。自作のコーディングエージェントやBashとテキストエディターのハーネス、computer useが当たります。標準の関数呼び出しには影響しません。
disable_parallel_tool_useで並列呼び出しそのものを止める選択もあります。使い分けはdisable_parallel_tool_useの効果はtool_choiceで変わるにまとめています。
並列で複数のサブエージェントを走らせる設計なら、失敗の扱いを個々のツール呼び出しでなく上位のオーケストレーション側に持たせる手もあります。Agent Teamsによる並列コードレビューはこの上位レイヤーでの失敗の扱い方を、Agent SDKのサブエージェント上限は上限の設計を示しています。
Claudeは失敗した呼び出しを次のターンで再送する
依存関係の失敗でis_error: trueを返したあとも、会話は終わりません。返したtool_resultを踏まえて、Claudeは次のターンで同じ呼び出しを送り直します。「一度失敗したら二度と実行しない」ような特別なリトライ制御は要らず、再送された呼び出しを通常どおり実行して結果を返せば会話は続きます。
注意したいのは、再送された呼び出しの実行タイミングです。1回目の並列実行で依存先が失敗したのに、2回目も同じ組み合わせを並列で実行すると、依存元の完了タイミング次第でまた失敗します。同じ依存関係の失敗が往復する場合は、再送された呼び出しをそのターンだけ順次実行に切り替える、という実行方式側の調整が選べます。プロンプトでの抑制と組み合わせれば、同じ失敗の繰り返しを減らせます。
まとめ
依存のある呼び出しが同じ応答に混ざりうるツールでは、並列より順次実行に倒すと再送の往復が減ります。computer useとbrowser useは順次実行が前提なので、ツールごとの定型文を使い分けます。