Claude API の Tool Use で外部データを取得する
概要
Claude API 単体では、モデルは学習時点までの知識しか持たず、リアルタイムの天気・在庫・社内DBの値といった外部データを答えられません。この壁を越える仕組みが Tool Use(ツール使用) です。
この記事では、Python SDK を使って「Claude がツールを呼び出す → アプリ側で外部データを取得 → 結果を返す → Claude が最終回答を作る」という往復ループをハンズオンで組み立てます。複数ツール・エラー処理・プロンプトキャッシュまで扱います。内容は公開情報に基づく一般的な実装パターンのみです。
環境
- Python 3.10+
anthropicSDK(1.x 系)/外部 API 用にrequests- モデル:
claude-sonnet-4-6
pip install anthropic requests export ANTHROPIC_API_KEY="(自分のキー)"
Tool Use の全体像
Tool Use は「Claude が関数を実行してくれる」機能ではありません。実際にコードを動かすのはアプリ側で、Claude は「このツールを、この引数で呼んでほしい」という要求を返すだけです。
- アプリがツール定義を添えてメッセージを送る
- Claude が
stop_reason: "tool_use"とtool_useブロックを返す - アプリがツールを実行し、
tool_resultを会話履歴に追加して再送する - Claude が結果を踏まえて最終回答を返す
この往復をアプリ側のループで回すのが肝です。1回の messages.create では完結しません。
まず最小構成:ツール定義と呼び出し要求
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "指定した都市の現在の天気を取得する。気温は摂氏で返る。",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "都市名。英語表記(例: Tokyo, Osaka)",
}
},
"required": ["city"],
},
}
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "東京の天気を教えて"}],
)
for block in response.content:
if block.type == "tool_use":
print(block.name, block.input, block.id)
# get_weather {'city': 'Tokyo'} toolu_...
description は Claude がツールを選ぶ判断材料そのものです。何を返すツールか・単位・前提・使いどころまで具体的に書くと精度が安定します。
外部データ取得を実装する
import requests
def get_weather(city: str) -> dict:
geo = requests.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"name": city, "count": 1}, timeout=10,
).json()
if not geo.get("results"):
return {"error": f"都市が見つかりません: {city}"}
loc = geo["results"][0]
w = requests.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": loc["latitude"],
"longitude": loc["longitude"],
"current": "temperature_2m,weather_code",
}, timeout=10,
).json()["current"]
return {
"city": loc["name"],
"temperature_c": w["temperature_2m"],
"weather_code": w["weather_code"],
}
社内 API でも DB クエリでも、関数の中身が変わるだけで構造は同じです。
往復ループを組む
import json
def run_conversation(user_message: str, max_turns: int = 10) -> str:
messages = [{"role": "user", "content": user_message}]
for _ in range(max_turns):
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return "".join(b.text for b in response.content if b.type == "text")
tool_results = []
for block in response.content:
if block.type != "tool_use":
continue
try:
if block.name == "get_weather":
result = get_weather(**block.input)
else:
result = {"error": f"unknown tool: {block.name}"}
except Exception as e:
result = {"error": f"tool execution failed: {e}"}
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result, ensure_ascii=False),
"is_error": "error" in result,
})
messages.append({"role": "user", "content": tool_results})
return "(最大ターン数に到達しました)"
print(run_conversation("東京とニューヨークの天気を比べて"))
実装で押さえるべき3点です。
- assistant の
contentは丸ごと履歴に戻す。分解して入れ直すとtool_use_idの対応が壊れます。 tool_resultはrole: "user"で送る。assistantにすると API エラーになります。- 1応答に複数の
tool_useが入りうる(「東京とニューヨーク」で天気APIを2回)。全部処理して全部まとめて返します。返し漏らすとエラーになります。
エラーの扱いと tool_choice
ツール実行の失敗は例外で落とさず、エラー内容を tool_result として返すのが実務的です。is_error: true を付けると、Claude は別の引数で再試行したり、ユーザーに確認したりと自己修正できます。
tool_choice の既定は auto(Claude が判断)。必ず呼ばせたいときは {"type": "any"}、特定ツール固定は {"type": "tool", "name": "..."}、禁止は {"type": "none"} です。エラーが続くと往復が止まらないため、最大反復回数の上限は必須です。
プロンプトキャッシュでコストを抑える
ツール定義やシステムプロンプトが大きい場合、tools の末尾要素に cache_control を付けると定義部がキャッシュされ、往復のたびのトークンコストと遅延を抑えられます。
tools = [
{"name": "get_weather", "description": "...",
"input_schema": {...},
"cache_control": {"type": "ephemeral"}}
]
確認方法
- 1周目で
stop_reason == "tool_use"、block.inputが schema 通りの型になること - 2周目以降で
stop_reason == "end_turn"に変わり最終テキストが得られること - 存在しないツール名や引数不足のとき、
is_errorを見て Claude が自己修正すること
毎周の messages を出力すると、履歴の積み方の誤り(tool_result 付け忘れ・ロール違い)がすぐ見えます。
まとめ
- Tool Use は Claude が要求し、実行はアプリが担う仕組み
tool_use → 実行 → tool_result 再送をstop_reasonが変わるまでループで回す- assistant 応答は丸ごと戻す/
tool_resultは user ロール/複数呼び出しは全部返す - エラーは
is_errorで返すと自己修正が効く/大きな定義はcache_controlでキャッシュ
まず1ツール1往復を動かし、そこから複数ツールとエラー処理を足していくのが、拡張しやすく確実です。
