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

311 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# リファレンス: 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 を動的に発見・ロードする仕組みとして説明されている。
- https://code.claude.com/docs/en/agent-sdk/tool-search
- 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 でロードする」ものとして説明されている。
- https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-reference
- https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool
- 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 改善が述べられている。
- https://www.anthropic.com/engineering/advanced-tool-use
### 8.2 本ドキュメントの推測と食い違う可能性がある点
- 公開されている Claude API の説明では、tool search は `tools` 配列、`defer_loading: true`、`tool_reference`、`tool_use` block を使う structured tool use の仕組みとして説明されている。そのため、「API は単に text-in/text-out で、ツール抽象は完全にハーネスのレイヤにある」と断定するのは強すぎる。
- 公式情報上は、deferred tool も API request の `tools` parameter に定義として渡し、その tool definition に `defer_loading: true` を付ける設計である。したがって「API リクエストの `tools` パラメータは終始固定(あるいは空)」という推測は、少なくとも公開 API の設計とは一致しない。
- `ToolSearch``select:<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 全体の内部実装形式として確認することはできない。
- https://code.claude.com/docs/en/configuration
### 8.3 現時点での整理
公開情報と観察事実を両立させるなら、次の程度に弱めて理解するのが安全:
> Claude Code の観察上は、ツール定義や呼び出しが prompt 内テキストとして見えている。ただし、公開されている Anthropic API の tool search は `tools` 配列、`defer_loading`、`tool_reference`、`tool_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 等で異なる) があり、それに合わせてレンダリングする必要がある。
具体的な責務分担は以下:
- ツール定義のレンダリング: モデル固有のテンプレート (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 の真実性が一本化されて実装が単純になる