首页 / 资讯中心 / 文章详情

OpenAI接口演进:从Chat Completions到Responses API迁移实战

OpenAI接口演进:从Chat Completions到Responses API迁移实战 ★ FEATURED ARTICLE
最近团队在把内部的 Agent 框架从 Chat Completions 往 Responses API 上迁移翻了不少开源项目的源码正好把旧接口和新接口的差异、以及开源兼容这一层的情况一起梳理一下。OpenAI 的接口规范从来不是一成不变从早期的 Completions到后来几乎统治第三方模型服务的 Chat Completions再到这两年大力推的 Responses每一步都对应着不同阶段的能力边界。如果你正在做 LLM 应用开发或者维护着某个模型网关、推理框架、Agent 编排层这篇文章值得花十分钟读完至少能帮你少踩几个迁移路上的坑。很多人以为 Responses API 只是 Chat Completions 换个名字其实完全不是。它改变了请求的数据结构、流式事件协议甚至把多轮对话状态这种本来是应用层承担的东西往服务端挪了一大截。开源世界对它的态度也很有意思嘴上说兼容实际大多还在旧接口上打转。这篇就把演进逻辑、接口差异、开源兼容的真相以及迁移过程中实测会遇到的问题一次说清楚。1. 从 Completions 到 Chat Completions接口演进的第一次分叉1.1 最初的 Completions 是个什么样的接口如果你是从 2022 年底开始接触 OpenAI API应该对text-davinci-003这类模型还有印象。那时候官方主推的接口叫 Completions路径是POST /v1/completions传一个prompt字符串模型给你续写一段文本。返回结构非常简单核心字段是choices[0].text后面跟着finish_reason告诉你是因为到了max_tokens还是遇到了停止符。这个接口的本质是文本续写器它不区分用户消息和系统消息。你想做聊天机器人就得自己在prompt里拼一大段话比如Human: hello\nAI:模型根据前面的内容继续输出。这种做法很原始但确实能跑通最简单的生成任务。参数也没有太多的花样temperature、max_tokens、top_p、n、stop大概就是全部的常用配置了。底层逻辑就是给定前缀预测后文。在当时这套接口训练和推理都很直接OpenAI 也没想过它要长成一个生态。但现在回看它的缺陷很明显没有角色体系没有上下文轮次管理没有工具调用的表达空间多轮对话全靠用户手工拼接字符串稍微复杂点的应用写起来都很难受。所以后来功能一多这套接口就撑不住了。1.2 Chat Completions 为什么能成为事实标准大约是 2023 年 3 月OpenAI 发布了POST /v1/chat/completions核心变化是引入了messages数组。每条消息有role和content角色包括system、user、assistant后来又加了tool。这个设计直接对应了聊天场景也让系统提示词有了标准位置。chat/completions之所以能成为事实标准不只是因为它比completions好用一点点而是因为它把角色和工具调用这两个关键概念带进了 API 体系。到 2023 年年中函数调用function_call和工具tools相继上线开发者可以在一次请求里告诉模型有哪些工具可用模型返回一个结构化的调用计划而不是一长串难以解析的文本。这种能力对 Agent 应用的爆发至关重要。真正让 Chat Completions 稳坐标准位置的原因是开源生态的跟进。vLLM、SGLang、FastChat、Ollama还有各种国产推理框架几乎不约而同地实现了/v1/chat/completions路由。原因很简单OpenAI 的客户端 SDK、LangChain、LlamaIndex 等上层工具默认都打这个接口只要服务端长成 OpenAI 的样子就能直接接入整个生态。这也导致后来的模型服务商们不管自己底层是什么对外都宣称兼容 OpenAI Chat Completions API。这个惯性到现在还很强大Responses API 想撼动它并不容易。2. Responses API 为什么出现新规范要解决什么问题2.1 旧接口的七宗罪先说清楚一件事Chat Completions 并不是真的烂到不能用而是它在面对 Agent 场景的时候边界太稀碎了。我根据自己的使用体验给旧接口列了几个痛点。第一无状态。每一次请求都必须把完整的历史消息重新传一遍服务端不维护对话状态。多轮一长token 消耗直线上升重复计算的负担也很重。第二流式与工具调用割裂。你开启stream之后普通的增量文本是一个结构工具调用的增量又是另一种结构而且不同版本之间字段还变来变去解析逻辑要写一堆分支。第三多模态和其他能力的接入方式不统一。图片输入在 chat 消息里用image_url文件搜索、代码解释器这类工具只能靠外挂参数功能一旦多起来接口就变得又臭又长。第四结构化输出方案分裂。JSON mode、function call、response_format 三套东西职责重叠经常让人不知道用哪个。第五可观测性弱。很多服务返回的usage字段不一致有的甚至不返回finish_reason在不同场景下含义也不同排错时很痛苦。第六Assistant 功能与 Chat Completions 不是一套体系。官方后来做的 Threads、Runs、Messages 虽然在 Assistant API 里解决了部分状态问题但它跟 Chat Completions 是两个完全不同的入口开发者要在两种模型之间反复横跳。第七引用和解释来源的能力缺失。如果模型使用了网页搜索或文件检索返回消息里没有一个标准字段告诉用户这句话来自哪个链接、哪份文档只能靠非标准扩展字段来传递。这些痛点单独看都不致命但堆在一起就说明旧接口已经不是为 Agent 时代设计的了。2.2 Responses API 的核心设计一个会话式的统一入口2024 年 10 月OpenAI 发布了 Responses API官方文档里直接把它定位为在未来的版本中取代 Chat Completions。它把很多原来分散的概念集中到了一个接口POST /v1/responses。新接口的核心数据模型变成了response。你输入的不再是messages而是input和instructions。input可以是一个字符串也可以是一个消息对象数组数组中允许出现user、assistant、function_call、function_call_output等多种 entry 类型。这就有意思了它把工具调用的过程和结果也当成了输入的一部分而不是像旧接口那样只能靠拼历史消息来模拟。新接口还引入了prior_response_id参数。你只要把上一次返回的 response id 传进来服务端就能识别这是同一段对话的后续不需要每轮都重发全部历史。这是从无状态API向有状态运行时迈出的一大步对长对话和 Agent 场景特别有用。返回的 response 对象里有一个output数组里面包含message、reasoning、function_call、web_search_call等不同类型的 item。每个 item 都有稳定的id方便你在应用层追踪状态。另外Responses API 把reasoning单独拎了出来。无论是 o 系列模型还是开启了思维链配置的模型推理过程统一放在output里的reasoning对象中不再跟正文混在一起也不需要在流式事件里猜来猜去。内置工具也做了收敛文件搜索、网页搜索、代码解释器、计算机操作这些都以工具定义的形式出现参数表达比旧接口更整洁。说到底Responses API 看起来不是把聊天接口升级了一下而是把 OpenAI 当初在 Assistant API 里验证过的新概念收敛成一个统一入口。它更接近一个 Agent 运行时的协议有状态、有引用、有工具生命周期。3. 开源兼容真相大家都在兼容什么、怎么兼容3.1 为什么开源项目都拿 OpenAI 规范当事实标准你去看现在市面上任何一个开源推理框架首页大概率写着OpenAI-compatible API。这个现象背后是典型的生态网络效应。开发者用openaiPython 包写出来的代码能跑在 OpenAI 云服务上也能跑在本地 vLLM 上迁移成本极低企业采购和私有化部署也喜欢兼容 OpenAI这个卖点因为它意味着内部已有的 SDK、监控、工具链不用换。但兼容 OpenAI API这句话需要拆开看。很多项目只是兼容了 HTTP 路径和参数名比如/v1/chat/completions能通更进一步的项目会保证响应结构基本一致至少choices[0].message.content能被客户端解析再进一步才谈得上语义兼容比如temperature对采样 distribution 的影响、tools的调用时机、stream_options的 include_usage 是否真的返回 usage。绝大多数开源项目停留在前两层真正语义兼容的并不多。不是开源社区不努力而是 OpenAI 的规范更新太快。今天加一个字段明天改一个枚举值后天又调了流式事件结构。开源项目要跟着改还得保证向后兼容那只能选择一种更稳妥的做法只实现一个最小子集把复杂功能挡在外面。这也就注定了开源兼容永远慢半拍。3.2 兼容层的典型实现方式兼容层按实现位置分大致有三类。第一类是纯代理转发比如各种一key托管的网关把 OpenAI 的请求格式原样转发给上游模型服务响应也原样传回。这类实现的优点是无侵入缺点是只在格式上兼容模型能力不匹配时就会出错。第二类是 SDK 层重写比如 FastChat 这类早期项目自己实现了一套 OpenAI 风格的 HTTP 接口内部再映射到模型的生成逻辑好处是可以在路由层做很多自定义处理坏处是需要持续跟进官方字段的变动。第三类是服务端转换比如 vLLM 虽然底层自己定义了 tokenizer 和 sampling 流程但在api.py里实现了 OpenAI 协议的路由把 chat 请求解析成内部采样参数再把生成结果包装成 OpenAI 响应格式。我自己在维护一个内部模型网关最深的体会是实现一个能跑的/v1/chat/completions不难难的是把字段含义对齐。你传max_tokens模型服务有没有真的限制输出长度你传stop数组底层的 tokenizer 是否按照 stop string 来终止你传tools模型有没有经过工具调用的指令微调如果没有它可能只是输出一段空泛的 JSON而不是真正的工具调用。这些差异在单测里看不出来一压测就露馅。所以很多网关项目在文档里会写best-effort compatibility——尽力兼容不保证完全一致。3.3 Responses API 的开源现状跟进的人不多因为涉及会话状态Responses API 发布之后开源项目的跟进速度明显比当年 Chat Completions 慢得多。到今天我查了不少主流框架vLLM 和 SGLang 仍然没有把responses作为一等公民接口。少数项目做了转发兼容前端拿到/v1/responses请求翻译成/v1/chat/completions再往下游发回来后重新包装成 response 对象。为什么这么慢因为 Responses API 的核心是会话状态。Chat Completions 是纯无状态的请求来了算一下返回完就结束。Responses 需要服务端记住prior_response_id对应的历史上下文、工具调用状态、文件引用关系这对无状态的推理服务来说是一个架构级的转变。很多开源框架被设计成无状态的水平扩展服务前面挂负载均衡请求落在任意一个节点都能处理。要支持prior_response_id要么把状态存到 Redis 这类外部存储要么让客户端把历史完整传上来那又回到了无状态的老路。所以你会看到一个很有意思的现象很多项目声称支持 Responses API但实际上只是把input数组翻译成messages把prior_response_id忽略掉把response.output_text从choices[0].message.content里取出来。这种兼容属于看起来兼容如果用到了多轮续传、引用溯源、工具执行历史它就会直接失效。这既是技术选择也是商业判断开源项目资源有限与其追一个生态还没长大、语义又复杂的接口不如先稳住 Chat Completions 这个基本盘。4. 实操从 Chat Completions 迁移到 Responses 的几个关键点4.1 同一个请求新旧接口到底差在哪我拿一个最简单的例子来对比。假设我们要让模型解释一个名词用旧接口写 Python大概是这个感觉from openai import OpenAI client OpenAI() response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是一个简洁的助手。}, {role: user, content: 什么是接口兼容} ] ) print(response.choices[0].message.content)换成 Responses API写法变成response client.responses.create( modelgpt-4o-mini, instructions你是一个简洁的助手。, input什么是接口兼容 ) print(response.output_text)看起来差别不大结构上却完全不同messages被拆成了instructions和input返回内容也不再藏在choices[0].message.content里而是直接暴露了output_text这个便捷字段。如果你的应用里用了response.choices[0].finish_reason新接口里你要去response.output数组里找最后一个 item 的status或者用response.usage中的output_tokens来辅助判断。如果你要传多轮对话历史旧接口是这么写的messages[ {role: user, content: 帮我记住一个数字42}, {role: assistant, content: 好的我记住了。}, {role: user, content: 我刚刚让你记住的数字是多少} ]新接口推荐的做法是response client.responses.create( modelgpt-4o-mini, input[ {role: user, content: 帮我记住一个数字42}, {role: assistant, content: 好的我记住了。}, {role: user, content: 我刚刚让你记住的数字是多少} ] )如果你已经有上一轮的 response id还能这样first_response client.responses.create( modelgpt-4o-mini, input帮我记住一个数字42 ) second_response client.responses.create( modelgpt-4o-mini, prior_response_idfirst_response.id, input我刚刚让你记住的数字是多少 )注意当使用prior_response_id时不要再把完整历史放进input否则服务端可能会因为上下文冲突而报错。我实测下来OpenAI 官方 SDK 会在这种情况下抛invalid_request_error提示不能同时提供prior_response_id和历史消息。4.2 参数映射与迁移要点从实际迁移的角度我整理了一张参数对照表不一定完全覆盖所有功能但高频使用的场景基本都在里面场景Chat CompletionsResponses API模型名modelmodel系统提示词messages[rolesystem]instructions用户输入messages[roleuser]input字符串或消息数组输出长度上限max_tokensmax_output_tokens采样温度temperaturetemperature注意部分推理模型不支持工具定义toolstools工具选择策略tool_choicetool_choice流式输出streamTruestreamTrue但事件类型不同多轮状态复用需自行拼历史prior_response_id结构化输出response_formattext.format或tools中的输出 schema停止标记stopstop会话级上下文不支持store、previous_response_id相关能力这里面最容易踩坑的是max_tokens和max_output_tokens。旧接口里max_tokens限制的是总输出 token 数新接口改成max_output_tokens之后语义更明确但如果你沿用旧字段名新版 SDK 会直接报 unrecognized parameter。另外temperature在某些推理类模型上是被忽略的你在 Responses API 里传了它不一定能生效需要先看模型文档。还有一个细节旧接口的response_format在 Responses API 里挪到了text下类似text: {format: {type: json_object}}不留意就会掉进 400 的坑。流式事件的变化更要小心。Chat Completions 的流式事件是choices[0].delta.content有一段吐一段。Responses API 的流式事件变成了response.output_text.delta并且多了response.created、response.output_item.added、response.output_text.done这类生命周期事件。如果你有自己的流式解析层迁移时不是改一个字段名那么简单而是要把事件驱动的逻辑整体重构一遍。4.3 实际迁移时建议的切换顺序如果你维护的是一个服务不建议一次性把线上流量全切过去。我建议分三步走。第一步先读透官方迁移文档把你代码里所有 OpenAI 调用点列出来标清楚哪些用了工具、哪些用了流式、哪些只做简单问答。第二步新写一个client.responses的调用分支在测试环境跑同一条 prompt把新旧响应的文本质量和工具调用结果做对比。模型本身没有变只是接口换了所以大部分场景下生成内容应该是一致的但如果有不一致往往出在instructions和messages里 system 消息的位置不同导致模型接收到的指令顺序有细微差别。第三步灰度切流用 10% 的流量验证稳定后再逐步放大。整个过程中响应日志里一定要带上接口版本号方便出问题时快速定位是旧链路还是新链路。5. 常见问题与排查技巧实录5.1 新接口实测最常遇到的几个问题我在迁移和做网关兼容的过程中整理了几个高频问题和对应的排查思路写下来供参考。第一个问题是 400 参数错误。常见原因是用max_tokens而不是max_output_tokens或者把system消息直接塞进了input数组。Responses API 的input数组支持的角色类型是user、assistant以及工具调用相关的类型不支持system。系统提示词只能放instructions。如果你的代码里已经习惯了通用的 roles 数组这里很容易翻车。第二个问题是流式解析乱掉。旧客户端的流式处理器不认识新事件类型常见的报错是AttributeError: ChatCompletionChunk object has no attribute output_text或者反过来。解决思路是统一抽象一层事件适配器把chat.completions和responses的事件都转成你自己的内部消息格式而不是让上层逻辑直接依赖某个 SDK 的类型。第三个问题是工具调用的id结构差异。Chat Completions 里工具调用的 id 在message.tool_calls[0].idResponses API 里工具调用的 id 在output数组里某个function_callitem 的call_id而且执行完工具之后你需要把结果回传给下一次请求回传的格式也得按照function_call_output来组织。我在网关层直接按照 OpenAI 新接口的 schema 转发发现很多内部模块还停留在旧的tool_calls解析逻辑这块要单独做一层映射。第四个问题是prior_response_id与input历史同时使用导致的冲突。正如前面说的你不能既传prior_response_id又传完整历史服务端会认为上下文重复。如果要用状态续传就把历史交给服务端去管理客户端只负责把prior_response_id和新的增量输入传给 API。第五个问题来自最近的网络求助热词missing optional dependency openai/codex-win32-x64. reinstall codex: npm in...。这是安装 OpenAI Codex CLI 时遇到的依赖问题原因是 npm 包在 Windows x64 平台下有对应的 optional dependency 没装上最常见是网络或镜像源问题导致 optional 包没下全。解决办法是先删掉node_modules和package-lock.json然后执行npm install --no-optional再手动安装对应的平台包或者切换 npm 镜像后重新执行npm install。如果在 Linux/macOS 上遇到类似问题也用同样的思路排查。5.2 迁移前先想清楚的三件事很多人一听说新接口出来了就急着把项目全量迁移我建议先做三个判断。第一你的应用是否依赖多轮状态和工具长流程如果只是一次性文本生成或者简单的聊天补全Chat Completions 依旧稳定没必要折腾。第二你的中间层是否支持流式事件适配如果你有一个自研的流式网关迁移成本主要在这里而不是在模型调用点。第三你的开源底座是否真的支持 Responses 的语义如果你的生产环境后面接的是 vLLM 或者 Ollama而它们还没有原生支持prior_response_id那前端切到 Responses 可能只是表面工作实际走的还是 chat 逻辑。我比较推荐的路线是新项目直接基于 Responses API 开发老项目保持 Chat Completions等你的模型服务端真正支持了会话状态再逐步把老项目迁移过去。不要为了追求 API 版本新而给自己制造无谓的兼容负担。5.3 开源兼容层如何应对 Responses 带来的挑战如果你维护的是开源网关或推理框架建议尽早把 Responses 的协议解析和转换模块做成插件化结构。核心做法是把 OpenAI 的协议层和你的模型服务层彻底解耦。协议层负责解析input、instructions、prior_response_id、output数组、流式事件模型服务层只负责拿到一个结构化的 prompt 或消息列表返回内部采样结果。中间通过一个内部的消息总线来交互这样未来官方再改字段只需要动协议层的映射不用重写推理逻辑。另外Responses API 引入了function_call_output这类需要跨请求保存的状态开源网关可以考虑在后端增加一个简单的 KV 存储接口用来暂存 response 的 item id 和对应的工具调用结果。如果不想引入外部存储也可以用模型服务本身的缓存机制实现短期状态但要注意多节点部署时的会话亲和性。这又是一个不小的工程改动所以短期之内我判断多数开源项目还会停留在翻译转发的阶段。这正好留出了生态空间谁先把状态兼容做好谁就能在这一轮的接口规范切换中吃到红利。6. 最后说几句实话从 Completions 到 Chat Completions再到 Responses每一次接口演进都不是简单的版本号升级而是把模型服务的定位从文本补全工具一步步推向Agent 运行时。我实际迁移之后的感受是Responses 的设计方向是对的但它目前还远称不上完善。很多地方仍在快速迭代比如 reasoning 的流式事件、内置工具的参数格式官方文档更新得很快社区里的最佳实践还不成熟。如果你正要开始一个新的 LLM 应用项目我的建议是把 API 调用层再加一层薄薄的抽象比如自己定义一个ChatService接口内部根据配置决定走client.responses还是client.chat.completions。不要让你的业务代码直接依赖任何一个 SDK 的返回类型。这不是墙头草而是应对这个时代最稳妥的做法。相信我这一年内模型的接口可能还会再变而你的业务代码不该因为接口变化就跟着重写一遍。
阅读完成 · 觉得有帮助?
咨询建站