函数调用 API:供应商对比
比较函数调用API的参数结构、调用ID、结果、严格模式、并行与流式支持,并规划适配和验证。

决策摘要
摘要
三家都会声明工具、返回结构化调用、等待应用执行,再消费带关联标识的结果;差异在字段名与消息编排规则。
内部只保留一份工具契约,用轻量适配器翻译三家格式,不让供应商消息结构进入业务逻辑。
严格 Schema 只能改善参数形状。服务端仍需校验值、鉴权、控制副作用,并把工具输出视为不可信输入。
不要只测一次成功调用,还要覆盖畸形参数、多工具、流式分片、超时、重试、重复副作用、结果顺序和模型恢复。
这份对比适合谁
如果你正在选择模型供应商、迁移现有 Agent,或建设跨模型编排层,这份指南适合你。默认你已经能够调用 API、定义 JSON Schema,并在可信后端执行工具。
先定义一份统一工具契约
真正稳定的单元不是某家 Payload,而是供应商无关的工具定义、标准化调用与标准化结果。供应商调用 ID 单独保存,适配器才能重建正确的回传消息。
type ToolDefinition = {
name: string;
description: string;
inputSchema: JsonSchema;
sideEffect: "read" | "write";
};
type ToolCall = {
provider: "openai" | "anthropic" | "google";
providerCallId: string;
name: string;
arguments: unknown;
raw: unknown;
};
type ToolResult = {
providerCallId: string;
ok: boolean;
content: unknown;
evidence?: { source: string; retrievedAt: string }[];
};OpenAI、Anthropic 与 Google 协议对比
| 关注点 | OpenAI | Anthropic | Google Gemini |
|---|---|---|---|
| 工具声明 | type: function 加函数 Schema | name、description 与 input_schema | type: function 加 Interactions 中的 parameters |
| 调用输出 | function_call | tool_use 内容块 | function_call 步骤 |
| 关联键 | call_id | tool_use_id | call_id;必要时保留签名与状态 |
| 结果回传 | function_call_output | 下一条用户消息中的 tool_result | function_result |
| 严格 Schema | strict: true;要求严格对象结构 | 支持 strict: true | 支持的 Schema 子集随 API 与模型而异 |
| 并行调用 | 支持,也可禁用 | 支持多个 tool_use 块,也可禁用 | 支持并行调用与组合调用 |
| 有状态续接 | 使用响应/会话状态,或发送先前消息项 | 在下一条用户消息中发送结果块 | 在 Interactions 中使用 previous_interaction_id |
版本边界:Google 同时存在较新的 Interactions API 术语,以及旧版 generateContent 的 functionCall/functionResponse。每个适配器只选择一个接口面,并明确记录。
OpenAI 实现:用 call_id 关联结果
请求中发送函数工具。模型输出 function_call 后,解析参数、执行已授权工具,再用相同 call_id 回传 function_call_output。应有意识地配置 tool_choice,不要默认强制调用。
const first = await client.responses.create({
model, input: userInput,
tools: [{ type: "function", name: "get_quote", description,
parameters: quoteSchema, strict: true }]
});
const call = first.output.find(x => x.type === "function_call");
const result = await executeSafely(call.name, JSON.parse(call.arguments));
const final = await client.responses.create({
model, previous_response_id: first.id,
input: [{ type: "function_call_output", call_id: call.call_id,
output: JSON.stringify(result) }]
});Anthropic 实现:保持 tool_use 消息顺序
Claude 会返回一个或多个 tool_use 块,通常伴随 stop_reason: tool_use。下一条 user 消息必须包含对应的 tool_result;结果块放在其他用户文字之前,执行失败用 is_error: true 标记。
const calls = response.content.filter(x => x.type === "tool_use");
const toolResults = await Promise.all(calls.map(async call => ({
type: "tool_result",
tool_use_id: call.id,
content: JSON.stringify(await executeSafely(call.name, call.input))
})));
messages.push({ role: "assistant", content: response.content });
messages.push({ role: "user", content: toolResults });Google 实现:保留状态与签名
在 Interactions API 中,函数调用是包含 id、name、arguments 的 step;回传引用该调用 ID 的 function_result。有状态模式使用 previous_interaction_id,无状态模式则必须完整重放历史 step,包括所需的思考签名元数据。
const call = interaction.outputs.find(x => x.type === "function_call");
const result = await executeSafely(call.name, call.arguments);
const next = await client.interactions.create({
model,
previous_interaction_id: interaction.id,
input: [{ type: "function_result", call_id: call.id,
result }]
});构建轻量供应商适配器
每个适配器只负责四件事:序列化工具定义、提取调用、序列化结果、保留供应商状态。校验、授权、执行、重试和证据应位于共享基础设施。
const turn = await adapter.ask({ messages, tools: canonicalTools });
const calls = adapter.extractCalls(turn);
const results = [];
for (const call of calls) {
const args = validate(call.name, call.arguments);
await authorize(identity, call.name, args);
results.push(await executeIdempotently(call, args));
}
const answer = await adapter.continue({
state: turn.state,
toolResults: results
});能跨三家工作的 Schema 设计
- 优先使用小型对象、明确属性和可预测的基础类型。
- 枚举保持简短,并在描述里写清单位、格式、时区和默认值。
- 统一层避免深度递归 Schema、模糊 union 与供应商私有扩展。
- OpenAI 严格模式下,对象需要
additionalProperties: false与 required 字段;可选值按需显式表达。 - 解码后再次校验。符合 Schema 不代表股票代码存在、日期范围被允许,或转账已经授权。
工具选择、严格模式与并行调用
| 控制项 | 适用场景 | 生产注意 |
|---|---|---|
| 自动 | 模型可以直接回答或调用工具。 | 适合作为混合对话默认值。 |
| 必需或强制 | 流程必须取得结构化数据。 | 不要强制执行不可逆动作。 |
| 严格 | 需要更稳定的参数形状。 | 业务规则仍必须在服务端执行。 |
| 并行 | 调用彼此独立且只读。 | 依赖调用或有副作用调用要串行。 |
流式处理与多轮状态
绝不能从未完成的参数分片直接执行工具。先缓存 delta,等待供应商确认调用结束,再统一解码、校验,并在执行前持久化调用 ID。无状态续接时,所有供应商要求的消息项与不透明签名都要原样保留。
规则:只解析完整 JSON;只执行已持久化调用;每个已接受调用必须恰好返回一个结果。
安全、成本与延迟控制
Schema 校验回答“形状是否正确”,授权回答“这个身份能否对这个资源执行该动作”,两者缺一不可。
网页、文档和 API 文本可能包含间接 Prompt Injection。保留来源、限制返回字段,并把结果放在供应商指定的 tool-result 结构中。
副作用前持久化确定性幂等键;重试应返回上次结果,而不是重复下单、发信、支付或写入。
限制截止时间、单轮最大调用数、并发数、结果大小和 token;用户请求取消时同步终止下游工作。
常见失败模式与修复
| 现象 | 常见原因 | 修复 |
|---|---|---|
| 供应商拒绝下一轮消息 | 结果 ID 缺失、错序或不匹配 | 持久化原始调用,并统一由适配器生成结果消息 |
| 工具执行两次 | 网络重试但没有幂等控制 | 执行前去重并缓存结果 |
| 参数偶发解析失败 | 解析未完成流片段或 Schema 含糊 | 聚合完成后再解析,并简化、收紧 Schema |
| 并行结果对应错误 | 按数组位置匹配 | 只按供应商调用 ID 匹配 |
| 模型执行了结果中的恶意文本 | 间接 Prompt Injection | 最小化、标记、净化并策略检查不可信内容 |
供应商无关的验证基准
不要用少量 Prompt 得出一个笼统“准确率”。应对每家供应商运行同一份版本化测试集,并按失败类型分别报告。
跨供应商迁移清单
- 冻结统一工具注册表,并为每个 Schema 建立版本。
- 映射工具字段、选择控制、调用 ID、结果信封和会话状态。
- 翻译前移除目标接口不支持的 Schema 特性。
- 以无副作用影子模式回放生产链路。
- 比较工具选择、参数有效性、恢复、延迟和成本。
- 先灰度只读工具;幂等与授权测试通过后再开放写工具。
QVeris 实现模式
供应商原生 Function Calling 管理模型侧消息循环;MCP 可以标准化客户端发现和调用工具服务器的方式;QVeris 位于能力层,帮助 Agent 发现、检查、路由、调用并审计真实世界工具与数据,而供应商适配器继续保持 OpenAI、Anthropic 或 Google 所要求的协议。
在 Prompt 暴露工具前,先找到能力并检查契约。
把已校验参数路由到选定服务商或数据源。
随结果返回来源、检索时间、单位和校验状态。
常见问题
通常是。名称不同,但应用循环等价:描述工具、接收结构化调用、在模型外执行,再回传带关联标识的结果。
没有通用答案。应针对实际模型与负载测试 Schema、流式处理、并行行为、恢复、可观测性、成本和迁移难度。
不会。模型只提出调用,应用负责校验、授权、执行和回传。
支持时可以开启,但服务端仍需校验。严格模式不会授予权限,也不保证业务语义正确。
先收集全部调用,只并发彼此独立的任务,并按原始调用 ID 关联每个结果。
执行前根据会话、调用 ID、工具名和标准化参数生成并持久化幂等键。
不会。MCP 标准化客户端与工具服务器交互,供应商 API 仍规定模型侧调用与结果消息。
QVeris 为真实世界能力补充发现、检查、路由和证据,适配器则管理供应商原生消息。
