Claude Media
並列ツール呼び出しが一部失敗したときのis_errorの返し方

並列ツール呼び出しが一部失敗したときのis_errorの返し方

並列で呼ばれたツールの一部を実行しなかったときも、is_error: trueのtool_resultを必ず返す必要があります。書き方と例外パターンをまとめます。

実行しなかった呼び出しにもtool_resultを返す

Claudeが1回の応答で複数のツールを呼んだとき、実行の順序や方式を決めるのはこちら側です。Messages APIは実行順を規定しません。並列に実行してもよいし、順番どおりに1つずつ実行してもよく、ツールの性質に合わせて選べます。

この自由度には裏返しがあります。どちらの方式を選んでも、実行方式にかかわらず守らなければならない返却の規則が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."
}

この規則を守らないと「tool_use ids were found without tool_result blocks immediately after」というエラーになります。次のリクエストが400で弾かれます。実行したかどうかにかかわらず、呼ばれたツールの数と返すtool_resultの数を常に一致させることが前提条件です。加えて、それぞれのtool_resultは対応するtool_use_idで紐づける必要があり、tool_resultブロック自体はそのメッセージ内の他のテキストコンテンツより前に置きます。この2点も、tool_resultの数を一致させることと同じくらい基本の前提です。

読み取り専用で副作用のない操作は並列で走らせてレイテンシを縮め、書き込みや送信のように順序が結果に関わる操作は順番に実行して途中で止める、という判断はこちら側のコードが持ちます。is_error: trueはこの判断の結果を、実行しなかった呼び出しについてもClaudeへ正しく伝え返すための仕組みです。

is_errorは「実行しなかった」と「実行して失敗した」の両方に使う

is_errorは本来、実行しなかった呼び出し専用のフィールドではなく、ツールを実行したものの結果がエラーだったことを伝えるための汎用フィールドです。ネットワークエラー・認証エラー・入力値の不正など、実行はしたが正常な結果が得られなかったケースで使うのが基本用途です。本記事が扱う「実行しなかった呼び出し」へのis_error: trueは、この汎用フィールドを転用した形になります。

どちらのケースでもis_error: trueというフィールド自体は同じですが、contentに書く文言で区別します。実行して失敗した場合は実際に発生したエラーメッセージをそのまま返し、実行しなかった場合は「なぜ実行しなかったか」を短く説明する文言を返します。Claudeはcontentの文面を読んで次の対応を判断するため、どちらのケースなのかが伝わる書き方をすることが、is_error: trueというフラグ自体よりも重要です。

実装上は、この2つのケースを1つの共通処理でまとめて扱いたくなります。しかし文言の作り方が異なる以上、この2つの一覧は別々に管理しておくのが得策です。実行しなかった呼び出しと実行して失敗した呼び出しを、tool_resultを組み立てる直前まで分けておくと、あとから文言を調整するときに扱いやすくなります。

実行タイミングで書き方が変わる2パターン

is_error: trueを使う場面は、実行方式によって性質が異なります。どちらの方式を選んでいるかを忘れずに、contentの書き方を切り替える必要があります。

実行方式(こちらが選ぶ)一部失敗が起きる状況is_errorの内容
順次実行一部失敗が起きる状況先行する呼び出しが失敗し、後続をあえて実行しなかったis_errorの内容「実行しなかった」ことを説明する文言
並列実行一部失敗が起きる状況前提となる呼び出しが完了する前に別の呼び出しが動き、依存先が未完了で失敗したis_errorの内容実際に発生したエラーメッセージをそのまま返す

順次実行を選んでいて先頭の呼び出しが失敗した場合、後続の呼び出しは「実行しなかった」ことをis_error: trueで伝えます。一方、並列実行を選んでいて呼び出し同士に依存関係があった場合は、依存元が完了する前に依存先が動いて自然に失敗します。そのときは実際に発生したエラーメッセージをそのまま返すのが基本です。Claudeはこのtool_resultを見て、次のターンで呼び出しを再送します。

3つのツールが同じ応答で呼ばれ、2つ目がファイル書き込み、3つ目がその書き込み結果を読み込む依存関係にあるケースを考えます。3つとも並列で実行した場合、3つ目が走るタイミングでは2つ目の書き込みがまだ未完了です。このとき返すtool_resultsは次のようになります。

[
  { "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."
  }
]

3つとも同じ1つのuserメッセージにまとめて返している点に注目します。1つだけ失敗したからといって、その結果を別のメッセージに分けたり、成功分だけを先に返したりする必要はありません。並列実行を続けたいなら、依存関係のある呼び出しだけを都度is_error: trueで弾き返すやり方でも運用できます。ただし後述するように、そもそも依存関係のある呼び出しを同じ応答にまとめさせない設計のほうが、再送のやり取りが減って効率的です。

computer useとbrowser useは順次実行必須で、扱いがさらに厳しい

computer useツールとbrowser useツールは、ここまで説明してきた通常のクライアントツールの規則よりもさらに厳格です。これらのツールセットに属する複数の呼び出しを1つの応答にまとめて返してくる「バッチアクション」を受け取った場合、実行順は選択制ではありません。必ず記載された順番どおりに1つずつ実行し、最初の失敗で止める必要があります。

スキップした呼び出しに対して何を返すべきかも、通常のクライアントツールのように自分で文言を考えるのではなく、ツールごとに個別に定義されています。通常のクライアントツールなら、汎用のis_error: trueと簡単な説明文だけで十分です。一方computer useやbrowser useのメンバーツールをスキップする際は、各ツールのドキュメントが指定する正確な文言を返す必要があります。汎用のツールと同じ感覚で自由な文言を書くと、フォーマットの前提が崩れることがあるので注意します。

tool_resultの中身にも通常のクライアントツールにはない制約があります。computer useやbrowser useのメンバーブロックに答えるtool_resultには、対応するtool_useブロックと同じtoolset_nameを書き戻す必要があります。値は"computer"または"browser"です。これを省くと拒否されます。contentに含められる型も狭く、textimageだけに限られます。browser useのタブ管理系メンバーに答える結果はbrowser_stateブロックだけです。通常のクライアントツールのtool_resultは、文字列・画像・ドキュメントなど比較的自由な形式を取れます。computer useとbrowser useはフォーマット自体がより厳格に決められている、という違いを押さえておくのが実装時の手戻りを防ぐコツです。

依存関係のある呼び出しを減らすシステムプロンプトの一文

そもそも依存関係のある呼び出しが同じ応答にまとめて返ってこなければ、この種の一部失敗は起きにくくなります。公式が示す対策は、システムプロンプトに次の一文を足すことです。

Only batch tool calls that are independent of each other.

これは並列ツール呼び出しを増やすシステムプロンプトの書き方で扱った並列化を促す指示とは逆方向の効果を持ちます。並列化そのものは維持しつつ、依存関係のある呼び出し同士がまとめて返ってくる頻度だけを下げる指示です。並列化を積極的に増やす指示と、この抑制の一文は両立します。目的が違うだけで、矛盾する設定ではありません。

この一文をシステムプロンプトに足しても、依存関係のある呼び出しがゼロになるわけではありません。あくまで頻度を下げる指示であり、is_error: trueによる事後の対処が不要になるわけではないという前提は変わりません。プロンプト側の予防と、実行時のis_errorによる後処理は、どちらか一方で済ませられるものではなく、両方を用意しておくのが実務上は安全です。依存関係で失敗した呼び出しにもis_error: truetool_resultをきちんと返している限り、想定していなかった組み合わせが起きても400にはなりません。400になるのは、返すべきtool_resultそのものを返し忘れたときだけです。

disable_parallel_tool_useで並列呼び出しそのものを止めるという判断もあります。使い分けはdisable_parallel_tool_useの効果はtool_choiceで変わるにまとめています。

並列で複数のサブエージェントを走らせる設計をしている場合、失敗の扱いを個々のツール呼び出しレベルでなく上位のオーケストレーション側で持たせる選択肢もあります。Agent Teamsによる並列コードレビューはこの上位レイヤーでの失敗の扱い方を、Agent SDKのサブエージェント上限は上限の設計を、それぞれ具体的に示しています。

Claudeは失敗した呼び出しを次のターンで再送する

依存関係の失敗によるis_error: trueを返したあと、それで会話が終わるわけではありません。返ってきたtool_resultの内容を踏まえて、Claudeは次のターンで同じ呼び出しを送り直してきます。アプリケーション側で「一度失敗したら二度と実行しない」ような特別なリトライ制御を組む必要はなく、通常どおりその呼び出しを実行して結果を返せば会話は続きます。

ここで注意したいのは、再送された呼び出しの実行タイミングです。1回目の並列実行で依存先が失敗したのであれば、2回目のリクエストで同じ組み合わせをまた並列で返してしまうと、依存元の完了タイミング次第で再び失敗する可能性が残ります。同じ失敗を繰り返さないための対処もあります。少なくとも一度失敗が起きた依存関係については、再送された呼び出しをそのターンだけ順次実行に切り替える、といった実行方式側の調整です。プロンプト側の抑制と実行方式の調整を組み合わせることで、同じ依存関係の失敗が何度も往復する事態を避けられます。

まとめ

並列であれ順次であれ、呼ばれたtool_useの数だけtool_resultを返すのが大前提です。実行しなかった呼び出しにはis_error: trueと理由を、依存関係で失敗した呼び出しには実際のエラーを返します。computer useとbrowser useはさらに厳格な順次実行ルールを持つため、汎用のクライアントツールと同じ感覚で扱わないことが重要です。is_errorは実行しなかった場合と実行して失敗した場合の両方に使う共通のフィールドです。contentの文面でその違いを正確に伝えることが、正しいtool_resultを書くうえでの要点になります。

この記事を共有:XはてブLinkedIn