Function Calling:Responses API 与 Chat Completions 对比

从 Wire Contract 对比函数定义、Call ID、结果消息、流式参数、授权、幂等性与路由兼容,并提供两套完整工具循环示例。

Responses 与 Chat Completions 都能让模型请求应用执行函数,但两者表达工具循环的方式不同。Chat Completions 把工具定义放在 tools[].function 中,通过 message.tool_calls 返回调用,并用 role: "tool" 消息接收结果。Responses 使用扁平的函数定义,返回带类型的 function_call Output Item,再通过 call_id 关联 function_call_output

模型不会替应用执行函数。应用代码必须校验模型提出的参数、确认当前请求有权执行该操作、严格按预期运行函数、把结果返回给模型,并在请求重试时防止副作用被重复执行。

工具循环包含四个步骤

端点使用的 JSON 不同,但应用状态机相同:

1. Declare an allowed function and its argument schema
2. Receive one or more model-proposed function calls
3. Validate, authorize, and execute each call in application code
4. Return each result using the call's correlation ID

只有完成第四步,模型才能根据工具结果生成答案。如果模型又请求一个工具,循环会使用新的 Call ID 再执行一次。

OpenAI Function Calling 指南把它定义为一个多步骤交换过程。把第一次工具调用误当成最终答案,是常见的集成错误。

对比两种 Wire Contract

两种端点的核心差异在结构,而不是概念。

关注点 Responses API Chat Completions
函数定义 tools[] 中的扁平条目,包含 namedescriptionparametersstrict tools[] 条目中,上述字段嵌套在 function
模型提出的调用 带类型的 function_call Output Item Assistant Message 中的 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" 的消息
常见流式参数 带类型的函数参数 Delta Event 带索引的 delta.tool_calls[] 片段
最终文本 带类型的 Output Item 与 Output Text Helper choices[0].message.content

不要按数组位置关联调用。并行调用与流式 Chunk 的到达顺序,可能不同于应用完成各个函数的顺序。显式的 Call ID 才是稳定的关联键。

定义一个严格的函数 Schema

下面的示例只暴露只读函数 get_delivery_status。模型可以请求查询订单状态,但不能修改订单、发起退款或访问任意存储。

参数 Schema 被有意限制得很窄:

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

strict: true 会要求支持该能力的模型遵循函数参数 Schema,但应用在执行前仍然必须解析并校验 JSON。不同模型或提供商支持的 pattern 等 JSON Schema 能力可能不同,因此函数参数也需要经过与 Structured Outputs 相同的兼容性检查。

使用 Responses API 实现工具循环

下面的 JavaScript 示例通过把模型 Output Item 与函数结果一并返回,显式保存完整交换记录。示例使用确定性的本地函数,使控制流清晰可见,同时避免引入另一个外部 API。

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 is required");

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

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,
  },
];

function executeTool(name, rawArguments) {
  if (name !== "get_delivery_status") {
    throw new Error(`Tool is not allowed: ${name}`);
  }

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

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

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");
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);

在有状态集成中,如果路由与存储策略允许,Responses 也可以从此前的 Response 继续。这里使用显式记录完整 Transcript 的方式,是为了避免假设每一条 OpenAI 兼容路由都会保存响应状态。

关键字段是模型输出上的 call.call_id,以及 function_call_output 上与之匹配的 call_id。使用错误的 ID 返回结果,会让数据与模型提出的请求失去关联。

使用 Chat Completions 实现工具循环

Chat Completions 可以复用同一个应用函数,但工具定义与结果消息需要采用 Chat 的包装结构。

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 is required");

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

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,
    },
  },
];

function executeTool(name, rawArguments) {
  if (name !== "get_delivery_status") {
    throw new Error(`Tool is not allowed: ${name}`);
  }

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

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

const messages = [
  { role: "user", content: "Where is 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);

这里必须先把包含 Tool Call 的第一条 Assistant Message 加回 messages,再追加工具结果。每个结果都使用 tool_call_id: call.id。遗漏 Assistant Tool Call 消息,或返回无法匹配的 ID,都会形成无效的对话历史。

把流式参数当成片段,而不是完整 JSON

在流式模式下,函数参数可能分片到达。只包含 {"order 的 Chunk 并不是错误 JSON,而是尚未完成的 JSON。

安全的解析器应为每个调用标识维护独立 Buffer:

  1. 从 Event 中识别 Call ID 或带索引的调用槽位;
  2. 把每个 Argument Delta 追加到该调用的 Buffer;
  3. 等待端点发出 Argument Done 或 Completed 信号;
  4. 只解析一次完整字符串;
  5. 完成后再校验并执行。

Responses 使用带类型的函数参数 Event 与 Output Item。Chat Completions 在 choices[].delta.tool_calls[] 中发送片段,其中 Index 标识正在构建的条目,最终 Tool Call 会带有稳定 ID。不要在第一个片段到达时就执行函数。

AI API Streaming Guide解释了为何连接、首个事件、首个有效输出、空闲超时与完成时间需要分开测量。

让工具执行安全且具备幂等性

严格参数可以改善结构,但不能提供授权。执行任何调用前,应当:

  • 只允许已注册的函数名称;
  • 设置参数大小上限后再解析;
  • 在应用代码中校验完整 Schema;
  • 确认当前用户或工作负载有权访问目标资源;
  • 分离只读工具与具有副作用的工具;
  • 返回模型前移除 Secret 与敏感输出;
  • 为执行设置 Deadline,并限制下游重试次数;
  • 记录安全的操作引用,不记录原始 Secret 或私密内容。

具有副作用的工具应使用由稳定应用请求与 Call Identity 派生的幂等键。在把结果返回模型之前先持久化已完成结果。如果网络重试重放同一次调用,应返回已保存结果,而不是再次退款、发消息、部署或写数据库。

模型生成的函数名和参数都是不可信输入。Schema 合法不代表操作已有权限,也不能证明资源归属,更不能代替事务边界。

明确定义多次调用的处理方式

示例设置 parallel_tool_calls: false,便于检查状态机。如果启用并行调用:

  • 每个结果都必须按 Call ID 关联,不能按完成顺序;
  • 限制一次 Response 可提出的最大调用数;
  • 在应用侧设置并发上限;
  • 预先定义一个工具失败时,是取消、阻塞还是允许其他工具成功;
  • 为每个已接受调用返回一个结果;
  • 当副作用的顺序会影响结果时,保持串行执行。

并行执行可以降低相互独立的读取操作延迟,却也增加授权、排序、重试与部分失败的复杂度。只有工作负载确实受益时才启用,不要仅仅因为存在这个参数就开启。

理解 Modelflare 的兼容边界

Modelflare 的 OpenAI 兼容代码会保留严格函数定义,并在符合条件的 OpenAI/Codex 路由上,对受支持的 Responses 与 Chat Completions 函数调用结构进行转换。这个映射有意小于完整的 Responses Tool Surface。

应用定义的 Function Tool 可以用两种格式表达。由提供商执行的 Responses 专属 Hosted Tool,例如搜索、代码执行或其他 Built-in Tool,并不等价于应用函数调用,不能假设它们能转换为 Chat Completions。

其他 OpenAI 兼容模型家族以原始 Chat Completions 透传方式提供,除非另行完成验证。对于这些路由,toolsstrict、并行调用、流式参数片段和特定 tool_choice 结构是否可用,取决于上游提供商。

启用一条路由前,应测试完整工具循环,而不是只检查第一次请求是否返回 200

测试 必须保留的证据
一次只读调用 函数名、解析后参数、Call ID、结果关联、最终文本
非法参数 明确校验失败,并且没有执行工具
未知函数 被应用 Allowlist 拒绝
不需要工具 正常返回最终文本,不伪造调用
流式调用 参数完成重组,并使用正确 Call ID
重复请求 只产生一次副作用或返回一次缓存结果,不重复执行
多次调用 不依赖完成顺序仍能正确关联
路由回退 保持相同模型、协议、工具 Schema 与结果契约

使用 Reliable AI API Routing让路由变化保持显式,并通过 AI API Key Security分离工作负载与权限。

根据应用契约选择格式

当应用需要带类型的 Output Item、Responses Event Model、状态延续选项,或其他已经验证的 Responses 能力时,选择 Responses。当应用已经维护稳定的 Message Transcript,并且所选提供商的 Tool Contract 已经过验证时,可以选择 Chat Completions。更宽泛的端点选择见 Responses API vs Chat Completions

两种端点应遵循相同的实现标准:显式 Tool Allowlist、严格参数 Schema、应用侧校验、执行前授权、按 Call ID 关联、对副作用实施幂等,以及把完整结果返回模型。让 Function Calling 工作流可靠的是这些控制,而不是端点名称。