工具調用(Tool Calling)
工具調用(Tool Calling,也叫 Function Calling)讓模型不再只是生成文本,而是在需要時返回一個結構化的「函數調用請求」。你的程序執行真實邏輯(查天氣、查數據庫、下單),把結果回傳給模型,模型再據此生成最終回覆。RouteAPI 在 /v1/chat/completions(OpenAI 格式)和 /v1/messages(Claude 格式)上都支持工具調用。
1. 工具調用概述
Section titled “1. 工具調用概述”什麼是工具調用
Section titled “什麼是工具調用”工具調用是一次多輪往返:
- 你在請求裡聲明一組「工具」(每個工具有名字、描述和參數 schema)。
- 模型判斷是否需要某個工具,如果需要,返回一個帶參數的調用請求,而不是直接回答。
- 你的代碼解析參數、執行真實邏輯、拿到結果。
- 你把結果回傳給模型,模型生成面向用戶的自然語言回覆。
模型本身不會執行任何代碼,它只負責決定「調用哪個工具」和「傳什麼參數」。真正的執行永遠發生在你這一側,這也意味著安全邊界(權限校驗、參數過濾)由你控制。
| 場景 | 說明 |
|---|---|
| 實時數據查詢 | 天氣、匯率、股價、庫存等模型訓練數據裡沒有的信息 |
| 內部系統集成 | 查數據庫、調內部 API、讀訂單狀態 |
| 執行動作 | 下單、發郵件、創建工單等有副作用的操作 |
| 結構化抽取 | 強制模型按固定 schema 輸出,等價於一種結構化輸出手段 |
| 智能體編排 | Agent 框架用工具調用驅動多步任務 |
工具調用能力取決於所選模型。主流模型(OpenAI GPT 系列、Claude 系列、Gemini 系列等)大多支持,但最大工具數、是否支持並行調用等細節各不相同。模型列表接口不返回工具調用能力字段,請查閱模型供應商文件,或直接用目標模型發一次帶 tools 的請求驗證,詳見 Models。
2. 工具定義格式(OpenAI)
Section titled “2. 工具定義格式(OpenAI)”在 /v1/chat/completions 中,工具通過頂層 tools 數組聲明,每個工具是一個 type: "function" 的嵌套對象。
tools 數組結構
Section titled “tools 數組結構”{ "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "查詢指定城市的當前天氣。城市名使用中文全稱。", "parameters": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,如 北京" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "description": "溫度單位,默認攝氏度" } }, "required": ["city"] } } } ]}function schema
Section titled “function schema”| 字段 | 類型 | 必填 | 說明 |
|---|---|---|---|
type | string | 是 | 固定為 "function" |
function.name | string | 是 | 工具名,只能包含字母、數字、下劃線和連字符 |
function.description | string | 建議 | 工具用途說明,模型據此決定是否調用 |
function.parameters | object | 否 | 參數定義,標準 JSON Schema |
description 的質量直接決定模型是否會正確選用工具。寫清用途、每個參數的含義、取值範圍和邊界條件,比調整任何採樣參數都更有效。
parameters(JSON Schema)
Section titled “parameters(JSON Schema)”parameters 使用標準 JSON Schema 描述參數結構:
type: 通常是"object"。properties: 每個參數的類型、描述、枚舉值。required: 必填參數名列表。enum: 限定取值範圍,能顯著降低模型傳錯值的概率。
沒有參數的工具也要給出空 schema: "parameters": { "type": "object", "properties": {} }。
3. 工具定義格式(Claude)
Section titled “3. 工具定義格式(Claude)”在 /v1/messages 中,工具定義是平鋪結構,沒有外層 type 和 function 包裝,參數字段名叫 input_schema。
tools 數組差異
Section titled “tools 數組差異”{ "tools": [ { "name": "get_weather", "description": "查詢指定城市的當前天氣。城市名使用中文全稱。", "input_schema": { "type": "object", "properties": { "city": { "type": "string", "description": "城市名,如 北京" }, "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] } }, "required": ["city"] } } ]}input_schema
Section titled “input_schema”input_schema 的內部結構和 OpenAI 的 parameters 完全一致,都是標準 JSON Schema。差異只在外層包裝。
兩種格式對比
Section titled “兩種格式對比”| 對比項 | OpenAI(/v1/chat/completions) | Claude(/v1/messages) |
|---|---|---|
| 外層包裝 | { "type": "function", "function": {...} } | 直接平鋪,無包裝 |
| 工具名字段 | function.name | name |
| 描述字段 | function.description | description |
| 參數字段 | function.parameters | input_schema |
| 參數 schema | 標準 JSON Schema | 標準 JSON Schema(一致) |
| 模型返回 | message.tool_calls 數組 | content 裡的 tool_use 塊 |
| 結果回傳角色 | 獨立的 role: "tool" 消息 | user 消息裡的 tool_result 塊 |
| 結果關聯字段 | tool_call_id | tool_use_id |
Claude 協議的完整細節(含 is_error、流式 input_json_delta 等)見 Claude Messages 協議。本頁後續示例默認以 OpenAI 格式為主。
4. tool_choice 選項
Section titled “4. tool_choice 選項”tool_choice 控制模型如何選擇工具。
| 值 | 行為 |
|---|---|
"auto" | 模型自行決定是否調用工具,以及調用哪個。有 tools 時的默認值 |
"none" | 禁止調用任何工具,模型只輸出文本 |
"required" | 必須調用至少一個工具,但由模型選擇調用哪個 |
{ "type": "function", "function": { "name": "get_weather" } } | 強制調用指定工具 |
auto(默認)
Section titled “auto(默認)”{ "tool_choice": "auto" }最常用。適合「用戶問題有時需要工具、有時直接回答」的通用場景。
{ "tool_choice": "none" }臨時關閉工具但保留定義。常用於「先讓模型總結,不要再調工具」的收尾階段。
required
Section titled “required”{ "tool_choice": "required" }強制模型走工具路徑。適合結構化抽取這類「必須產出結構化結果」的場景。
指定特定工具
Section titled “指定特定工具”{ "tool_choice": { "type": "function", "function": { "name": "get_weather" } }}Claude 格式的對應寫法是 { "type": "tool", "name": "get_weather" },required 對應 { "type": "any" },none 對應 { "type": "none" }。
5. 工具調用流程
Section titled “5. 工具調用流程”一次完整的工具調用至少包含兩輪請求。
第一輪:模型返回 tool_calls
Section titled “第一輪:模型返回 tool_calls”發送帶 tools 的請求:
curl https://api.routeapi.ai/v1/chat/completions \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.5", "messages": [ { "role": "user", "content": "北京現在天氣怎麼樣?" } ], "tools": [ { "type": "function", "function": { "name": "get_weather", "description": "查詢指定城市的當前天氣。", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } } ] }'模型判斷需要調用工具時,finish_reason 為 tool_calls,message.content 為 null,tool_calls 裡帶上調用請求:
{ "choices": [ { "index": 0, "message": { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" } } ] }, "finish_reason": "tool_calls" } ]}注意 arguments 是一個 JSON 字符串,不是對象,需要你自己 JSON.parse / json.loads 解析。模型偶爾會生成不合法的 JSON,解析要放在 try/catch 裡。
按 function.name 分發到你的真實邏輯,用解析後的參數執行:
import json
args = json.loads(tool_call["function"]["arguments"])result = get_weather(**args) # "北京,晴,23 攝氏度"第二輪:傳遞工具結果
Section titled “第二輪:傳遞工具結果”把第一輪的 assistant 消息(含 tool_calls)原樣加回 messages,再追加一條 role: "tool" 消息攜帶結果。tool_call_id 必須與第一輪的 id 完全一致:
{ "model": "gpt-5.5", "messages": [ { "role": "user", "content": "北京現在天氣怎麼樣?" }, { "role": "assistant", "content": null, "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" } } ] }, { "role": "tool", "tool_call_id": "call_abc123", "content": "北京,晴,氣溫 23 攝氏度,濕度 45%。" } ], "tools": []}模型生成最終響應
Section titled “模型生成最終響應”第二輪請求返回自然語言結果,finish_reason 恢復為 stop:
{ "choices": [ { "message": { "role": "assistant", "content": "北京現在是晴天,氣溫 23 攝氏度,濕度 45%,比較舒適。" }, "finish_reason": "stop" } ]}6. 多輪工具調用
Section titled “6. 多輪工具調用”連續調用多個工具
Section titled “連續調用多個工具”模型可能需要多輪工具調用才能完成任務:先查訂單號,再用訂單號查物流。每一輪都遵循「模型返回 tool_calls → 執行 → 回傳結果」的循環,直到 finish_reason 變回 stop。生產代碼應寫成循環,並設置最大輪數上限防止無限循環:
MAX_TURNS = 5for _ in range(MAX_TURNS): resp = call_model(messages) msg = resp["choices"][0]["message"] messages.append(msg) if not msg.get("tool_calls"): break # 模型給出最終回覆 for tc in msg["tool_calls"]: result = dispatch(tc) # 執行並返回字符串 messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result, })並行工具調用
Section titled “並行工具調用”一輪裡模型可能同時請求多個互不依賴的工具(如同時查北京和上海的天氣),tool_calls 會是一個多元素數組。你需要為每一個 tool_call 都追加一條對應的 role: "tool" 消息,tool_call_id 一一對齊,缺一條下一輪就會報錯。
如果想禁用並行、強制模型一次只調一個工具,OpenAI 格式加 "parallel_tool_calls": false,Claude 格式在 tool_choice 裡加 "disable_parallel_tool_use": true。
7. 流式工具調用
Section titled “7. 流式工具調用”設置 stream: true 時,工具調用參數是逐片增量返回的。
流式輸出中的 tool_calls
Section titled “流式輸出中的 tool_calls”每個 SSE chunk 的 delta.tool_calls 裡帶 index(標識第幾個工具調用),function.arguments 是參數 JSON 的一個片段:
data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_abc123","type":"function","function":{"name":"get_weather","arguments":""}}]}}]}
data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]}}]}
data: {"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"北京\"}"}}]}}]}
data: {"choices":[{"finish_reason":"tool_calls"}]}
data: [DONE]如何處理增量 delta
Section titled “如何處理增量 delta”按 index 分組累積:id 和 name 通常只在第一個片段出現,arguments 需要把所有片段拼接完整後再解析。不要在拼接過程中嘗試解析中間態(那是不完整的 JSON)。
buffers = {} # index -> {"id", "name", "args"}for chunk in stream: for tc in chunk["choices"][0]["delta"].get("tool_calls", []): i = tc["index"] buf = buffers.setdefault(i, {"id": "", "name": "", "args": ""}) if tc.get("id"): buf["id"] = tc["id"] fn = tc.get("function", {}) if fn.get("name"): buf["name"] = fn["name"] if fn.get("arguments"): buf["args"] += fn["arguments"]
# 流結束後再解析for buf in buffers.values(): args = json.loads(buf["args"])Claude 格式的流式工具參數通過 input_json_delta 事件的 partial_json 增量返回,按 index 累積後解析,細節見 Claude Messages 協議。
8. 各模型支持情況
Section titled “8. 各模型支持情況”支持工具調用的模型
Section titled “支持工具調用的模型”RouteAPI 聚合的多數主流模型都支持工具調用,包括 OpenAI GPT 系列、Claude 系列、Gemini 系列等。先用模型列表接口確認模型在你帳戶下可用:
curl https://api.routeapi.ai/v1/models \ -H "Authorization: Bearer $ROUTEAPI_KEY"該接口只返回模型是否可用及可用端點,不返回工具調用能力。是否支持工具調用請以供應商文件或一次實際帶 tools 的請求為準。
各模型的限制
Section titled “各模型的限制”不同模型在以下維度存在差異,請在測試環境驗證後再上線:
| 維度 | 說明 |
|---|---|
| 最大工具數 | 單次請求可聲明的工具數量上限因模型而異 |
| 並行調用 | 部分模型不支持一輪返回多個 tool_calls |
tool_choice 支持度 | required / 指定特定工具並非所有模型都支持 |
| 參數複雜度 | 深層嵌套或超大 JSON Schema 可能被部分模型截斷或忽略 |
| 流式增量粒度 | arguments 分片方式在不同模型間不一致,務必按 index 累積 |
9. 完整示例
Section titled “9. 完整示例”天氣查詢工具示例(端到端)
Section titled “天氣查詢工具示例(端到端)”以下三段代碼功能等價:定義工具 → 第一輪拿到 tool_calls → 執行 → 第二輪回傳結果 → 拿到最終回覆。
curl(手動完成兩輪):
# 第一輪:發起帶工具的請求curl https://api.routeapi.ai/v1/chat/completions \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.5", "messages": [{ "role": "user", "content": "北京現在天氣怎麼樣?" }], "tools": [{ "type": "function", "function": { "name": "get_weather", "description": "查詢指定城市的當前天氣。", "parameters": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } }] }'
# 第二輪:把工具結果回傳(tool_call_id 用第一輪返回的值)curl https://api.routeapi.ai/v1/chat/completions \ -H "Authorization: Bearer $ROUTEAPI_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.5", "messages": [ { "role": "user", "content": "北京現在天氣怎麼樣?" }, { "role": "assistant", "content": null, "tool_calls": [{ "id": "call_abc123", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"北京\"}" } }] }, { "role": "tool", "tool_call_id": "call_abc123", "content": "北京,晴,氣溫 23 攝氏度,濕度 45%。" } ] }'Python(OpenAI SDK,自動完成兩輪):
import jsonimport osfrom openai import OpenAI
client = OpenAI( api_key=os.environ["ROUTEAPI_KEY"], base_url="https://api.routeapi.ai/v1",)
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查詢指定城市的當前天氣。城市名使用中文全稱。", "parameters": { "type": "object", "properties": { "city": {"type": "string", "description": "城市名,如 北京"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["city"], }, }, }]
def get_weather(city: str, unit: str = "celsius") -> str: # 這裡替換為真實的天氣服務調用 return f"{city},晴,氣溫 23 攝氏度,濕度 45%。"
messages = [{"role": "user", "content": "北京現在天氣怎麼樣?"}]
# 第一輪resp = client.chat.completions.create( model="gpt-5.5", messages=messages, tools=tools)msg = resp.choices[0].message
if msg.tool_calls: # assistant 消息必須原樣加回,否則 tool_call_id 無法對齊 messages.append(msg) for tc in msg.tool_calls: args = json.loads(tc.function.arguments) result = get_weather(**args) messages.append( {"role": "tool", "tool_call_id": tc.id, "content": result} )
# 第二輪 resp = client.chat.completions.create( model="gpt-5.5", messages=messages, tools=tools )
print(resp.choices[0].message.content)Node.js(openai 包,自動完成兩輪):
import OpenAI from 'openai';
const client = new OpenAI({ apiKey: process.env.ROUTEAPI_KEY, baseURL: 'https://api.routeapi.ai/v1',});
const tools = [ { type: 'function', function: { name: 'get_weather', description: '查詢指定城市的當前天氣。城市名使用中文全稱。', parameters: { type: 'object', properties: { city: { type: 'string', description: '城市名,如 北京' }, unit: { type: 'string', enum: ['celsius', 'fahrenheit'] }, }, required: ['city'], }, }, },];
function getWeather(city, unit = 'celsius') { // 這裡替換為真實的天氣服務調用 return `${city},晴,氣溫 23 攝氏度,濕度 45%。`;}
const messages = [{ role: 'user', content: '北京現在天氣怎麼樣?' }];
// 第一輪let resp = await client.chat.completions.create({ model: 'gpt-5.5', messages, tools,});const msg = resp.choices[0].message;
if (msg.tool_calls) { messages.push(msg); // assistant 消息原樣加回 for (const tc of msg.tool_calls) { const args = JSON.parse(tc.function.arguments); const result = getWeather(args.city, args.unit); messages.push({ role: 'tool', tool_call_id: tc.id, content: result }); }
// 第二輪 resp = await client.chat.completions.create({ model: 'gpt-5.5', messages, tools, });}
console.log(resp.choices[0].message.content);數據庫查詢工具示例
Section titled “數據庫查詢工具示例”把工具當成對內部系統的安全封裝。關鍵:SQL 不應由模型直接生成,而應由模型選參數、你的代碼拼接參數化查詢,避免注入。
tools = [ { "type": "function", "function": { "name": "query_order", "description": "根據訂單號查詢訂單狀態和金額。", "parameters": { "type": "object", "properties": { "order_id": { "type": "string", "description": "訂單號,形如 ORD-20260917-001", } }, "required": ["order_id"], }, }, }]
def query_order(order_id: str) -> str: # 用參數化查詢,絕不把模型輸出直接拼進 SQL row = db.execute( "SELECT status, amount FROM orders WHERE order_id = %s", (order_id,), ).fetchone() if row is None: return json.dumps({"found": False}) return json.dumps({"found": True, "status": row[0], "amount": row[1]})工具結果建議回傳 JSON 字符串,模型能更穩定地解析結構化字段。
多工具編排示例
Section titled “多工具編排示例”同時聲明多個工具,模型按需選擇甚至並行調用。這裡用天氣 + 匯率兩個工具:
tools = [ { "type": "function", "function": { "name": "get_weather", "description": "查詢指定城市的當前天氣。", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"], }, }, }, { "type": "function", "function": { "name": "get_exchange_rate", "description": "查詢兩種貨幣之間的匯率。", "parameters": { "type": "object", "properties": { "from_currency": {"type": "string", "description": "如 USD"}, "to_currency": {"type": "string", "description": "如 CNY"}, }, "required": ["from_currency", "to_currency"], }, }, },]
dispatch = { "get_weather": lambda city: f"{city},晴,23 攝氏度。", "get_exchange_rate": lambda from_currency, to_currency: f"1 {from_currency} = 7.2 {to_currency}",}
messages = [{"role": "user", "content": "北京天氣怎麼樣?順便告訴我美元對人民幣的匯率。"}]
# 循環處理多輪 / 並行工具調用,設上限防止死循環for _ in range(5): resp = client.chat.completions.create( model="gpt-5.5", messages=messages, tools=tools ) msg = resp.choices[0].message messages.append(msg) if not msg.tool_calls: break # 並行調用時 tool_calls 是多元素數組,每個都要回傳結果 for tc in msg.tool_calls: args = json.loads(tc.function.arguments) result = dispatch[tc.function.name](**args) messages.append( {"role": "tool", "tool_call_id": tc.id, "content": result} )
print(messages[-1]["content"])10. 錯誤處理
Section titled “10. 錯誤處理”工具調用引入了模型側、你的代碼側、上游服務側三處可能出錯的環節。
工具參數驗證失敗
Section titled “工具參數驗證失敗”模型可能返回不合法的 JSON,或缺少必填參數、傳入超出 enum 範圍的值。務必:
- 用 try/catch 包裹
JSON.parse/json.loads。 - 解析後按 schema 校驗必填字段和取值範圍。
- 校驗失敗時,把錯誤信息作為工具結果回傳,讓模型自我糾正,而不是直接拋異常終止對話:
try: args = json.loads(tc.function.arguments) city = args["city"] # 校驗必填except (json.JSONDecodeError, KeyError) as exc: result = f"參數解析失敗:{exc}。請重新給出合法的 city 參數。"else: result = get_weather(city)
messages.append({"role": "tool", "tool_call_id": tc.id, "content": result})工具執行超時
Section titled “工具執行超時”工具背後是真實服務,可能超時或不可用。給每個工具調用設置超時上限,超時後把結果回傳為一條明確的錯誤說明,讓模型改走別的策略:
try: result = call_service(args, timeout=5)except TimeoutError: result = "服務超時,未獲取到數據,請稍後重試或改用其他方式。"不要對有副作用的工具(下單、發郵件)做自動重試,否則可能重複執行。冪等設計或先查後寫更安全。
工具返回錯誤
Section titled “工具返回錯誤”業務層錯誤(訂單不存在、無權限)同樣應回傳給模型,而不是靜默返回空值。回傳結構化錯誤信息,模型能據此給用戶合理解釋:
messages.append({ "role": "tool", "tool_call_id": tc.id, "content": json.dumps({"error": "order_not_found", "order_id": order_id}),})Claude 格式對應地在 tool_result 塊上設 "is_error": true,見 Claude Messages 協議。
- 工具調用能力、最大工具數、並行調用支持度取決於所選模型,上線前請在測試環境驗證。
arguments永遠是 JSON 字符串而非對象,務必顯式解析。- 流式場景務必按
index累積arguments分片,拼接完整後再解析。 - 明確傳入
0或false的可選參數會被視為用戶顯式設置,不會當作缺省丟棄。 - 記錄每次請求的 request ID、模型 ID、狀態碼和 token 用量,便於排查。錯誤結構詳見 錯誤與調試。