Function Calling:Responses API 與 Chat Completions 比較
從 Wire Contract 比較函式定義、Call ID、結果訊息、串流參數、授權、冪等與路由相容性。
Responses 與 Chat Completions 都能要求應用執行函式,但兩者表示 Tool Loop 的方式不同。Chat Completions 把定義放在 tools[].function,回傳 message.tool_calls,並以 role: "tool" 訊息接收結果;Responses 使用扁平 Function Definition、回傳帶型別的 function_call Output Item,再透過 call_id 連結 function_call_output。
模型不會執行你的函式。應用必須驗證參數、確認授權、精確執行、回傳結果,並在請求重試時避免重複副作用。
Tool Loop 有四個步驟
1. 宣告允許的函式與參數 Schema
2. 接收模型提出的一個或多個 Function Call
3. 在應用程式中驗證、授權並執行
4. 使用每次呼叫的 Correlation ID 回傳結果
第四步完成後,模型才能依據工具結果產生最終答案。若模型再次要求工具,使用新的 Call ID 重複流程。OpenAI Function Calling 指南也將其定義為多步交換;把第一個 Tool Call 當成最終答案是常見整合錯誤。
比較 Wire Contract
| 項目 | Responses API | Chat Completions |
|---|---|---|
| Function Definition | 扁平 tools[],包含 name、description、parameters、strict |
同欄位放在 tools[].function |
| 模型提出的呼叫 | function_call Output Item |
Assistant tool_calls[] |
| 關聯識別 | call_id |
Tool Call id,結果使用 tool_call_id |
| 函式名稱 | function_call.name |
tool_calls[].function.name |
| 參數 | function_call.arguments JSON 字串 |
tool_calls[].function.arguments JSON 字串 |
| 結果 | function_call_output Input Item |
role: "tool" Message |
| 串流參數 | Typed Argument Delta Events | delta.tool_calls[] Fragments |
| 最終文字 | Typed Output Items / Helper | choices[0].message.content |
不要依 Array Position 關聯呼叫。平行呼叫與串流 Chunk 的完成順序可能不同;明確的 Call ID 才是穩定 Join Key。
定義嚴格的 Function Schema
以下唯讀函式只允許查詢配送狀態:
{
"type": "object",
"properties": {
"order_id": {
"type": "string",
"pattern": "^ORDER-[0-9]{4}$"
}
},
"required": ["order_id"],
"additionalProperties": false
}
strict: true 會要求支援的模型遵循參數 Schema,但應用仍須解析與驗證。Pattern 等 JSON Schema 功能可能依模型或供應商而異,因此 Function Arguments 也應通過 Structured Outputs 相容性測試。
使用 Responses API 實作
Responses 中的 Function Definition 是扁平物件:
const tools = [{
type: "function",
name: "get_delivery_status",
description: "Return the current delivery status for one order.",
parameters: {
type: "object",
properties: {
order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
}];
const first = await client.responses.create({
model,
input: "Where is ORDER-1001?",
tools,
parallel_tool_calls: false,
});
const calls = first.output.filter(item => item.type === "function_call");
const outputs = calls.map(call => ({
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(executeTool(call.name, call.arguments)),
}));
const final = await client.responses.create({
model,
input: [...first.output, ...outputs],
tools,
parallel_tool_calls: false,
});
關鍵是模型 Output 的 call.call_id 必須與結果的 call_id 完全一致。Stateless 整合應把前一次 Output Item 與 Function Result 一起送回;Stateful Continuation 則必須確認路由與儲存政策支援,不應假設每個 OpenAI-compatible 路由都保存 Response State。
使用 Chat Completions 實作
Chat Completions 在 function 下包住定義,並把結果加入 Message Transcript:
const tools = [{
type: "function",
function: {
name: "get_delivery_status",
description: "Return the current delivery status for one order.",
parameters: {
type: "object",
properties: {
order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
},
required: ["order_id"],
additionalProperties: false,
},
strict: true,
},
}];
const first = await client.chat.completions.create({ model, messages, tools });
const assistant = first.choices[0].message;
messages.push(assistant);
for (const call of assistant.tool_calls ?? []) {
messages.push({
role: "tool",
tool_call_id: call.id,
content: JSON.stringify(executeTool(
call.function.name,
call.function.arguments,
)),
});
}
必須先把包含 Tool Call 的 Assistant Message 加回 messages,再附加每個 role: "tool" 結果。缺少原始訊息或使用不相符的 tool_call_id 都會形成無效對話歷史。
將串流參數視為 Fragment
串流中的 {"order 不是無效 JSON,而是不完整 JSON。安全 Parser 應:
- 依 Call Identity 或 Indexed Slot 建立獨立 Buffer;
- 把每個 Argument Delta 附加到正確 Buffer;
- 等待 Arguments Done 或 Completed Signal;
- 只對完整字串執行一次 JSON Parse;
- 完成驗證後才執行函式。
Responses 使用 Typed Events;Chat Completions 使用 choices[].delta.tool_calls[],Index 代表進行中的項目,最終 Tool Call 才帶有穩定 ID。第一個 Fragment 到達時絕不能執行。更多時間與完成邊界見 AI API Streaming 指南。
讓工具執行安全且冪等
- 只允許已註冊的 Function Name;
- 在大小限制內解析參數,並於應用端驗證完整 Schema;
- 依目前 User 或 Workload 授權資源操作;
- 分離唯讀工具與具有副作用的工具;
- 回傳模型前移除 Secret 與敏感輸出;
- 使用 Deadline 與有限的下游 Retry;
- 保存安全 Operation Reference,而不是原始私密內容。
具有副作用的工具應由穩定 Application Request 與 Call Identity 產生 Idempotency Key,並在回傳模型前持久化完成結果。網路重試再次送出相同呼叫時,回傳已保存結果,而不是第二次退款、傳訊、部署或修改資料庫。
模型產生的 Function Name 與 Arguments 都是不可信輸入。Schema 合法不代表有權限、擁有資源或能取代 Transaction Boundary。
決定如何處理多個呼叫
範例使用 parallel_tool_calls: false 讓 State Machine 容易檢查。開啟平行呼叫時:
- 一律依 Call ID 關聯結果,不依完成順序;
- 限制每次 Response 的 Call 數量與應用 Concurrency;
- 定義單一失敗是否取消、阻擋或與成功結果共存;
- 每個已接受 Call 都必須回傳一個結果;
- 順序重要的副作用必須序列化。
平行執行可縮短獨立讀取的時間,但會增加授權、順序、Retry 與部分失敗複雜度。只有工作負載確實受益時才開啟。
理解 Modelflare 相容邊界
Modelflare 會在合格 OpenAI/Codex 路由之間保留 Strict Function Definition,並對支援的 Function Call Shape 進行 Responses 與 Chat Completions 映射。這個範圍刻意小於完整 Responses Tool Surface。
應用定義的 Function Tool 可在兩種格式中表示;由供應商執行的 Search、Code Execution 或其他 Hosted Tool 並不等同於 Application Function Calling,不能假設可轉成 Chat Completions。其他 OpenAI-compatible 模型家族在未單獨驗證前採 Raw Chat Completions Pass-through,由上游決定 tools、strict、Parallel Calls、Streaming Arguments 與 tool_choice 支援。
上線前應完成下列測試,而不是只確認第一個 Request 回傳 200:
| 測試 | 必要證據 |
|---|---|
| 唯讀呼叫 | Function Name、Parsed Arguments、Call ID、Result Linkage、Final Text |
| 無效參數 | 明確拒絕且未執行工具 |
| 未知函式 | 被應用 Allowlist 拒絕 |
| 不需工具 | 正常 Final Text,沒有虛構呼叫 |
| 串流呼叫 | 完整重組參數與正確 Call ID |
| 重複請求 | 一次副作用或一次快取結果 |
| 多重呼叫 | 不受完成順序影響的關聯 |
| Route Fallback | 保持模型、協定、Schema 與結果合約 |
路由切換應保持明確,可參考可靠 AI API 路由;工作負載與權限隔離可參考 AI API Key 安全。
依應用合約選擇格式
需要 Typed Output Items、Responses Event Model、State Continuation 或其他已驗證能力時使用 Responses。已有穩定 Message Transcript,且選定 Provider 的 Tool Contract 經過驗證時使用 Chat Completions。更完整比較見 Responses API 與 Chat Completions。
兩種格式都應遵守同一標準:明確 Tool Allowlist、Strict Argument Schema、應用端驗證、執行前授權、依 Call ID 關聯、對副作用做冪等,以及把完整結果送回模型。可靠性來自這些控制,而不是 Endpoint 名稱。