对话协议是一层 HTTP 契约:客户端 POST JSON,服务端返回 JSON(或 SSE)。契约规定三件事——上下文如何编码、模型输出落在哪个字段、以及生成停止后客户端读哪一个键决定下一步。
当前主流是三套:
| Chat Completions | Responses | Messages | |
|---|---|---|---|
| 厂商 | OpenAI | OpenAI | Anthropic |
| 方法 / 路径 | POST /v1/chat/completions |
POST /v1/responses |
POST /v1/messages |
| Host | api.openai.com |
同左 | api.anthropic.com |
| 核心对象 | messages[](Message) |
input / output(Item) |
messages[] + content[](Content Block) |
| 系统提示 | messages[].role = "system" 或 "developer" |
顶层 instructions |
顶层 system |
| 停止信号 | choices[0].finish_reason |
status + output[].type |
stop_reason |
OpenAI 推荐新集成走 Responses,Chat Completions 仍保持支持。Assistants API 已弃用,日落时间为 2026-08-26。GPT-5.4 起,Chat Completions 在 reasoning: none 时不再接受 tool calling。
三套协议的差异来自对象模型,不是「会不会对话」。
对象模型
Chat Completions 以 Message 为基本单位。系统指令、用户输入、助手输出、工具结果都是 { role, content },靠 role 区分职责。一次响应把助手 Message 放进 choices[];n > 1 时 choices 长度大于 1。
Responses 以 Item 为基本单位。message、function_call、function_call_output、reasoning 以及托管工具调用是不同类型,靠 type 区分。官方迁移文档的表述是:Chat 把多种关注点粘在一条 Message 上,Responses 把它们拆开。Responses 去掉了 n。
Messages 以 Message + Content Block 为基本单位。一条 assistant Message 的 content 是块数组,同一轮里可以按序出现 thinking、text、tool_use。系统提示不进入 messages,而在顶层 system。官方正文仍写:不要在 messages 里使用 role: "system"(schema 里后来出现的 mid_conv_system 只用于中途改指令)。
因此协议转换不是改 baseURL。至少要映射:系统提示的位置、工具 schema 的外壳、工具结果的承载对象、停止信号的键名、流式增量的事件形状。
HTTP 头
认证、版本、限流不在 JSON body 里。漏头或取值不合法,请求在进入模型之前就会 400 / 401。
OpenAI(Chat Completions 与 Responses 共用)
| Header | 取值 | 约束 |
|---|---|---|
Authorization |
Bearer <secret> |
必填。<secret> 为 API key,或 Workload Identity Federation 换取的 access token |
Content-Type |
application/json |
JSON body 时必填 |
OpenAI-Organization |
org-... |
可选。空字符串会 401:OpenAI-Organization header should match organization for API key |
OpenAI-Project |
proj_... |
可选。与上一行共同决定计费项目。SDK 读 OPENAI_ORG_ID / OPENAI_PROJECT_ID |
X-Client-Request-Id |
客户端 trace | 可选。仅 ASCII,≤ 512 字符,否则 400 |
OpenAI-Beta |
如 assistants=v2 |
Chat / Responses 主路径不需要 |
请求头合计须小于 64 KiB。Azure OpenAI 常见认证是 api-key: <key>,不是 Bearer。
响应头:x-request-id(排障)、openai-organization、openai-version(当前文档为 2020-10-01,REST 兼容版本)、openai-processing-ms、x-ratelimit-limit-requests / remaining-requests / reset-requests、x-ratelimit-*-tokens,以及可选的 x-ratelimit-*-project-tokens。流式响应的 Content-Type 为 text/event-stream。
Anthropic Messages
| Header | 取值 | 约束 |
|---|---|---|
x-api-key |
sk-ant-api03-... |
与 Authorization 二选一 |
Authorization |
Bearer <token> |
<token> 来自 POST /v1/oauth/token,不能把 API key 再包一层 Bearer |
anthropic-version |
2023-06-01 |
必填。缺失、或被代理复写成 2023-06-01, 2023-06-01,均为 400。2023-01-01 的 SSE 语义不同(前缀累加 + data: [DONE]) |
content-type |
application/json |
必填 |
anthropic-beta |
feature-name-YYYY-MM-DD |
可选,多个用逗号。无效或互斥 → 400 invalid_request_error |
anthropic-dangerous-direct-browser-access |
true |
仅浏览器直连需要,用于 CORS |
Amazon Bedrock 上版本写在 body:"anthropic_version": "bedrock-2023-05-31",beta 为 "anthropic_beta": ["..."] 数组。
响应头:request-id(无 x- 前缀)、anthropic-organization-id、anthropic-workspace-id、retry-after(429 时的等待秒数)、anthropic-ratelimit-requests-*、anthropic-ratelimit-input-tokens-*、anthropic-ratelimit-output-tokens-*。*-reset 是 RFC 3339 绝对时间,不能按相对秒数 sleep。多数模型下缓存命中不计入 ITPM;OTPM 按实际生成计,不按 max_tokens 预扣。
请求体
先看必填项,再看语义相同但键名不同的字段。
| 语义 | Chat Completions | Responses | Messages |
|---|---|---|---|
| 模型 | model(必填) |
model |
model(必填) |
| 上下文 | messages(必填) |
input:string 或 Item 数组。纯字符串等价一条 role: "user" |
messages(必填,上限 10 万条) |
| 系统提示 | messages 内 role: "system" / "developer" |
顶层 instructions |
顶层 system(string 或 text 块数组) |
| 输出上限 | max_tokens(旧);新模型 / o 系列用 max_completion_tokens |
max_output_tokens |
max_tokens 必填。0 表示只预热 cache |
| 停词 | stop:string 或最多 4 个 string。o3 / o4-mini 不支持 |
无对等字段 | stop_sequences: string[] |
| 采样 | temperature(0–2)、top_p |
同左。部分推理模型不接受 temperature |
temperature(0–1)、top_p。开启 thinking 时 temperature 必须为 1 |
| 流式 | stream;stream_options.include_usage |
stream;stream_options.include_obfuscation |
stream |
| 工具定义 | tools |
tools |
tools |
| 工具策略 | tool_choice:"none" / "auto" / "required" / { type, function.name } |
"none" / "auto" / "required" / { type: "function", name } |
{ type: "auto" | "any" | "tool" | "none" }。any ≡ OpenAI required |
| 并行工具 | parallel_tool_calls |
parallel_tool_calls;另有 max_tool_calls |
tool_choice.disable_parallel_tool_use |
| 结构化输出 | response_format |
text.format |
output_config.format |
| 推理配置 | reasoning_effort |
reasoning |
thinking(budget_tokens ≥ 1024 且 < max_tokens) |
| 多候选 | n |
不支持 | 不支持 |
| 跨轮状态 | store |
store(默认 true)+ previous_response_id / conversation |
无服务端会话。每轮重传 messages |
| 用户标识 | user / safety_identifier |
同左 | metadata.user_id |
| 缓存控制 | 隐式;可选 prompt_cache_key |
同左 | 显式 cache_control |
Chat Completions 的 functions / function_call 已弃用,应使用 tools / tool_choice。
messages[].role(Chat)的取值是 developer | system | user | assistant | tool,以及已弃用的 function。tool 消息必须带 tool_call_id。assistant 在纯 tool 回合允许 content: null。user 的多模态块类型为 image_url,URL 位于 image_url.url,detail 为 auto | low | high。
Responses 的 input Item 常见类型:
message:role+content。输入文本块类型是input_text,回放助手文本块类型是output_textfunction_call:模型发起的调用function_call_output:客户端回传reasoning:推理过程。多轮需原样回放;store: false时通过include: ["reasoning.encrypted_content"]取得密文
previous_response_id 引用上一轮响应的 id(resp_...)。它不继承上一轮顶层 instructions。链路中的历史 input token 仍计入本轮 input。store: false 被 ZDR 组织强制开启。
Messages 请求里,content 写成字符串等价于 [{ "type": "text", "text": "..." }]。响应侧没有这种简写,content 恒为数组。若最后一条 role 为 assistant 且内容不完整,模型会从该处续写。
结构化输出的键路径不同,不能互换:
// Chat Completions
{ "response_format": { "type": "json_schema", "json_schema": { "name": "person", "strict": true, "schema": { } } } }
// Responses
{ "text": { "format": { "type": "json_schema", "name": "person", "strict": true, "schema": { } } } }
// Messages
{ "output_config": { "format": { "type": "json_schema", "schema": { } } } }
type: "json_object" 只约束合法 JSON,不约束 schema。三家均不接受根节点为 $ref 的 JSON Schema,须先展开。
响应体
| 语义 | Chat Completions | Responses | Messages |
|---|---|---|---|
| 对象种类 | object: "chat.completion" |
object: "response" |
type: "message" |
| 响应 id | id(chatcmpl-...) |
id(resp_...) |
id(msg_...),不同于响应头 request-id |
| 时间戳 | created(Unix 秒) |
created_at |
无对等顶层字段 |
| 读正文 | choices[0].message.content |
SDK output_text;或遍历 output[] 中 type == "message" 的 content[].text(块类型 output_text) |
content[] 中 type == "text" 的 .text |
| 停止信号 | choices[0].finish_reason |
status + output[].type |
stop_reason |
| 输入用量 | usage.prompt_tokens |
usage.input_tokens |
usage.input_tokens(不含 cache 读写) |
| 输出用量 | usage.completion_tokens |
usage.output_tokens |
usage.output_tokens |
| 合计 | usage.total_tokens |
usage.total_tokens |
input + cache_creation + cache_read(再加 output) |
| 缓存命中 | usage.prompt_tokens_details.cached_tokens |
usage.input_tokens_details.cached_tokens |
usage.cache_read_input_tokens |
| 推理用量 | completion_tokens_details.reasoning_tokens |
output_tokens_details.reasoning_tokens |
output_tokens_details.thinking_tokens |
停止信号的枚举需要对齐后再做转换:
| 语义 | finish_reason |
Responses | stop_reason |
|---|---|---|---|
| 自然结束 | stop |
status == "completed" 且 output 中无待回传的 function_call |
end_turn |
| 命中停词 | stop |
无 | stop_sequence(具体串在 stop_sequence) |
| 输出上限 | length |
status == "incomplete" 且 incomplete_details.reason == "max_output_tokens" |
max_tokens |
| 客户端工具 | tool_calls |
output[].type == "function_call"。不能用 status == "incomplete" 判断 |
tool_use |
| 安全拦截 | content_filter |
incomplete_details.reason == "content_filter",或 output 中 type == "refusal" |
refusal |
| 长轮次暂停 | 无 | 无 | pause_turn(将完整响应作为下轮 assistant 回放即可续写) |
| 上下文溢出 | 无独立枚举 | 无独立枚举 | model_context_window_exceeded |
| 旧 function | function_call(弃用) |
无 | 无 |
Responses 的 status 取值是 completed | failed | in_progress | cancelled | queued | incomplete,描述本次生成是否结束,不描述是否需要执行客户端工具。error 仅在 failed 时非空。不要假设 output[0] 是最终文本:推理模型通常先输出 type: "reasoning"。
Messages 非流式时 stop_reason 恒非空。流式时 message_start 中为 null,终态只出现在 message_delta。
工具调用
工具协议分三段:定义(请求 tools)、调用(响应中的调用对象)、回传(下一轮请求中的结果对象)。JSON Schema 本身可以相同,外壳和关联键不同。
定义
Chat Completions 使用外部标签(externally tagged):
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a city",
"strict": true,
"parameters": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"],
"additionalProperties": false
}
}
}
Responses 使用内部标签(internally tagged),不再嵌套 function。省略 strict 时会尝试严格模式,schema 不兼容则回退,并在返回的工具上标注 strict: false:
{
"type": "function",
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"],
"additionalProperties": false
}
}
Messages 的 schema 字段名为 input_schema:
{
"name": "get_weather",
"description": "Get weather for a city",
"input_schema": {
"type": "object",
"properties": {
"location": { "type": "string", "description": "City and state, e.g. San Francisco, CA" }
},
"required": ["location"]
}
}
{ "type": "web_search" }(Responses)或 { "type": "web_search_20250305", "name": "web_search" }(Messages)属于服务端工具,由平台在同一次请求内执行,客户端不走回传协议。
调用
Chat Completions:位于 choices[0].message.tool_calls[]。
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"San Francisco, CA\"}"
}
}
function.arguments 的类型是 string,须 JSON.parse。content 在此回合常为 null。
Responses:位于 output[],type 为 function_call。
{
"type": "function_call",
"id": "fc_001",
"call_id": "call_abc123",
"name": "get_weather",
"arguments": "{\"location\":\"San Francisco, CA\"}"
}
id 是 Item id;回传必须使用 call_id。arguments 同样是 string。
Messages:位于 content[],type 为 tool_use。
{
"type": "tool_use",
"id": "toolu_01ABC",
"name": "get_weather",
"input": { "location": "San Francisco, CA" }
}
input 的类型是 object,不要再 JSON.parse。
回传
Chat Completions 增加一条 Message:
{ "role": "tool", "tool_call_id": "call_abc123", "content": "18°C, cloudy" }
并行 N 个 tool_calls 必须回 N 条 role: "tool",tool_call_id 一一对应,缺一则 400。上一轮 assistant(含 tool_calls)必须一并重放。
Responses 增加一个 Item。使用 previous_response_id 时只需提交增量;手动管理状态时须同时回放对应的 function_call:
{ "type": "function_call_output", "call_id": "call_abc123", "output": "18°C, cloudy" }
结果字段名是 output,不是 content。
Messages 没有 role: "tool"。结果放在 user Message 的 content 中:
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01ABC", "content": "18°C, cloudy" }
]
}
上一轮 assistant 的 content(含全部 tool_use)必须原样重放。开启 extended thinking 时,thinking 块及其 signature 同样必须原样回放,修改会导致 400。同一条 user 中若同时有文本,tool_result 须排在文本之前。失败时设 "is_error": true。tool_choice.type == "any" 与 extended thinking 互斥。
客户端循环的判定条件可以收成:
Chat: finish_reason == "tool_calls" → tool_calls / tool_call_id
Responses: output[].type == "function_call" → function_call / call_id
Messages: stop_reason == "tool_use" → tool_use / tool_use_id
否则: stop | completed | end_turn → 读取文本字段
流式
stream: true 后,三套协议都使用 Content-Type: text/event-stream,但帧格式不同。客户端应发送 Accept: text/event-stream,避免中间代理按 JSON 缓冲。
Chat Completions 是 data-only SSE:每帧 data: <chat.completion.chunk>,以字面量 data: [DONE] 结束。增量在 choices[0].delta。tool_calls[].id 与 function.name 只在该 call 的首帧出现,后续帧只有 function.arguments 碎片。用 tool_calls[].index 分桶,对 arguments 做字符串拼接后再 parse。finish_reason 在最后一帧非 [DONE] 给出。usage 默认不出现,须设置 stream_options.include_usage = true,位于 [DONE] 前一个 choices: [] 的 chunk;连接中断则丢失。openai-python 的 accumulate_delta 对 string 执行 +=,对 list 按 index 对齐,不累加 index / type。
Messages 是 named SSE:event: 与 data: 成对。content_block_start 中 tool_use.input 为 {} 占位;实参来自同 index 的 delta.type == "input_json_delta" 的 partial_json 拼接。文本块的 delta 类型是 text_delta。stop_reason 只在 message_delta 中出现。message_delta.usage.output_tokens 是累计值,不能再与 message_start.usage 相加。ping 丢弃。无 [DONE]。
Responses 是 typed 事件。文本听 response.output_text.delta。工具参数听 response.function_call_arguments.delta(arguments += delta),完整值在 response.function_call_arguments.done。生命周期事件为 response.created / response.completed / response.failed / response.incomplete。不能用 Chat 的 choices[0].delta.content 消费。
缓存
Anthropic 使用显式断点。cache_control 可写在顶层(自动标到最后一个可缓存块),或写在具体的 system / tools / content 块上:{ "type": "ephemeral", "ttl": "5m" | "1h" },默认 5 分钟。可缓存前缀顺序为 tools → system → messages。修改工具描述会使后续前缀全部失效。断点必须落在跨请求不变的最后一块。低于模型最小可缓存长度(512–4096,依型号)时静默不生效。
计量关系:
总输入 = cache_read_input_tokens + cache_creation_input_tokens + input_tokens
input_tokens 只覆盖断点之后的部分。
OpenAI 为隐式自动缓存,请求侧没有 cache_control。命中量读 cached_tokens。官方给出的内部对比是 Responses 相对 Chat Completions 缓存利用率高 40%–80%。previous_response_id 减少的是重传字节,不把历史 token 计费清零。
协议选择
已有大量 Chat Completions 集成、且只需补全与客户端 function:继续使用 Chat Completions,按流量迁移。
需要完整 messages 以便审计、回放、跨厂商转换:使用 Messages。代价是每轮重传上下文,缓存断点由客户端维护。
需要托管工具(搜索、沙箱、远程 MCP)、或推理 Item 必须在多轮中保留:使用 Responses。服务端状态与内置工具会提高对 OpenAI 的耦合。
字段映射以官方文档为准:Migrate to the Responses API、Create chat completion、Create a model response、Messages、Streaming。