Tool Calling
讓模型提出工具呼叫,由應用安全執行並回傳結果。
Tool Calling 讓模型根據對話選擇一個或多個工具,並生成呼叫所需的結構化參數。模型不會替應用存取資料庫、呼叫內部服務或執行寫操作;真正的工具執行始終由你的程式負責。
它適合查詢訂單、讀取天氣、檢索知識庫、計算資料,以及經過授權後執行明確的業務操作。
不要直接執行模型生成的參數
工具名稱和參數都是模型輸出。執行前必須完成 JSON 解析、Schema 驗證、權限檢查和業務白名單;支付、刪除、傳送訊息等高風險操作還應要求使用者確認並使用冪等控制。
完整呼叫循環
1. 定義工具
使用清晰的名稱、用途說明和 JSON Schema 告訴模型何時可以呼叫工具,以及參數應採用什麼結構。
2. 請求模型
將使用者輸入和工具定義一起傳送。模型可能直接回答,也可能回傳零個、一個或多個工具呼叫。
3. 驗證並執行
根據工具名稱尋找本地實作,驗證所有參數和呼叫者權限,再設定逾時執行。不要允許模型自行指定任意函式、URL、SQL 或 Shell 命令。
4. 回傳工具結果
使用協議回傳的呼叫 ID,把執行結果與原工具呼叫關聯起來,然後再次請求模型。
5. 讀取最終回答
模型可以基於工具結果生成提供給使用者的回答,也可能繼續提出新的工具呼叫。應用應設定最大循環次數並識別終止條件。
定義一個只讀工具
下面的工具只查詢天氣,不產生外部寫入:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查詢指定城市的目前天氣",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名稱,例如上海"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"]
}
},
"required": ["city", "unit"],
"additionalProperties": false
}
}
}工具描述應該說明“做什麼”和“何時使用”。參數名保持穩定,枚舉盡量收窄,並用 required 和 additionalProperties 限制不必要輸入。
完成一次 Chat 工具循環
下面顯示宣告工具、讀取呼叫、執行本地函式、回傳結果和讀取最終回答的完整主路徑:
import json
from openai import OpenAI
client = OpenAI(
api_key="your-api-key",
base_url="https://api.agtcloud.cn/v1",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "查詢指定城市的目前天氣",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
},
},
"required": ["city", "unit"],
"additionalProperties": False,
},
},
}
]
def get_weather(city: str, unit: str) -> dict[str, str | int]:
if city != "上海" or unit != "celsius":
raise ValueError("範例只允許查詢上海攝氏溫度")
return {"city": city, "unit": unit, "temperature": 25}
messages = [{"role": "user", "content": "上海現在多少度?"}]
response = client.chat.completions.create(
model="MODEL_ID",
messages=messages,
tools=tools,
tool_choice="auto",
)
assistant_message = response.choices[0].message
messages.append(assistant_message)
for tool_call in assistant_message.tool_calls or []:
if tool_call.function.name != "get_weather":
raise ValueError("模型請求了未註冊的工具")
arguments = json.loads(tool_call.function.arguments)
result = get_weather(**arguments)
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result, ensure_ascii=False),
}
)
if assistant_message.tool_calls:
final_response = client.chat.completions.create(
model="MODEL_ID",
messages=messages,
tools=tools,
)
print(final_response.choices[0].message.content)
else:
print(assistant_message.content)正式應用應使用 JSON Schema 驗證庫驗證 arguments,並為工具實現設定獨立權限、逾時、錯誤類型和審計記錄。
不同協議如何表示工具呼叫
| 協議 | 工具定義 | 模型呼叫 | 工具結果回傳 |
|---|---|---|---|
| Chat Completions | tools[].function | Assistant Message 中的 tool_calls | role: tool 與 tool_call_id |
| Responses | tools 中的 Function Tool | function_call Output Item | function_call_output 與 call_id |
| Anthropic Messages | tools 與 input_schema | tool_use Content Block | tool_result Content Block |
| Gemini | functionDeclarations | Part 中的 functionCall | Part 中的 functionResponse |
欄位名稱和巢狀結構不能混用。完整結構分別檢視 Chat Completions、Responses、Anthropic Messages 和 Gemini Generate Content Reference。
多個呼叫與 Streaming
- 不要假設每次只能呼叫一個工具;按呼叫 ID 分別驗證、執行和回傳。
- 只有相互獨立且業務允許的只讀呼叫才適合併行執行。
- 串流參數可能被拆成多個增量;必須串接完成後再解析 JSON。
- 回傳工具結果後,模型仍可能提出下一輪呼叫;應用需要最大輪數和總逾時。
- 同一個寫操作重試時使用業務冪等鍵,避免重複扣款、傳送或建立資源。
失敗處理
工具執行失敗時,不要偽造成功結果。可以向模型回傳經過去識別化的錯誤類別,例如參數無效、權限不足、上游暫時不可用,讓模型向使用者解釋或選擇允許的替代方案。
以下資訊不應直接回傳給模型或使用者:內部堆疊、資料庫結構、存取權杖、私有網路位址和未去識別化的第三方回應。
上線前檢查
- 工具只能從註冊表或白名單選擇。
- 參數在執行前透過 Schema 和業務規則驗證。
- 每個工具都有權限、逾時、最大結果大小和錯誤處理。
- 高風險寫操作經過使用者確認,並支援冪等和審計。
- 呼叫循環有最大輪數,不能由模型無限繼續。
- 串流工具參數在完整結束後才會執行。
需要邊接收邊串接工具參數時,繼續閱讀 Streaming;需要讓最終回答遵循 JSON Schema 時,閱讀 Structured Output。