defer_loadingでプロンプトキャッシュを壊さずツールを追加する仕組み
Claude APIのdefer_loadingは、ツール定義をシステムプロンプトのプレフィックスから外すことでプロンプトキャッシュを保ったままツールを増やせます。仕組みと制約を解説します。
defer_loadingはツール定義をどこから除外するのか
Claude APIのdefer_loading: trueは、ツール定義をリクエストのシステムプロンプトのプレフィックスから除外するプロパティです。除外されたツールはキャッシュされる側に入らないので、defer_loading付きのツールを増やしても既存のキャッシュは無効になりません。
通常はtools配列のツール定義を1つ変えるだけで、プレフィックス階層(tools→system→messages)のキャッシュが丸ごと無効になります。遅延させたツールはこの階層の外に置かれるため、追加しても壊れるものがありません。mid-conversationのツール変更でtools配列をキャッシュごと保つ手法とも、狙いが重なります。
キャッシュブレークポイント(cache_control)をどこに置くかはプロンプトキャッシュのブレークポイントをツール定義のどこに置くかで扱っています。こちらは、そこから先のdefer_loading固有の制約(cache_controlとの併用、全遅延の禁止、MCP、エラー処理)を掘り下げます。
遅延されたツールはどこに現れるのか
遅延したツールを、Claudeは直接呼び出せません。会話の最初にClaudeの文脈にあるのは、Tool Search Toolと非遅延のツールだけです。Tool Search Toolの変種は2つあります。
tool_search_tool_regex_20251119: Claudeが正規表現でツールを探すtool_search_tool_bm25_20251119: Claudeが自然言語のクエリでツールを探す
発見から呼び出しまでは、次の流れで進みます。Tool Search Toolが生まれた経緯とツール選択精度への効果はAdvanced Tool Useで扱っています。
遅延ツールが呼ばれるまで
- 1
Claudeが検索する
Tool Search Toolに
server_tool_useとして検索を依頼します。検索はAnthropicのサーバー側で走るので、クライアントはtool_resultを返しません。 - 2
ヒットしたツールが tool_reference で返る
既定では最大5件が
tool_referenceブロックとして会話の本文側に並びます。プレフィックスは書き換わりません。 - 3
APIが完全な定義に展開する
tool_referenceはClaudeに渡る前に、名前・説明・スキーマを持つ定義へ展開されます。 - 4
Claudeが呼び出す
発見されたツールを、通常のツールと同じように
tool_useで呼びます。
次のリクエストは、遅延ツールの定義をtools配列に書いたままです。違うのはdefer_loading: trueが付いている点だけです。
{
"tools": [
{ "type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex" },
{
"name": "query_database",
"description": "Run a read-only SQL query against the reporting database",
"input_schema": { "type": "object", "properties": { "sql": { "type": "string" } } },
"defer_loading": true
}
]
}発見の前後でプレフィックスは変わらないため、キャッシュは発見前のターンから発見後のターンまで連続してヒットします。公式のキャッシュ設計ページでも、Tool searchの行は「発見されたツールはtool_referenceとして読み込まれ、プレフィックスのキャッシュは保たれる」とされています。
キャッシュを壊す変更は、遅延では防げない
遅延が守るのは、ツール定義の増減に伴うプレフィックス側の無効化です。キャッシュを壊す要因はほかにもあり、次の変更は遅延ツールを使っていても同じ範囲を無効にします。
| 変更 | 無効になる範囲 |
|---|---|
| ツール定義の変更 | 無効になる範囲キャッシュ全体(tools・system・messages) |
tool_choiceの変更 | 無効になる範囲messagesのキャッシュ |
disable_parallel_tool_useの変更 | 無効になる範囲messagesのキャッシュ |
| 画像の有無の切り替え | 無効になる範囲messagesのキャッシュ |
| WebSearch・引用の切り替え | 無効になる範囲systemとmessagesのキャッシュ |
非遅延側のツールを書き換えれば、遅延ツールの数にかかわらずキャッシュ全体が無効になります。遅延させても、毎ターン使う中核ツールの定義は安定させておく必要があります。tool_choiceをターンごとに変える運用も、遅延とは無関係にmessagesのキャッシュを落とします。
strict modeのグラマー構築とは独立して動く
defer_loadingはstrict mode(ツール呼び出しの出力をスキーマに厳密一致させる仕組み)のグラマー構築とは別軸です。グラマーは全ツールセットから作られるので、どのツールを遅延させても再コンパイルは起きず、プロンプトキャッシュとグラマーキャッシュの両方が維持されます。1つのツールにdefer_loadingとstrictを併記することもできます。
遅延ツールとcache_controlの併用は公式ページで記述が分かれる
Tool Searchの公式ページでは、defer_loading: trueのツールにcache_controlを同時に指定するとAPIが400を返すとされています。一方、ツール定義のプロパティ一覧の説明には、defer_loadingとcache_controlとstrictを同じツールに設定できるとあります。
遅延ツールはキャッシュされるプレフィックスに含まれません。そこにブレークポイントを置いても対象がないので、400を返すという前者の記述のほうが、ツールセットの注意書きとも整合します。ブレークポイントは非遅延のツールに置きます。置き方の基礎はClaude APIの明示的キャッシュブレークポイントの置き方にあります。
Computer use・Browser useのクライアントツールセットは、遅延の扱いが一段厳しくなります。
通常のツールとクライアントツールセットの違い
通常のツール
defer_loadingはツール定義に直接書きます。
クライアントツールセット
defer_loadingはconfigsの中でメンバーごとに設定し、エントリ自体に書くとリクエストが拒否されます。有効なメンバー全員で同じ値にそろえ、発見されると全員が一括で展開されます。cache_controlはツールセットのエントリにだけ置けますが、遅延メンバーのエントリには置きません。
全部を遅延させるとなぜ400になるのか
全ツールにdefer_loading: trueを付けると、Tool Search Tool自身も遅延されて、発見の入口がなくなります。APIは次の400を返します。
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "At least one tool must have defer_loading=false. All tools cannot be deferred."
}
}原因の典型は、Tool Search Toolにdefer_loadingを付けたケースです。直し方は、Tool Search Toolの定義から外すだけです。もう1つ、tool_referenceが参照する名前がtools配列に無いとTool reference 'unknown_tool' not found in available toolsが返ります。参照先の定義を、通常はdefer_loading: true付きで足せば解消します。
これらはリクエスト自体が拒否される400です。検索の実行が失敗する場合は、HTTPステータスは200のまま、結果ブロックの中にエラーが入ります。
{
"type": "tool_search_tool_result",
"tool_use_id": "srvtoolu_01ABC123",
"content": {
"type": "tool_search_tool_result_error",
"error_code": "invalid_tool_input",
"error_message": "Invalid regular expression pattern: missing ) at position 1"
}
}error_codeは4種類で、どれも「ツールが無かった」のではなく「検索が失敗した」ことを示します。
invalid_tool_input: 入力が不正。正規表現が壊れている、パターンが200文字を超えている、などunavailable: 検索を実行できなかった。タイムアウトやサービス側の障害などtoo_many_requests: ツール検索へのレート制限超過execution_time_exceeded: 検索が実行時間の上限を超えた
HTTPが200なので、ステータスコードだけを見るエラー処理はこれらを素通りさせます。結果ブロックのcontent.typeがtool_search_tool_result_errorかどうかを、別に確認する必要があります。
MCPとカスタム検索でのdefer_loading指定
MCPコネクタ(mcp_toolset)経由のツールには、個々の定義にdefer_loadingを書きません。サーバー全体にはdefault_configで、特定のツールにはconfigsで指定します。allowlistやdenylistを含む接続設定の全体はClaude APIのMCPコネクタにまとめています。
{
"type": "mcp_toolset",
"server_name": "enterprise-tools",
"default_config": { "defer_loading": true },
"configs": {
"search_docs": { "defer_loading": false }
}
}この例では、enterprise-toolsの全ツールが既定で遅延し、頻繁に使うsearch_docsだけが非遅延で残ります。サーバーごとにdefault_configを持てるので、サーバーAは全遅延、サーバーBは一部非遅延という分け方もできます。ClaudeとPrometheusの連携のようにツールが多くなりがちなサーバーで、一括指定が効きます。
埋め込み検索のような独自の検索ロジックは、カスタムツールとして実装し、ヒットしたツール名をtool_referenceで返せば、ビルトインと同じ展開経路に乗ります。実装手順はTool Searchを自作する記事にあります。
{
"type": "tool_result",
"tool_use_id": "toolu_your_search_call",
"content": [{ "type": "tool_reference", "tool_name": "discovered_tool_name" }]
}返したツール名には対応する定義がtools配列に必要で、通常はdefer_loading: trueを付けておきます。
Claude Codeでは、MCPツールの遅延をENABLE_TOOL_SEARCH環境変数が制御します。未設定ならMCPツールは既定で遅延され、trueにするとMCPツールをすべて遅延します。CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETASを設定していると、自分で設定したこの変数の値は無視されます。ANTHROPIC_BASE_URLが第一者以外のホストを指すとMCPのツール検索は既定で無効になり、プロキシがtool_referenceを通すならENABLE_TOOL_SEARCH=trueで戻せます。Microsoft FoundryのAzureホスト型デプロイはツール検索をサーバー側で拒否するため、この変数では上書きできません。trueでも、Google CloudのAgent Platformで動くClaude 4.5世代より前のモデルでは、MCPツールは遅延されません。
値はtrueのほかに3つあります。autoは、ツール定義の合計がコンテキストウィンドウの10%未満なら最初からまとめて読み込み、10%に達すると全部を遅延させます。auto:Nは閾値をauto:5のように%で指定し、falseは全ツールを最初から読み込みます。
生のAPIでdefer_loadingを直接書く場合とは別の設定経路です。
上限と、使うかどうかの目安
数値の上限は次のとおりです。
ツール検索の上限
遅延できるツール
10,000個
1リクエストあたり、defer_loading: true の数
検索結果の既定
5件
Claudeがlimitで1〜10,000に変更できる
正規表現パターン
200文字
これを超えると invalid_tool_input
BM25のクエリ
500文字
自然言語クエリの上限
遅延を使う目安として、公式は次の条件のどれかを挙げています。
- 利用できるツールが10個以上ある
- ツール定義が合計で1万トークンを超える
- ツールが増えて選択精度が落ちている(30〜50個を超えると落ち始める)
- 複数のMCPサーバー(200個以上のツール)を束ねている
逆に、ツールが10個未満、全ツールを毎回使う、定義が合計100トークン未満のときは、通常のツール呼び出しの方が向いています。検索の課金について、ツール検索は別のサーバーツールとして計上されません。検索で読み込まれた定義は、通常のツール定義と同じ入力トークンです。
Bedrockではサーバー側のツール検索はInvokeModel APIだけで使え、Converse APIでは使えません。
使い分け早見表
| 状況 | defer_loadingの向き | 理由 |
|---|---|---|
| 常時使う3〜5個の中核ツール | defer_loadingの向き付けない | 理由公式がこの個数を非遅延で残すよう勧めている |
| 使用頻度の低い数十〜数百のツール | defer_loadingの向き付ける | 理由プレフィックスから外れ、キャッシュを保ったまま追加できる |
| ツール構成を頻繁に変える運用 | defer_loadingの向き付ける | 理由遅延側の増減ではプレフィックスが変わらない |
| MCPサーバー経由のツール | defer_loadingの向きサーバー単位のdefault_config | 理由個々の定義に触れずに一括で適用できる |
名前にgithub_やslack_のような接頭辞をそろえると、1回の検索で関連ツールがまとめてヒットします。システムプロンプトに「SlackやGitHubのツールを検索できます」と利用可能なカテゴリを書いておく工夫も、公式が挙げています。
ストリーミングでの見え方
ストリーミングでは、検索の呼び出しはserver_tool_useブロックのcontent_block_startとして流れ、検索パターンはinput_json_deltaで届きます。検索結果は別のブロックで、tool_search_tool_resultの中にtool_referenceの一覧が入っています。
content_block_start: server_tool_use (name: tool_search_tool_regex)
content_block_delta: {"pattern":"weather"}
content_block_start: tool_search_tool_result
→ tool_references: [{"type":"tool_reference","tool_name":"get_weather"}]「今どのツールが使えるようになったか」を追うなら、tool_search_tool_resultのtool_referencesを起点にします。
ツールの出し入れとキャッシュの安定を分ける
Prompt Cachingの基本は、変わりにくいものを前に、変わりやすいものを後ろに置くことです。遅延は、ツール数の増減をこの前提に持ち込まなくて済む設計です。頻繁に出入りするツールを遅延に倒せば、ツールカタログの入れ替えとキャッシュの安定を別々に管理できます。
効きやすいのは、複数のMCPサーバーを組み合わせる構成や、テナントごとにツール構成が違う構成です。共通の中核ツールを非遅延で共有していれば、テナントごとの遅延ツールの違いは会話本文側にしか出ないので、プレフィックスのキャッシュは共有できる設計になります。ただし、テナント別のツール構成でキャッシュがどれだけ共有されるかは、usageのcache_read_input_tokensで自分の構成について確かめる必要があります。
まとめ
どのツールを非遅延に残し、cache_controlをどこに置くかは、毎ターン使うかどうかで決まります。運用では、usageのキャッシュ読み取りと、検索結果ブロックのcontent.typeの両方を見ておくと、崩れた箇所を切り分けられます。サーバーツールの実行結果側の自動キャッシュは別の仕組みで、サーバーツール結果は5分TTLで自動キャッシュされる仕組みで扱っています。