Claude Media
tool_resultとtool_useの400エラー — 正しい整形ルール

tool_resultとtool_useの400エラー — 正しい整形ルール

tool_useの直後にtool_resultを置き、tool_resultを先頭にまとめてtextはあとに置く整形ルールと、破ったときに出る400エラーの実際の文言をまとめます。

tool_resultの整形ルール

Claude APIはツール呼び出しの結果を、他のAPIのような専用のtoolロールではなくuserメッセージの中に埋め込みます。この構造には守るべき整形ルールが3つあり、破ると400エラーになります。

ルール

tool_resultの整形ルール3つ

  • 直後に置く

    tool_resultのuserメッセージは、対応するtool_useを含むassistantメッセージのすぐ次に置きます。間に別のメッセージは挟めません。

  • 配列の先頭に置く

    tool_resultを含むuserメッセージでは、tool_resultブロックがcontent配列の先頭です。テキストを書くなら、すべてのtool_resultよりあとに置きます。

  • 未解決のサーバーツールがあれば結果だけ

    結果ブロックがまだ無いサーバーツール(API側が実行するツール)の呼び出しが残っているときは、tool_resultだけでuserメッセージを組みます。テキストを足すとターンが早期に終わったと扱われます。

tool_resultより先にテキストを置くと400になる

もっとも基本的な違反は、tool_resultより先にテキストを置くことです。

{
  "role": "user",
  "content": [
    { "type": "text", "text": "Here are the results:" },
    { "type": "tool_result", "tool_use_id": "toolu_01" }
  ]
}

このリクエストは400で失敗し、次の文言が返ります。

tool_use ids were found without tool_result blocks immediately after: toolu_01PjgRJLbXrXEMZwDNYLnBqk. Each tool_use block must have a corresponding tool_result block in the next message.

直すには、tool_resultを配列の先頭に移動し、テキストをあとに回します。

{
  "role": "user",
  "content": [
    { "type": "tool_result", "tool_use_id": "toolu_01" },
    { "type": "text", "text": "What should I do next?" }
  ]
}

クライアント側のツール(自分のコードで実行するツール)だけを呼んだ応答であれば、この並び順で通ります。

Claude APIには他社のAPIのような専用のtoolロールがなく、ツール呼び出しの結果はuserメッセージの中のtool_resultブロックとして送ります。このため、ブロックの位置と順序がAPIの解釈に関わります。

中身が空、または画像・文書を含むtool_result

tool_resultのcontentフィールドは任意です。ツールが実行はできたが返す値が特に無い場合、contentを省略した最小構成でも有効です。

{ "type": "tool_result", "tool_use_id": "toolu_01A09q90qw90lq917835lq9" }

contentには文字列のほか、text・image・document・search_result型のブロックを配列で渡せます。天気の数値と一緒にグラフ画像を返す、検索結果のドキュメントを添える、といった構成もtool_resultブロック1つの中で表現できます。

{
  "type": "tool_result",
  "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
  "content": [
    { "type": "text", "text": "1週間の気温推移です" },
    { "type": "image", "source": { "type": "base64", "media_type": "image/png", "data": "..." } }
  ]
}

このようにtextブロックとimageブロックを同じcontent配列に並べれば、説明文とグラフ画像を1つのtool_resultにまとめて返せます。

コンピューター操作ツールとブラウザー操作ツールのtool_useは、toolset_nameフィールド("computer"または"browser")を持ちます。これに答えるtool_resultには同じtoolset_nameを書き戻す必要があり、省くと拒否されます。この結果のcontentに入れられるのはtextとimageだけで、ブラウザー操作ではさらにbrowser_stateブロックを1つ加えられます。

複数のtool_useをまとめて返す

Claudeは1つのassistantターンで複数のtool_useブロックを返すことがあります。並列に実行するか順番に実行するかは呼び出し側が決めてよく、APIは実行順序を指定しません。ただしコンピューター操作・ブラウザー操作のバッチアクションは例外で、現れた順に実行し、最初の失敗で止めます。返し方のほうには決まりがあります。すべてのtool_resultを、1つのuserメッセージにまとめることです。

// ❌ 誤り: tool_resultごとに別のuserメッセージ
[
  { "role": "assistant", "content": [/* tool_use_1, tool_use_2 */] },
  { "role": "user", "content": [/* tool_result_1 */] },
  { "role": "user", "content": [/* tool_result_2 */] }
]
 
// ✅ 正しい: 1つのuserメッセージに全部まとめる
[
  { "role": "assistant", "content": [/* tool_use_1, tool_use_2 */] },
  { "role": "user", "content": [/* tool_result_1, tool_result_2 */] }
]

結果と呼び出しの対応はtool_use_idで取ります。並列ツール呼び出しが期待どおりに出ないとき、もっとも多い原因は結果の整形ミスです。tool_resultを個別のuserメッセージに分けて送る書き方は、Claudeが以降の並列呼び出しを避けるようになる原因として挙げられています。

実行を一部スキップした場合(順番に実行していて途中の呼び出しが失敗した、など)も、その呼び出しのtool_resultは省略できません。is_error: trueと短い説明を付けて返します。並列で実行して前提の呼び出しが間に合わず失敗したときは、その自然なエラーメッセージをis_error: trueで返せば、Claudeが次のターンで呼び出しをやり直します。依存する呼び出しが同時に出るのを減らすには、システムプロンプトに「Only batch tool calls that are independent of each other.」と書きます。is_errorの書き方はtool_resultのis_errorとエラーメッセージの書き方です。

サーバーツールが未解決のまま残る場合の400エラー

Web検索のようなサーバーツールは、API側が実行するのでtool_resultを自分で用意する必要がありません。ただし同じ並列呼び出しの中でサーバーツールとクライアントツールが一緒に呼ばれると、APIはサーバーツールを実行せずに応答を返します。クライアントツールを先に実行させるためです。

この応答はstop_reason: "tool_use"で、server_tool_useブロックのidに対応する結果ブロックがありません。目印はそれだけなので、server_tool_useとmcp_tool_useの各idに結果ブロックが付いているかを調べて見分けます。

{
  "stop_reason": "tool_use",
  "content": [
    { "type": "server_tool_use", "id": "srvtoolu_01HxbWnMRmbWyMfUtJKC45rA",
      "name": "web_search", "input": { "query": "example article" } },
    { "type": "tool_use", "id": "toolu_01PjgRJLbXrXEMZwDNYLnBqk",
      "name": "run_command", "input": { "command": "uname -a" } }
  ]
}
手順

混在応答への返信の組み立て方

  1. 1

    未解決のサーバーツールを見つける

    server_tool_useとmcp_tool_useのうち、同じidの結果ブロックが無いものを探します。

  2. 2

    クライアントツールを実行する

    tool_useブロックごとに自分のコードで実行し、tool_use_idを合わせたtool_resultを作ります。

  3. 3

    tool_resultだけのuserメッセージを送る

    テキストは足さず、tools配列も変えずに送ります。APIが保留中のサーバーツールを実行して続きを返します。

直接呼ばれたサーバーツールなら、次の応答のcontentは、前の応答のserver_tool_useのidに答える結果ブロックから始まります。サーバーツールの結果ブロックを自作して足す必要はありません。

くらべる

tool_resultのあとにテキストを置けるか

置ける

クライアントツールだけを呼んだ応答

tool_resultの後ろに「次はどうすればいい?」のような新しいテキストを続けられます。

置けない

未解決のサーバーツールが残る応答

テキストを足すとターンが終わったと扱われ、400になります。追加の入力はターンが完了してから、別のuserメッセージで送ります。

テキストを混ぜた場合の文言は次のとおりです(web_searchの例です。web_fetchなど他のサーバーツールでも、ツール名が変わるだけで同じ形です)。

`web_search` tool use with id `srvtoolu_01HxbWnMRmbWyMfUtJKC45rA` was found without a corresponding `web_search_tool_result` block

tools配列から、待機中だったサーバーツールの定義自体を外して再送した場合も400です。文言の末尾は次のとおりです。

but no `web_search` tool was provided

文言から原因を切り分けられます。tool_use ids were found without tool_result blocks immediately afterは、tool_resultを省いた場合や、他の内容のあとに置いた場合のエラーです。... was found without a corresponding ..._tool_result blockは、サーバーツールが解決しないままターンを終えた場合のエラーです。

プログラマティックツール呼び出しでは、同じ応答の形が別の意味になります。クライアントのtool_useはcode_executionツールの中で動くコードから出たもので、そのコードはtool_resultを待って一時停止しています。結果を送ると保留ツールの開始ではなく、実行の再開になります。返信するuserメッセージの形は同じですが、応答のcontainerフィールドのidも一緒に戻します。

エラーにはならないが応答が壊れるケース

tool_resultのあとにテキストを置くこと自体は、前述のとおり構造として正当です。ただし、そのテキストの中身によっては別の問題が起きます。tool_resultのすぐあとに「結果はこちらです」のような要約めいたテキストを差し込むと、Claudeが空の応答を返すようになることがあります。

// 避けたい書き方: tool_resultの直後に要約テキストを添える
{
  "role": "user",
  "content": [
    { "type": "tool_result", "tool_use_id": "toolu_123", "content": "6912" },
    { "type": "text", "text": "Here's the result" }
  ]
}
 
// 推奨: tool_resultだけを返す
{
  "role": "user",
  "content": [
    { "type": "tool_result", "tool_use_id": "toolu_123", "content": "6912" }
  ]
}

これは400エラーにはなりません。それでも、tool_resultのあとに毎回テキストが挿入されると、Claudeはそのパターンに合わせてターンを終えることがあります。結果は、2〜3トークンだけの空の応答とstop_reason: "end_turn"です。ツール結果を返すだけのターンでは、tool_resultブロックのみを送ってください。

構造を直しても空の応答が続くときは、空の応答をそのまま送り返さず、新しいuserメッセージで続きを促します。空の応答を再送しても、Claudeはすでに完了と判断しているので状況は変わりません。

tool_resultの中身は外部由来として扱う

整形ルールとは別に、tool_resultの中身そのものの扱いにも注意が要ります。ツール結果には、Webページ・受信メール・ユーザーのアップロードファイル・外部APIの応答など、自分の管理下にないコンテンツが入り込みます。この中身は、自分の管理下にない外部データとして扱います。第三者が内容に介入できる立場にあれば、Claudeを別方向へ誘導する指示文を埋め込まれるおそれがあります(間接的なプロンプトインジェクション)。

信頼できない可能性のあるコンテンツは、systemプロンプトや素のusertextブロックではなく、tool_resultブロックの中に留めます。

Tool Runnerに任せる選択肢

これらの整形ルールを自前で管理したくない場合、SDKのTool Runnerがツール呼び出しのループ、エラーの包み込み、型の検証を引き受けます。Tool Runnerは各SDKでベータ版として提供されています。承認待ちを挟む、独自のログを残す、条件付きで実行するといった制御が必要なときは、手動のループを組み、本記事の整形ルールを直接扱います。

Tool Runnerの使い方はTool RunnerでAnthropic APIのツール呼び出しループを自動化するにまとめています。ツール定義やモデル選択、料金体系を含めたAPI全体の入口はAnthropic API完全ガイドです。

動作するリクエストの例

以下は、1つのクライアントツールを呼び、その結果を正しい形式で返す一連のやり取りです。

curl -sS https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5-5",
    "max_tokens": 1024,
    "tools": [{
      "name": "get_weather",
      "description": "Get the current weather in a given location",
      "input_schema": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"]
      }
    }],
    "messages": [
      {"role": "user", "content": "What is the weather in Tokyo?"},
      {"role": "assistant", "content": [
        {"type": "tool_use", "id": "toolu_01", "name": "get_weather",
         "input": {"location": "Tokyo, Japan"}}
      ]},
      {"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": "toolu_01", "content": "22 degrees, sunny"}
      ]}
    ]
  }'

3つ目のメッセージは、tool_resultだけを含み、assistantメッセージの直後に置かれています。ここでtool_resultより前にテキストを置くと400、後ろに要約を足すと空の応答の原因になります。

まとめ

400が出たときは、エラー文言の書き出しで読み直す節が決まります。tool_use ids were found without...なら、「tool_resultより先にテキストを置くと400になる」の節へ進みます。見るのはtool_resultの位置と順序です。... was found without a corresponding ..._tool_result blockなら「サーバーツールが未解決のまま残る場合の400エラー」の節です。

同じ400でも、パラメーターの型や料金上限などリクエスト全体の妥当性に起因するエラーは別範囲です。分類とリトライ設計はClaude APIのエラーハンドリング設計にあります。

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