OpenAI 协议和 Anthropic 协议到底差在哪里?从一条请求走到工具调用
你把模型供应商从 OpenAI 换成 Anthropic,最初通常只改一个 base_url 和一个 model。真正接入工具调用、流式输出和多模态之后,问题就出现了:同样叫 messages 的字段,为什么内容结构不一样?OpenAI 返回 tool_calls,Anthropic 返回 tool_use,它们能不能直接互换?
答案是:它们解决的是同一个问题,但不是同一个协议。 本文真正比较的主线,是 OpenAI Responses API、OpenAI Chat Completions 和 Anthropic Messages API;这里的“OpenAI 协议”不是一套统一标准。Embeddings 只在后文作为向量接口边界说明,不参与工具调用闭环。
更准确地说,OpenAI 和 Anthropic 都提供了基于 HTTP、JSON 和流式事件的模型 API;社区常说的“OpenAI 协议”“Anthropic 协议”,通常是对各自 API 请求、响应和事件约定的简称,而不是一个由双方共同维护的网络标准。
本文用一条任务贯穿对比:
用户问:“帮我查询北京今天的天气,如果需要调用天气工具就调用它,然后给我一句简短建议。”
文章不只比较 OpenAI Responses API 和 Anthropic Messages API。OpenAI 至少要分清三类接口:Responses、Chat Completions 和 Embeddings。本文按新项目优先使用 Responses 来组织示例;Chat Completions 主要用于维护已有代码、兼容现有网关,或明确依赖其 messages / choices 结构的场景。Embeddings 则负责把文本转换成向量,不能因为它们都使用 JSON 就把它们当成同一种“聊天协议”。
本文的主线仍然是一次工具调用,但会以 OpenAI Responses 与 Anthropic Messages 为主对照,再解释 Chat Completions 的兼容差异和 Embeddings 的独立边界。OpenAI Responses API 使用自己的 input、output item、response event 和工具模型,不能把 Chat Completions 的字段机械套过去。官方入口:
- OpenAI Responses:https://platform.openai.com/docs/api-reference/responses
- OpenAI Chat Completions:https://platform.openai.com/docs/api-reference/chat
- OpenAI Embeddings:https://platform.openai.com/docs/api-reference/embeddings
- Anthropic Messages:https://docs.anthropic.com/en/api/messages
先建立一个正确的分层模型
一次模型调用至少有四层:
你的 Agent 业务代码
↓ 把内部消息转换成供应商格式
OpenAI Responses / Anthropic Messages
↓ HTTPS + JSON,请求和响应
供应商的生成模型服务
↓
文本、工具调用、图片内容、流式事件
Chat Completions 是另一种 OpenAI 生成接口,主要承担兼容与迁移场景。
文档检索链路另行使用 Embeddings API:
文本 → 向量 → 向量数据库 → 检索结果 → 生成接口这里容易混淆三件事:
| 名称 | 它解决的问题 | 例子 |
|---|---|---|
| API 协议约定 | 请求和响应字段怎么组织 | messages、input、tool_calls |
| 向量接口约定 | 文本如何变成可检索的向量 | input、model、embedding |
| 传输方式 | 数据怎样到达服务端 | HTTPS、JSON、SSE 流式响应 |
| Agent Loop | 工具调用后如何继续任务 | 模型请求 → 执行工具 → 把结果发回模型 |
模型不会直接读取你的数据库,也不会真的执行本地函数。模型只能返回一段文字或结构化的工具调用意图;真正执行工具的是你的程序。两套协议的差异,主要发生在“如何描述消息”和“如何表达这个意图”这两个位置。
OpenAI 的三类接口:Responses、Chat Completions 与 Embeddings
这三个名字经常同时出现在 SDK 里,但职责完全不同。对于新写的 OpenAI Agent 或多工具应用,可以优先从 Responses API 开始;Chat Completions 仍然有大量存量用户和兼容层,不能当作已经消失的接口。
| 接口 | 输入模型 | 输出模型 | 典型用途 |
|---|---|---|---|
| Responses | input,也可以带消息和内容块 | output[],由多个 output item 组成 | 新项目、工具、内置工具和更丰富的响应事件 |
| Chat Completions | messages | choices[].message 或流式 choices[].delta | 存量对话、兼容网关、已有 Agent |
| Embeddings | 一段或多段文本 | data[].embedding 向量数组 | 语义搜索、RAG、聚类、相似度计算 |
Chat Completions:兼容性更强的消息接口
Chat Completions 的核心是 messages。调用方把 system、user、assistant 和 tool 消息整理成一段对话历史,模型返回一个或多个 choice。它的典型读取路径是:
response.choices[0].message.content
response.choices[0].message.tool_callsChat Completions 仍适合大量已有的聊天和 Agent 代码,也适合需要兼容 OpenAI 风格 /v1/chat/completions 网关的场景。但对于新项目,不应因为旧 SDK 示例很多就默认选择它。它的消息角色和返回结构不能代表 Responses API 的 item 结构。
OpenAI Responses:新项目优先考虑的响应接口
Responses API 更像一个统一的响应容器。本文用它代表 OpenAI 新项目的一种组织方式,而不是把“Responses 优先”当成所有项目都必须遵守的规则。简化后的请求可以写成:
{
"model": "gpt-4.1",
"instructions": "你是一个简洁的天气助手。",
"input": "查询北京今天的天气,并给我一句建议。",
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
]
}Responses 的结果不是 Chat Completions 的 choices[0].message,而是 output 中的 item。例如,文本输出可以是 type: "message" 的内容,函数调用则是 type: "function_call" 的 item。工具执行后,程序再把 function_call_output 作为后续输入交给模型。SDK 提供的 output_text 是便捷读取方式,不应当被误认为底层响应只有一个字符串。
因此,Chat Completions 和 Responses 至少有这些差异:
| 语义 | Chat Completions | Responses |
|---|---|---|
| 主要输入 | messages | input,另有 instructions 等字段 |
| 文本读取 | choices[].message.content | output 中的 message content,SDK 可读 output_text |
| 函数调用 | message.tool_calls[] | output 中的 function_call item |
| 工具结果 | role: "tool" + tool_call_id | function_call_output 输入项,对应调用 ID |
| 流式模型 | completion chunk | response / output item 事件 |
这也是为什么适配器不能只写一个 OpenAIAdapter,然后默认 Chat 和 Responses 共用一套序列化代码。正确做法是先统一成内部的 AssistantTurn、ToolCall 和 ToolResult,再分别实现 Chat adapter 与 Responses adapter。
Embeddings:向量接口,不是聊天接口
Embeddings 解决的问题不是“让模型回答什么”,而是把输入文本映射成固定维度的浮点数向量。典型请求是:
{
"model": "text-embedding-3-small",
"input": [
"北京今天会下雨吗?",
"北京今日天气预报"
],
"encoding_format": "float"
}响应的核心结构是:
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.0123, -0.0456, 0.0789]
}
],
"model": "text-embedding-3-small",
"usage": {
"prompt_tokens": 12,
"total_tokens": 12
}
}实际向量会有更高维度,上面的数组只是示意。Embedding 的数据流通常是:
原始文档
→ 切分 chunk
→ 调用 Embeddings API
→ 得到向量
→ 写入向量数据库
→ 查询文本也向量化
→ 计算相似度并取回文档
→ 再交给 Chat 或 Responses 生成答案它和 Chat、Responses 的边界很重要:
- Embeddings 没有
messages、tool_calls或对话历史。 - Embeddings 不会生成自然语言答案,也不会执行工具。
- Embeddings 通常不走 SSE 文本流,而是一次返回向量结果。
- 建库和查询必须使用兼容的 embedding 模型、预处理方式和向量维度。
- 换 embedding 模型后,旧向量通常不能直接与新向量混用,需要重新生成索引。
- 向量接口的成功,只说明向量生成成功,不说明 RAG 的召回质量或最终回答正确。
Anthropic 的 Messages API 是生成式消息接口;Anthropic 官方 API 并没有一个可以直接等同于 OpenAI /v1/embeddings 的通用 Embeddings endpoint。需要使用 Anthropic 模型做生成、另一个供应商做向量化时,Embedding provider 应在架构中单独抽象,而不是假设每个 LLM provider 都同时提供向量服务。
同一个任务,两个请求长什么样
OpenAI Responses API
下面用新项目优先的 Responses API 表达天气查询。字段删去了鉴权、超时和其他可选参数,只保留理解协议所需的部分:
{
"model": "gpt-4.1",
"instructions": "你是一个简洁的天气助手。",
"input": "查询北京今天的天气,并给我一句建议。",
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "查询指定城市的天气",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名" }
},
"required": ["city"],
"additionalProperties": false
}
}
],
"tool_choice": "auto"
}Responses API 的典型结构是:
instructions承担系统级指令,input承担本轮输入或消息内容。- 工具定义直接位于
tools数组中,函数工具使用name、description和parameters。 - 模型返回的是
outputitem,而不是 Chat Completions 的choices[].message。 - 模型发起函数调用时,响应中通常会有
type: "function_call"的 output item。 - 程序执行完工具后,用
function_call_output输入项把结果关联回原调用。
Anthropic Messages
Anthropic Messages API 的请求表达相似任务时,结构更偏向“消息由内容块组成”:
{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"system": "你是一个简洁的天气助手。",
"messages": [
{
"role": "user",
"content": "查询北京今天的天气,并给我一句建议。"
}
],
"tools": [
{
"name": "get_weather",
"description": "查询指定城市的天气",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
]
}Anthropic 的关键特点是:
system通常是顶层字段,而不是messages中一个role: "system"的元素。- 一条消息的
content可以是字符串,也可以是内容块数组。 - 工具定义直接包含
name、description和input_schema。 - 模型发起工具调用时,assistant 的
content中会出现type: "tool_use"的内容块。 - 程序返回工具结果时,通常继续发送一条
role: "user"的消息,其中包含type: "tool_result"内容块。
因此,Responses 和 Anthropic Messages 虽然语义对应,却不能直接复制字段:
| 语义 | OpenAI Responses | Anthropic Messages |
|---|---|---|
| 系统提示 | instructions | 顶层 system |
| 本轮输入 | input | messages[].content |
| 工具名称 | name | name |
| 工具参数 Schema | parameters | input_schema |
| 模型发起调用 | output[] 中的 function_call | assistant.content[] 中的 tool_use |
| 调用参数 | arguments,通常是 JSON 字符串 | input,通常已经是对象 |
| 调用 ID | call_id | tool_use.id |
| 工具结果 | function_call_output 输入项 | role: user + tool_result 内容块 |
最重要的差异:角色模型不同,内容模型也不同
OpenAI 更像“消息角色 + 可选字段”
在 Chat Completions 中,开发者通常先看消息的 role,再根据角色读取字段:
{
"role": "assistant",
"content": "我先查询一下。",
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}
]
}注意两个细节:
content可能为空,但tool_calls不为空。不能用“没有文本”判断模型没有返回内容。arguments经常是 JSON 字符串。解析失败、流式拼接不完整、模型输出非法 JSON,都需要由适配层处理。
Anthropic 更像“角色消息 + 内容块数组”
Anthropic 的 assistant 消息可能是这样:
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "我先查询一下。"
},
{
"type": "tool_use",
"id": "toolu_123",
"name": "get_weather",
"input": {
"city": "北京"
}
}
]
}这套结构的好处是文本、图片、工具调用和其他内容可以放在同一条消息中;代价是调用方不能只写一个 message.content 字符串处理所有情况。
工程上不要把 Anthropic 的 content 强行压扁成字符串,也不要把 OpenAI 的 tool_calls 假设成普通文本。适配器应该先把供应商响应转换成自己的中间表示:
struct AssistantTurn {
text: Option<String>,
tool_calls: Vec<ToolCall>,
stop_reason: StopReason,
}
struct ToolCall {
id: String,
name: String,
arguments: JsonValue,
}供应商字段只在边界层出现,Agent Loop 只认识 AssistantTurn。这比在业务代码里到处写 if openai ... else anthropic ... 更容易维护。
工具调用:模型提议,程序执行,结果再回传
两套协议都遵循同一个闭环:
sequenceDiagram
participant U as 用户
participant A as Agent 程序
participant M as 模型 API
participant T as 工具
U->>A: 查询北京天气
A->>M: 消息 + 工具定义
M-->>A: tool call: get_weather(city=北京)
A->>T: 执行本地或远程函数
T-->>A: 返回温度、天气和更新时间
A->>M: 原 assistant 调用 + tool result
M-->>A: 最终文字回答
A-->>U: 今天北京……OpenAI Responses 的回传形式
Responses 不把工具结果包装成 role: "tool" 消息,而是把它作为下一次输入中的 function_call_output item,并通过 call_id 关联原来的函数调用:
[
{
"type": "function_call_output",
"call_id": "call_123",
"output": "{\"temperature\":18,\"condition\":\"晴\"}"
}
]如果使用 previous_response_id 维持响应链,还需要按照 Responses API 的规则传递前一响应关系;如果自行保存完整历史,则应保留函数调用 item 和对应结果。
OpenAI Chat Completions 的回传形式(兼容格式)
程序拿到 tool_calls 后,需要保留 assistant 的工具调用消息,再追加工具结果:
[
{
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"北京\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "call_123",
"content": "{\"temperature\":18,\"condition\":\"晴\"}"
}
]Anthropic 的回传形式
Anthropic 要把模型原样返回的 tool_use 内容放进 assistant 消息,然后用 user 消息携带 tool_result:
[
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_123",
"name": "get_weather",
"input": { "city": "北京" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_123",
"content": "{\"temperature\":18,\"condition\":\"晴\"}"
}
]
}
]这里最容易出错的不是字段名,而是消息历史的完整性:
- 不要只保存工具结果,丢掉模型原来的工具调用。
- 不要用数组下标匹配工具调用,要使用调用 ID。
- 一次 assistant 消息可能包含多个工具调用,程序应决定串行还是并行执行。
- 工具失败也要回传结构化错误,而不是让 Agent Loop 永久等待。
- 工具结果进入上下文后,模型才有机会生成最终回答;工具本身不会替模型完成总结。
流式响应:同一个 SSE 外壳,里面的事件不同
流式输出一般通过 Server-Sent Events,也就是 SSE 返回一系列事件。很多 SDK 把它包装成迭代器,但底层事件仍然不是同一套。
OpenAI Responses 的典型思路
Responses 流式返回 response 和 output item 生命周期事件。文本、函数调用开始、函数参数增量和响应结束,分别由不同类型的 response event 表达。不要把它当成 Chat Completions 的 choices[].delta:
response.created
response.output_item.added
response.output_text.delta
response.function_call_arguments.delta
response.function_call_arguments.done
response.output_item.done
response.completed函数参数同样可能分多次到达。必须按 call_id 累积参数,等参数完成后再解析和校验,不能收到第一段 JSON 就执行工具。
OpenAI Chat Completions 的典型思路(兼容格式)
OpenAI Chat Completions 流式返回的是一系列 chat.completion.chunk。文本增量通常在:
{
"choices": [
{
"delta": {
"content": "北京今天"
}
}
]
}工具调用也会被拆成增量片段。尤其是 function.arguments 可能分多次到达:
"{\"city\":"
"\"北京\"}"所以不能收到第一段就执行工具。正确做法是按 tool_call 的索引或 ID 累积名称和参数,等确认调用结束后再解析 JSON。
Anthropic 的典型思路
Anthropic 流式事件更明确地区分消息和内容块生命周期,常见事件包括:
message_start
content_block_start
content_block_delta
content_block_stop
message_delta
message_stop文本增量和工具输入增量分别出现在内容块事件中。工具输入通常需要将多个 input_json_delta 片段拼起来,等内容块结束后再解析。
两套流式事件的设计差异可以概括成:
| 需求 | OpenAI Responses | OpenAI Chat Completions | Anthropic Messages |
|---|---|---|---|
| 文本增量 | response output text 事件 | choices[].delta.content | content_block_delta 中的文本增量 |
| 工具增量 | function call arguments 事件 | delta.tool_calls | input_json_delta 等内容块事件 |
| 结束判断 | response.completed 等 response 事件 | finish_reason 或 [DONE] | message_stop、内容块结束等事件 |
| 解析策略 | 依赖 response event、item 和 call ID | 依赖 choice、index 和字段增量 | 依赖消息、内容块和事件类型 |
适配层最好不要向上层暴露原始 SSE 事件,而是统一成类似下面的事件:
TextDelta("北京今天")
ToolCallStarted(id, name)
ToolArgumentsDelta(id, "{\"city\":")
ToolCallFinished(id)
Usage(input_tokens, output_tokens)
TurnFinished(reason)这样 UI 只负责显示文本,Agent Loop 只负责等待 ToolCallFinished 后执行工具,不需要知道某个供应商叫它 content_block_stop 还是 [DONE]。
系统提示和上下文管理,不能只做字段搬运
两套 API 都支持系统级指令,但位置不同只是表面问题,更深的问题是:你的内部上下文模型必须先把“系统指令”和“对话消息”分开。
推荐的内部结构是:
RequestContext
├── system_instructions: Vec<TextOrBlock>
├── messages: Vec<UserOrAssistantMessage>
├── tools: Vec<ToolDefinition>
├── generation: GenerationOptions
└── metadata: RequestMetadata发送到 OpenAI Chat Completions 时,可以把系统指令转换成 messages 中的 system 消息;发送到 OpenAI Responses 时通常对应顶层 instructions;发送到 Anthropic 时则对应顶层 system。如果内部结构一开始就把 system 当作普通 user/assistant 消息,后面会遇到这些问题:
- 不同供应商对 system 消息位置的要求不同。
- 压缩上下文时,不应把系统指令和普通历史一起随意删掉。
- 多段系统指令可能需要合并、分块或加入缓存控制信息。
- 某些供应商专属参数不能无损迁移,应该在 provider options 中单独保存。
同理,temperature、最大输出 token、停止原因、缓存、思维过程、引用和供应商扩展,也不能假设两边具有完全相同的含义。适配器的目标应是保留业务真正需要的语义,而不是制造一个看起来字段完全对齐的假统一接口。
多模态消息:字符串只是最小公分母
OpenAI 和 Anthropic 都可以在消息中表达图片等内容,但内容块的类型和字段并不相同。一个跨供应商的内部消息至少应区分:
Text("请描述这张图片")
ImageUrl("https://...")
ImageBytes(mime="image/png", data=...)
ToolResult(...)然后分别转换:
- OpenAI 侧转换为它支持的文本和图片内容对象。
- Anthropic 侧转换为对应的
text、image等内容块。
不要把图片 URL 当成普通文本拼进 prompt。这样模型可能只看到一串地址,而不是图片内容;也不要默认所有供应商、模型和账号配置都支持相同的图片格式、尺寸和来源方式。多模态能力必须以具体模型和官方文档为准。
OpenAI 兼容接口到底是什么
很多本地模型服务、代理网关和第三方平台会说“兼容 OpenAI API”。通常它们指的是:
客户端使用 OpenAI SDK
↓ 修改 base_url、api_key 或 model
第三方服务接收 /v1/chat/completions 风格请求这提供了很好的迁移入口,但“兼容”至少有三个等级:
- 请求格式兼容:能接受
messages、model、tools。 - 响应格式兼容:返回
choices、message、usage,并支持流式 chunk。 - 语义兼容:工具调用、并行调用、停止原因、JSON Schema、视觉输入和错误行为都足够接近。
很多服务只做到前两级。普通文本聊天可能正常,遇到工具调用就暴露差异。例如:
- 工具参数被返回成文本,而不是
tool_calls。 - 流式响应缺少完整的 tool call index。
finish_reason的取值和 OpenAI 不一致。response_format被忽略或只支持部分 JSON Schema。usage只在流结束时返回,甚至不返回。
因此,把 base_url 换掉只能证明 HTTP 请求发出去了,不能证明 Agent 能可靠运行。至少要用文本、图片、单工具、多工具、工具失败和流式工具参数做一组兼容性测试。
Anthropic 也提供了面向 OpenAI SDK 的兼容路径,但官方兼容说明明确提醒:兼容层适合迁移和测试,并不意味着 OpenAI 与 Anthropic 的所有能力和语义完全等价。需要 Anthropic 特有能力时,应直接使用 Anthropic API 或官方 SDK。参考:Anthropic OpenAI SDK compatibility:https://docs.anthropic.com/en/api/openai-sdk。
设计适配器时,推荐三层而不是两个大分支
一个可维护的实现可以拆成三层:
第一层:业务消息模型
UserMessage / AssistantTurn / ToolResult / Image
第二层:规范化 Agent 事件
TextDelta / ToolCall / Usage / Error / TurnFinished
第三层:供应商适配器
OpenAIChatAdapter / AnthropicMessagesAdapter业务层只表达意图
业务层说“这里有一个工具,名字是 get_weather,参数是这个 JSON Schema”,不关心它应该放在 function.parameters 还是 input_schema。
适配器负责有损转换
适配器要明确记录哪些字段可以转换,哪些字段只能降级或拒绝:
内部 system 指令 → OpenAI Chat 的 `messages[].role=system`
→ OpenAI Responses 的 `instructions`
→ Anthropic top-level `system`
内部 ToolCall → tool_calls + function.arguments
→ tool_use + input
内部 ToolResult → role=tool
→ role=user + tool_result
内部 stop 条件 → provider-specific 参数如果某个功能没有可靠映射,不要静默丢弃。可以采用三种策略:
- 能安全降级就降级,并记录 warning。
- 不能保证语义时返回“不支持该能力”。
- 业务强依赖时,在能力协商阶段直接拒绝请求。
能力矩阵比一个布尔值更有用
不要只有一个 supports_tools: true。更实用的是:
| 能力 | OpenAI Chat | Anthropic Messages | 适配器需要关注 |
|---|---|---|---|
| 文本输入输出 | 支持 | 支持 | 基本路径 |
| 工具调用 | 支持 | 支持 | 调用 ID、参数格式、回传角色 |
| 流式文本 | 支持 | 支持 | 事件归一化 |
| 流式工具参数 | 支持 | 支持 | 增量拼接和结束判断 |
| 图片输入 | 依模型而定 | 依模型而定 | MIME、来源、大小限制 |
| JSON 结构化输出 | 依模型和接口而定 | 依模型和接口而定 | 不要只看字段名 |
| 推理/思维相关字段 | 依接口和模型而定 | 依接口和模型而定 | 不要把内部推理当普通文本 |
最常见的六个错误
1. 只替换 URL 和模型名
这只适用于最简单的纯文本请求。涉及工具和流式响应时,还要改请求序列化、响应解析和工具结果回传。
2. 把 content 永远当字符串
Anthropic 的 content 可以是内容块数组;OpenAI 的 assistant 消息也可能同时包含文本和工具调用。正确做法是先解析成内部内容列表。
3. 把模型返回的工具调用当成工具结果
tool_use 或 tool_calls 只是模型提出的调用。你的程序必须验证工具名、校验参数、执行函数、限制权限,再把结果回传。
4. 忘记保存原始 assistant 工具消息
下一次请求不仅需要工具结果,还需要让模型看到“自己刚刚调用了什么”。缺少这条历史,供应商可能拒绝请求,也可能让模型失去上下文。
5. 流式收到半截 JSON 就执行工具
工具参数可能跨多个事件到达。必须按调用 ID 累积,确认结束,解析并校验后再执行。
6. 把供应商专属字段伪装成通用字段
例如缓存、引用、推理摘要、服务等级、预测输出或特定响应事件,都可能没有一一对应关系。通用层只应暴露已经定义清楚、跨供应商确实成立的语义。
一套最低限度的适配器测试
如果你正在实现网关、模型路由器或 Agent 框架,建议至少覆盖这些用例:
T1 纯文本:system + user → 最终文本
T2 空文本工具调用:assistant 只返回工具调用
T3 文本 + 工具调用:同一 assistant 消息同时包含两者
T4 单工具结果:成功回传后生成最终答案
T5 工具异常:超时、参数错误、业务错误能回传
T6 并行工具:一次返回两个调用,ID 不串线
T7 流式文本:增量拼接后的文本与非流式一致
T8 流式工具:参数分片后得到合法 JSON
T9 多模态:图片内容没有被错误降级成 URL 文本
T10 向量化:输入顺序、向量维度和 index 能正确对应
T11 取消与重试:取消后不重复执行不可幂等工具测试断言不要只看 HTTP 状态码。至少还要检查:
- 规范化后的文本是否正确。
- 工具名和参数是否保持不变。
- 工具调用 ID 是否能够闭环。
- stop reason 是否被正确转换。
- token usage 是否在支持时被记录。
- 流式和非流式是否最终得到等价的 Agent 状态。
最后:该选哪套协议
如果你只做 OpenAI 新项目,本文建议优先从 Responses API 开始;如果是在维护已有 Chat Completions 代码、对接固定的 OpenAI-compatible 网关,继续使用 Chat Completions 也完全合理。如果你只做 Anthropic 模型,也不需要为了“统一”先套一层 OpenAI 格式。这里的“优先”是针对本文示例和新项目起点的工程建议,不是要求所有存量项目迁移。
如果你要同时接入多个供应商,建议采用下面的判断:
- OpenAI 新项目优先使用 Responses API,尤其是 Agent、工具调用、内置工具和多模态场景。
- 已有 OpenAI 项目可以继续使用 Chat Completions,不要为了迁移而忽视现有兼容成本。
- 业务代码使用自己的消息、工具和事件模型。
- 供应商边界使用官方 SDK 或清晰的 HTTP adapter。
- 纯文本迁移可以依赖兼容接口快速迁移。
- Agent、工具调用、多模态和流式场景优先验证原生协议语义。
- 不兼容的能力显式暴露或拒绝,不要静默丢失。
OpenAI Chat Completions、Responses 和 Anthropic Messages 的共同点,是都把生成式模型接入封装成“输入进、结构化响应出”;Embeddings 则是另一条“文本进、向量出”的接口链路。真正的差异在于它们如何建模消息、内容块、响应 item、工具生命周期、流式事件和向量结果。
理解这层差异之后,“换一个模型供应商”就不再是改 URL 的运气题,而会变成一个可以测试、可以降级、可以维护的适配问题。
官方文档
- OpenAI Responses API:https://platform.openai.com/docs/api-reference/responses
- OpenAI Chat Completions:https://platform.openai.com/docs/api-reference/chat
- OpenAI Embeddings:https://platform.openai.com/docs/api-reference/embeddings
- OpenAI Function calling:https://platform.openai.com/docs/guides/function-calling
- Anthropic Messages API:https://docs.anthropic.com/en/api/messages
- Anthropic Tool use:https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview
- Anthropic Streaming:https://docs.anthropic.com/en/api/messages-streaming
- Anthropic OpenAI SDK compatibility:https://docs.anthropic.com/en/api/openai-sdk
云服务器推荐
如果你需要部署博客、Docker 服务或个人项目,可以参考:
本文包含推广链接。通过链接购买不会增加你的费用,但我可能获得少量佣金。
评论