A
AIエージェントの仕組み
ch4-s3 · Calculator Tool

電卓ツールを作る

約 14 分

この回のゴール

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_usetool_resultid で紐付けます:

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_evalsympy - サンドボックス実行環境 - 専用の DSL パーサー

を使います。


まとめ

この回の限界(次への動機)

電卓 1 つだけならシンプル。実運用では 複数のツール を Claude に並べて、場面に応じて選ばせる必要があります。 👉 次回「複数ツールと選択」で、tool_choice パラメータと並列ツール使用を学びます。

参考文献

📝 理解度クイズ (3 問) 💡 ログインすると進捗が保存されます

💬 このサブステップの Q&A

まだ質問はありません。最初の質問を投稿してみましょう。

質問の投稿にはログインが必要です。