
【CCA Foundations対策 / Claude API編 #10】Tool Useの応用——複数ツールの管理と組み込みツール
Anthropic Academyの「Claude APIを使用した構築」コースをもとに解説しています。
#8・#9 でツール定義からエージェントループまでの基本を押さえた。今回はその先——複数ツールを管理する設計パターン、ツールを増やしすぎると起きる問題、そしてClaudeに組み込まれた2つの特殊ツールを解説する。
この記事でわかること:
- 複数ツールをスケーラブルに管理するルーターパターン
- ツール数が増えると選択信頼性が下がる理由と
tool_choiceの使い分け - ストリーミング時の fine-grained tool calling の仕組みとトレードオフ
- テキスト編集ツール・Web検索ツールの使い方と「組み込み」の意味
複数ツールを追加する——ルーターパターン
ツールを追加するときは、既存の構造を変えなくてよい。必要な変更は2箇所だけ:
toolsリストにスキーマを追加するrun_tool関数にelifを追加する
図書館システムを例に、3つのツールを追加する場合を見ていく。
import json
import anthropic
client = anthropic.Anthropic()
model = "claude-sonnet-4-6"
# ツール実装
def search_books(keyword: str) -> list:
"""キーワードで図書を検索する"""
mock_catalog = [
{"id": "B001", "title": "Pythonではじめる機械学習", "author": "田中一郎"},
{"id": "B002", "title": "Python自動化レシピ", "author": "佐藤二郎"},
{"id": "B003", "title": "データ分析入門", "author": "鈴木三郎"},
]
return [b for b in mock_catalog if keyword.lower() in b["title"].lower()]
def check_availability(book_id: str) -> dict:
"""貸出状況を確認する"""
status = {"B001": True, "B002": False, "B003": True}
available = status.get(book_id, False)
return {"book_id": book_id, "available": available,
"status": "貸出可能" if available else "貸出中"}
def reserve_book(book_id: str, member_id: str) -> dict:
"""図書を予約する"""
return {"book_id": book_id, "member_id": member_id,
"result": "予約完了", "pickup_date": "2026-04-10"}
# ツールルーター
def run_tool(name: str, tool_input: dict):
if name == "search_books":
return search_books(**tool_input)
elif name == "check_availability":
return check_availability(**tool_input)
elif name == "reserve_book":
return reserve_book(**tool_input)
raise ValueError(f"未知のツール: {name}")
run_tool に elif を追加するだけで新しいツールを組み込める。ループ処理(run_tools / run_conversation)は#9のコードをそのまま使いまわせる。
result = run_conversation(
"Pythonの入門書を探して予約したい。会員番号はM42です",
tools=[search_books_schema, check_availability_schema, reserve_book_schema],
)
print(result)
「Pythonではじめる機械学習」(B001)が貸出可能です。
会員番号M42で予約を完了しました。受取日は2026年4月10日です。
Claudeは search_books → check_availability → reserve_book の順に3回ツールを呼び出し、最終的に自然な文章でまとめた回答を返す。
ツールを与えすぎると選択信頼性が下がる
ツールを追加するのは簡単だが、数を増やすほど選択の精度が落ちるという問題がある。
たとえば4〜5個のツールしかないときはClaudeがどれを使うか判断しやすい。しかし18個になると選択肢が多すぎて、関係のないツールを呼んだり、どのツールを使うべきか迷って誤選択したりする確率が上がる。
さらに、専門外のツールを持たせると誤用が起きやすい。たとえば「回答を合成する」役割のエージェントにWeb検索ツールを与えると、必要ないのに検索を始めることがある。各エージェントは自分の役割に必要なツールだけを持つのが原則。
📋 試験ガイドより
公式試験ガイドのDomain 2(Tool Design & MCP Integration)Task 2.3では、エージェントへのツール配布について「18個のように多すぎるツールを与えると意思決定の複雑さが増し、選択信頼性が低下する」と明記されている。各エージェントの役割に必要なツールだけをスコープして渡す「スコープドアクセス」が設計原則として取り上げられている。
tool_choice で選択モードを制御する
ツールをいつ使うかの制御には tool_choice パラメータを使う。
| 値 | 挙動 |
|---|---|
{"type": "auto"} |
Claudeが必要と判断したときだけツールを使う(デフォルト) |
{"type": "any"} |
必ずいずれかのツールを呼ぶ。会話的な回答だけを返させたくないときに使う |
{"type": "tool", "name": "search_books"} |
必ず指定したツールを呼ぶ。最初のステップを強制したいときに使う |
# 必ずsearch_booksから始めさせる
response = client.messages.create(
model=model,
max_tokens=1024,
messages=messages,
tools=[search_books_schema, check_availability_schema, reserve_book_schema],
tool_choice={"type": "tool", "name": "search_books"},
)
"any" と "auto" は混同しやすい。"auto" はClaudeが「ツールなしで答えられる」と判断したら普通のテキスト回答を返す。"any" にすると必ずどれかのツールを呼ぶことが保証される。
ストリーミング中のツール引数を受け取る
ストリーミングとツールを組み合わせると、Claudeがツールの引数を生成する様子をリアルタイムで受け取れる。このとき InputJsonEvent という新しいイベントを処理する必要がある。
for chunk in stream:
if chunk.type == "input_json":
print(chunk.partial_json, end="", flush=True) # 引数の断片
current_args = chunk.snapshot # ここまでの累積JSON
デフォルト動作とfine-grainedの違い
デフォルトでは、APIはJSON全体をバリデーションしながらバッファリングする。トップレベルのキーと値のペアが完成するまでチャンクを溜め込み、バリデーションが通ってからまとめて送信する。これがストリーミング中に「遅延→まとめて受信」というバースト挙動になる原因。
fine_grained=True を渡すと、このバリデーションを無効化してチャンクを即座に送信する。
run_conversation(messages, tools=[save_article_schema], fine_grained=True)
ただし、バリデーションがないため Claude が生成途中の不完全なJSONを受け取ることがある。アプリ側で json.JSONDecodeError を捕捉する処理が必須になる。
try:
parsed = json.loads(chunk.snapshot)
except json.JSONDecodeError:
pass # 不完全なJSONなので次のチャンクを待つ
使い分けの判断基準: ツール引数の生成中にリアルタイムの進捗表示が必要なとき以外は、デフォルト(バリデーションあり)のままで十分。
組み込みツール①:テキスト編集ツール
ここまで扱ってきたツールはすべて「スキーマを自分で書き、実装も自分で書く」ものだった。Claude には、スキーマがあらかじめ組み込まれている特殊なツールが2種類ある。その1つがテキスト編集ツール。
テキスト編集ツールができること:
- ファイルやディレクトリの内容を表示する
- 指定した行範囲だけを表示する
- ファイル内のテキストを置換する
- 新しいファイルを作成する
- 指定した行にテキストを挿入する
- 直近の編集を取り消す
「組み込み」の意味とよくある誤解
重要な点: スキーマはClaudeが知っているが、実際にファイルを操作するコードは自分で書く必要がある。
通常のツールは「スキーマを書く → 実装を書く」の2ステップが必要。テキスト編集ツールは「スキーマを書く」ステップが不要なだけで、「Claudeがファイルを読みたい」と要求してきたときにそれを実行するコードは自分で実装しなければならない。
APIに渡す際は、モデルごとに異なる type 文字列を使ったスタブスキーマを渡す:
def get_text_edit_schema(model: str) -> dict:
if model.startswith("claude-3-7-sonnet"):
return {"type": "text_editor_20250124", "name": "str_replace_editor"}
elif model.startswith("claude-3-5-sonnet"):
return {"type": "text_editor_20241022", "name": "str_replace_editor"}
# claude-sonnet-4-6 など最新モデルはドキュメントで確認
return {"type": "text_editor_20250124", "name": "str_replace_editor"}
response = client.messages.create(
model=model,
max_tokens=2048,
messages=messages,
tools=[get_text_edit_schema(model)],
)
このスタブスキーマを見たClaudeが、内部でフルスペックのスキーマに展開してファイル操作を要求してくる。Claude が view_file・str_replace・create_file などを要求してきたら、アプリ側でそれを実行する。
組み込みツール②:Web検索ツール
Web検索ツールはテキスト編集ツールとは異なり、実装も不要なフルマネージドのツール。スキーマを渡すだけで、ClaudeがAPIに接続してWebを検索し、結果と引用をまとめて返してくれる。
web_search_schema = {
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 5,
}
response = client.messages.create(
model=model,
max_tokens=2048,
messages=[{"role": "user", "content": "2026年の日本の最低賃金はいくらですか?"}],
tools=[web_search_schema],
)
注意: Web検索ツールはAnthropicコンソールの設定画面でWeb Searchを有効化しないと使えない。
レスポンスのブロック構造
Web検索ツールを使ったレスポンスは複数の種類のブロックで構成される:
| ブロック種別 | 内容 |
|---|---|
TextBlock |
Claudeの説明文 |
ServerToolUseBlock |
Claudeが実際に使用した検索クエリ |
WebSearchToolResultBlock |
検索結果全体 |
WebSearchResultBlock |
個別の検索結果(タイトル・URL) |
| Citation ブロック | Claudeの回答を裏付けるテキストの引用元 |
Citation ブロックを活用すると、回答のどの部分がどのURLから来たかをUIに表示できる。情報の透明性が求められるアプリケーションに向いている。
ドメインを絞って信頼性を上げる
allowed_domains を指定すると検索対象を特定ドメインに限定できる。
# 医学・健康情報の場合:公的機関に限定する
web_search_schema = {
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 3,
"allowed_domains": ["mhlw.go.jp", "niph.go.jp"],
}
指定しない場合、信頼性の低いブログや広告サイトを参照する可能性がある。ドメインを絞ることで回答品質と信頼性を一定に保てる。
よくある誤解まとめ
| 誤解 | 実際 |
|---|---|
| ツールを多く渡すほどClaudeの能力が上がる | ツールが多すぎると選択の複雑さが増して誤選択が起きやすくなる。4〜5個が目安 |
tool_choice="auto" と "any" は同じ |
"auto" はツール不要と判断したらテキストのみ返す。"any" は必ずいずれかのツールを呼ぶ |
| テキスト編集ツールは完全に実装不要 | スキーマはClaudeが持つが、ファイル操作の実装は自分で書く必要がある |
| Web検索ツールはスキーマを渡せばすぐ使える | コンソールでWeb Searchを有効化していないと使えない |
fine_grained=True にすれば必ず速くなる |
バリデーションが無効になるため無効JSONが来ることがある。エラーハンドリングが必須 |
allowed_domains を省略しても品質は変わらない |
省略するとランダムなブログ等を参照する可能性がある。信頼性が求められる場合は必ず指定する |
設計の判断基準
| 場面 | やりがちな選択 | 正しい選択 | 判断の根拠 |
|---|---|---|---|
| Claudeがツールを逐次的に呼び出してラウンドトリップが多い | よく使う組み合わせを1つのツールに統合する | まずプロンプトで「関連ツールはまとめてリクエストして」と指示する | プロンプト変更は最小コストで対処できる。ツール統合は設計変更が大きく汎用性も失われる |
| ツールが多くて誤選択が起きる | 各ツールのdescriptionをより詳しく書く | エージェントを役割ごとに分割して必要な4〜5ツールだけを渡す | descriptionの改善は部分的な効果。ツール数自体を減らすスコープ絞りが根本解決 |
| ツールを必ず使わせたい | プロンプトに「必ず〇〇ツールを使って」と指示する | tool_choice="any" または forced に変更する |
プロンプト指示はLLMの確率的動作を100%制御できない。tool_choiceによる構造的な保証が確実 |
| 特定の順序でツールを実行させたい | プロンプトで「まず〇〇を呼んでから」と指示する | tool_choice={"type": "tool", "name": "..."} で最初のツールを強制する |
順序の強制はtool_choiceで構造的に保証する方がプロンプト指示より確実 |
まとめ
- 複数ツールの追加は「スキーマをリストに追加・ルーターに
elifを追加」の2ステップだけ - ツールを与えすぎると選択信頼性が下がる。各エージェントの役割に必要なものだけを渡す
tool_choiceの3モード(auto / any / forced)でツール使用の強制度を制御できる- ストリーミング + ツールでは
fine_grained=Trueで即時チャンクを受け取れるが、無効JSONのハンドリングが必要になる - テキスト編集ツールは「スキーマ組み込み・実装は自分」、Web検索ツールは「スキーマも実装も組み込み」という違いがある
- Web検索ツールは
allowed_domainsでドメインを絞ると回答の信頼性が上がる - 次回(#11)ではRAGの基礎——テキスト分割・埋め込み・検索の仕組みを扱う