前回はマルチターン会話とシステムプロンプトの使い方を紹介しました。今回はいよいよ Tool Use(ツール呼び出し)です。個人的にこれが一番「Claude APIっぽいな」と感じる機能で、使いこなせると一気にできることが広がります。
きっかけはシンプルで、LambdaとDynamoDBを使った処理をClaudeに判断させたかったんですよね。「今日の注文件数を調べて」みたいな自然言語の指示で、ClaudeがDB照会まで動いてくれたら最高だな、と。それがそのままこの記事のネタになっています。
この記事でわかること
- Claude APIのTool Use(ツール呼び出し)の仕組み
- Pythonを使った最小構成の実装方法
- 実装時のハマりポイント3つ
- 複数ツールの定義と使い分け
- tool_choiceパラメータの活用
- DynamoDBとの連携実例
Tool Useとは何か、まず仕組みを整理する
「AIが関数を実行する」という説明をよく見かけますが、厳密にはちょっと違います。ClaudeはAI単体でコードを実行するわけではなくて、「この関数を呼んでほしい」という意思(tool_useブロック)を返してくるだけ。実際に関数を動かすのはこちら側(Python)の役目です。
流れをざっくり書くとこうなります。
- ツールの定義(名前・説明・引数のスキーマ)をAPIリクエストに含めて送る
- Claudeが「このツールを使うべき」と判断したら
stop_reason: "tool_use"で返ってくる - レスポンスのツール名と引数を使って、こちら側で関数を実行する
- 実行結果を
tool_resultとしてClaudeに送り返す - Claudeが結果を踏まえて最終回答を生成する
最初に知ったとき「結局自分でif文書くの…?」と思ったんですが、「Claudeがどのツールをいつ呼ぶかを判断してくれる」という部分がかなり便利で、使ってみると考え方が変わりました。
最小構成で動かしてみる
まずはシンプルな天気取得ツールを例に、基本の実装を確認します。
ツールの定義
ツールは辞書形式で定義します。一般的には name・description・input_schema といった情報を持たせます。descriptionの質がClaudeの判断精度に直結するので、ここは丁寧に書いた方がいいです。
import os
import json
import anthropic
from dotenv import load_dotenv
load_dotenv()
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": "指定した都市の現在の天気と気温を返します。天気に関する質問が来た場合はこのツールを使ってください。",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "都市名(例: Tokyo, Osaka)"
}
},
"required": ["city"]
}
}
]
description は日本語でも動くことが多いですが、英語の方が精度が上がることもあるようです(自分の体感)。チームやコードベースの事情に合わせて判断するのが無難かと。
ツールの実装と呼び出しループ
ツールの実体はただのPython関数です。今回はモックデータで返しますが、実際はここで外部APIやDBを叩きます。
def get_weather(city: str) -> str:
# 実際は外部APIを呼ぶ
mock_data = {
"Tokyo": {"temp": 22, "condition": "晴れ"},
"Osaka": {"temp": 24, "condition": "くもり"},
}
data = mock_data.get(city, {"temp": "不明", "condition": "不明"})
return json.dumps({"city": city, **data}, ensure_ascii=False)
def run_tool(name: str, input_data: dict) -> str:
if name == "get_weather":
return get_weather(**input_data)
return json.dumps({"error": f"unknown tool: {name}"})
def chat_with_tools(user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
for _ in range(5): # 無限ループ防止
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
messages=messages,
)
if response.stop_reason == "end_turn":
# ツールを使わず直接回答した場合
for block in response.content:
if hasattr(block, "text"):
return block.text
if response.stop_reason == "tool_use":
# assistantのレスポンスをそのままmessagesに追加
messages.append({"role": "assistant", "content": response.content})
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = run_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id, # ここ注意: block.id と一致させる
"content": result,
})
messages.append({"role": "user", "content": tool_results})
continue
break
return "(応答を取得できませんでした)"
print(chat_with_tools("東京の天気を教えて"))
実行すると「東京の現在の天気は晴れ、気温は22℃です。」のような回答が返ってきます。
ハマりポイント3つ
実際に書いてみてつまずいた箇所をまとめておきます。
① tool_use_id はブロックの id と完全一致させる
tool_resultを返すとき、tool_use_idにはClaudeが発行した block.id を渡す必要があります。ここがズレると「どのツール呼び出しの結果かわからない」状態になってエラーになります。自前でIDを生成しようとして詰まりました。
② tool_result は role: “user” の content 配列に入れる
これが地味に落とし穴でした。ツール結果を返すとき、role: "user" の content 配列の中に tool_result ブロックを並べる必要があります。さらに、その前に assistant 側の tool_use メッセージが先に messages に追加されている必要があります。順序を間違えるとエラーになることがあります。
③ ループの上限を設定する
Tool Use が連続して起きるケース(Claudeが複数のツールを順番に呼ぶ場合など)では、while True だと無限ループのリスクがあります。上のコードでは for _ in range(5) で上限を設けています。実運用では適切な回数を検討してください。
複数ツールを定義して使い分けてもらう
Tool Use の真価は複数ツールを並べたときに出てきます。Claudeがユーザーの質問内容を見て、どのツールを呼ぶか(あるいは呼ばないか)を判断してくれます。
tools = [
{
"name": "get_weather",
"description": "指定した都市の現在の天気と気温を返します。",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "都市名"}
},
"required": ["city"]
}
},
{
"name": "get_stock_price",
"description": "指定した銘柄コードの現在の株価を返します。",
"input_schema": {
"type": "object",
"properties": {
"symbol": {"type": "string", "description": "銘柄コード(例: AAPL, 7203)"}
},
"required": ["symbol"]
}
},
{
"name": "get_news",
"description": "指定したトピックの最新ニュースを3件返します。",
"input_schema": {
"type": "object",
"properties": {
"topic": {"type": "string", "description": "検索するトピック"}
},
"required": ["topic"]
}
}
]
「Appleの株価と最近のニュースを教えて」と聞くと、状況によってはClaudeが get_stock_price と get_news を順番に(もしくは並列で)呼び出そうとします。天気には関係ないので get_weather は呼ばれません。ここが面白いところで、条件分岐を自分で書かなくていい。
余談ですが、description の書き方で Claudeのツール選択が結構変わります。「〜に関する質問が来た場合はこのツールを使ってください」みたいに具体的に書くと、意図した通りに動きやすくなる印象があります。
tool_choice で呼び出しを制御する
デフォルトでは Claudeが自律的にツールを使うかどうかを判断しますが、tool_choice パラメータで制御もできます。
response = client.messages.create(
model="claude-opus-4-5",
max_tokens=1024,
tools=tools,
tool_choice={"type": "any"}, # ツールを必ず使う
# tool_choice={"type": "auto"}, # デフォルト(Claudeが判断)
# tool_choice={"type": "tool", "name": "get_weather"}, # 特定ツールを強制
messages=messages,
)
type: "any" は定義済みのツールの中から必ず何か使わせたいとき。type: "tool" で名前を指定すると、そのツールを強制的に呼ばせることもできます。テストや特定ユースケースで使うことがあります。
正直、これを使う場面はまだそこまで多くないですが、挙動を制御したいときには便利です。
実用例:DynamoDBのデータをClaudeに取ってこさせる
最初に書いた「LambdaからDynamoDBのデータをClaudeに判断させたい」という動機で作った例です。実際のコードに近い構成で書いています。
import boto3
from boto3.dynamodb.conditions import Key
dynamodb = boto3.resource("dynamodb", region_name="ap-northeast-1")
table = dynamodb.Table("Orders")
def query_orders(date: str, status: str = None) -> str:
kwargs = {
"IndexName": "DateIndex",
"KeyConditionExpression": Key("order_date").eq(date),
}
if status:
kwargs["FilterExpression"] = "order_status = :s"
kwargs["ExpressionAttributeValues"] = {":s": status}
res = table.query(**kwargs)
return json.dumps({
"date": date,
"count": res["Count"],
"items": res["Items"][:5] # 先頭5件だけ返す
}, ensure_ascii=False, default=str)
tools = [
{
"name": "query_orders",
"description": "指定した日付の注文データをDynamoDBから取得します。ステータスで絞り込みも可能です。",
"input_schema": {
"type": "object",
"properties": {
"date": {
"type": "string",
"description": "注文日(YYYY-MM-DD形式)"
},
"status": {
"type": "string",
"description": "注文ステータス(例: pending, shipped, delivered)。省略可。"
}
},
"required": ["date"]
}
}
]
「今日の発送済み注文って何件ある?」という自然な質問に対して、Claudeが日付とステータスを解釈して query_orders を呼び出し、結果を人間が読みやすい形で回答してくれます。実際に動かしてみたときは「あ、これ便利だな」と素直に思いました。
ただ、IAMの権限周りとかLambdaでの実行環境の設定はそれなりに面倒なので、そこは別途きちんと整理する必要があります。
※この記事にはプロモーションが含まれます
ちなみに、Aiarty Image Enhancer(AI画像高画質化ツール。ノイズ除去・8倍拡大に対応)も気になっています。Aiarty Image Enhancer![]()
まとめ
Tool Useは仕組みを理解してしまえばシンプルです。「Claudeが判断してこちらが実行する」という分業の構造さえ押さえれば、あとはツールの定義と関数の実装を積み上げるだけ。複数のツールを組み合わせることで、自然言語で複雑な処理を指示できる柔軟性が生まれます。
次のステップとしては、エラーハンドリングを強化したり、ツールの結果をもう一度Claudeに読ませて精査させるなど、さらに信頼性を高めた実装も考えられます。まずは基本の流れを押さえて、自分のユースケースに合わせて拡張していくのが良さそうです。

