news 2026/10/5 12:35:50

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenAI接口演进:从Chat Completions到Responses API迁移实战

最近团队在把内部的 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( model="gpt-4o-mini", messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "什么是接口兼容?"} ] ) print(response.choices[0].message.content)

换成 Responses API,写法变成:

response = client.responses.create( model="gpt-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( model="gpt-4o-mini", input=[ {"role": "user", "content": "帮我记住一个数字:42"}, {"role": "assistant", "content": "好的,我记住了。"}, {"role": "user", "content": "我刚刚让你记住的数字是多少?"} ] )

如果你已经有上一轮的 response id,还能这样:

first_response = client.responses.create( model="gpt-4o-mini", input="帮我记住一个数字:42" ) second_response = client.responses.create( model="gpt-4o-mini", prior_response_id=first_response.id, input="我刚刚让你记住的数字是多少?" )

注意,当使用prior_response_id时,不要再把完整历史放进input,否则服务端可能会因为上下文冲突而报错。我实测下来,OpenAI 官方 SDK 会在这种情况下抛invalid_request_error,提示不能同时提供prior_response_id和历史消息。

4.2 参数映射与迁移要点

从实际迁移的角度,我整理了一张参数对照表,不一定完全覆盖所有功能,但高频使用的场景基本都在里面:

场景Chat CompletionsResponses API
模型名modelmodel
系统提示词messages[role=system]instructions
用户输入messages[role=user]input字符串或消息数组
输出长度上限max_tokensmax_output_tokens
采样温度temperaturetemperature(注意部分推理模型不支持)
工具定义toolstools
工具选择策略tool_choicetool_choice
流式输出stream=Truestream=True,但事件类型不同
多轮状态复用需自行拼历史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].id,Responses 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 的返回类型。这不是墙头草,而是应对这个时代最稳妥的做法。相信我,这一年内模型的接口可能还会再变,而你的业务代码不该因为接口变化就跟着重写一遍。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 12:34:23

DeepSeek Harness 桌面端:安装、skill 部署与内网使用指南

DeepSeek Harness 出官方桌面端了,这个消息对我来说比等一款游戏发布还让人高兴。过去半年我一直在终端里调 skill、理工作流,每次都要打开好几个窗口,上下文一断就得重新来一遍。桌面端的出现,终于把 DeepSeek Harness 从"命…

作者头像 李华
网站建设 2026/10/5 12:33:20

AI编程从碰运气到工程化:Superpowers技能框架实战指南

1. 为什么“快”不等于“可靠”:AI编程的真实困境用AI写代码这件事,很多人第一反应是“快”。确实快,快到什么程度?一个CRUD接口,以前手敲半小时,现在提示词一贴,十秒钟出结果。但问题也恰恰出在…

作者头像 李华
网站建设 2026/10/5 12:30:10

芒果害虫检测数据集实战:VOC与YOLO双格式解析及YOLOv8训练指南

简介:采用Pascal VOC与YOLO双格式标注的芒果害虫检测数据集,内容覆盖10个常见害虫类别,包括象鼻虫、甲虫、蝗虫、粉蚧、蛾类、叶蜂、蛞蝓、茎蛀虫、黄蜂等,适用于目标检测模型训练与农业病虫害识别研究,尤其适合具备YO…

作者头像 李华
网站建设 2026/10/5 12:28:08

基于阻抗控制的工业机器人轨迹跟踪Simulink/Simscape仿真

项目标题是“基于阻抗控制的工业机器人轨迹跟踪系统 Simulink/Simscape 仿真”,这是我最近在仿真环境里反复折腾的一套东西。做机器人控制的工程师应该都有体会:轨迹跟踪如果不和环境交互,跑再漂亮的轨迹都是“纸面功夫”,一旦机械…

作者头像 李华
网站建设 2026/10/5 12:28:08

LG V20刷TWRP与Magisk获取root权限完整实战指南

手里这台LG V20是2017年入的港版双卡,当时看中的就是可换电池、副屏和那颗ES9218P四核DAC。这些年系统停在Android 8.0就没再升过,官方早不管了,可机器硬件还能打。这次趁着周末,我把它翻出来彻底收拾了一遍:换掉原厂r…

作者头像 李华
网站建设 2026/10/5 12:26:24

8MB小模型如何匹敌大模型?模型压缩三大核心技术解析

看到Cactus Needle 3这个参数时,我第一反应是怀疑自己少看了一个单位。8MB是什么概念?一张手机照片的三分之一,一个普通GIF动图的大小,甚至很多网页都比它重。而它要对比的是同代旗舰级大模型DeepSeek V4 Flash。这两个数字摆在一…

作者头像 李华