Claude Media
mid-conversationのツール変更でtools配列をキャッシュごと保つ

mid-conversationのツール変更でtools配列をキャッシュごと保つ

Claude APIのmid-conversationツール変更は、tools配列を編集せずにツールの提供・撤回を切り替え、プロンプトキャッシュを壊さない仕組みです。対応モデル・ヘッダー・配置制約・メッセージ内定義を扱います。

Claude APIで会話の途中からツールを増やしたり止めたりしたいとき、tools配列を書き換えるとプロンプトキャッシュ全体が無効になります。mid-conversationのツール変更は、tools配列は最初から一度も変えず、role: "system"メッセージの中のtool_additionとtool_removalブロックでツールの提供・撤回だけを切り替える仕組みです。ベータ機能で、Claude APIではinline-tools-2026-09-15ヘッダーを付けます。systemメッセージ自体の配置制約はmid-conversation system messageの解説にまとめています。

tools配列を編集するとキャッシュ全体が壊れる理由

tools配列は、キャッシュのハッシュ対象になるリクエストのプレフィックスの中で、トップレベルのsystemフィールドよりもさらに前に位置します。プロンプトキャッシュはプレフィックスの先頭から一致する範囲までしか再利用できません。toolsを1個増減させるだけで、そこから後ろの全ターン(システムプロンプト・会話履歴の全体)がキャッシュミスになります。

mid-conversationシステムメッセージが解決する「systemフィールドを編集するとキャッシュが壊れる」問題と、同じ構造の問題がツール側にもあるということです。

解き方も同じ発想です。ツールの完全な集合は、最初のリクエストで一度だけtoolsに宣言し、以後は一切編集しません。途中でツールを増減させたいときは、そのタイミングでsystemメッセージを1件追加し、tool_addition・tool_removalブロックで提供状態だけを切り替えます。tools配列が変わらないので、それより前のキャッシュ済みプレフィックスはそのまま再利用されます。

tool_additionとtool_removalの書き方

tool_additionとtool_removalは、role: "system"メッセージのcontent配列に入れるブロックで、同じメッセージ内でtextブロックと混在させられます。変更は、そのメッセージの位置から会話の先へ向かって効きます。次のリクエストは、最初のuserターンの後でget_weatherを撤回する例です。

curl https://api.anthropic.com/v1/messages \
  -H "anthropic-beta: inline-tools-2026-09-15" \
  -d '{
    "model": "claude-opus-5-5",
    "tools": [{"name": "get_weather", "description": "...", "input_schema": {...}}],
    "messages": [
      {"role": "user", "content": "Say OK."},
      {
        "role": "system",
        "content": [
          {"type": "tool_removal", "tool": {"type": "tool_reference", "name": "get_weather"}}
        ]
      }
    ]
  }'

toolフィールドの既定の使い方は、ツールを新たに定義するのではなく、toolsに宣言済みのツールを名前で参照する形です。参照の種類は3つあります。

3種類

ツールの参照方法

  • tool_reference

    通常のツール用です。{"type": "tool_reference", "name": "..."}でtools配列内の名前を指定します。

  • mcp_tool_reference

    MCPコネクタ経由のツールを1つずつ指します。server_nameとnameの組で指定します。

  • mcp_toolset_reference

    MCPサーバーのツールをまとめて指します。server_nameだけで指定します。

toolsに宣言されていない名前を参照すると400エラーになります。Claude APIではerror.details.error_codeがtool_reference_unresolvedになるので、エラーの切り分けに使えます。

未提供のまま置いておくdefer_loading

toolsに宣言したツールは、defer_loading: trueを付けない限り会話の最初から常にClaudeへ提供されます。defer_loading: trueを付けたツールは、toolsに定義が載っていても、tool_additionブロックで呼び出すまでモデルには見えません。

tool_additionにはもう1つ用途があります。一度tool_removalで撤回したツールを、後からtool_additionで再提供できます。初回の開放と再提供が同じブロックで書けるので、「撤回済み」と「まだ見せていない」を区別して管理する必要がありません。

ブロック / 宣言効果
toolsに通常宣言効果会話の最初から提供される
toolsにdefer_loading: trueで宣言効果tool_additionまで非提供
tool_addition効果未提供のツールを提供状態にする(初回開放も再提供も)
tool_removal効果提供中のツールを撤回する

調査フェーズから実行フェーズへツールを開放する流れ

コードベースを調べてから修正を加えるエージェントを例にします。読み取り系は通常どおり宣言し、書き込み系はdefer_loading: trueで宣言だけしておく構成です。

手順

書き込みツールを段階的に開放する

  1. 1

    最初のリクエストで宣言を固定する

    read_file・grepは通常どおり、write_fileはdefer_loading: trueを付けてtoolsに入れます。Claudeに見えるのは読み取り系だけです。

  2. 2

    承認のuserターンの直後で開放する

    ユーザーが修正の承認を返したら、write_fileをtool_referenceで参照するtool_addition入りのsystemメッセージを1件追加します。toolsは最初から変わらないので、調査フェーズで積んだキャッシュは生きたままです。

  3. 3

    不要になったら撤回する

    修正が終わった後のターンで書き込みツールが要らなければ、tool_removalで撤回します。モデルが誤って書き込みに使う余地自体がなくなります。

ツールをメッセージの中で定義する(ベータ)

最初のリクエストの時点で存在を知らないツールは、toolsに宣言できません。inline-tools-2026-09-15ヘッダーを付けると、tool_additionがツールの完全な定義を持てます。名前で参照する従来の形と、定義そのものを運ぶ形の違いは次のとおりです。

くらべる

tool_additionの2つの渡し方

tool_reference

参照で渡す

toolsに宣言済みのツールを名前で指します。最初から分かっているツールを、defer_loadingと組み合わせて後から見せる使い方です。旧ヘッダーmid-conversation-tool-changes-2026-07-01でも動き、Claude API・Amazon Bedrock・Google Cloudで使えます。

tool_definition

定義で渡す

toolに{"type": "tool_definition", "definition": {...}}を入れ、definitionにtoolsの1エントリと同じ内容を書きます。会話の途中で初めて決まるツールや、あとからスキーマが変わるツールに向きます。旧ヘッダーでは受け付けられません。

definitionには、カスタムツールのほか、Anthropic定義のクライアントツール・サーバーツールを使えます。cache_controlやdefer_loadingも通常どおり書けます。ベータ期間中は、コンピューター使用ツールのように、メッセージ内で定義できないツール型があり、その場合は400エラーで返ります。そうしたツールはtoolsに宣言して参照で追加します。

同じ名前で別の定義を送ると、その位置から先は新しい定義に置き換わります。スキーマの更新や、サーバーツールを新しいバージョンへ移すときの方法です。同じ定義をもう一度送っても何も変わらないので、リトライ時に再送しても安全です。別種のツールと名前が衝突すると400エラー(tool_name_conflict)になります。

定義で渡すときの規則は4つあります。

  • 先に分かっているものはtoolsに置く。後から見せたいだけならdefer_loading: trueで宣言して参照で開放し、定義で渡すのは最初に未知のものか、あとで変わるものだけにします。
  • toolsに非遅延のツールを最低1つ残す。全部が遅延だと、最初に定義で渡したツールがプロンプトの先頭側を変え、そのリクエストで1回キャッシュを丸ごと外します。ツール検索ツールは非遅延として数えられます。
  • 日付付きのツール型は自分のベータヘッダーを持つ。定義で渡したサーバーツールが専用ヘッダーを要求するなら、以後のリクエストにも毎回付けます。
  • cache_controlはブロックかdefinitionのどちらか一方にだけ書く。ブレークポイントの上限にも数えられ、遅延ツールの定義には付けられません。

上限もあります。メッセージ内で定義したツールが次のいずれかを超えると、400エラー(available_tools_limit_exceeded)になります。

  • メッセージ後に利用可能な遅延ツールが1万個を超える
  • 最初のuserメッセージ以降に定義したツールが1万個を超える
  • 最初のuserメッセージ以降に定義して今も利用可能なツール定義の合計が4MB(4,194,304バイト)を超える
  • 描画されたツールのテキスト自体が4MBを超える

MCPサーバーを途中から足す

MCPコネクタのサーバーも途中から足せます。inline-tools-2026-09-15に加えてmcp-client-2026-09-15ヘッダー(Claude APIで使えるヘッダーです)を送り、tool_additionのdefinitionにmcp_toolsetを入れます。接続先のURLやトークンは従来どおりmcp_serversに書き、tool_additionには載せません。mcp_toolsetの中身はtoolsに置くものと同じで、default_configやconfigsも使えます。

どこに置けてどこに置けないか

内容(text・tool_addition・tool_removal)を持つsystemメッセージは、userターン(tool_resultを含むものも可)の直後、またはサーバーツール結果で終わるassistantターンの直後に置けます。その次はassistantターンが続くか、メッセージ配列の末尾である必要があります。

ただしtool_addition・tool_removalには例外が1つあります。サーバーツール結果で終わるassistantターンは、一時停止(pause)したターンです。その直後には、textブロックだけのsystemメッセージなら置けますが、ツール変更のブロックは置けません。

状況tool_addition / tool_removalの可否
userターンの直後tool_addition / tool_removalの可否可
tool_resultを含むuserターンの直後tool_addition / tool_removalの可否可(エージェントループの定番配置)
一時停止したassistantターンの直後tool_addition / tool_removalの可否不可。ターンを再開してから次のsystemメッセージで送る
tool_useブロックとtool_resultの間tool_addition / tool_removalの可否不可(400エラー)
メッセージ配列の先頭tool_addition / tool_removalの可否不可(contentを持つsystemメッセージは先頭に置けない)

連続するsystemメッセージは1つのまとまり(システムセクション)として扱われ、配置ルールもまとめて判定されます。位置の制約がないメッセージ(たとえばeffortだけを設定する空contentのメッセージ)にtext入りのメッセージを続けると、全体が配置ルールに従います。

ターン限りで消えるsystemメッセージ(clear_at: "next_user_message")は、テキスト専用です。tool_addition・tool_removalを入れると400エラーになるので、ツール変更はclear_atなしの別のsystemメッセージで送ります。

プロンプトキャッシュと組み合わせるときの実務

キャッシュはリクエストにcache_controlが含まれるときだけ働きます。トップレベルの自動キャッシュを使うか、明示的なブレークポイントを変わらない範囲の末尾(ツール定義の末尾や会話履歴の安定した位置)に置きます。ツール変更のsystemメッセージ自体はキャッシュエントリを作りません。

ツール変更のメッセージはキャッシュ済みプレフィックスより後ろに追加されるので、プレフィックスのハッシュは変わらず、次のリクエストもそこまではキャッシュから読まれます。一度会話に入ったメッセージは安定した履歴の一部になり、次のターンでブレークポイントを後ろへ進めれば、通常のターンと同じようにキャッシュから読まれます。

一度送信したツール変更入りのsystemメッセージを後から編集・削除すると、その時点以降のキャッシュがすべて無効になります。Claude Fable 5.1・Claude Opus 5.5・Claude Sonnet 5.5では、以降のassistantターンのthinkingブロックも無効になります。変更が必要なら書き換えず、新しいsystemメッセージを追記します。compactionとプロンプトキャッシュを両立させるときの「履歴を書き換えない」原則と同じ考え方です。

セキュリティ上の注意 — 外部データを判断材料にしない

systemメッセージの中身は、Claudeにとって運営者からの指示として扱われます。tool_addition・tool_removalも同じで、どのツールを提供・撤回するかという判断が、強い権限を持つ指示としてモデルに届きます。

ツール結果や外部から取得したドキュメントの内容をそのまま条件にして、ブロックを機械的に組み立てる実装は避けます。信頼できないテキストで提供・撤回を切り替えると、攻撃者の仕込んだ入力で意図しないツールが開放される経路になりかねません。ツール結果は通常どおりtool_resultに収め、ブロックを発行するかどうかはアプリケーション側のロジックで決めます。

よくあるつまずき

  • ベータヘッダーを付け忘れる: ヘッダーが無いと、このベータの機能は使えません。Claude APIではinline-tools-2026-09-15を付けます。
  • Sonnet 5で使おうとする: 対応はFable 5.1・Mythos 5.1・Fable 5・Mythos 5・Opus 5.5・Opus 4.8・Opus 5・Sonnet 5.5です。Sonnet 5は対象外で、トップレベルのsystemフィールドを使います。
  • 撤回したツールをtoolsから消してしまう: tool_removalは一時的な非提供で、削除ではありません。再提供したくなったときのために、tools側の定義は残します。

まとめ

toolsを最初の宣言のまま固定し、提供状態だけをsystemメッセージで切り替えると、長いキャッシュ済み会話でもツール構成を変えられます。ツールが最初から分かっているならdefer_loadingと参照、途中で初めて決まるならtool_definitionと、使い分けの軸はそこに絞れます。

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