先给结论:新项目优先采用 Responses API;只做单轮文本、已经稳定运行的 Chat Completions 可以继续维护,再按功能收益分阶段迁移。 OpenAI 当前的官方迁移指南明确写着,Responses 是 Chat Completions 的演进,也是新项目的推荐接口;同一页也明确说明 Chat Completions 仍受支持。它不是“明天就不能用”的旧接口。
真正的区别也不是把 /v1/chat/completions 改成 /v1/responses。前者把一次生成组织成消息列表和候选答案,后者把模型消息、推理、工具调用和工具结果都建模为不同类型的 Item。只改 URL,最容易在输出解析、上下文、工具回传或流式 UI 上留下静默错误。
两套接口的最小心智模型
Chat Completions 的中心对象是 messages。应用把历史消息重新发送给模型,再从 choices[0].message 读取答案:
jsconst completion = await client.chat.completions.create({ model: "gpt-5.6", messages: [ { role: "developer", content: "回答要简洁。" }, { role: "user", content: "给这个错误日志归类。" }, ], }); const text = completion.choices[0].message.content;
Responses 的中心对象是 input 和类型化的 output Item。纯文本场景可以使用 SDK 的 output_text 便捷属性,但工具调用、引用、状态或其他输出类型仍要遍历 output:
jsconst response = await client.responses.create({ model: "gpt-5.6", instructions: "回答要简洁。", input: "给这个错误日志归类。", }); const text = response.output_text; for (const item of response.output) { console.log(item.type, item.status); }
这一区别会产生几项直接后果:
| 合同 | Chat Completions | Responses API | 迁移时要改什么 |
|---|---|---|---|
| 端点 | /v1/chat/completions | /v1/responses | SDK 调用或 HTTP URL |
| 输入 | messages | 字符串或 Item 列表 input,另有 instructions | 请求构造与类型定义 |
| 输出 | choices[].message | output[] 类型化 Item | 解析器、日志与错误分支 |
| 多个候选 | 可用 n 返回多个 choice | 一次只产生一个 generation | 依赖多候选的业务逻辑 |
| 会话状态 | 通常由应用重发消息 | 可用 previous_response_id、Conversations 或手动重放 Item | 状态存储与恢复策略 |
| 结构化输出 | response_format | text.format | schema 配置位置 |
| 流式返回 | chunk 与 choices[].delta | 有类型的语义事件 | 事件分发器和完成条件 |
因此,“能复用简单的 role/content 消息数组”只说明最小文本请求容易搬迁,不代表两套协议可以互换。
状态便利性和数据存储是两件事
Chat Completions 的多轮对话通常由应用维护:保存历史,把需要的消息重新放进下一次 messages。Responses 可以让下一轮带上 previous_response_id:
jsconst first = await client.responses.create({ model: "gpt-5.6", input: "把这份告警归纳成三类。", }); const next = await client.responses.create({ model: "gpt-5.6", previous_response_id: first.id, instructions: "现在只给出需要立即处理的一类。", input: "继续。", });
这能减少应用手动拼接上下文的工作,但有两个容易忽略的边界。
第一,Responses API reference说明,上一轮的 instructions 不会因为传了 previous_response_id 就自动沿用;需要持续生效的开发者指令,下一轮仍要明确提供。
第二,状态续接不等于数据不落盘。OpenAI 的数据控制说明对 Responses、store、Zero Data Retention、background mode、缓存和托管工具分别规定了边界。当前文档说明,Responses 在默认存储或 store: true 时可能保留至少 30 天的应用状态,但组织级设置和例外会改变行为。若你的业务有留存要求,不应只记住一个 store: false,而要根据组织配置和实际利用面重新核对。
工具调用为什么不能只改响应字段

两套接口都能调用自定义函数,但往返信封不同。
在 Chat Completions 中,模型的工具请求位于 assistant message 的 tool_calls;应用执行函数后,追加一个 role: "tool" 的消息,并用 tool_call_id 对应原调用。
在 Responses 中,模型返回独立的 function_call Item;应用回传 function_call_output Item,并用 call_id 建立对应关系:
jsconst calls = response.output.filter( (item) => item.type === "function_call" ); const toolOutputs = calls.map((call) => ({ type: "function_call_output", call_id: call.call_id, output: JSON.stringify(runTool(call.name, call.arguments)), })); const final = await client.responses.create({ model: "gpt-5.6", previous_response_id: response.id, input: toolOutputs, });
这里必须遍历全部调用,而不是假设永远只有第一个。还要分别验证:参数 JSON 解析失败怎么办、工具超时如何回传、并行调用如何关联、工具结果之后是否还有下一轮工具调用。OpenAI 的函数调用指南给出了两套接口的完整往返示例。
如果你使用的是 web search、file search、code interpreter 或 remote MCP 等托管工具,Responses 的优势更明显:同一请求可以包含多个类型化的工具 Item。但具体模型是否支持某个工具,仍要看当前模型页;“接口支持”不能推出“任何模型都支持”。
流式 UI 要重写事件分发,而不是换一个取文本表达式
Chat Completions 流通常循环读取 chunk,从 chunk.choices[0].delta.content 追加文本。Responses 使用有明确类型的语义事件,例如文本增量、输出 Item 完成和整个 response 完成事件。官方流式响应指南建议按事件类型处理。
这会改变前端和可观测性代码:
- 文本增量只更新正在显示的文本;
- 工具调用参数增量进入工具状态,而不是聊天文本;
- Item 完成用于收口单个输出;
- response 的
completed、failed或incomplete决定整次请求最终状态; - 用户取消、网络断线和模型生成不完整必须拥有不同的 UI 与重试策略。
仅以 HTTP 200 或“收到过文本增量”作为成功条件并不够。Responses 对象本身拥有状态生命周期,迁移后的日志至少应记录 response ID、最终状态、输出 Item 类型、usage 和错误/不完整原因。
新项目与旧系统应采用不同决策
对于新项目,Responses 通常是更稳的默认选择,尤其是以下任一条件成立时:需要推理模型的连续上下文、托管工具、多步函数调用、原生多模态输入、长任务状态或未来扩展到代理工作流。
对于稳定的旧系统,继续使用 Chat Completions 也可能是正确决定,特别是业务只是单轮文本或简单结构化输出,现有解析、监控和供应商抽象都围绕 messages/choices,而 Responses 暂时没有带来能覆盖迁移风险的收益。支持仍在,不需要为了“接口更新”制造一次大爆炸式重写。
第三方标注“OpenAI compatible”时更要谨慎。兼容 Chat Completions 不自动等于兼容 Responses;即使端点存在,也要分别核对 Item 类型、流式事件、工具信封、状态续接和错误对象。OpenAI 官方文档只能证明 OpenAI 自身的合同,不能替第三方供应商背书。
用一次影子迁移把风险变成可验收结果

一个可控迁移可以按以下顺序推进:
- 先封装内部中间结果,不让业务代码直接读取
choices[0]或任意output[0]。 - 用同一批代表性请求同时调用两条路径,对比最终文本、结构化字段、拒绝和截断行为,而不是只比字符串。
- 为多轮场景明确选择
previous_response_id、Conversation 或手动重放,验证进程重启后的恢复方式。 - 迁移全部工具往返,覆盖零次、一次、并行多次、失败与重试。
- 重写 streaming 事件分发和完成条件,验证取消、断线、
incomplete与failed。 - 最后检查存储、日志脱敏、usage 计量和第三方兼容性,再逐步放量。
切换完成的标准不是“返回了一段正确文字”,而是旧接口承担的每一项可观察合同都有了新实现:状态能恢复、工具调用不丢、结构化输出能解析、流式 UI 能收口、失败能定位。做到这一步,迁移才从字段替换变成了受控的协议升级。



