Tool Use:讓 Claude 呼叫你的工具

在前面的系列文章中,我們已經學會了如何使用 Anthropic Messages API 進行基本的文字對話。但 Claude 的能力遠不止於此——透過 Tool Use(工具使用,也稱為 Function Calling),你可以讓 Claude 呼叫你自定義的工具函數,實現查詢天氣、存取資料庫、呼叫第三方 API 等外部操作。這是建構 AI Agent 最關鍵的能力之一。

什麼是 Tool Use(Function Calling)?

Tool Use 讓你在 API 請求中定義一組工具(tools),每個工具描述了名稱、用途和輸入參數的 JSON Schema。當使用者的提問需要外部資訊時,Claude 會自動判斷應該呼叫哪個工具,並產生結構化的工具呼叫請求。你的應用程式負責實際執行該工具,再將結果回傳給 Claude,由 Claude 整合所有資訊後產生最終回覆。

整個流程可以用以下步驟概括:

  1. 你在 API 請求中定義可用的工具(tools)及其 JSON Schema
  2. Claude 分析使用者的問題,判斷是否需要呼叫工具
  3. Claude 回傳 stop_reason: "tool_use"tool_use 區塊
  4. 你的程式接收工具呼叫請求,執行對應的函數
  5. 將執行結果以 tool_result 格式回傳給 Claude
  6. Claude 根據工具結果產生最終的自然語言回覆

定義工具 Schema

每個工具定義包含三個核心欄位:name(工具名稱)、description(工具描述)和 input_schema(輸入參數的 JSON Schema)。好的工具描述是提升 Claude 判斷準確度的關鍵。

欄位類型說明
namestring工具名稱,需符合 ^[a-zA-Z0-9_-]{1,64}$
descriptionstring詳細描述工具的功能、使用時機和行為特性
input_schemaobject符合 JSON Schema 規範的參數定義
input_examplesarray(選填)範例輸入,幫助 Claude 更理解工具的使用方式

以下是一個天氣查詢工具的定義範例:

{
  "name": "get_weather",
  "description": "取得指定地點的目前天氣資訊。地點需包含城市名稱,可選擇溫度單位。當使用者詢問天氣狀況時使用此工具。",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "城市名稱,例如 Taipei, Taiwan"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "溫度單位,預設為 celsius"
      }
    },
    "required": ["location"]
  }
}

工具描述的最佳實踐

  • 詳細描述:至少 3-4 句話說明工具的功能、使用時機和限制
  • 參數說明:每個參數都應有清楚的 description,包含格式範例
  • 合併相關操作:將相關功能合併為一個工具搭配 action 參數,減少選擇模糊性
  • 命名空間:使用 github_list_prsslack_send_message 等有意義的前綴
  • 精簡回傳:工具回傳結果只包含 Claude 需要的關鍵資訊

在 Messages API 中傳遞工具定義

工具定義透過 API 請求的 tools 頂層參數傳遞。以下是使用 Python SDK 的完整範例:

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[
        {
            "name": "get_weather",
            "description": "取得指定地點的目前天氣資訊",
            "input_schema": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "城市名稱,例如 Taipei, Taiwan"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"],
                        "description": "溫度單位"
                    }
                },
                "required": ["location"]
            }
        }
    ],
    messages=[
        {"role": "user", "content": "台北現在的天氣如何?"}
    ]
)

print(response)

處理 tool_use 回應

當 Claude 判斷需要使用工具時,API 回應的 stop_reason 會是 "tool_use",並在 content 陣列中包含一個或多個 tool_use 區塊。每個區塊包含三個重要欄位:

欄位說明
id此次工具呼叫的唯一識別碼,用於後續回傳結果時對應
name被呼叫的工具名稱
input符合工具 input_schema 的參數物件

以下是 Claude 回應的範例:

{
  "id": "msg_01Aq9w938a90dw8q",
  "model": "claude-sonnet-4-6",
  "stop_reason": "tool_use",
  "role": "assistant",
  "content": [
    {
      "type": "text",
      "text": "我來幫你查詢台北目前的天氣狀況。"
    },
    {
      "type": "tool_use",
      "id": "toolu_01A09q90qw90lq917835lq9",
      "name": "get_weather",
      "input": { "location": "Taipei, Taiwan", "unit": "celsius" }
    }
  ]
}

回傳工具執行結果(tool_result)

收到 tool_use 回應後,你需要在你的程式中執行對應的工具函數,然後將結果以 tool_result 格式回傳給 Claude。回傳時需包含 tool_use_id(對應原始呼叫的 id)和 content(執行結果)。

# 假設我們已經執行了天氣查詢函數
weather_data = get_weather_from_api("Taipei, Taiwan", "celsius")

# 將結果回傳給 Claude
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,  # 同樣的工具定義
    messages=[
        {"role": "user", "content": "台北現在的天氣如何?"},
        # 包含 Claude 的完整回應(含 text 和 tool_use)
        {"role": "assistant", "content": assistant_response.content},
        # 回傳工具執行結果
        {
            "role": "user",
            "content": [
                {
                    "type": "tool_result",
                    "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
                    "content": "台北目前天氣:晴天,氣溫 28°C,濕度 75%"
                }
            ]
        }
    ]
)

收到 tool_result 後,Claude 會將工具回傳的資料整合進自然語言回覆中,例如:「台北目前是晴天,氣溫 28°C,相對濕度為 75%。」

多輪工具對話流程

在實際應用中,一次對話可能需要多次工具呼叫。建構 Agentic Loop(代理迴圈)的標準做法是持續檢查 stop_reason,直到 Claude 的回應不再是 "tool_use" 為止:

import anthropic
import json

client = anthropic.Anthropic()

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/Taipei"}
            },
            "required": ["timezone"]
        }
    }
]

# 工具執行函數
def execute_tool(name, input_data):
    if name == "get_weather":
        return json.dumps({"temp": "28°C", "condition": "晴天"})
    elif name == "get_time":
        return json.dumps({"time": "2026-06-02 14:30:00"})
    return "未知工具"

# Agentic Loop
messages = [{"role": "user", "content": "台北現在幾點?天氣如何?"}]

while True:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=tools,
        messages=messages
    )
    
    # 將 assistant 回應加入對話
    messages.append({"role": "assistant", "content": response.content})
    
    # 如果不需要工具呼叫,結束迴圈
    if response.stop_reason != "tool_use":
        break
    
    # 處理所有工具呼叫
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            result = execute_tool(block.name, block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": result
            })
    
    # 回傳工具結果
    messages.append({"role": "user", "content": tool_results})

# 輸出最終回覆
for block in response.content:
    if hasattr(block, 'text'):
        print(block.text)

多工具同時使用(Parallel Tool Use)

Claude 可以在單一回應中同時呼叫多個工具。例如,使用者問「台北和東京的天氣分別如何?」時,Claude 可能在一次回應中發出兩個 tool_use 區塊。你需要執行所有工具呼叫,並將所有結果一次回傳:

# Claude 的回應可能包含多個 tool_use 區塊
# response.content = [
#   TextBlock(text="我來同時查詢兩個城市的天氣。"),
#   ToolUseBlock(id="toolu_01...", name="get_weather", input={"location": "Taipei"}),
#   ToolUseBlock(id="toolu_02...", name="get_weather", input={"location": "Tokyo"})
# ]

# 回傳所有結果
tool_results_message = {
    "role": "user",
    "content": [
        {
            "type": "tool_result",
            "tool_use_id": "toolu_01...",
            "content": "台北:晴天,28°C"
        },
        {
            "type": "tool_result",
            "tool_use_id": "toolu_02...",
            "content": "東京:多雲,22°C"
        }
    ]
}

如果你不需要平行工具呼叫功能,可以在 API 請求中設定 "parallel_tool_use": false 來停用它,這樣 Claude 每次只會回傳一個工具呼叫。

強制使用特定工具(tool_choice)

透過 tool_choice 參數,你可以控制 Claude 是否以及如何使用工具。這在某些場景特別有用,例如強制 Claude 使用結構化輸出格式。

選項行為使用場景
autoClaude 自行決定是否呼叫工具(預設值)一般對話場景
anyClaude 必須使用至少一個工具確保一定會觸發工具呼叫
tool強制使用指定的工具結構化輸出、特定流程控制
none禁止使用任何工具純文字回覆場景
# 強制使用特定工具
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "tool", "name": "get_weather"},
    messages=[{"role": "user", "content": "台北的狀況如何?"}]
)

# 必須使用任一工具
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=tools,
    tool_choice={"type": "any"},
    messages=[{"role": "user", "content": "查詢台北資訊"}]
)

錯誤處理

工具執行過程中難免會遇到錯誤,例如 API 逾時、參數無效等情況。你可以在 tool_result 中使用 is_error: true 來告知 Claude 工具執行失敗,Claude 會根據錯誤訊息產生適當的回覆:

# 工具執行失敗時的回傳格式
{
    "type": "tool_result",
    "tool_use_id": "toolu_01A09q90qw90lq917835lq9",
    "content": "ConnectionError: 天氣服務 API 暫時無法使用(HTTP 500)",
    "is_error": True
}

錯誤處理的最佳實踐:

  • 提供具體錯誤訊息:避免使用「失敗」等籠統描述,應說明具體原因和建議的下一步
  • 包含重試提示:例如「頻率限制超過,請在 60 秒後重試」
  • 參數驗證:在執行工具前先驗證參數,提早回報無效輸入
  • 使用 strict 模式:在工具定義中加入 strict: true,確保 Claude 的工具呼叫一定符合你的 Schema

Strict 模式:保證 Schema 一致性

在工具定義中加入 strict: true,可以確保 Claude 的工具呼叫輸入一定完全符合你的 JSON Schema,避免缺少必要參數或型別不匹配的問題:

{
  "name": "get_weather",
  "description": "取得指定地點的天氣資訊",
  "strict": true,
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "城市名稱"
      }
    },
    "required": ["location"],
    "additionalProperties": false
  }
}

完整實戰範例

以下提供三個常見的 Tool Use 應用場景,幫助你理解如何在實際專案中運用。

範例一:天氣查詢 Agent

import anthropic
import json
import httpx

client = anthropic.Anthropic()

# 定義天氣工具
weather_tool = {
    "name": "get_weather",
    "description": "取得指定城市的即時天氣資訊,包含溫度、濕度、天氣狀況描述。",
    "input_schema": {
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "城市名稱,例如 Taipei, Tokyo, New York"
            }
        },
        "required": ["city"]
    }
}

def fetch_weather(city: str) -> str:
    """模擬天氣 API 查詢"""
    # 實際應用中替換為真實的天氣 API 呼叫
    weather_data = {
        "Taipei": {"temp": 28, "humidity": 75, "condition": "晴天"},
        "Tokyo": {"temp": 22, "humidity": 60, "condition": "多雲"},
        "New York": {"temp": 18, "humidity": 55, "condition": "陰天"}
    }
    data = weather_data.get(city, {"temp": 20, "humidity": 50, "condition": "未知"})
    return json.dumps(data, ensure_ascii=False)

def chat(user_message: str):
    messages = [{"role": "user", "content": user_message}]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=[weather_tool],
            messages=messages
        )
        
        messages.append({"role": "assistant", "content": response.content})
        
        if response.stop_reason != "tool_use":
            break
        
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                if block.name == "get_weather":
                    result = fetch_weather(block.input["city"])
                else:
                    result = json.dumps({"error": "未知工具"})
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result
                })
        
        messages.append({"role": "user", "content": tool_results})
    
    return response.content[-1].text

# 執行
print(chat("台北和東京今天天氣怎麼樣?"))

範例二:計算機工具

import anthropic
import json

client = anthropic.Anthropic()

calculator_tool = {
    "name": "calculator",
    "description": "執行數學運算。支援加減乘除、次方、平方根等基本運算。當使用者需要精確的數學計算時使用。",
    "input_schema": {
        "type": "object",
        "properties": {
            "expression": {
                "type": "string",
                "description": "數學表達式,例如 '(15 + 27) * 3' 或 '144 ** 0.5'"
            }
        },
        "required": ["expression"]
    }
}

def calculate(expression: str) -> str:
    """安全地執行數學運算"""
    try:
        # 限制只允許數學運算相關的字元
        allowed = set("0123456789+-*/().** ")
        if not all(c in allowed for c in expression):
            return json.dumps({"error": "不支援的運算符"})
        result = eval(expression)
        return json.dumps({"result": result})
    except Exception as e:
        return json.dumps({"error": str(e)})

messages = [{"role": "user", "content": "幫我算一下 (125 + 375) * 2.5 是多少?"}]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    tools=[calculator_tool],
    messages=messages
)

# 處理工具呼叫
if response.stop_reason == "tool_use":
    messages.append({"role": "assistant", "content": response.content})
    
    for block in response.content:
        if block.type == "tool_use":
            result = calculate(block.input["expression"])
            messages.append({
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result
                }]
            })
    
    final = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=1024,
        tools=[calculator_tool],
        messages=messages
    )
    print(final.content[0].text)

範例三:資料庫查詢工具

import anthropic
import json
import sqlite3

client = anthropic.Anthropic()

# 定義多個資料庫相關工具
db_tools = [
    {
        "name": "query_database",
        "description": "對 SQLite 資料庫執行 SELECT 查詢。僅支援讀取操作,不允許修改資料。回傳查詢結果的 JSON 格式。",
        "input_schema": {
            "type": "object",
            "properties": {
                "sql": {
                    "type": "string",
                    "description": "SQL SELECT 查詢語句"
                }
            },
            "required": ["sql"]
        }
    },
    {
        "name": "list_tables",
        "description": "列出資料庫中所有表格名稱及其欄位結構。",
        "input_schema": {
            "type": "object",
            "properties": {},
            "required": []
        }
    }
]

def execute_query(sql: str) -> str:
    """執行 SQL 查詢"""
    conn = sqlite3.connect("app.db")
    try:
        # 安全檢查:僅允許 SELECT
        if not sql.strip().upper().startswith("SELECT"):
            return json.dumps({"error": "僅允許 SELECT 查詢"})
        cursor = conn.execute(sql)
        columns = [desc[0] for desc in cursor.description]
        rows = [dict(zip(columns, row)) for row in cursor.fetchall()]
        return json.dumps({"columns": columns, "rows": rows, "count": len(rows)}, ensure_ascii=False)
    except Exception as e:
        return json.dumps({"error": str(e)})
    finally:
        conn.close()

def list_tables() -> str:
    """列出所有資料表"""
    conn = sqlite3.connect("app.db")
    try:
        cursor = conn.execute(
            "SELECT name FROM sqlite_master WHERE type='table'"
        )
        tables = []
        for (name,) in cursor.fetchall():
            cols = conn.execute(f"PRAGMA table_info({name})").fetchall()
            tables.append({
                "table": name,
                "columns": [{"name": c[1], "type": c[2]} for c in cols]
            })
        return json.dumps(tables, ensure_ascii=False)
    finally:
        conn.close()

# 使用範例
messages = [{"role": "user", "content": "資料庫裡有哪些表格?最近的訂單有哪些?"}]

while True:
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=4096,
        tools=db_tools,
        messages=messages
    )
    messages.append({"role": "assistant", "content": response.content})
    
    if response.stop_reason != "tool_use":
        break
    
    tool_results = []
    for block in response.content:
        if block.type == "tool_use":
            if block.name == "query_database":
                result = execute_query(block.input["sql"])
            elif block.name == "list_tables":
                result = list_tables()
            else:
                result = json.dumps({"error": "未知工具"})
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": result
            })
    
    messages.append({"role": "user", "content": tool_results})

# 輸出最終回覆
for block in response.content:
    if hasattr(block, "text"):
        print(block.text)

Server Tools 與 Client Tools 的差異

Anthropic API 支援兩種工具類型。Client Tools(客戶端工具)由你的應用程式執行,包含自定義工具和 Anthropic 定義的 Schema 工具(如 bash、text_editor)。Server Tools(伺服器端工具)由 Anthropic 的基礎設施執行,例如 web_search、code_execution 等,你不需要處理工具呼叫和結果回傳的流程。

特性Client ToolsServer Tools
執行位置你的應用程式Anthropic 基礎設施
需要處理 tool_result
範例自定義函數、bash、text_editorweb_search、code_execution
額外計費僅一般 Token 費用可能有額外使用量計費

Token 計費注意事項

使用 Tool Use 時,額外的 Token 消耗來自三個部分:工具定義本身(tools 參數中的名稱、描述和 Schema)、tool_use 區塊(API 請求與回應中的工具呼叫內容)、以及 tool_result 區塊(工具執行結果)。此外,啟用工具時系統會自動注入一段特殊的系統提示,不同模型消耗的 Token 數略有不同,大約在 300-350 tokens 左右。

延伸閱讀

總結

Tool Use 是讓 Claude 從「聊天機器人」進化為「AI Agent」的關鍵能力。透過定義工具 Schema、處理工具呼叫迴圈、回傳執行結果,你可以讓 Claude 與任何外部系統互動——從簡單的天氣查詢到複雜的資料庫操作,甚至是多系統的企業級整合。

掌握 Tool Use 的核心概念後,你已經具備了建構功能強大的 AI 應用的基礎。在下一篇文章中,我們將探討更進階的主題,繼續擴展你的 Claude API 開發技能。