函式呼叫:Responses API 與 Chat Completions 比較

從傳輸協定層比較函式定義、呼叫 ID、結果訊息、串流參數、授權、冪等性與路由相容性。

函式呼叫:Responses API 與 Chat Completions 比較

Responses 與 Chat Completions 都能要求應用執行函式,但兩者表示工具呼叫流程的方式不同。Chat Completions 把定義放在 tools[].function,回傳 message.tool_calls,並以 role: "tool" 訊息接收結果;Responses 使用扁平的函式定義,回傳帶型別的 function_call 輸出項目,再透過 call_id 連結 function_call_output。

模型不會執行你的函式。應用必須驗證參數、確認授權、精確執行、回傳結果,並在請求重試時避免重複副作用。

工具呼叫流程有四個步驟

1. 宣告允許呼叫的函式及其參數結構描述
2. 接收模型提出的一次或多次函式呼叫
3. 在應用程式中驗證、授權並執行每次呼叫
4. 使用每次呼叫的關聯 ID 回傳對應結果

第四步完成後,模型才能依據工具結果產生最終答案。若模型再次要求工具,使用新的呼叫 ID 重複流程。OpenAI Function Calling 指南也將其定義為多步交換;把第一個工具呼叫當成最終答案是常見整合錯誤。

比較傳輸契約

項目 Responses API Chat Completions
函式定義 扁平 tools[],包含 name、description、parameters、strict 同欄位放在 tools[].function
模型提出的呼叫 function_call 輸出項目 助理訊息中的 tool_calls[]
關聯識別 call_id 工具呼叫的 id,結果使用 tool_call_id
函式名稱 function_call.name tool_calls[].function.name
參數 function_call.arguments JSON 字串 tool_calls[].function.arguments JSON 字串
結果 function_call_output 輸入項目 role: "tool" 訊息
串流參數 帶型別的參數增量事件 delta.tool_calls[] 片段
最終文字 帶型別的輸出項目 / 輔助函式 choices[0].message.content

不要依陣列位置關聯呼叫。平行呼叫與串流片段的完成順序可能不同;明確的呼叫 ID 才是穩定的關聯鍵。

定義嚴格的函式參數結構

以下唯讀函式只允許查詢配送狀態:

{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "pattern": "^ORDER-[0-9]{4}$"
    }
  },
  "required": ["order_id"],
  "additionalProperties": false
}

strict: true 會要求支援的模型遵循參數結構描述,但應用仍須解析與驗證。pattern 等 JSON Schema 功能可能依模型或供應商而異,因此函式參數也應通過 Structured Outputs 相容性測試。

使用 Responses API 實作

Responses 中的函式定義是扁平物件:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.MODELFLARE_API_KEY,
  baseURL: "https://modelflare.dev/v1",
});

const model = process.env.MODEL_ID;
if (!model) throw new Error("缺少必要的 MODEL_ID");

const orders = new Map([
  ["ORDER-1001", { status: "in_transit", eta: "2026-08-06" }],
]);

const tools = [
  {
    type: "function",
    name: "get_delivery_status",
    description: "回傳指定訂單目前的配送狀態。",
    parameters: {
      type: "object",
      properties: {
        order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
      },
      required: ["order_id"],
      additionalProperties: false,
    },
    strict: true,
  },
];

function executeTool(name, rawArguments) {
  if (name !== "get_delivery_status") {
    throw new Error(`不允許呼叫工具:${name}`);
  }

  const args = JSON.parse(rawArguments);
  if (!/^ORDER-[0-9]{4}$/.test(args.order_id)) {
    throw new Error("order_id 無效");
  }

  return orders.get(args.order_id) ?? { status: "not_found" };
}

const first = await client.responses.create({
  model,
  input: "ORDER-1001 配送到哪裡了?",
  tools,
  parallel_tool_calls: false,
});

const calls = first.output.filter((item) => item.type === "function_call");
if (calls.length === 0) {
  console.log(first.output_text);
  process.exit(0);
}

const toolOutputs = 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, ...toolOutputs],
  tools,
  parallel_tool_calls: false,
});

console.log(final.output_text);

關鍵是模型輸出的 call.call_id 必須與結果的 call_id 完全一致。無狀態整合應把前一次輸出項目與函式結果一起送回;若要延續有狀態的回應,則必須確認路由與儲存政策確實支援,不應假設每個 OpenAI 相容路由都會保存回應狀態。

使用 Chat Completions 實作

Chat Completions 在 function 下包住定義,並把結果加入訊息紀錄:

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.MODELFLARE_API_KEY,
  baseURL: "https://modelflare.dev/v1",
});

const model = process.env.MODEL_ID;
if (!model) throw new Error("缺少必要的 MODEL_ID");

const orders = new Map([
  ["ORDER-1001", { status: "in_transit", eta: "2026-08-06" }],
]);

const tools = [
  {
    type: "function",
    function: {
      name: "get_delivery_status",
      description: "回傳指定訂單目前的配送狀態。",
      parameters: {
        type: "object",
        properties: {
          order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" },
        },
        required: ["order_id"],
        additionalProperties: false,
      },
      strict: true,
    },
  },
];

function executeTool(name, rawArguments) {
  if (name !== "get_delivery_status") {
    throw new Error(`不允許呼叫工具:${name}`);
  }

  const args = JSON.parse(rawArguments);
  if (!/^ORDER-[0-9]{4}$/.test(args.order_id)) {
    throw new Error("order_id 無效");
  }

  return orders.get(args.order_id) ?? { status: "not_found" };
}

const messages = [
  { role: "user", content: "ORDER-1001 配送到哪裡了?" },
];

const first = await client.chat.completions.create({
  model,
  messages,
  tools,
  parallel_tool_calls: false,
});

const assistant = first.choices[0].message;
const calls = assistant.tool_calls ?? [];
if (calls.length === 0) {
  console.log(assistant.content);
  process.exit(0);
}

messages.push(assistant);
for (const call of calls) {
  const result = executeTool(call.function.name, call.function.arguments);
  messages.push({
    role: "tool",
    tool_call_id: call.id,
    content: JSON.stringify(result),
  });
}

const final = await client.chat.completions.create({
  model,
  messages,
  tools,
  parallel_tool_calls: false,
});

console.log(final.choices[0].message.content);

必須先把包含工具呼叫的助理訊息加回 messages,再附加每個 role: "tool" 結果。缺少原始訊息或使用不相符的 tool_call_id 都會形成無效的對話紀錄。

將串流參數視為片段

串流中的 {"order 不是無效 JSON,而是不完整的 JSON。安全的解析器應:

  1. 依呼叫識別或索引位置建立獨立緩衝區;
  2. 把每個參數增量附加到正確緩衝區;
  3. 等待參數完成或回應完成訊號;
  4. 只對完整字串執行一次 JSON 解析;
  5. 完成驗證後才執行函式。

Responses 使用帶型別的事件;Chat Completions 使用 choices[].delta.tool_calls[],索引代表進行中的項目,最終工具呼叫才帶有穩定 ID。第一個片段到達時絕不能執行。更多時間與完成邊界見 AI API 串流指南。

讓工具執行安全且冪等

  • 只允許已註冊的函式名稱;
  • 在大小限制內解析參數,並於應用端驗證完整結構描述;
  • 依目前使用者或工作負載授權資源操作;
  • 分離唯讀工具與具有副作用的工具;
  • 回傳模型前移除密鑰與敏感輸出;
  • 設定截止時間,並限制下游重試次數;
  • 保存安全的操作參照,而不是原始私密內容。

具有副作用的工具應由穩定的應用請求與呼叫識別產生冪等鍵,並在回傳模型前持久化完成結果。網路重試再次送出相同呼叫時,回傳已保存結果,而不是第二次退款、傳訊、部署或修改資料庫。

模型產生的函式名稱與參數都是不可信輸入。參數結構符合約束不代表有權限、擁有資源,也不能取代交易邊界。

決定如何處理多個呼叫

範例使用 parallel_tool_calls: false 讓狀態機容易檢查。開啟平行呼叫時:

  • 一律依呼叫 ID 關聯結果,不依完成順序;
  • 限制每次回應的呼叫數量與應用並行度;
  • 定義單一失敗是否取消、阻擋或與成功結果共存;
  • 每個已接受的呼叫都必須回傳一個結果;
  • 順序重要的副作用必須序列化。

平行執行可縮短獨立讀取的時間,但會增加授權、順序、重試與部分失敗的複雜度。只有工作負載確實受益時才開啟。

理解 Modelflare 相容邊界

Modelflare 會在合格的 OpenAI/Codex 路由之間保留嚴格的函式定義,並在 Responses 與 Chat Completions 之間映射受支援的函式呼叫格式。這個範圍刻意小於 Responses 的完整工具能力。

應用定義的函式工具可在兩種格式中表示;由供應商執行的搜尋、程式碼執行或其他託管工具,並不等同於應用程式的函式呼叫,不能假設可轉成 Chat Completions。其他 OpenAI 相容模型家族在未單獨驗證前,採用原樣透傳的 Chat Completions;由上游決定是否支援 tools、strict、平行呼叫、串流參數與 tool_choice。

上線前應完成下列測試,而不是只確認第一個請求回傳 200:

測試 必要證據
唯讀呼叫 函式名稱、已解析參數、呼叫 ID、結果關聯、最終文字
無效參數 明確拒絕且未執行工具
未知函式 被應用允許清單拒絕
不需工具 正常最終文字,沒有虛構呼叫
串流呼叫 完整重組參數與正確呼叫 ID
重複請求 一次副作用或一次快取結果
多重呼叫 不受完成順序影響的關聯
路由備援 保持模型、協定、結構描述與結果契約

路由切換應保持明確,可參考可靠 AI API 路由;工作負載與權限隔離可參考 AI API 金鑰安全。

依應用合約選擇格式

需要帶型別的輸出項目、Responses 事件模型、有狀態延續或其他已驗證能力時使用 Responses。已有穩定的訊息紀錄,且選定供應商的工具契約經過驗證時使用 Chat Completions。更完整比較見 Responses API 與 Chat Completions。

兩種格式都應遵守同一標準:明確的工具允許清單、嚴格的參數結構描述、應用端驗證、執行前授權、依呼叫 ID 關聯、對副作用做冪等,以及把完整結果送回模型。可靠性來自這些控制,而不是端點名稱。