この回のゴール
- 最小の 電卓ツール を Claude API で動かす
tool_use/tool_resultの 2 往復メッセージング を完全に理解する- 各ステップでどのメッセージが飛んでいるかを把握する
- ここまで学んだ JSON Schema が実戦で効く場面を体験する
1. 全体の流れ
[あなた] ──── tools=[calculator] を付けて request ───▶ [Claude]
│
│ 「計算が必要だな、
│ calculator ツールを呼ぼう」
│
[あなた] ◀── tool_use ブロック (input={expression: "..."}) ── [Claude]
│
│ (あなたのコードで実行)
│ eval("2+3*4") → 14
│
[あなた] ──── tool_result (content="14") を付けて request ─▶ [Claude]
│
│ 「結果は 14 か、
│ これで答えられる」
│
[あなた] ◀── text: "答えは 14 です" ───── [Claude]
重要: Claude API は ステートレス なので、2 回目のリクエストには 1 回目のメッセージ全部 + tool_result を含めて送ります。
2. メッセージ構造の詳細
ステップ 1: 最初のリクエスト
response1 = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
tools=[{
"name": "calculator",
"description": "数式を評価して結果を返す。",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Python の式として評価可能な数式。例: '2+3*4'"
}
},
"required": ["expression"],
}
}],
messages=[
{"role": "user", "content": "734521 × 892143 はいくつ?"}
],
)
ステップ 2: レスポンスを見る
response1.stop_reason # "tool_use"
response1.content # [ToolUseBlock(name="calculator", id="toolu_ABC", input={"expression": "734521 * 892143"})]
stop_reason == "tool_use" になったら「ツール呼び出しを要求されている」合図。
ステップ 3: ツールを実行
tool_use_block = next(b for b in response1.content if b.type == "tool_use")
expression = tool_use_block.input["expression"] # "734521 * 892143"
result = eval(expression) # 655199895003
⚠️ 実運用では eval は危険。サンドボックスや AST パーサー を使う。
ステップ 4: 2 回目のリクエスト(tool_result を含める)
response2 = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
tools=[...], # 同じ tools
messages=[
{"role": "user", "content": "734521 × 892143 はいくつ?"},
{"role": "assistant", "content": response1.content}, # 👈 全部含める
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use_block.id, # 👈 ID マッチング
"content": str(result),
}]
}
],
)
ステップ 5: 最終応答
response2.stop_reason # "end_turn"
response2.content # [TextBlock(text="734521 × 892143 = 655,199,895,003 です。")]
3. ID マッチングが肝
tool_use と tool_result は id で紐付けます:
tool_use.id: "toolu_01ABC..."
tool_result.tool_use_id: "toolu_01ABC..." ← 一致させる
これを間違うと Claude が混乱します。
4. messages 配列の構造まとめ
messages = [
# 1. ユーザーの最初の質問
{"role": "user", "content": "..."},
# 2. Claude のツール呼び出し要求
{"role": "assistant", "content": [tool_use_block]},
# 3. ツール実行結果 (user ロール!)
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "...", "content": "..."}]},
# 4. Claude の最終応答 (ここで返ってくる)
]
ポイント:
- tool_result の ロールは user(不思議だけど仕様)
- assistant の content は ブロックのリスト にできる
- 複数ツール呼び出しがある場合、すべての結果を 1 つの user メッセージ にまとめる
5. セキュリティ警告 ⚠️
eval() は任意の Python コードを実行できるので 絶対に本番で使わない。
実運用では:
- ast.literal_eval や sympy
- サンドボックス実行環境
- 専用の DSL パーサー
を使います。
まとめ
- ツール呼び出しの最小実装は 2 回のリクエスト:
toolsを渡し、Claude がtool_useを返す- ツール実行結果を
tool_resultとして返し、Claude が最終応答 tool_use.idとtool_result.tool_use_idを 一致 させることtool_resultのロールはuser(仕様)- セキュリティ:
evalを本番で使わない
この回の限界(次への動機)
電卓 1 つだけならシンプル。実運用では 複数のツール を Claude に並べて、場面に応じて選ばせる必要があります。
👉 次回「複数ツールと選択」で、tool_choice パラメータと並列ツール使用を学びます。
参考文献
- Anthropic Tool Use Guide
- Python
ast.literal_eval— 安全な式評価