ankuro.dev
← ブログ一覧に戻る
【CCA Foundations対策 / Claude API編 #10】Tool Useの応用——複数ツールの管理と組み込みツール
2026-04-03#Claude#API#Python#生成AI#入門#Claude Certified Architect

【CCA Foundations対策 / Claude API編 #10】Tool Useの応用——複数ツールの管理と組み込みツール

Anthropic Academyの「Claude APIを使用した構築」コースをもとに解説しています。

#8・#9 でツール定義からエージェントループまでの基本を押さえた。今回はその先——複数ツールを管理する設計パターン、ツールを増やしすぎると起きる問題、そしてClaudeに組み込まれた2つの特殊ツールを解説する。

この記事でわかること:

  • 複数ツールをスケーラブルに管理するルーターパターン
  • ツール数が増えると選択信頼性が下がる理由と tool_choice の使い分け
  • ストリーミング時の fine-grained tool calling の仕組みとトレードオフ
  • テキスト編集ツール・Web検索ツールの使い方と「組み込み」の意味

複数ツールを追加する——ルーターパターン

ツールを追加するときは、既存の構造を変えなくてよい。必要な変更は2箇所だけ:

  1. tools リストにスキーマを追加する
  2. 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_toolelif を追加するだけで新しいツールを組み込める。ループ処理(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_filestr_replacecreate_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の基礎——テキスト分割・埋め込み・検索の仕組みを扱う

← 前回:Tool Useの基礎② 振り返りクイズ:Tool Use編(#8〜#10)