yoi/docs/ref/claude-code-deferred-tools.md

21 KiB
Raw Blame History

リファレンス: Claude Code の deferred tools 機構

調査日: 2026-04-30。Claude Codeこのセッションのハーネスが採用しているツール提示・遅延ロード方式について、自身の system prompt と system-reminder の内容から観察できた事実と、そこから合理的に推測される実装方針をまとめる。Pod / insomnia でハーネス側のツール抽象を設計する際の参考資料。

ハーネス内部の実装は確認していないため、観察事実と推測を分けて記載する。


1. 観察事実

1.1 ツール定義の表現

system prompt 冒頭に、ツール定義が以下の形式の テキストブロック として埋め込まれている:

<functions>
<function>{"description": "...", "name": "Read", "parameters": {...JSONSchema...}}</function>
<function>{"description": "...", "name": "Edit", "parameters": {...}}</function>
...
</functions>

<function> は JSONSchema を含む単行 JSON。Anthropic API の tools パラメータに渡る構造化データではなく、プロンプトの一部としてレンダリングされたテキストである。

1.2 ツール呼び出しの表現

モデル側の出力も XML タグ列で行う(function_calls / invoke / parameter などのタグ)。ネイティブの tool_use content block ではない。ハーネスがこのテキストをパースし、対応するツールを実行する。

1.3 deferred tools の宣言

特定のツール群は最初の <functions> ブロックに含まれず、代わりに system-reminder で 名前リストだけ が提示される:

The following deferred tools are now available via ToolSearch.
Their schemas are NOT loaded — calling them directly will fail with InputValidationError.
Use ToolSearch with query "select:<name>" to load tool schemas before calling them:
AskUserQuestion
CronCreate
...

このリストには名前のみで schema は無い。

1.4 ToolSearch によるロード

ToolSearch 自体は最初から完全なスキーマで利用可能(通常のツールとして system prompt 冒頭の <functions> に含まれる)。select:<name> クエリを投げると、tool_result の本文として:

<functions>
<function>{"description": "...", "name": "AskUserQuestion", "parameters": {...}}</function>
</functions>

が返ってくる。「上のツール定義と同じエンコーディング」と明示されており、以降そのツールは通常通り呼べるようになる。

1.5 パラメータ値のエンコーディング規約

ツール呼び出しの <parameter> タグの中身は、値の型に応じて異なるエンコーディングを使う:

  • プリミティブ (string / number / boolean): そのままテキスト
  • 配列・オブジェクト: JSON 文字列としてシリアライズしてテキストに

system prompt 末尾にも以下のように明記されている:

When making function calls using tools that accept array or object parameters
ensure those are structured using JSON.

つまり「XML が外側の骨格、中身は型に応じてテキスト/JSON」という二層構造。


2. パラダイムの推測: prompted tool use

観察事実から、Claude Code は Anthropic API の structured tool use ではなく prompted tool use を採用している(あるいはハイブリッド)と判断できる。

項目 structured prompted
ツール定義 API リクエストの tools 配列 system prompt 内のテキスト
ツール呼び出し tool_use content block テキスト中の特定タグ
検証 API レイヤschema 強制) ハーネスレイヤ(自前パース&検証)
拡張性 API の制約に縛られる 自由XML/JSON/独自形式)

Claude Code の振る舞いはすべて prompted 側に寄っている。API は単に「テキストを生成するモデル」として使われ、ツール抽象は完全にハーネスのレイヤにある。


3. deferred tools が成立する仕組み(推測)

prompted tool use 前提で考えると、deferred tools は素直に説明できる:

  1. ハーネスは 全ツールのレジストリ を内部に持つ
  2. リクエスト時、初期 <functions> ブロックには「コアツール + ToolSearch」だけを描画。残りは system-reminder で名前のみ列挙
  3. モデルが ToolSearch を呼ぶと、ハーネスはレジストリから該当 schema を引き、tool_result の テキスト として <function>...</function> を返す
  4. モデルはそのテキストを参照しつつ、対応する tool 呼び出しタグを生成
  5. ハーネスはタグをパースし、レジストリで再度バリデーションして実行

検証の真実は レジストリ であり、context にスキーマテキストが現れたかどうかではない。スキーマテキストは「モデルが正しい引数を生成するためのプロンプト材料」として機能する。


4. 設計上のメリット

4.1 prompt cache のプレフィックス安定化

Anthropic の prompt caching はプレフィックスマッチで効く。tools パラメータや system プロンプト前半が変わると、それ以降の cache が一括無効化される。

deferred tools 方式では:

  • API リクエストの tools パラメータは終始固定(あるいは空)
  • 初期 system prompt の <functions> ブロックも固定
  • ToolSearch の結果は 会話末尾の tool_result に積まれるだけ → 前方プレフィックスは揺らがない

→ ツール群が大量にあってもプレフィックスキャッシュが安定する。

4.2 context 圧縮

全ツールの schema をいきなり system prompt に展開すると、肥大化して入力トークンを浪費する。MCP サーバが大量のツールを expose する世界では現実的でない。deferred 方式なら そのセッションで実際に使うツールの schema だけ が context に乗る。

4.3 ツール数のスケーラビリティ

レジストリに登録するだけなら理論上数百〜数千ツールでも扱える。モデルには「使えるツール名リスト」だけ見せ、必要に応じて schema を取り寄せる構造。


5. トレードオフ

  • 1ターンの遅延: ツールを呼ぶ前に ToolSearch が必要。初回だけだが UX 上のレイテンシは増える
  • モデルの認知負荷: 「使う前にロードする」を学習・指示する必要があるsystem-reminder で明示している)
  • ハルシネーション余地: 名前を知っているが schema を知らない状態で呼ぼうとして InputValidationError を起こすケースが発生しうる
  • ハーネス側の責務増: パース・検証・レジストリ管理がすべてハーネス側に乗る。バグると安全性に直結

6. Pod / insomnia への示唆

Pod でローカル LLM をエージェントとして動かす場合、同様の課題が発生する:

  • 提供したいツールが増えると context が肥大化
  • ローカルモデルは structured tool use の精度が API モデルに劣ることが多い → prompted 方式の方が安定する場合がある
  • KV cache の効きを最大化したい(ローカルだと特に prefill コストが重い)

deferred tools 方式は prompted tool use を前提とする限り、これらの課題への自然な解になる。具体的には:

  1. ハーネス内にツールレジストリを持ち、tools メタデータと実装を分離
  2. system prompt には固定の core tools だけ展開、それ以外は名前で示唆
  3. tool_search 相当のツールで schema を引ける動線を用意
  4. パース・検証はハーネス側で完結、モデルへの API 呼び出しは text-in/text-out に統一

特に「prompt cache のプレフィックス安定化」は、ローカル推論でも KV cache 再利用に直接効く。


7. 未確認事項

  • Anthropic API の tools パラメータが実際に空なのか、core tools だけ入っているのか、ハイブリッドなのかは確認できていない
  • ToolSearch の結果テキストが context に残り続けるのか、後続の compaction で削られるのか
  • レジストリのスコープ(セッション固定 / プラグインで動的追加 / MCP 経由など)の境界
  • system-reminder で名前リストが提示されるタイミングが固定なのか動的なのか

これらを確認するには Claude Code のソース公開部分か、API リクエストのキャプチャが必要。


8. Codex による Web 検証

検証日: 2026-04-30。Codex で Web 上の公開情報を確認した範囲では、本ドキュメントの「deferred tools の目的・メリット・トレードオフ」は概ね妥当。ただし、「Claude Code が structured tool use ではなく prompted tool use を採用している」という断定は、公開情報だけでは裏取りできない。

8.1 公式情報で確認できたこと

  • Claude Code / Claude Agent SDK には ToolSearch / tool search が存在する。公式ドキュメントでは、すべての tool definition を upfront に context window へ入れる代わりに、必要な tool を動的に発見・ロードする仕組みとして説明されている。
  • tool search は大量ツール環境向けの context 効率化として説明されている。Claude Code docs では、50 tools で 10-20K tokens を消費しうること、30-50 tools を超えると tool selection accuracy が落ちることが述べられている。
  • Claude Code docs では、tool search はデフォルト有効で、ENABLE_TOOL_SEARCH により true / auto / auto:N / false を設定できるとされている。
  • tool search は MCP server 由来の tool や custom SDK MCP server 由来の tool にも適用される。
  • 初回 discovery には search step の追加 round-trip が発生する。ツール数が 10 未満程度なら、全 tool を upfront に読む方が速い場合がある。
  • Claude API docs には、tool definition property として defer_loading が記載されている。defer_loading: true は「初期 system prompt から tool を除外し、tool search が tool_reference を返した時に on demand でロードする」ものとして説明されている。
  • prompt caching との関係も公式に説明されている。defer_loading: true の tools は rendered tools section から除外され、cache key 計算前の prefix に現れない。発見後の full definition は conversation body 側に展開されるため、prompt cache を保ちやすい。
  • Anthropic の engineering blog でも Tool Search Tool は紹介されている。そこでは「Tool Search Tool だけを upfront にロードし、3-5 個程度の relevant tools を on demand に発見する」設計として説明され、token 使用量削減と tool selection accuracy 改善が述べられている。

8.2 本ドキュメントの推測と食い違う可能性がある点

  • 公開されている Claude API の説明では、tool search は tools 配列、defer_loading: truetool_referencetool_use block を使う structured tool use の仕組みとして説明されている。そのため、「API は単に text-in/text-out で、ツール抽象は完全にハーネスのレイヤにある」と断定するのは強すぎる。
  • 公式情報上は、deferred tool も API request の tools parameter に定義として渡し、その tool definition に defer_loading: true を付ける設計である。したがって「API リクエストの tools パラメータは終始固定(あるいは空)」という推測は、少なくとも公開 API の設計とは一致しない。
  • ToolSearchselect:<name> で schema text を返す、という観察は、このセッションのハーネス上の事実としては扱えるが、公式 API docs の表現とは異なる。公式 API では search tool が tool_reference を返し、それが conversation body 内で full tool definition に展開されると説明されている。
  • <functions><function>...function_calls / invoke / parameter の XML タグ列は、観察対象のハーネスで見えている表現として記録できる。ただし Claude Code 内部 system prompt は公式に公開されていないため、Web 上の公式情報だけで Claude Code 全体の内部実装形式として確認することはできない。

8.3 現時点での整理

公開情報と観察事実を両立させるなら、次の程度に弱めて理解するのが安全:

Claude Code の観察上は、ツール定義や呼び出しが prompt 内テキストとして見えている。ただし、公開されている Anthropic API の tool search は tools 配列、defer_loadingtool_referencetool_use を使う structured tool use として説明されている。したがって、Claude Code 内部が完全な prompted tool use なのか、API の structured tool use を CLI / ハーネス側で別表現にレンダリングしているのか、あるいはそのハイブリッドなのかは未確認。

Pod / insomnia への示唆としては、deferred tools の設計目的である context 圧縮、tool selection accuracy の維持、prefix cache の安定化は公式情報でも裏付けられる。一方で、Anthropic API の現在の公開設計を参考にするなら、tool_search 相当の実装は「単なる schema text の注入」だけでなく、内部 registry 上の tool reference、ロード済み tool の状態管理、検証レイヤを明確に分けて設計する方がよい。


9. ツール I/O の実際のフォーマット

(2026-05-01 追記)

deferred tools の本論からはやや脇道だが、ToolSearch がスキーマテキストを context に注入することで「ツールが使える状態」になる仕組みを理解するためには、ツール定義・呼び出しの実フォーマットと、Anthropic API の公開 surface との対応関係を押さえておく必要がある。

9.1 ツール定義の入力フォーマット

system prompt 冒頭に置かれる:

<functions>
<function>{"description": "...", "name": "Read", "parameters": {...JSONSchema...}}</function>
</functions>

外側は XML、<function> の中身は単行 JSON。JSON 部分は name, description, parameters の 3 フィールドで、parameters は標準的な JSONSchema (type: "object", properties, required, additionalProperties 等)。

9.2 ツール呼び出しの出力フォーマット

モデル側の生成は完全に XML タグ列:

<function_calls>
<invoke name="Read">
<parameter name="file_path">/foo/bar</parameter>
</invoke>
</function_calls>

<parameter> の中身は §1.5 のエンコード規則に従う。

9.3 標準 Anthropic API との関係

開発者から見える API surface は完全に JSON ベース:

  • リクエスト: tools: [{name, description, input_schema}]
  • レスポンス: tool_use content block (JSON)

ところが Claude Code 上の観察では XML+JSON のハイブリッド表現が実際に流れている。両者の整合は次のように理解できる:

標準 API (structured tool use) Claude Code (prompted tool use)
開発者が渡す形式 JSON (tools 配列) — (ハーネス内製)
モデルが受け取る prompt 非公開 (推測: XML+JSON) XML+JSON (観察可)
モデルが返す表現 非公開 (推測: XML タグ) → API が parse XML タグ (観察可)
開発者が受け取る形式 tool_use block (JSON)

標準 API では JSON ↔ モデル内部表現の変換が API サーバ側で隠蔽されている。Claude Code が観察できるのはその「裸の」表現で、Anthropic がモデル訓練に用いているフォーマットそのものと推測される (同じモデルなので)。

9.4 バリデーションとリトライの内製化

この構造を見ると、Tool Call API は実質「フォーマット規約 + schema validation + retry」をプロバイダー側に押し込めた仕様と読める:

  1. フォーマット規約: XML 骨格と parameter エンコード規則
  2. バリデーション: schema 違反の検出
  3. リトライ: malformed なら API 内部で再生成し、開発者には完成品だけ返す
  4. 訓練投資: そのフォーマットで RLHF / SFT 済み

開発者が tool_use block を常に正しい JSON として受け取れるのは、(4) のおかげで失敗率が低く、(1)-(3) のおかげで失敗時も隠蔽されているから。Cline 等の prompted tool use 実装が同じことをやろうとしても、(4) が効かないため精度・安定性で見劣りしていたのは、この訓練投資の差で説明できる。

ただし Claude Code のハーネスは token-level 制約 (grammar-based sampling) を入れていないことが、§10 の実演から推測できる。「自由に生成 → パース失敗なら error を tool_result で返して retry」という設計で、token 制約は使っていない。これは inference サーバ側の実装コストを避けつつ、(4) の訓練品質に依存する方針。

9.5 ローカル LLM への含意

Pod / insomnia でローカル LLM を使う場合、(4) が効かない。最近のローカル向けエージェントモデルは tool use 用に訓練されているので XML パースのような原始的処理は不要だが、各モデルが訓練された自前のフォーマット (Hermes / Llama / Qwen / Mistral 等で異なる) があり、それに合わせてレンダリングする必要がある。

なお、Anthropic と OpenAI のツール呼び出しアプローチの比較 (XML タグ vs 特殊制御トークン、JSON Schema vs TypeScript namespace、thinking block vs 3 チャネル分離) は tool_approach_comparison.md を参照。ローカルモデル向けの設計では、Claude 系の知見 (本書: deferred / registry / context 圧縮) と OpenAI 系の知見 (Harmony: トークン保証 / チャネル分離 / 公開仕様) を役割で使い分けるのが自然。

具体的な責務分担は以下:

  • ツール定義のレンダリング: モデル固有のテンプレート (chat template の tools 拡張等) に合わせる
  • 出力パース: モデルが生成した形式 (タグ / JSON / 独自トークン) をハーネスでパース
  • バリデーション: 自前で schema 照合
  • リトライ: パース失敗・schema 違反時に error を返してモデル側に修正させる

これは Claude Code が内製化しているもののローカル版そのもの。


10. 実演: schema 未ロードでの呼び出し

(2026-05-01 実演)

§3 の推測「検証の真実はレジストリであり、context にスキーマテキストが現れたかどうかではない」を確認するため、deferred tool である TaskList を ToolSearch せずに直接呼び出した。

10.1 結果

受理された (No tasks found が返ってきた)。InputValidationError は発生しなかった。

10.2 含意

  1. schema 未ロード = 呼び出せない、ではない。少なくとも引数なしで呼べるツールでは、schema text が context に無くても通る
  2. ハーネスのバリデーションは「schema text が context にあるか」ではなく、実引数が registry の schema に合致するかで判定している
  3. system-reminder の "calling them directly will fail with InputValidationError" は厳密には常に真ではない。引数が schema と矛盾しないケース (特に必須引数のないツールに引数なしで呼ぶ場合) では素通りする
  4. context に積まれる schema text は、モデルが正しい引数を生成するための 誘導 / プロンプト材料 であって、validation の入力ではない

§3 の推測がそのまま裏付けられた形になる。

10.3 system-reminder の役割の再解釈

警告が「常に fail する」と読めるのは過剰表現で、実際には「引数 schema が必要なツールは引数指定が必須なので、schema を知らずに呼べば事実上 fail する」というモデルへの誘導と理解するのが正確。registry 側のバリデーションは引数の中身を見ているだけで、context に schema text があるかは見ていない。

10.4 設計上の含意

Pod / insomnia 側で同様の機構を作る場合:

  • 「schema text が context にあるか」を validation 条件にする必要はない (むしろしない方が単純)
  • registry に常時全 tool を登録しておき、context へのレンダリングだけ deferred にする
  • モデルが (誘導を無視して) schema 未ロードのツールを呼んでも、引数が合っていれば実行してよい
  • この方が registry の真実性が一本化されて実装が単純になる