Claude APIを使っていて、「AIに外部の関数やAPIを呼ばせたい」という場面ってけっこうあると思うんです。自分がまさにそれで、LambdaからDynamoDBのデータを取ってくる処理をClaudeに判断させたくて、Tool Use(ツール使用)という機能を調べました。
最初は「関数呼び出し」って言葉で何となくイメージしてたんですが、実際の仕組みは思ってたより少し独特で。せっかくなので自分用の整理も兼ねて書いておきます。
- Tool Useとは何か、どういう仕組みで動くか
- ツール定義(
input_schema)の書き方 - Pythonでの基本的な実装フロー(リクエスト→判定→実行→返却)
- 複数ツールを定義するときのコツ
- よくあるハマりポイント
Tool Useって何?まず仕組みを理解する
よく「AIが関数を実行する」という説明を見かけますが、厳密には少し違います。ClaudeがAI単体で直接コードを実行するわけではなく、「この関数を呼んでほしい」という意思(tool_useブロック)を返してくるだけです。実際に関数を実行するのはこちら側(Python)の仕事。
フロー的にはこういう感じです。
- ツールの定義と一緒にユーザーのメッセージをAPIに送る
- Claudeが「ツールを使う必要がある」と判断したら
stop_reason: "tool_use"で返ってくる - レスポンスの中にツール名と引数(input)が入っているので、こちら側で該当の関数を実行する
- 実行結果を
tool_resultとしてClaudeに送り返す(ここがけっこうフォーマット厳しめ) - Claudeが結果を踏まえて最終的な回答を生成する
余談ですが、最初これを知ったとき「結局自分でif文書くの…?」と思ったんですよね。でも実際使ってみると、「Claudeが状況を判断してどのツールをいつ呼ぶか決めてくれる」という部分がかなり便利で、考え方が変わりました。
事前準備:SDKのインストールとAPIキーの設定
インストール
公式のanthropic SDKをpipで入れます。
pip install -U anthropic python-dotenv
APIキーの設定
APIキーは環境変数で管理するのが定石です。.envファイルに書いてdotenvで読み込むのがラクです。
# .env
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxx
import os
from dotenv import load_dotenv
import anthropic
load_dotenv()
client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
ANTHROPIC_API_KEY を環境変数に設定してあれば、anthropic.Anthropic() だけでも自動的に読み込まれます。ハードコーディングはやめましょう(自戒)。
Tool Use入門:ツール定義の書き方とinput_schemaのポイント
ツールは辞書形式で定義します。基本的に必要なキーは3つ:name(ツール名)、description(説明)、input_schema(引数のJSONスキーマ)です。
たとえば「指定した都市の天気を取得するツール」を定義するとこうなります。
tools = [
{
"name": "get_weather",
"description": (
"指定された都市の現在の天気情報を取得します。"
"ユーザーが天気について質問したときに使用してください。"
),
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名(例:Tokyo, Osaka)"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "温度の単位"
}
},
"required": ["location"]
}
}
]
descriptionの書き方が地味に重要
description はClaudeがツールを使うかどうかを判断するための情報源です。ここが曖昧だとClaudeが適切なタイミングでツールを呼んでくれなかったり、逆に不必要に呼んだりします。
- 「何をするツールか」だけでなく「いつ使うか」も書くと精度が上がる
- 返ってくるデータの形式や内容についても触れておくと親切
- あまり長すぎるのも逆効果なので、2〜3文でまとめるのがちょうどいい
input_schemaはJSON Schema形式
input_schema はJSON Schemaの書き方に従います。type・properties・required あたりを押さえておけばとりあえず動きます。よく使う型は string、integer、number、boolean、array あたりです。
PythonでのTool Use実装フロー:基本の全体像
実際に動くコードを順番に見ていきます。天気取得ツールを例に、一連の流れをまとめました。
Step 1:ツール定義とリクエスト送信
まずツールを定義して、ユーザーメッセージとセットでAPIに投げます。
import anthropic
import json
client = anthropic.Anthropic()
# ツール定義
tools = [
{
"name": "get_weather",
"description": "指定された都市の天気情報を取得します。天気について聞かれたときに使用してください。",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "都市名(例:Tokyo)"
}
},
"required": ["location"]
}
}
]
# 最初のリクエスト
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "東京の天気を教えて"}
]
)
print(response.stop_reason) # → "tool_use"
print(response.content) # → ToolUseBlock が含まれている
Step 2:ツール呼び出しの判定と実行
stop_reason が "tool_use" の場合、response.content の中に tool_use ブロックが入っています。ここからツール名と引数を取り出して、実際の関数を呼びます。
# ダミーの天気取得関数(実際はAPIを叩くなど)
def get_weather(location: str, unit: str = "celsius") -> dict:
return {
"location": location,
"temperature": 22,
"unit": unit,
"condition": "晴れ"
}
# tool_use ブロックを処理する
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
tool_use_id = block.id
# ツール名に応じて関数を実行
if tool_name == "get_weather":
result = get_weather(**tool_input)
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
})
else:
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps({"error": "unknown tool"}, ensure_ascii=False),
"is_error": True
})
Step 3:結果をClaudeに返して最終回答を得る
実行結果を tool_result としてメッセージ履歴に追加し、もう一度APIにリクエストを送ります。
# メッセージ履歴を組み立てる
messages = [
{"role": "user", "content": "東京の天気を教えて"},
{"role": "assistant", "content": response.content}, # Claudeのtool_use応答
{"role": "user", "content": tool_results} # tool_resultを返す(※tool_resultを先頭に置く)
]
# 最終回答を取得
final_response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
print(final_response.content[0].text)
# → 「東京は現在22℃で晴れです。」など
このときの stop_reason は "end_turn" になります。ここまでが基本の1サイクルです。
複数ツールを使う場合と繰り返し処理
複数ツールの定義
1回のリクエストに複数のツールを渡せます。Claudeが状況に応じてどれを使うか判断してくれます。
tools = [
{
"name": "get_weather",
"description": "都市の天気情報を取得します。",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "都市名"}
},
"required": ["location"]
}
},
{
"name": "get_time",
"description": "指定されたタイムゾーンの現在時刻を取得します。",
"input_schema": {
"type": "object",
"properties": {
"timezone": {"type": "string", "description": "タイムゾーン(例:Asia/Tokyo)"}
},
"required": ["timezone"]
}
}
]
ループで繰り返す場合
実用的なエージェント的な処理では、「ツール呼び出し→結果返却」を何度か繰り返すことがあります。stop_reason が "end_turn" になるまでループする書き方が基本になります。
messages = [{"role": "user", "content": "東京の天気と現在時刻を教えて"}]
# ツール実行関数のマッピング
tool_functions = {
"get_weather": get_weather,
"get_time": get_time
}
max_loops = 10
for _ in range(max_loops):
response = client.messages.create(
model="claude-opus-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
# アシスタントの返答をメッセージ履歴に追加
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
# ツール呼び出しが不要になったら終了
print(response.content[0].text)
break
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
func = tool_functions.get(block.name)
if func:
result = func(**block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps(result, ensure_ascii=False)
})
else:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": json.dumps({"error": "unknown tool"}, ensure_ascii=False),
"is_error": True
})
# tool_resultは「assistantのtool_useの直後」に置く必要あり
messages.append({"role": "user", "content": tool_results})
else:
raise RuntimeError("tool loop exceeded max_loops")
無限ループにならないよう、実際のコードでは最大ループ回数を設定しておくのが安全です。
Claude API Tool Use実装でハマりやすいポイントまとめ
自分が実際に詰まったこと、調べてよく出てきたことを書いておきます。
tool_use_id はちゃんと紐づける
tool_result を返すときの tool_use_id は、Claudeが発行した block.id と完全一致させる必要があります。ここがズレると「どのツール呼び出しの結果か」わからなくなってエラーになります。
tool_resultは「直後」「先頭」がルール
これは地味に落とし穴なんですが、ツール結果(tool_result)は、対応するassistant側のtool_useメッセージの直後に置く必要があります。さらに、role: "user" の content 配列の中では tool_result ブロックを先頭に置くのがルールです。ここを破ると400で怒られがちです。
descriptionは英語でも動く
description は日本語でも動きますが、英語のほうが若干精度が高い印象があります(個人的な所感です)。本番で使う場合は英語で書いてみるのも手かもしれません。
required に入れるかどうかを意識する
input_schema の required は、「省略されたら困るもの」を入れるのが基本です。必須にしておくと、Claudeはそのフィールドを埋めようとします(ただし、ユーザー入力から値が確定できない場合は質問で確認してくることもあります)。「これがないと関数が動かない」というパラメータは必ず required に入れておきましょう。
tool_result の content は文字列 or ブロック配列が安全
tool_result の content は、文字列(例:JSON文字列)か、{ "type": "text", "text": "..." } みたいなcontentブロックの配列で返せます。辞書をそのまま入れると形式が合わずエラーになることがあるので、json.dumps() で文字列にしてから渡すのが無難です。
まとめ:PythonでClaude API Tool Useを使うための要点
Claude APIのTool Useについて、基本から実装の流れまで整理しました。
- Tool UseはClaudeが「この関数を呼んで」と返すだけで、実行は自分でやる
- ツール定義は
name・description・input_schemaの3セット descriptionの質がClaudeの判断精度に直結するstop_reason: "tool_use"→ ツール実行 →tool_result返却 →stop_reason: "end_turn"が基本サイクル- 複数ツール・繰り返し処理もループで対応できる
tool_use_idの紐づけとjson.dumps()でのシリアライズを忘れずに
最初は少し面倒に見えますが、一度パターンを掴んでしまえば「Claudeに何かを判断させて外部処理を動かす」という設計がすごく柔軟にできるようになります。LambdaとDynamoDBをつなぐ用途でも普通に使えたので、AWSユーザーの方にもおすすめです。
参考になれば!

