Function Calling 不是“模型替你执行代码”,而是一个由应用控制的工具调用循环:发送工具定义 → 模型返回一个或多个调用 → 应用校验并执行 → 用原始 call_id 回传结果 → 继续请求,直到模型返回最终回答。生产系统不要只处理第一个调用,也不要把 strict 当成权限验证。
官方资料(2026-10-30 核对):Function calling guide · Responses API reference · Chat Completions reference · 迁移到 Responses
Function Calling 的五步循环
- 定义工具:给出稳定的名称、明确描述和参数 JSON Schema,只暴露当前任务需要的工具。
- 发送请求:模型决定是否调用工具;
tool_choice可改变这个选择范围。 - 读取全部调用:Responses 中查找
function_callItems;不要假定只会返回一个。 - 应用执行:解析参数后做 Schema、业务、身份和权限校验,再调用数据库或内部 API。
- 回传并继续:用对应的
call_id发送function_call_output,重复循环,直到没有新调用。
模型只负责提出调用;支付、发信、删除、退款等副作用必须由服务端基于当前用户权限再次确认。工具返回值也应只包含模型完成任务所需的数据,避免把密钥、完整用户资料或内部错误栈放进上下文。
Responses API 完整最小示例
1. 定义 strict 工具并发起请求
import OpenAI from "openai";
const client = new OpenAI();
const tools = [{
type: "function",
name: "get_order_status",
description: "按订单号查询订单状态",
strict: true,
parameters: {
type: "object",
properties: {
order_id: { type: "string", description: "订单号,如 A-102" }
},
required: ["order_id"],
additionalProperties: false
}
}];
let response = await client.responses.create({
model: "YOUR_MODEL",
instructions: "只在用户要求查订单时调用工具。",
input: "帮我查订单 A-102",
tools
});strict: true 时,每个对象应设置 additionalProperties: false,并把所有 properties 列进 required。需要“可选字段”时,可让字段类型包含 null,而不是从 required 中删除。
2. 执行所有调用,并按 call_id 回传
for (let step = 0; step < 5; step++) {
const calls = response.output.filter(
item => item.type === "function_call"
);
if (calls.length === 0) break;
const outputs = [];
for (const call of calls) {
const args = JSON.parse(call.arguments);
validateAndAuthorize(call.name, args);
const result = await runToolIdempotently(call.name, args);
outputs.push({
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(result)
});
}
response = await client.responses.create({
model: "YOUR_MODEL",
instructions: "只在用户要求查订单时调用工具。",
previous_response_id: response.id,
input: outputs,
tools
});
}
const answer = response.output_text;关键不是代码长短,而是三个不变量:执行全部调用、使用模型返回的 call_id、给循环设置步数上限。如果模型在工具调用旁返回 reasoning Items,使用推理模型时要按官方端点要求保留并传回这些 Items,不能只摘出函数名和参数。
Responses 与 Chat Completions 的工具调用差异
| 环节 | Responses API | Chat Completions |
|---|---|---|
| 工具定义 | 工具字段直接包含 name、description、parameters | 函数定义通常位于工具对象的 function 内 |
| 模型返回 | response.output 中的 function_call Item | choices[0].message.tool_calls[] |
| 关联标识 | call_id | 每个 tool call 的 id |
| 工具结果 | function_call_output + call_id | role: "tool" + tool_call_id |
| 继续上下文 | 可用 previous_response_id,或自己传回所需 Items | 把 assistant 的 tool_calls 消息和随后各 tool 消息加入 messages |
| 结束判断 | 没有新的 function_call,再读最终 message/output_text | 没有 tool_calls,再读 message.content;工具阶段常见 finish_reason 为 tool_calls |
如果你正在迁移端点,先看 Responses API 与 Chat Completions 区别。两套格式不能只改 URL:调用对象、结果回传、状态和流式事件都不同。
strict 模式解决什么,不解决什么?
它能解决参数形状
- 字段名、类型、枚举和必填项按支持的 JSON Schema 子集输出。
- 减少参数缺失、额外字段和类型漂移。
- Schema 不兼容时尽早暴露配置错误,而不是让错误参数进入工具。
它不能解决业务与安全
- 订单号存在,不代表当前用户有权查看该订单。
- 金额符合 number,不代表金额在业务允许范围内。
- 工具名称正确,不代表当前会话允许调用删除、支付或发信。
- 参数结构正确,也可能包含模型误解、过期数据或提示注入诱导。
因此应用侧仍要做 allowlist、身份校验、资源归属检查、数值范围、幂等键和审计日志。Schema 是输入边界,不是授权系统。
tool_choice 和并行调用怎么选?
| 配置 | 模型行为 | 适合场景 |
|---|---|---|
auto | 可以不调用,也可调用一个或多个工具 | 普通助手和开放式问答 |
required | 至少调用一个工具 | 答案必须建立在实时系统数据上 |
| 强制指定函数 | 只允许目标函数 | 流程已明确进入某个动作步骤 |
allowed_tools | 从工具集合中进一步缩小允许范围 | 多阶段 Agent、最小权限 |
none | 禁止工具调用 | 纯文本步骤或安全降级 |
默认要假设一次响应可能有多个调用。若业务必须串行,或工具有相互依赖,可设置 parallel_tool_calls: false,使一次最多产生一个调用;否则就为每个调用分别执行并回传结果。即使并行,也要限制并发、超时和重试,避免模型一次请求放大后端压力。
生产环境的安全与稳定性清单
- 最小工具集:每一步只提供真正允许的工具,名称和描述要互斥、清晰。
- 验证两层:先验证 Schema,再验证用户身份、资源归属、业务状态和数值范围。
- 副作用幂等:支付、发信、创建订单使用幂等键;重试不能重复执行。
- 边界明确:设置工具超时、最大输出大小、调用总步数和每轮并发数。
- 错误可读:把安全的结构化错误回传给模型,不泄露 Token、连接串或内部栈。
- 可观测:记录 response id、call_id、工具名、耗时、结果状态和业务追踪 ID;敏感参数脱敏。
- 故障分类:429 按限流退避,5xx/超时有限重试;参数或权限错误不要无脑重试。
部署前还应使用固定评测集覆盖“不调用工具、调用一个、调用多个、参数非法、权限不足、工具超时、重复回传、模型继续调用”八类路径。相关排障见 OpenAI API 429 与速率限制、500/503 和超时。
最常见的六个实现错误
- 只处理 calls[0]:后续调用丢失,模型拿不到完整数据。
- 自己生成关联 ID:必须回传原调用的
call_id或tool_call_id。 - 解析参数后直接执行:JSON 可解析不等于通过权限和业务规则。
- 把工具结果当最终答案:工具结果还要交给模型,直到没有新调用。
- 循环没有上限:模型和工具可能互相触发,导致成本和副作用失控。
- 把 Function Calling 当 Structured Outputs:前者连接外部动作,后者约束最终 JSON;选择规则见 Structured Outputs 与 JSON mode。
Function Calling 是模型请求你的应用执行工具;Webhook 是 OpenAI 向服务器发送事件。若工具链需要接收后台终态,请单独按 OpenAI Webhooks 验签、去重与重试指南搭建接收端。
执行工具前既要校验 Schema、权限和业务规则,也要按风险审核对话内容;字段与流程见 OpenAI Moderation API 指南。
常见问题
Function Calling 是让模型直接执行函数吗?
不是。模型只返回要调用的工具名称和 JSON 参数;你的应用必须校验参数、检查权限、真正执行函数,再把结果回传给模型。
Responses API 怎样把工具结果与原调用对应起来?
读取 function_call Item 的 call_id,并用同一 call_id 发送 function_call_output。不要用数组位置、函数名或自己生成的 ID 猜测对应关系。
strict: true 能保证什么?
它让函数参数在支持的 JSON Schema 子集内遵循 Schema。每个对象要设置 additionalProperties: false,所有 properties 都要列入 required;可选值可用包含 null 的联合类型。它不替代业务校验或权限控制。
一次响应会返回多个 Function Call 吗?
会。应用应假设可能有零个、一个或多个调用,逐个执行并回传结果。确实需要限制并行调用时,可设置 parallel_tool_calls: false,使一次最多产生一个调用。
tool_choice 应该怎么选?
默认 auto 允许模型不调用、调用一个或多个工具;required 要求至少调用一个;也可强制指定函数、限制 allowed_tools,或用 none 禁止工具。生产环境应只暴露当前步骤真正允许的工具。
为什么回传工具结果后模型没有给最终答案?
工具调用是循环而不是固定两步。模型可能继续请求其他工具;应用要反复处理 function_call,直到没有新调用,再读取最终 message/output_text,并设置最大步数避免死循环。
Function Calling 与 Structured Outputs 有什么区别?
Function Calling 用于让模型请求外部动作或数据;Structured Outputs 用于约束模型最终文本结果的 JSON 形状。两者都可使用 strict Schema,但调用循环、call_id 和工具执行只属于 Function Calling。
Realtime 仍需由后端校验参数、权限和幂等,再回传工具结果;建连、VAD 与打断状态机见 Realtime API 指南。