ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenAI API演进:从Completions到Responses的兼容真相与迁移指南

OpenAI API演进:从Completions到Responses的兼容真相与迁移指南 最近好几个项目团队都来问我同一个问题OpenAI 的接口规范演进到了一个新阶段从最早的 Completions 到统治生态的 Chat Completions再到现在的 Responses API到底要不要跟着切开源社区说的OpenAI 兼容到底兼容的是哪一层为什么明明照着官方文档写还是会撞上unexpected endpoint or method (post /chat/completions)这种报错这篇文章我就把自己实际迁移、排查、对接开源网关的经验完整梳理一遍。不吹不黑从协议演进逻辑讲到底层兼容真相再给出可以直接抄走的切换方案和避坑清单。适合正在做 LLM 应用、维护 API 网关、或者本地跑推理服务的工程师参考。1. 为什么要从 Completions 演进到 Responses1.1 Completions 时代的底层逻辑早期的 OpenAI 接口规范演进其实非常直白就是/v1/completions一个端点打天下。你传一个prompt模型给你续写出一段纯文本。请求体大概长这样model、prompt、max_tokens、temperature、top_p、n、stop、logprobs返回结构是choices[].text。这套设计在 GPT-3 时代是合理的因为那时候产品形态就是输入一段话输出一段文本。但问题也很明显它没有角色概念没有 system、user、assistant 的区分。你要做多轮对话就得自己在业务层把历史消息拼接成一个超长 prompt 塞进去。拼接本身倒还好真正麻烦的是截断和上下文管理——你永远要自己数 token小心翼翼地保留最近的若干轮对话生怕把系统指令挤出上下文窗口。用一句话概括它像一个没有记忆的输入法只负责把光标后面的内容接下去至于前面聊过什么模型并不知道。还有一个隐性成本因为没有统一的消息结构每个接入方拼 prompt 的方式都不同有人用Q: ... A: ...分隔有人用Human: ... Assistant: ...分隔。这种混乱直接导致同一批模型在不同产品里的效果参差不齐调优经验也无法沉淀。所以当 OpenAI 决定拥抱对话场景时接口层必须有一次彻底的重构。1.2 Chat Completions 的统治地位2023 年 3 月gpt-3.5-turbo发布同时带来了/v1/chat/completions。这个接口最核心的资产是messages数组system、user、assistant三种角色由服务端统一处理多轮对话的上下文格式。开发者不需要再手动拼接历史了直接把消息列表扔给接口token 管理、角色区分这类脏活全都收归服务端。这个设计的统治力强到超乎想象。Chat Completions 发布之后几乎所有开源推理框架都把OpenAI 兼容默认实现为/v1/chat/completions。vLLM、Ollama、LM Studio、llama.cpp 的服务端清一色优先对齐这个端点。原因很简单这是需求最密集、调用最频繁、生态里真实流量最大的协议。于是出现了一个很有意思的现象很多开发者现在提到OpenAI 接口脑子里第一反应就是/chat/completions。他们直接跳过了 Completions 时代以为 OpenAI 接口天生就是 messages 数组这套格式。这个潜意识的默认恰恰是后面理解 Responses API 时容易绕弯的原因——你以为接口演进是技术层面的优化实际上它是产品形态和生态风向的写照。1.3 Responses API 想解决什么问题2024 年 OpenAI DevDay 上Responses API 正式亮相端点变成/v1/responses。我第一反应是命名变了是不是只是把 messages 改成了别的字段真去读文档才发现它的野心比我想的大得多。Chat Completions 虽然解决了对话格式统一的问题但只覆盖了对话这个最基础的产品形态。一旦你开始做 Agent问题就来了工具调用需要两段式协作先让模型输出tool_calls业务层执行工具再把结果拼回消息列表发第二轮一次完整的多轮工具调用流程可能要在客户端维护一个不断膨胀的上下文。代码写起来繁琐还是小事真正麻烦的是上下文管理非常容易出错——函数结果拼错位置、消息顺序乱掉、截断策略写错任何一个低级失误都会让 Agent 行为变得不可控。Responses API 的思路是把 Agent 需要的通用能力直接收进接口层而不是让每个开发团队自己造轮子。它把工具调用做成了服务端原生的执行机制引入状态变量让多轮会话可以在接口层面串联同时内置了 web search、file search、code interpreter 这类开箱即用的能力。用我自己的话说它不再是一个补全端点而是一个Agent 运行时。这也是为什么这一节的标题强调想解决什么问题——它的目标用户不是写聊天机器人的团队而是写复杂工作流、多工具编排的团队。2. Responses 接口到底改了什么协议细节对比2.1 端点与请求体的关键差异先把最直观的差异列出来我直接对比两个端点的请求结构。维度Chat CompletionsResponses端点POST /v1/chat/completionsPOST /v1/responses对话输入messages数组input可以是字符串、消息数组或 item 数组角色字段system / user / assistant支持system / developer / user / assistantdeveloper用于更细粒度的指令分级工具声明tools独立字段function_call单独配置直接内联在tools数组中工具调用由服务端统一调度返回结构choices[].messageoutput数组内含多种类型的 item多轮状态客户端自己拼历史previous_response_id直接串联历史上下文这里要单独说一下input。很多想从 Chat Completions 迁过来的同学第一反应是把messages改名为input就完了这是最大的误区。Responses 的input支持三种形态一个普通字符串表示直接给我这段文本的响应一个简化消息数组表示多轮对话一个更完整的 item 数组允许你传message、function_call、function_call_output等更丰富的结构化内容。第三类 item 形态才是 Agent 场景下真正用得到的东西它让工具调用结果不再依赖笨拙的字符串拼接。2.2 响应结构与状态语义Chat Completions 的返回是一层choices包裹着message拿到message.content基本就够用了。Responses 的返回是output数组数组里的每个元素都有各自的typemessage表示自然语言回复function_call表示需要调用工具的请求reasoning表示推理过程的中间产物还有web_search、file_search、code_interpreter这类内置工具的触发结果。我刚开始看这个结构的时候觉得比原来复杂用久了反而觉得清晰——因为它把 Agent 生命周期里的所有事件统一成了响应项你可以像处理日志流一样遍历output按类型分发到不同的处理逻辑。官方 SDK 还给message类型提供了.output_text这种便捷属性直接拿到纯文本回复不需要自己去嵌套数组里挖内容这个细节对迁移体验的改善很大。另一个值得关注的语义升级是状态追踪。Chat Completions 时代多轮对话的上下文完全靠客户端组装服务端无状态Responses 引入了previous_response_id把前一轮响应的 ID 传给下一次请求服务端就能直接串联上下文。这意味着复杂的多轮 Agent 流程可以在接口层维护记忆客户端不需要每一轮都回传全部历史消息。设计上确实优雅但要注意只有使用官方 API 时才能享受这个特性任何本地模型和第三方兼容层大概率不会实现它。2.3 兼容层要理解的关键点Responses 不是字符串换数组我见过一些团队在规划网关改造时把 Responses 当成一次简单的字段映射messages换成inputmessage.content取output_text以为写个转换函数就完事。如果只做这些你实现的是看起来像 Responses 的薄壳不是真正的 Responses API。Responses API 的核心价值在服务端的行为编排工具调度的生命周期、内置搜索和代码执行、推理过程管理、上下文状态串联这些能力全部发生在 API 服务端。开源网关要做完整兼容意味着你不仅要把请求翻译过去还要实现背后那套调度逻辑。这也是为什么我判断真正值得投入的做法是在网关层做一个协议映射桥对外同时暴露/chat/completions和/responses两个端点内部把/responses请求翻译成若干次工具调用和消息传递而不是在字段层面做静态转换。3. 开源兼容的真相为什么大家都在兼容 Chat Completions而不是兼容 Responses3.1 一个让很多人懵掉的报错unexpected endpoint or methodPOST /chat/completions我在多个项目的部署现场都见过这个报错某个开源 CLI 工具或应用在连接自建服务时输出类似[error] unexpected endpoint or method. (post /chat/completions). returning 2紧接着程序退出。很多人的第一反应是OpenAI 又改了协议但实际查下来99% 的情况跟 OpenAI 无关是你连接的网关没实现这个路由。举个我排查过的例子某个团队把 OpenAI 兼容网关部署在内网客户端工具的 base_url 配的是网关根地址工具内部自动拼接路径/chat/completions但那个网关版本只实现了/v1/completions和/v1/embeddings根本没有/chat/completions这个路由所以服务端返回了 unexpected endpoint 错误。还有些情况是 base_url 写重复了比如本地服务监听路径是/v1/chat/completions客户端又把base_url配成了http://host/v1最终拼接出/v1/v1/chat/completions这种鬼路径同样触发这个报错。排查思路其实很简单第一步直接用 curl 打一次目标端点看真实返回内容第二步检查 base_url 的拼接规则确认没有重复路径段第三步确认请求头和认证信息符合服务端预期。先手动打出一次 200再去调客户端这是排查一切 OpenAI 兼容层连接问题的最有效路径。3.2 开源项目为什么总是慢半拍很多人问为什么 Responses API 都出来这么久了开源推理框架还没大力跟进我的看法是开源项目对接口的兼容从来不是按官方文档的重要性排序而是按真实流量的分布排序。绝大多数开源 LLM 应用框架在调用模型时走的都是/v1/chat/completions。比如 LangChain、LlamaIndex 这类中间件默认的模型适配器就是 Chat Completions主流 Agent 框架虽然支持工具调用底层也是通过 messages 数组模拟对话历史。对这些框架来说Responses API 是一个在本地推理场景里用不上、在远端 API 场景里依赖官方密钥的协议。本地推理框架没有动力去实现一个只有接入官方服务才能发挥全部能力的接口——服务端的工具调度和内置搜索是它们没法在本地复刻的东西。而且兼容一个新接口的成本被严重低估。你以为只是新增一个路由实际上要处理请求解析、流式输出格式、错误语义、字段映射、多类型 item 的序列化与反序列化还要为每个模型的行为差异写测试。对一个靠社区维护的项目来说这是一笔不小的投入。维护者更理性的选择是先把 Chat Completions 的流式、工具调用、各类模型的兼容性打磨到极致再观望 Responses API 的生态接受度。3.3 兼容真相的底层规律接口兼容与流量对齐如果你长期维护 API 网关类项目会发现一个规律所谓OpenAI 兼容本质上是一份与真实流量对齐的契约不是对官方文档的完整复刻。社区公认的OpenAI 兼容最小集通常是三件套/v1/models、/v1/chat/completions、/v1/embeddings。把这三个接口做稳定绝大多数开源应用就能跑起来。至于/v1/completions很多新项目干脆不实现了因为生态里已经没有新流量往那里去/v1/responses则被排在更长远的规划里等用户真的开始在自建服务上调用它维护者才会把它加上。这个规律对你的直接指导意义是如果你要为团队做技术选型不要只看某个网关宣称兼容 OpenAI要查它实际实现的是哪几个端点如果你自研网关建议按照真实流量分布来排优先级把 Chat Completions 和 embeddings 做到位Responses 作为增量能力预留扩展点。接口兼容不是做慈善而是做成本更低的生意。4. 迁移实操从 Chat Completions 切换到 Responses4.1 快速识别自己的代码依赖了哪些能力迁移之前先对自己的调用方式做一个分类诊断。我的判断清单大概是这样的项目只有简单的多轮聊天不涉及工具调用、不长上下文留在 Chat Completions 完全没问题它依然被官方完整支持迁移收益很小。项目涉及工具调用和 Agent 编排但代码量不大上下文管理还能控制住可以选择迁移到 Responses享受服务端的工具调度和状态串联。项目重度依赖 web search、file search、代码解释器这类内置能力应该直接切 Responses这些能力在 Chat Completions 里没有原生支持自己实现性价比极低。项目跑在本地模型或第三方兼容层上别急着切 Responses先确认你的服务端真的实现了这个端点否则就是给自己埋坑。我的建议是把当前代码里最疼的那个点作为迁移的决策依据。如果只是因为没有用过新接口就想切那通常说明不值得切如果是被工具调用的两段式协作烦透了那 Responses 的价值是实实在在的。4.2 两段可直接抄的代码示例下面用官方 Python SDK 展示最典型的写法差异先看旧的 Chat Completionsfrom openai import OpenAI client OpenAI() # 自动读取 OPENAI_API_KEY resp client.chat.completions.create( modelgpt-4o, messages[ {role: system, content: 你是资深的架构师}, {role: user, content: 对比一下 Completions 和 Responses 的差异}, ], ) print(resp.choices[0].message.content)再看对应的 Responses 写法from openai import OpenAI client OpenAI() resp client.responses.create( modelgpt-4o, input[ {role: developer, content: 你是资深的架构师}, {role: user, content: 对比一下 Completions 和 Responses 的差异}, ], ) print(resp.output_text)两段代码看起来都挺干净但背后的语义完全不同。第一个例子里的messages是客户端把历史消息全量提交第二个例子里的input可以是一个字符串也可以是一条消息数组还可以是包含工具调用结果的结构化 item 数组。resp.output_text更是省去了遍历output数组的步骤直接拿到最终的文本输出。切换初期先掌握这两个差异点就可以跑通 80% 的基础对话场景。4.3 迁移中的常见坑响应的解析、流式事件、重试语义迁移过程中最容易踩的坑集中在三个地方。第一个是响应解析。你在 Chat Completions 里一直用的choices[0].message.content在 Responses 里不存在了如果直接照搬会拿到None。正确做法是用 SDK 提供的output_text或者自己遍历output数组按item.type message过滤再取内容。第二个是流式接口的差异。Chat Completions 的流式事件名和数据增量格式跟 Responses 的流式格式完全不同。如果你在业务代码里硬解析流式事件迁移时必须同步更新解析逻辑。官方 SDK 的流式写法虽然也封装了不少但事件类型变了你的事件分发器还是要改。第三个是重试语义。Responses API 的某些内置工具调用发生在服务端耗时可能比普通的单轮对话长得多。按原来的超时和重试策略很容易把明明还在正常执行的请求误判为超时然后重复提交。我在实际项目中就把超时时间从 30 秒调到了 120 秒并且为previous_response_id串联的请求专门设计了去重逻辑这个问题才算解决。还有一个我强烈推荐的做法在网关层做双协议出口。对外保留/chat/completions给旧客户端新增/responses给新客户端两边走同一套调度逻辑。这个方案能大幅降低迁移风险至少不用在同一天逼所有应用全部切换。5. Codex 信号与工具链演进5.1 Codex CLI 与 ChatGPT 登录顺着接口演进这条线必然会看到 Codex 的出现。Codex 是 OpenAI 官方推出的命令行编码代理把对话 代码执行 文件修改集成到一个代理式的工作流里。首次使用时的引导就是welcome to codex然后让你sign in with ChatGPT完成授权。它不再是你 IDE 里的自动补全插件而是一个能直接跑在代码仓库旁边、自己读文件、自己执行命令的代理。我实际用下来的感受是它代表了接口规范演进的一个信号——OpenAI 已经不再满足于提供文本补全能力而是在提供完整的执行环境。这对开发者的意义在于你将来选型时不能只看模型的对话能力还要关注它背后的接口层和执行工具链是否是一体的。5.2 一个真实的安装坑missing optional dependency openai/codex-win32-x64我在 Windows 环境装 Codex 时踩过一个很典型的坑npm install过程中提示missing optional dependency openai/codex-win32-x64. reinstall codex: npm install。看字面意思就知道这是 npm 安装可选平台依赖时Win32 对应的平台二进制包没有成功拉取导致命令行工具无法运行。排查和解决并不复杂先查node_modules/openai目录里是不是缺了codex-win32-x64这个包然后清理 npm 缓存重新安装如果还不行就手动执行提示里的npm install openai/codex-win32-x64补装。这类问题在网络波动或镜像源同步不及时时很常见不用慌。这个坑也侧面反映了一个趋势OpenAI 正在把它的工具链打包成完整的桌面级分发物而不再只是一串 HTTP 接口。对开发者的建议是使用这类工具前先确认平台支持情况和安装网络的稳定性别等报错再查。5.3 从接口规范到工具链生态的启示把 Responses API 和 Codex 放在一起看逻辑就清楚了OpenAI 的接口规范演进本质是把模型能力封装层和面向开发者的执行环境绑定在一起。接口层面的抽象粒度越来越粗行为越来越多客户端要自己写的逻辑越来越少。这对我们选技术栈有一个明确的提醒以后选择网关或框架时别只看它支不支持 OpenAI 品牌要看它对新接口的跟进速度、对工具调用的支持程度、对流式协议的完整覆盖。OpenAI 兼容这个词正在变得不够用准确的说法应该是兼容到哪个协议版本、覆盖到哪些行为语义。6. 我的实战避坑清单与最终建议6.1 接口选型决策表直接给一张可以参考的决策表按场景选接口场景推荐接口理由纯多轮聊天、简单文本生成Chat Completions生态最成熟兼容性最好迁移收益小工具调用、Agent 编排Responses 或 Chat Completions functionsResponses 更省心但如果兼容层不支持就留在后者重度依赖 web search / file searchResponses这些能力在 Chat Completions 里没有原生实现本地模型、自建推理服务Chat Completions本地服务多优先兼容此端点Responses 容易踩空服务端状态串联、长上下文多轮Responsesprevious_response_id省去客户端维护历史的成本这张表的核心逻辑是不要因为新而选新要因为解决当前最疼的问题而选新。6.2 key 获取与调用环境的安全提醒接口无论怎么演进调用凭证的管理永远是绕不开的一环。API key 请直接从官方平台的安全页面创建不要在代码里硬编码更不要提交到 Git 仓库。我见过不少项目把 key 直接写在前端代码里等于把钱包密码贴在了门口。共享 key 和不明来源的中转 key 也建议敬而远之。你无法控制其在传输链路中是否被记录一旦出现异常消耗或安全事故责任很难说清。如果你所在区域的网络访问存在问题先确认自己的调用环境是否符合服务商的支持范围再用企业层面采购的合规网关或官方支持的服务入口而不是依赖灰色渠道。拿不到合规环境的时候最稳妥的选择是把服务部署在合规区域再通过内部网络调用。6.3 给开源维护者的一点建议如果你在维护推理服务网关或中间件我的建议是把 Chat Completions 的流式和工具调用稳定性当作基本盘这两个能力直接影响主流框架的接入体验Responses 的兼容则用协议翻译映射层来应对对外暴露新端点内部复用已有的消息处理和工具调度逻辑避免为单个新协议重写一套引擎。接口兼容的本质是服务端能力和客户端预期之间的契约。如果你想减少后续迭代的维护成本最值得做的事是把内部的请求语义抽象成一份统一的中间表示让新的后端协议只做适配层不碰核心逻辑。这样无论是接 Responses还是接未来可能出现的新协议成本都能控制在可接受范围内。最后说一点个人体会。我在实际项目中验证过一条稳妥的路径先让网关对外同时暴露chat/completions和responses两个端点内部通过映射层共享调度逻辑新项目直接走 Responses老项目继续走 Chat Completions两边并行等到所有流量都验证没问题再做全面切换。踩过unexpected endpoint or method那个报错之后我养成了一个习惯部署任何自称 OpenAI 兼容的服务第一件事就是拿 curl 打一个真实请求验证路由别信文档直接看返回状态码。希望这篇文章帮你避开我踩过的这些坑也帮你更清醒地判断接口演进的方向——协议总在变但底层需求永远是那三件事更少的客户端状态、更稳的工具调度、更完整的执行能力。
返回列表