
1. 接口换代这件事为什么值得每个调用方停下来看一眼如果你最近半年动过 OpenAI 相关的代码大概率会有一种文档怎么又变了的错愕感。前两年大家写接口调用脑子里默认的模板就是POST /v1/chat/completions传一个messages数组拿一个choices[0].message.content收工。这套心智模型太稳了稳到很多人把它当成了OpenAI 接口的同义词。但从 Responses 这套新规范开始铺开之后情况变了官方主推的调用形态、返回结构、工具调用方式、状态管理思路都在往另一个方向走。而与此同时市面上大量OpenAI 兼容的开源服务、本地推理框架、第三方网关还停留在 Completions 的语义上。于是就出现了一个非常现实的撕裂——你按新文档写的代码打到兼容端点上可能直接给你一个 502或者返回一个你根本没见过的字段结构。这篇东西不是官方文档的翻译也不是新特性一览。我想聊的是Completions 和 Responses 这两套规范到底差在哪、为什么会这么演进、开源生态的兼容到底兼容到了什么程度、以及你在实际项目里该怎么选、怎么迁、怎么避坑。适合正在做 AI 应用后端、正在接第三方模型服务、或者正在维护一套多模型统一网关的开发者看。哪怕你只是偶尔调一下 API理解这套差异也能帮你少踩几个明明参数没错却报错的坑。先说结论性的判断后面再展开Completions 是无状态的一次性问答Responses 是有状态的多轮交互 内置工具编排。这不是简单的字段改名而是交互范式的切换。理解了这一点后面所有的兼容问题、报错、迁移成本都能找到根因。2. Completions 的极简心智模型以及它为什么撑不住了2.1 一次请求就是一次完整对话Completions 的设计哲学非常朴素你把到目前为止的整个对话历史全部塞进messages数组里发过去服务端算完把这一轮的回复还给你。服务端不记得你是谁不记得你上一轮说了什么所有的记忆都在客户端手里。这就是典型的无状态设计。这种设计的好处是简单、可预测、易缓存、易水平扩展。你甚至可以把整个请求体当成一个纯函数输入相同输出在 temperature0 时基本一致。对于早期那种一问一答的聊天机器人、文本补全、简单分类任务这套模型完全够用。但问题也随之而来。当你想做多轮对话时客户端必须自己维护一个不断增长的messages数组每一轮都要把历史全量重发。对话越长token 消耗越大而且你没法让服务端帮你做任何跨轮次的事情。2.2 工具调用是外挂上去的Completions 时代的 function calling本质上是把工具描述塞进请求模型返回一个我想调用某个函数的结构然后由你的客户端去执行再把结果作为一条新消息塞回下一轮请求。整个循环是客户端驱动的。这意味着什么意味着模型自己不会记住它调用过什么工具、拿到过什么结果。每一次工具调用都是一次全新的、需要你把上下文重新拼好的请求。对于简单场景没问题但一旦涉及多步推理、多工具串联、需要中间状态的任务客户端代码就会迅速膨胀成一坨状态机。2.3 流式输出的语义比较粗糙Completions 的流式返回是 SSE每个 chunk 里是一个 delta。你能拿到正在生成的文本片段但拿不到更细粒度的语义事件——比如模型开始调用工具了工具执行完了这一轮推理结束了。这些信息在 Completions 里要么没有要么得靠你自己解析文本去猜。所以当应用场景从聊天扩展到智能体Agent时Completions 这套模型就开始力不从心了。它不是设计得不好而是它诞生的年代大家还没想清楚模型作为编排中枢这件事该怎么落地。3. Responses 到底改了什么从问答到会话对象3.1 服务端开始帮你记状态Responses 最核心的变化是引入了会话状态的服务端管理。你不再需要每一轮都把完整历史重发而是可以通过previous_response_id之类的机制让服务端把上下文串起来。这带来的直接好处是客户端代码变薄了token 浪费少了多轮交互的语义也更清晰了。打个比方Completions 像是每次打电话都要把之前聊过的所有内容重新复述一遍Responses 则像是有一个会话记录你只需要说接着上面聊对方就知道上下文。这个类比不严谨但能帮你快速建立直觉。3.2 输出结构从文本变成事件流Responses 的返回不再是一个扁平的choices数组而是一组有类型的输出项output items。文本是一类工具调用是一类推理过程可能又是一类。每一类都有自己的结构你可以按类型去消费而不是从一大段文本里正则抠。这对做 Agent 的人来说是质变。以前你要判断模型是不是想调工具得看它返回的文本里有没有特定的 JSON 结构现在它直接告诉你这是一个工具调用项语义明确解析稳定。3.3 内置工具与编排能力Responses 把一些常见的工具能力比如代码执行、文件检索、网页搜索这类做成了内置工具模型可以在服务端直接调用而不需要你的客户端去实现整个执行循环。这大幅降低了构建复杂 Agent 的门槛。当然代价是灵活性——内置工具的行为你控制不了那么细。但对于大量我就想让模型查个东西然后回答的场景内置工具省掉的工作量是巨大的。3.4 一张表看清两套规范的核心差异维度CompletionsResponses状态管理无状态客户端维护历史服务端可维护会话状态请求核心字段messages数组输入项 会话引用返回结构choices[].message有类型的 output items工具调用客户端驱动循环支持服务端内置工具编排流式语义文本 delta 为主细粒度语义事件适用场景简单问答、补全、分类多轮 Agent、复杂编排这张表建议你存下来。后面遇到任何这个接口到底该用哪个的纠结对照一下就有答案了。4. OpenAI 兼容这四个字含金量到底有多少4.1 兼容的是路径还是语义这是整个开源生态里最容易被误解的地方。很多项目在 README 里写OpenAI compatible你兴冲冲地把base_url一改结果发现简单的 chat 能用一上工具调用就崩一上流式就乱一上新的 Responses 端点直接 404 或者 502。原因很简单大部分兼容兼容的是 Completions 的请求/响应形状而不是 Responses 的语义。它们实现了/v1/chat/completions这个路径字段名对得上返回结构长得像于是就叫兼容了。但 Responses 那套有状态、有类型事件、有内置工具的模型绝大多数开源实现根本没跟上。4.2 502 和 404 背后的真实原因你可能会遇到类似这样的报错请求打到某个本地或第三方的/v1/responses端点返回unexpected status 502 bad gateway。很多人第一反应是服务挂了其实更常见的情况是这个端点压根没实现网关把请求转发到了一个不存在的上游或者上游不认识这个路径于是返回了错误。还有一种情况是路径存在但语义不匹配。比如你按 Responses 的格式发了请求但服务端只认 Completions 的字段它解析失败可能返回 400也可能因为内部异常返回 5xx。所以看到 502先别急着怀疑网络先确认你打的那个端点到底实现了哪套规范。4.3 兼容层的三种典型实现深度我把市面上常见的兼容实现分成三档你可以对照自己用的服务判断第一档路径兼容。只实现了/v1/chat/completions字段基本对齐工具调用可能只支持最基础的格式流式能用但事件粒度粗。这是大多数。第二档语义兼容。不仅路径对工具调用的循环、流式的 delta 语义、错误码都尽量对齐能跑通大部分 Completions 场景。少数做得好的开源网关在这一档。第三档规范兼容。同时实现了 Completions 和 Responses 两套且 Responses 的状态管理、输出项类型、内置工具都有对应实现。这一档目前很少且往往只覆盖部分能力。理解这三档你就能预判换一个base_url之后哪些功能会挂。我的经验是只要你的应用用到了工具调用或复杂流式换端点前一定要先跑一遍针对性的冒烟测试别信 README 里那句fully compatible。5. 迁移这件事怎么迁才不翻车5.1 先判断你到底需不需要迁不是所有项目都值得迁到 Responses。如果你的应用就是简单的单轮问答、文本分类、内容生成Completions 完全够用而且生态成熟、兼容面广、踩坑资料多。为了用新东西而迁移是最不划算的技术决策之一。真正需要认真考虑 Responses 的场景通常有这几个特征多轮交互且历史很长、需要模型自主编排多个工具、需要细粒度的流式事件来驱动 UI、需要服务端帮你管理会话状态。如果你的场景命中了两条以上迁移的收益才明显。5.2 抽象一层别把规范写死在业务里不管你迁不迁我都强烈建议在业务代码和具体接口规范之间加一层薄薄的适配层。业务层只依赖你自己定义的对话工具调用流式事件这些概念具体走 Completions 还是 Responses由适配层决定。这样做的好处是当你想换端点、换规范、甚至同时支持两套时改动被限制在适配层里业务代码一行不动。我见过太多项目把choices[0].message.content这种取值逻辑散落在几十个文件里一旦规范变了改到怀疑人生。5.3 迁移的实操顺序如果你决定迁建议按这个顺序来风险最低先并行。新代码走 Responses老代码继续走 Completions两套并存用配置开关切换。先迁读再迁写。先把返回解析改成能同时吃两种结构确认没问题再改请求构造。工具调用单独测。这是差异最大的部分一定要有独立的测试用例覆盖多步工具调用。流式单独测。事件粒度不同UI 层如果依赖具体事件类型要重点验证。灰度放量。别一次性全切按流量比例慢慢放观察错误率和延迟。提示迁移期间一定要保留回滚开关。规范迁移最怕的就是切过去发现某个边缘场景挂了又切不回来。5.4 一个容易忽略的坑token 计费口径变了Completions 和 Responses 在 token 统计上不完全一致尤其是涉及工具调用、推理过程、会话复用时。如果你有成本监控或配额系统迁移后一定要重新校准计费逻辑否则可能出现账单对不上的情况。这个坑不致命但很烦。6. 那些热搜词背后藏着真实的踩坑现场6.1 unexpected status 502 这类报错的排查链路前面提过 502这里给一条完整的排查链路你可以照着走第一步确认端点是否存在。用最简单的 GET 或一个最小请求打过去看是 404 还是 502。404 说明路径没实现502 说明网关层出了问题。第二步确认请求体格式。把请求体换成对应规范的最小合法格式排除是字段不匹配导致的内部异常。第三步看网关日志。如果是自建网关日志里通常能看到上游返回了什么。502 往往是上游连接失败或超时。第四步确认上游服务是否真的支持该规范。很多兼容服务只支持 Completions你打 Responses 路径它内部转发失败就给你 502。这条链路的核心思路是先分清是路径不存在还是语义不匹配还是上游真挂了三者处理方式完全不同。6.2 本地开发环境的路径陷阱热搜里出现过http://127.0.0.1:15721/v1/responses这种本地地址。本地调试时特别容易踩的坑是你以为本地服务实现了 Responses其实它只是把/v1/responses转发到了某个只认 Completions 的后端。表现就是简单请求能过复杂请求报错。我的建议是本地调试时先用 curl 或 Postman 手动打一次最小请求确认端点行为再写代码。别一上来就集成到应用里出了问题分不清是应用的问题还是端点的问题。6.3 命令行工具与 IDE 插件的配置坑热搜里还有cline openai compatible 配置、npm install -g openai/codex这类词。这类工具通常需要你填base_url和api_key。最常见的坑是base_url 要不要带/v1。不同工具要求不一样有的要你填到根有的要填到/v1填错了就是 404。模型名对不对。兼容服务往往只支持特定模型名你填了个它不认识的直接报错。工具调用格式。IDE 插件类工具大量依赖工具调用如果兼容服务对工具调用的支持不完整插件会表现得时好时坏。配置这类工具时我的习惯是先用官方文档给的最小配置跑通再逐项加自定义。一次性把所有配置填满出问题根本不知道是哪一项导致的。6.4 注册、API Key 这类前置问题热搜里openai注册、openai api key、openai的api key获取方法这些词高频出现说明大量新手卡在第一步。这块我不展开具体流程各平台政策会变但给一个通用建议API Key 一定要放在环境变量或密钥管理服务里绝对不要硬编码进代码、更不要提交到仓库。我见过太多因为 key 泄露导致账单爆炸的案例这个坑的代价是真金白银。7. 面向未来的选型建议别赌单一规范7.1 双规范支持会是常态从趋势看Completions 不会立刻消失Responses 也不会一夜之间统一天下。未来一段时间同时支持两套规范会是主流服务的标配。你的应用架构也应该为这种双轨制做好准备而不是把宝押在其中一套上。具体做法就是前面说的适配层。适配层内部可以维护两套实现对外暴露统一的接口。这样无论上游怎么变你的业务都是稳的。7.2 关注语义兼容而非路径兼容选第三方服务或开源框架时别只看它有没有/v1/chat/completions这个路径。要看它的工具调用循环是否完整、流式事件是否细粒度、错误码是否规范、状态管理是否支持。这些才是决定你能不能长期用下去的关键。一个简单的判断方法拿一个涉及多步工具调用的真实场景去测。能跑通的基本靠谱跑不通或者行为诡异的趁早换。7.3 把可替换性当成架构目标AI 接口这块变化太快今天的主流可能明年就边缘化了。所以架构上要追求可替换性换模型、换服务商、换规范成本都应该可控。这不是过度设计而是这个领域的现实要求。我自己的做法是所有和模型交互的地方都走一个内部 SDK业务代码只调这个 SDK。SDK 内部怎么适配外部规范业务不关心。这样每次上游变动我只需要改 SDK 一处。8. 我在实际项目里踩过的几个具体坑说几个真实的、文档里不会写的细节。第一个坑流式响应的结束事件不一致。Completions 的流式通常以[DONE]结束Responses 的事件流结束方式不同。如果你的客户端只认[DONE]迁到 Responses 后会一直等不到结束表现为卡住。解决办法是显式处理两种结束信号。第二个坑工具调用的参数是流式拼接的。在流式模式下工具调用的参数可能分多个 chunk 到达你需要自己拼接完整再解析。很多人第一次做流式工具调用直接拿第一个 chunk 去 JSON.parse必然失败。第三个坑错误信息的结构不同。Completions 和 Responses 的错误返回结构不完全一样如果你的错误处理逻辑写死了某一种另一种就会解析失败导致报错了但看不到原因。建议错误处理也做兼容。第四个坑超时设置。Responses 因为涉及服务端编排和内置工具单次请求耗时可能比 Completions 长不少。如果你沿用 Completions 时代的超时设置很容易误判为超时失败。迁移后记得重新评估超时阈值。第五个坑并发与会话状态。一旦用了服务端会话状态就要考虑并发场景下会话是否会串。同一个会话 ID 被两个请求同时使用行为可能不符合预期。这块要仔细设计。9. 给不同阶段开发者的落地建议如果你是刚入门先用 Completions 把基本调用、流式、工具调用跑通建立直觉。别一上来就啃 Responses概念太多容易劝退。如果你是正在做 Agent 应用认真评估 Responses 的内置工具和状态管理能帮你省多少事。如果省得多就迁如果你的编排逻辑很特殊内置工具反而限制你那就继续用 Completions 自己搭。如果你是在维护多模型网关双规范支持是绕不开的。早点把适配层设计好比后期打补丁强得多。如果你是在选第三方服务把语义兼容程度作为核心评估项别被OpenAI compatible这几个字忽悠。这套规范演进还远没到终局后面大概率还会有新的形态出现。与其追着每个新特性跑不如把架构的可替换性做扎实。规范会变但好的抽象层能让你以不变应万变。我在几个项目里反复验证下来最省心的做法永远是业务不碰规范规范关在适配层里。这句话听起来像废话但真正做到的项目少之又少。