コンテンツにスキップ

Tool Use(Function Calling)

Claudeに外部ツール(関数)を使わせる方法を学びます。天気取得、DB検索、API呼び出しなどをClaudeが判断して実行できるようになります。


基本的な流れ

1. ツール定義をAPIに渡す
2. Claudeがツール呼び出しを返す(tool_use)
3. アプリがツールを実行して結果を返す(tool_result)
4. Claudeが結果を元に最終回答を生成

ツール定義

JSONスキーマでツールの名前・説明・パラメータを定義する。

tools = [
    {
        "name": "get_weather",
        "description": "指定された都市の現在の天気を取得する",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "都市名(例: 東京、大阪)"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                    "description": "温度の単位"
                }
            },
            "required": ["city"]
        }
    }
]

良いツール定義のポイント

  • descriptionを具体的に書く — Claudeはこれを読んで使うかどうか判断する
  • propertiesの各フィールドにもdescriptionをつける
  • requiredを適切に設定して、必須パラメータを明示する

実行フロー(完全な例)

import anthropic
import json

client = anthropic.Anthropic()

# 1. ツール定義
tools = [
    {
        "name": "get_weather",
        "description": "指定された都市の現在の天気を取得する",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "都市名"}
            },
            "required": ["city"]
        }
    }
]

# 2. 最初のリクエスト
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "東京の天気を教えて"}
    ]
)

# 3. Claudeがtool_useを返す
# response.stop_reason == "tool_use"
# response.content == [
#   {"type": "text", "text": "東京の天気を確認しますね。"},
#   {"type": "tool_use", "id": "toolu_01...", "name": "get_weather", "input": {"city": "東京"}}
# ]

# 4. ツールを実際に実行(ここはアプリ側の実装)
def get_weather(city):
    # 実際にはAPIを叩く
    return {"temp": 22, "condition": "晴れ", "humidity": 45}

# tool_useブロックを取得
tool_use = next(b for b in response.content if b.type == "tool_use")
result = get_weather(tool_use.input["city"])

# 5. 結果をClaudeに返す
final_response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    messages=[
        {"role": "user", "content": "東京の天気を教えて"},
        {"role": "assistant", "content": response.content},
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": tool_use.id,
                    "content": json.dumps(result, ensure_ascii=False)
                }
            ]
        }
    ]
)

print(final_response.content[0].text)
# => "東京は現在22°Cで晴れています。湿度は45%です。"

複数ツールの利用

複数のツールを定義すると、Claudeが状況に応じて適切なツールを選ぶ。1回のレスポンスで複数ツールを呼ぶこともある。

tools = [
    {
        "name": "get_weather",
        "description": "都市の天気を取得",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string"}
            },
            "required": ["city"]
        }
    },
    {
        "name": "get_exchange_rate",
        "description": "通貨の為替レートを取得",
        "input_schema": {
            "type": "object",
            "properties": {
                "from_currency": {"type": "string"},
                "to_currency": {"type": "string"}
            },
            "required": ["from_currency", "to_currency"]
        }
    }
]

並列ツール呼び出し

Claudeが1回のレスポンスで複数のtool_useブロックを返すことがある。

# "東京と大阪の天気を比較して" と聞くと:
# response.content == [
#   {"type": "tool_use", "name": "get_weather", "input": {"city": "東京"}},
#   {"type": "tool_use", "name": "get_weather", "input": {"city": "大阪"}}
# ]

# 全てのtool_resultをまとめて返す
tool_results = []
for block in response.content:
    if block.type == "tool_use":
        result = execute_tool(block.name, block.input)
        tool_results.append({
            "type": "tool_result",
            "tool_use_id": block.id,
            "content": json.dumps(result, ensure_ascii=False)
        })

tool_choice — ツール利用の制御

動作
{"type": "auto"} Claudeが判断(デフォルト)
{"type": "any"} 必ず何かのツールを使う
{"type": "tool", "name": "get_weather"} 指定ツールを必ず使う
response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any"},  # 必ずツールを使わせる
    messages=[...]
)

エラーハンドリング

ツール実行が失敗した場合、is_errorフラグで伝える。

{
    "type": "tool_result",
    "tool_use_id": tool_use.id,
    "content": "都市名が見つかりませんでした: ホゲ市",
    "is_error": True
}

Claudeはエラー内容を理解して、ユーザーに適切に説明したり、別のアプローチを試みる。


実践パターン

パターン1: ループ処理(エージェント的な使い方)

messages = [{"role": "user", "content": user_input}]

while True:
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=4096,
        tools=tools,
        messages=messages,
    )

    # ツール呼び出しがなければ終了
    if response.stop_reason == "end_turn":
        print(response.content[0].text)
        break

    # ツール実行して結果を追加
    messages.append({"role": "assistant", "content": response.content})
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            result = execute_tool(block.name, block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": json.dumps(result, ensure_ascii=False)
            })
    messages.append({"role": "user", "content": tool_results})

パターン2: ツールでの構造化データ抽出

ツールの「実行」をスキップして、Claudeに構造化データを出力させるテクニック。

tools = [
    {
        "name": "extract_info",
        "description": "テキストから構造化情報を抽出する",
        "input_schema": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "email": {"type": "string"},
                "company": {"type": "string"}
            },
            "required": ["name", "email"]
        }
    }
]

response = client.messages.create(
    model="claude-sonnet-4-20250514",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "extract_info"},
    messages=[
        {"role": "user", "content": "田中太郎(tanaka@example.com)、株式会社ABC所属"}
    ]
)

# tool_use.input にJSONスキーマ準拠の構造化データが入る
extracted = response.content[0].input
# => {"name": "田中太郎", "email": "tanaka@example.com", "company": "株式会社ABC"}

参考リンク