メインコンテンツまでスキップ

ツール検索と遅延読み込み

上級
What you'll learn
  • defer_loading についてほぼ全員が誤解している点、つまりリクエストが小さくなるわけではないことを理解する
  • regex 版と BM25 版の検索バリアントを使い分け、それぞれのクエリ長の上限を把握する
  • server_tool_use / tool_search_tool_result / tool_reference という3つのブロックの組を読み取り、何をそのまま返送すべきかを正確に把握する
  • cache_control の競合を含め、400 を返す5種類のリクエスト形式を避ける
  • 遅延読み込みを MCP コネクタのツールセットに組み込み、組み込みの検索を独自のエンベディングに置き換える

2つの問題、そしてお金に関係するのはそのうち1つだけである理由​

すべてのツール定義を最初にまとめて読み込むと、カタログが大きくなるにつれて2通りの形で行き詰まります。

1つ目は分かりやすいものです。Anthropic 自身の例を挙げると、5つのサーバー(GitHub、Slack、Sentry、Grafana、Splunk)を使う MCP 構成では、Claude が何も作業を始める前にツール定義だけで約5万5千トークンを消費します。これは会話のターンごとに毎回支払う税金のようなものです。

2つ目こそ、アーキテクチャを実際に変えるものです。利用可能なツールが30〜50個を超えると、ツール選択の精度が低下します。 その先ではツールが増えても能力は増えず、モデルが混雑したメニューから選ぶことになるだけです。遅延読み込みは、モデルが現在のリクエストに関連する3〜5個のツールしか目にしないため、コストの問題と精度の問題を同じ一手で解決します。Anthropic は、そのマルチサーバー構成でコンテキストが 85パーセント以上削減されたと報告しています。

経済性や公表されている精度の差分を知りたい場合は、AILmanac の MCP のトークンコストで扱っています。このページは配線(実装)の話です。

誰もが誤解しているメンタルモデル​

コードを書く前に、正しておくべき誤解があります。

defer_loading は送信する内容を減らすわけではありません。遅延対象のツールも含め、すべてのツールの完全な定義(名前、説明、完全な input_schema)を、リクエストのたびに毎回 tools 配列に入れる必要があります。

HTTP リクエストボディは小さくなりません。アップロード帯域も変わりません。API は検索を実行して見つかった参照を展開するために、サーバー側でそれらの定義を必要とするからです。

変わるのはコンテキストウィンドウに入る内容であり、したがって課金対象となる内容です。遅延対象の定義はシステムプロンプトのプレフィックスから除外されます。Claude がその1つを発見すると、API は会話の中に tool_reference ブロックをインラインで追加し、モデルが目にする前に完全な定義へ展開します。

このインライン追加という詳細が、2つ目の分かりにくい点です。展開はプレフィックスではなく会話の中で行われるため、キャッシュされたプレフィックスが無効化されることはありません。遅延読み込みとプロンプトキャッシュは両立します。strict ツール使用も同様です。ツール呼び出しの出力を制約する文法は完全なツールセットから構築されるので、会話の途中でツールが追加されても再コンパイルは発生しません。

有効化する​

Guided walkthrough1 of 5
  1. バリアントを1つ選びます。tool_search_tool_regex_20251119 か tool_search_tool_bm25_20251119 です。これはサーバーツールで、Anthropic のインフラ上で動作し、ベータヘッダーは不要です。

最小限の動作するリクエスト:検索ツール1つ、遅延ツール2つ

curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
  "model": "claude-opus-5-5",
  "max_tokens": 2048,
  "messages": [{"role": "user", "content": "What is the weather in San Francisco?"}],
  "tools": [
    {
      "type": "tool_search_tool_regex_20251119",
      "name": "tool_search_tool_regex"
    },
    {
      "name": "get_weather",
      "description": "Get the weather at a specific location",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": {"type": "string"},
          "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
        },
        "required": ["location"]
      },
      "defer_loading": true
    },
    {
      "name": "search_files",
      "description": "Search through files in the workspace",
      "input_schema": {
        "type": "object",
        "properties": {
          "query": {"type": "string"},
          "file_types": {"type": "array", "items": {"type": "string"}}
        },
        "required": ["query"]
      },
      "defer_loading": true
    }
  ]
}'

検索ツールがプレフィックスに負担させるコストに注目してください。自身のエントリ分だけです。2つの遅延定義はボディには含まれていますが、コンテキストには入りません。

regex か BM25 か​

どちらのバリアントも同じ4つのフィールド、つまりツール名、説明、引数名、引数の説明を検索し、この機能をサポートするすべてのモデルで利用できます。違いは、Claude がクエリに何を書くかだけです。

tool_search_tool_regex_20251119tool_search_tool_bm25_20251119
Claude が書くものPython の re.search() パターン自然言語クエリ
入力フィールドpatternquery
最大長200文字500文字
マッチング大文字小文字を区別しないBM25 ランキング
向いている場合名前に名前空間がある(github_、slack_)説明が文章のように書かれている

regex 版は設定を誤りやすいバリアントです。名前からキーワード検索のように思えますが、そうではないからです。Claude は本物のパターンを書きます。"weather"、"get_.*_data"、"database.*query|query.*database" といった具合です。期待するツールがモデルに見つけられない場合は、モデルのせいにする前に、自分でパターンを試してください。

失敗した regex マッチをローカルで再現する

import re
# Claude writes broad patterns, e.g. ".*weather.*" — not exact names.
# Search the same four fields the API searches.
haystacks = ["get_weather", "Get the weather at a specific location", "location", "unit"]
print(any(re.search(r"weather", h, re.IGNORECASE) for h in haystacks))

マッチ漏れへの実践的な対処は、より巧妙なパターンではほぼありません。説明文にキーワードを入れることです。ユーザーが実際に使う言葉を書いてください。ツール名はサービスごとに名前空間を付け、1つのパターンでグループ全体を拾えるようにします。

レスポンスを読む​

検索が成功すると、アシスタントのターンに3種類の新しいブロックが次の順で現れます。

{
"role": "assistant",
"content": [
{ "type": "text", "text": "I'll search for tools to help with the weather information." },
{
"type": "server_tool_use",
"id": "srvtoolu_01ABC123",
"name": "tool_search_tool_regex",
"input": { "pattern": "weather", "limit": 10 }
},
{
"type": "tool_search_tool_result",
"tool_use_id": "srvtoolu_01ABC123",
"content": {
"type": "tool_search_tool_search_result",
"tool_references": [{ "type": "tool_reference", "tool_name": "get_weather" }]
}
},
{
"type": "tool_use",
"id": "toolu_01XYZ789",
"name": "get_weather",
"input": { "location": "San Francisco", "unit": "fahrenheit" }
}
],
"stop_reason": "tool_use"
}

扱いのルールは3つです。

  1. srvtoolu_... の ID に対して tool_result を返してはいけません。 検索はすでに Anthropic のサーバー上で実行済みです。それに応答すると API はリクエストを拒否します。tool_result を返すのは toolu_... の ID に対してだけで、そこからは通常のツール使用になります。
  2. tool_reference ブロックを自分で展開してはいけません。 対応する定義が tools にある限り、API が展開します。
  3. tool_search_tool_result は履歴にそのまま残してください。 後続のターンが、どのツールがすでに発見済みかを知る手がかりになります。

何にもマッチしなかった検索はエラーではありません。tool_references 配列が空の tool_search_tool_search_result が返り、次にどうするかは Claude が判断します。

limit についてもう1つ見落としやすい点があります。デフォルトは 5 件ですが、Claude は自身の検索入力で 1 から 10,000 までの任意の値を設定できます。あなたが直接制御することはできません。大量の定義が流れ込む数に厳格な上限が必要なら、それは後述のカスタム実装を選ぶ理由になります。

5つの 400​

このうち4つは推論の前に起こるリクエスト形式のエラーで、1つは誰も予期しない競合です。

Guided walkthrough1 of 5
  1. "At least one tool must have defer_loading=false. All tools cannot be deferred." よくある原因は、検索ツールを含む配列全体に defer_loading: true を一括で付けてしまうループです。検索ツールは決して遅延させないでください。

実行中の検索失敗は異なります。HTTP 200 で返り、エラーは tool_search_tool_result_error オブジェクトの中に入ります。

error_code何が起きたか
invalid_tool_input不正な正規表現、または200文字の制限を超えたパターン
unavailable検索を実行できなかった(タイムアウトまたはサービス利用不可)
too_many_requestsツール検索操作に特化したレート制限
execution_time_exceeded検索が実行時間の上限を超えた

これらは回復可能なものとして扱ってください。会話は損なわれておらず、ターン全体を失敗させるよりも、Claude に別のパターンで再試行させるのが誠実な対応であることがほとんどです。周辺のリトライ方針はエラーとレート制限を参照してください。

プロンプトキャッシュ​

結論は良好です。遅延定義はキャッシュされたプレフィックスの外にあるため、会話の途中でツールを追加・発見してもキャッシュは無効化されません。プレフィックスのルールはプロンプトキャッシュを、ヒットを実証する必要がある場合はキャッシュ診断を参照してください。

落とし穴は上に挙げたものです。defer_loading: true と cache_control は1つのツールに対して相互排他です。現在ブレークポイントが長い配列の最後のツールにあり、その配列を遅延させると、気づかないうちに「キャッシュあり」から「400」へ移ってしまいます。ブレークポイントは、常に読み込んでおくつもりのツールに固定してください。

MCP コネクタ:ツールではなくツールセットに設定する​

MCP コネクタを通じてリモートサーバーからツールが届く場合、個々の定義には触れません。定義はあなたのものではないからです。mcp_toolset エントリで設定します。サーバー全体には default_config で一度に、ツールごとには configs で指定します。

常に必要な1つのツールを除いて、MCP サーバー全体を遅延させる

{
"type": "mcp_toolset",
"mcp_server_name": "observability",
"default_config": { "defer_loading": true },
"configs": {
  "search_events": { "defer_loading": false }
}
}

これは実際に最も早く効果が出る形です。複数のサーバーを集約すると、まさにこの機能が想定する200個以上のツールという状況になり、常に使える状態にしておく価値のある入口ツールが1つか2つはほぼ確実にあります。

検索を独自のものに置き換える​

組み込みのバリアントはどちらも字句ベースです。ツールが表現ではなく意味で見つけやすい場合は、検索を自分で実装してください。通常のカスタムツールを定義し、通常の tool_result の中で tool_reference ブロックを返します。

API が展開してくれるクライアント側の検索結果

{
"type": "tool_result",
"tool_use_id": "toolu_your_tool_id",
"content": [
  { "type": "tool_reference", "tool_name": "discovered_tool_name" }
]
}

参照する名前はすべて、トップレベルの tools 配列に完全な定義が必要で、通常は defer_loading: true を付けます。展開の経路は同一で、入れ替えるのは検索(取得)の部分だけです。形式の違いに注意してください。tool_search_tool_result はサーバー側の形式で、組み込みバリアントの内部用です。カスタムツールは、上に示した標準の tool_result 形式を返します。Anthropic はエンベディングを使った実践的なレシピを公開しています。

これは limit の問題からの逃げ道でもあります。いくつの参照を返すかは、自前の検索が決めます。

使わないほうがよい場合​

前提が成り立たないとき、遅延読み込みは何も得をもたらさず、検索の往復分のコストだけがかかります。次のいずれかに当てはまる場合に使ってください。

  • 利用可能なツールが10個以上
  • ツール定義が合計でおよそ1万トークンを超える
  • カタログの拡大に伴い、選択精度が目に見えて低下している
  • 複数の MCP サーバーを集約している(200個以上のツール)
  • ツールライブラリが増え続けている

ツールが10個未満の場合、すべてのツールが毎回のリクエストで使われる場合、またはカタログ全体が約100トークン未満の場合は、通常のツール呼び出しのままにしてください。課金の形にも注意してください。ツール検索は独立したサーバーツールとしては計測されません。 usage.server_tool_use にツール検索用のフィールドはありません。かかるトークンは、コンテキストに取り込まれる定義の分だけで、他のツール定義と同じ価格です。

キャパシティプランニング用のハードな上限は次のとおりです。1リクエストあたり遅延ツール10,000個、regex パターン200文字、BM25 クエリ500文字。

プラットフォーム上の注意点​

1日を無駄にしやすい、デプロイ面での補足が2つあります。Amazon Bedrock では、サーバー側のツール検索は InvokeModel API でのみ動作し、Converse API では動作しません。Claude Platform on AWS では、Messages API を直接使い、InvokeModel/Converse の区別がないため、Claude API とまったく同じように動作します。Claude Opus 4.1 以前は、この機能をまったくサポートしていません。

ツール検索の用語
Enter キーまたはスペースキーでカードを裏返します。左右の矢印キーでカードを移動できます。用語を表示しました。
1 / 8

理解度チェック

0/6
  1. 405個のツールのうち400個に defer_loading: true を設定しました。POST する JSON のサイズはどうなりますか?
  2. ループが tools 配列全体に defer_loading: true を付けてしまいました。API は何を返しますか?
  3. レスポンスに、id が srvtoolu_01ABC の server_tool_use ブロックと、id が toolu_01XYZ の tool_use ブロックが含まれています。何を返送しますか?
  4. 配列の最後のツールにすでにキャッシュブレークポイントがありました。この配列を遅延させると、何が壊れますか?
  5. regex 版で、Claude が Grafana のツールをなかなか見つけられません。最初の一手として最善なのは?
  6. ツール検索はどのように課金されますか?

出典とさらなる読み物​

次へ​