ARTICLE DETAIL

资讯详情

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

OpenAI接口演进:从Chat Completions到Responses API的迁移指南

OpenAI接口演进:从Chat Completions到Responses API的迁移指南 如果你的项目最近突然抛出unexpected endpoint or method (POST /chat/completions)这类报错先别急着怀疑代码写错了。大概率你是在不知不觉间从 Completions 那一代摸到了 Responses 这一代。OpenAI 的接口规范正在经历一次明显但很多人还没反应过来的演进从早期的文本补全到 Chat Completions 时代再到以 Agent 场景为核心的 Responses API。而“开源兼容”这四个字在过渡期里往往不是开箱即用而是需要自己动手缝补的真相。这篇文章我想把这条演进线完整梳理一遍为什么要从 Completions 迁到 Responses两个规范在请求、响应、多轮、工具调用上到底差在哪里以及最容易被忽略的——开源项目和新版 SDK 之间存在哪些兼容断层遇到unexpected endpoint、Codex 安装失败这类报错该怎么定位。如果你正在用 OpenAI API 做应用或者维护开源轮子又或者纠结要不要现在迁移这篇应该能帮你省不少排查时间。1. 为什么要从 Completions 迁到 Responses一次接口规范的范式调整1.1 Completions 时代的缝补史从“补全”到“对话”再到“工具”最早 OpenAI 对外提供的接口是POST /v1/completions客户端传入prompt模型返回一段completion本质是文本续写。那时候没有 system、user、assistant 的角色概念你给一段话它往后接一段话简单粗暴。后来 ChatGPT 火了OpenAI 才发现“对话”才是绝大多数应用的真正形态于是补了POST /v1/chat/completions引入了messages数组用 role 区分系统指令、用户输入和模型回复。问题在于这套体系是“缝补式”演进的。对话刚做出来时没考虑工具调用后来 Agent 概念兴起OpenAI 又往 Chat Completions 里塞了tools和tool_calls结构化输出需求来了又追加了response_format多轮对话的上下文太大又只能靠业务方自己裁剪历史消息。结果是接口本身变得越来越重但语义始终停留在“一次问答”的层面。开发者在聊天气泡之上自己搭状态机、自己维护历史、自己处理工具调用循环接口只是被当作一个文本生成器使用。这种模式不是不能跑只是越跑越别扭。1.2 Responses 接口的设计动机Agent 场景成了第一公民Responses API端点POST /v1/responses的定位从一开始就不是 Chat Completions 的简单改名而是把“完成任务”这件事作为核心语义。它把请求入口从messages改成了inputinput既可以直接传字符串也可以传结构化的消息列表多轮上下文用previous_response_id引用上一次响应避免每次都重放完整 history工具调用、推理过程、搜索结果都作为不同类型的输出事件放在output数组里客户端可以按需消费而不是每次都要自己解析choices[0].message.tool_calls。我用一个例子帮你建立直观感受。Chat Completions 时代一个带工具的多轮请求messages 里会堆满 system 指令、用户消息、带 tool_calls 的 assistant 消息、tool 角色的函数结果整个上下文越长越大到了 Responses你只需要把当前任务写在input里通过previous_response_id让服务端记住上一轮的状态上下文管理对业务方来说几乎透明。这就是为什么 OpenAI 要把 Codex 这类命令行编程代理直接构建在 Responses 上——因为编程代理不是“问一句答一句”而是“给一个任务循环调用工具、读取结果、修正计划直到完成”接口如果不在语义层支持这种循环上层实现就会非常痛苦。1.3 兼容层的真实定位新旧世界之间的“大坝”官方并没有立刻砍掉 Chat Completions新旧两个端点会并行存在很长一段时间。但这不代表它们等价。新版 SDK 里client.chat.completions.create()和client.responses.create()走的是两套完全不同的请求构造、响应解析和事件流格式。很多开源中转项目、网关、SDK 适配层在这段时间做的本质上是在中间加了一个“协议翻译层”——接收 Responses 格式的请求内部转换成 Chat Completions 发给上游或者反过来。这个翻译层就是“开源兼容的真相”它能把 80% 的常规请求翻译过去但流式事件、推理级别、结构化输出、多轮引用这类细节在不同实现里丢失程度完全不同。所以你会看到同一个应用用官方端点正常切到某些开源网关就报unexpected endpoint or method这不是模型的问题而是兼容层只做了一半。理解这层设计是判断“要不要迁移、迁移后有没有坑”的前提。2. 接口规范深度对比请求体、响应体与关键参数的差异2.1 端点与核心请求字段从 messages 到 input 的语义切换先看请求层面的对比。我用一张表格把高频差异列出来后面逐项解释。维度/v1/chat/completions/v1/responses请求入口messages 数组input可传字符串或消息数组系统指令messages 中 rolesystem 的消息独立字段 instructions多轮上下文重放完整 messages 历史previous_response_id 引用上一次响应结构化输出response_format 参数text.format 参数推理过程控制无统一参数reasoning.effortlow/medium/high工具调用tools tool_choicetools tool_choice但响应结构不同流式stream: true输出 chat.completion.chunkstream: true输出语义化事件序列最核心的变化是input替代了messages。过去你必须把 system、user、assistant 的历史消息全部拼好再发出去现在大多数场景下直接给一句任务描述就行。如果确实需要多轮结构化信息input依然接受消息数组字段格式和 Chat Completions 的 messages 接近但系统指令建议放到instructions里。这个拆分的意图很明确指令是稳定不变的运行配置输入是每次实际要处理的任务两者不该混在一条历史消息流里。2.2 响应结构变化从 choices[0].message 到 output 数组响应差异是迁移中体感最明显的地方。Chat Completions 的响应里核心内容是choices[0].messagecontent是文本tool_calls是可选数组如果启用了流式你要处理逐帧 chunk。Responses 的响应则是output数组数组里的每个元素都带type字段有的是message最终文本回复有的是function_call模型决定调用哪个工具有的是reasoning推理过程摘要还有搜索类调用之类的事件类型。看一段最简对比。旧写法取文本text resp.choices[0].message.content新写法取文本text resp.output_textoutput_text是 SDK 提供的便捷属性自动把 output 数组中所有 message 类型的文本内容拼接起来90% 的应用用这一个属性就够了。真正需要遍历 output 的场景是工具调用和流式处理这时候你要判断事件type分别处理。Chat Completions 时代工具调用结果埋在 message 的一个嵌套结构里Responses 时代function_call 是顶层事件读取路径反而变短了。2.3 工具调用与多轮会话把复杂状态塞进一次请求的代价工具调用的语义变化值得单独说。Chat Completions 的循环是这样的请求 - 模型返回 assistant 消息带 tool_calls - 开发者把每条 tool_call 的执行结果拼成 roletool 的消息 - 塞进 messages 重新请求 - 直到模型不再返回工具调用。所有状态都在 messages 里所有历史都由业务方背着。Responses 的模式是请求 - 响应里出现 function_call 事件 - 开发者执行对应工具 - 把工具结果作为新的 input 再次请求。看起来差别不大但这个“把结果传回去”的动作不再需要重放全部历史可以配合previous_response_id轻量化上下文。而且一次响应里可以包含多个不同的 output 事件比如先有推理再有工具调用再有最终回复开发者按顺序消费即可。这里必须澄清一个容易误解的点Responses API 不是 Agent 运行时它不会替你去执行工具。它只是用一种更清晰的方式告诉你“该调用什么”“上下文应该怎么衔接”真正的工具执行、循环控制、终止条件依然要自己写。它解决的问题是语义混乱不是替你搭 Agent。3. 开源兼容的真相为什么很多开源项目还在报错3.1 兼容的三种层次官方 SDK、网关/中转、业务框架聊开源兼容要先分清三个层面因为每一层的兼容状态完全不同。第一层是官方 SDK。openaiPython 包从 1.60 开始原生支持client.responses.create()你只要升级包版本就能用。这套 SDK 会同时暴露chat.completions和responses两套入口不会互相干扰。第二层是网关/中转类项目比如 LiteLLM、new-api、one-api 这类。它们为了兼容不同的模型提供商通常会把请求规范的差异在自己这一层抹平。但抹平是需要逐个端点适配的不少开源网关在 2025 年上半年仍然只做透了/v1/chat/completions/v1/responses要么没有实现要么只是简单入口后续逻辑没跟全。第三层是业务框架比如 LangChain、LlamaIndex它们在各自版本里逐步增加了对 Responses 的支持但如果你用的是老版本调用responses就会触发各种异常。所以你在社区里看到“开源兼容”的讨论要习惯先问一句兼容的是哪一层是官方 SDK 认这个端点还是中间网关会做协议翻译还是业务框架已经适配了响应结构很多报错恰恰是因为这三层之间版本错位。3.2 “unexpected endpoint or method” 到底是谁的锅回到文章开头那个报错unexpected endpoint or method (POST /chat/completions)。这种情况十有八九不是 OpenAI 官方返回的而是网关层的自定义错误信息。它出现的原因通常是你的 SDK 用 Chat Completions 格式请求但自建网关版本比较老或者网关把 /chat/completions 这个端点归类为“未支持端点”直接拒了。还有一种变体是请求打到/v1/responses网关还没有实现这个端点返回 404或者同样给一句unexpected endpoint or method。排查思路很简单按顺序来。第一步确认你的 SDK 到底调的是哪个端点在代码里打印client.base_url和实际请求日志或者用抓包工具看一眼第二步确认这个 base_url 指向谁如果是自建网关就去网关日志里找对应请求的到达记录和路由结果第三步直接用 curl 分别打/v1/chat/completions和/v1/responses看哪个端点通、哪个不通。大多数情况下问题都能定位到“网关版本没跟上”或“base_url 拼写错误导致 URL 不是 /v1 路径”。别一上来就觉得是模型出问题了分清 SDK 层、网关层、模型层能省掉大把时间。3.3 开源项目适配 Responses 的典型改法协议翻译层怎么补如果你维护网关类项目想快速支持 Responses最常见的方案不是从零实现而是做协议翻译把进来的/v1/responses请求转换成/v1/chat/completions请求转发给上游模型供应商。具体要做三件事。第一把input里的字符串包装成[{role: user, content: ...}]格式instructions映射成 system 消息第二把上游 Chat Completions 的choices[0].message转换成 Responses 的output数组结构其中纯文本转成typemessage的事件tool_calls转成typefunction_call的事件第三把流式 chunk 重新映射成 Responses 风格的流事件。这套转换在常规场景下能跑通但有两个容易翻车的点。一是previous_response_id网关如果没实现多轮状态存储这个字段就只能透传或者干瞪眼二是 reasoning 事件Chat Completions 在很多模型上不返回推理内容转换层就没有数据可映射下游如果依赖typereasoning就会拿到空结果。所以我的建议是如果网关只是为了兼容最普通的对话请求翻译层足够如果应用重度依赖工具调用、推理过程和多轮状态最好还是直接让网关支持原生 Responses不要走翻译层否则后续排查成本很高。4. 迁移实操从 Chat Completions 到 Responses 的落地步骤4.1 迁移前的判断标准你的场景适合立刻迁吗不是所有项目都该马上迁。我的判断标准分成四类。如果你的场景是简单问答、单轮文本生成、聊天机器人界面Chat Completions 完全够用没必要为了追新而迁移官方也不会短期内下线它。如果你的场景是多轮对话但历史不长迁移收益也不大因为 Responses 的previous_response_id优势在你这里体现不出来。如果你的场景是工具调用密集、多步推理、需要长时间上下文保持比如编程代理、数据分析助手、客服工单处理那迁移收益非常明显建议尽早开始。最后一种情况要特别提一下如果你依赖自建网关先确认网关是否支持/v1/responses透传或翻译网关不支持本地代码改成 Responses 只会更难受。同时要做一次 SDK 版本盘点。官方 Python 包最好升到 1.60 以上Node.js 对应的openai包同理。如果业务框架里封装了自己的chat()方法先看清楚底层调的是哪个入口别换了一个入口返回值格式变了业务代码跟着崩。4.2 最小迁移示例用 Python SDK 改写一个工具调用对话一个最小迁移示例胜过千言万语。先看 Chat Completions 版本from openai import OpenAI client OpenAI() tools [ { type: function, function: { name: get_weather, description: 查询指定城市的当前天气, parameters: { type: object, properties: { city: {type: string} }, required: [city] } } } ] messages [ {role: system, content: 你是一个天气助手。}, {role: user, content: 北京今天天气怎么样} ] resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, ) msg resp.choices[0].message if msg.tool_calls: # 执行工具后把 tool 结果追加进 messages循环请求 for tc in msg.tool_calls: messages.append({role: assistant, tool_calls: msg.tool_calls}) messages.append({ role: tool, tool_call_id: tc.id, content: execute_tool(tc.function.name, tc.function.arguments), }) resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, toolstools, ) print(resp.choices[0].message.content)换成 Responses 版本结构是这样的from openai import OpenAI client OpenAI() tools [ { type: function, name: get_weather, description: 查询指定城市的当前天气, parameters: { type: object, properties: { city: {type: string} }, required: [city] }, } ] resp client.responses.create( modelgpt-4o-mini, instructions你是一个天气助手。, input北京今天天气怎么样, toolstools, ) for item in resp.output: if item.type function_call: result execute_tool(item.name, item.arguments) resp client.responses.create( modelgpt-4o-mini, instructions你是一个天气助手。, input[ {role: user, content: 北京今天天气怎么样}, {role: assistant, content: , tool_calls: [{id: item.call_id, type: function, name: item.name, arguments: item.arguments}]}, {role: tool, tool_call_id: item.call_id, content: result}, ], ) print(resp.output_text)两个版本的差异很明显。旧版要靠开发者手动维护 messages 数组不断追加新版的核心逻辑更直白工具调用作为顶层事件出现最后用output_text拿最终文本。注意工具定义里新版把function包裹层去掉了name、description、parameters直接平铺在工具对象里这个细节容易踩坑。4.3 流式输出、结构化输出与 Key 安全迁移中的三个高频细节迁移中三个高频细节每一个都值得单独讲。先说流式输出。Responses 的流式事件不再是逐帧choices[0].delta.content而是一系列语义化事件比如response.output_text.delta携带增量文本response.completed表示整个响应结束response.function_call_arguments.delta携带工具参数的增量。如果你之前的前端是基于 SSE chunk 渲染的迁移后需要改事件解析逻辑stream client.responses.create( modelgpt-4o-mini, input讲个段子, streamTrue, ) for event in stream: if event.type response.output_text.delta: print(event.delta, end)再说结构化输出。Chat Completions 里你用response_format{type: json_object}Responses 里换成了text参数resp client.responses.create( modelgpt-4o-mini, input返回一个 JSON包含 name 和 age 字段, text{format: {type: json_schema, name: user_info, schema: {...}}}, )json_schema模式比json_object更严格能少很多字段缺失的坑但 schema 里每个字段都要写清楚不然容易把自己绕进去。最后说 Key 安全。热词里有人提到“openai api key 分享”这里我明确说永远不要分享 API Key无论是发在 GitHub 还是发到群里。泄露一个 key 的代价不只是盗刷额度还可能连累账号风控。正确做法是用环境变量加载不要在代码里写死给子账号分配最小权限发现泄露立刻在后台吊销并轮换。4.4 Codex CLI 安装与登录避坑从 “missing optional dependency” 说起Responses API 背后一个很重要的落地产品就是 OpenAI 官方的命令行编程代理 Codex。安装时很多 Windows 用户会遇到missing optional dependency openai/codex-win32-x64这个报错提示重装npm i -g openai/codex。这个报错的本质是 npm 安装过程中平台相关的可选依赖没有被正确拉下来原因通常有三种npm 版本太老、缓存脏数据、网络中断导致平台包没下全。处理办法按顺序尝试。先把 npm 升级到最新npm install -g npmlatest然后清缓存npm cache clean --force最后重装npm i -g openai/codex。如果还是失败可以把node_modules里的openai/codex-win32-x64目录手动删掉再触发重装。安装成功后运行codex会看到welcome to codex, openais command-line coding agent. sign in with chatgpt to continue之类的提示说明需要登录。Codex 支持 ChatGPT 账号登录也支持 API Key 模式具体根据自己的账号类型选。5. 常见问题与排查技巧实录5.1 常见报错速查表把文章里提到的、社区里高频出现的报错整理成表方便排查时直接对号入座。报错信息常见原因解决思路unexpected endpoint or method (POST /chat/completions)自建网关版本不支持该端点或 base_url 配置错误确认 base_url 指向检查网关日志升级网关或换端点missing optional dependency openai/codex-win32-x64npm 平台可选依赖安装失败升级 npm、清缓存、重装 codexInvalid API key/Authorization header missingkey 错误、环境变量未加载、网关透传丢弃了请求头检查环境变量和 base_url确认网关是否透传 Authorization 头404 /v1/responses not found网关或兼容层不支持 Responses 端点更换网关版本或改用官方端点或降级到 Chat Completions迁移后流式输出乱码或空内容前端还在解析旧 chunk 格式没有适配新事件类型改成消费response.output_text.delta事件5.2 排错方法论先分清“SDK 层、网关层、模型层”遇到问题别慌先做三层定位。这是我在排接口问题时的固定套路。第一层SDK 层。确认当前openai包版本确认调用的入口是chat.completions.create还是responses.create确认base_url是官方地址还是自建网关地址。可以在代码里临时打印请求参数或者在环境变量里开启 debug 日志。第二层网关层。如果你走的是自建网关直接看网关的访问日志。重点关注请求是否到达、路由匹配到了哪个上游、上游返回的状态码是什么。很多网关的错误信息是自定义的unexpected endpoint or method这种话一看就是网关在拦截不是模型返回。第三层模型层。跳过所有中间层直接用 curl 打官方端点验证模型本身是否正常。比如curl https://api.openai.com/v1/responses \ -H Authorization: Bearer $OPENAI_API_KEY \ -H Content-Type: application/json \ -d {model:gpt-4o-mini,input:ping}如果官方端点正常那问题基本锁定在网关兼容层如果官方端点也报错再回头看 key、账号和模型参数。按这个顺序排查绝大多数问题十分钟内能找到根源。5.3 我在实际迁移中踩过的几个坑最后分享几个我自己的经验可能比文档更实用。第一个坑是协议翻译层的信息丢失。我一开始图省事把responses请求统一走到网关的翻译层转成chat/completions常规文本对话没问题但一旦应用依赖reasoning事件翻译层返回的结果里就没有这一块下游逻辑拿不到推理内容表现就是“模型偶尔抽风”。后来我看了原始请求日志才发现不是模型问题是翻译层没做事件映射。所以关键结论是功能越复杂的应用越要直接走原生 Responses 端点不要依赖翻译。第二个坑是 SDK 混用。项目里有老模块用chat.completions新模块用responses两者返回结构完全不同如果业务代码里共用了一个统一的parse_response()函数很容易把output_text当成choices[0].message.content来取结果取到None还不报错。我的习惯是在工具函数层做隔离分别封装parse_chat_response()和parse_response_response()免得混用。第三个坑是 API Key 的安全分散管理。以前总习惯在多个环境文件里各放一个 key后来发现只要有一个环境文件被提交到 Git泄露面就很大。现在我的做法是所有 key 集中放到密钥管理服务里应用运行时读取环境变量本地开发用.env并且加入.gitignore。一旦发生泄露只轮换那一把 key而不是手忙脚乱地到处改。第四个坑是关于迁移节奏。不要在生产环境里一把梭地把所有请求从 Chat Completions 切到 Responses。稳妥的做法是先跑 shadow 模式新旧两套请求同时发新链路只记录结果不接入业务对比几天观察流式、工具调用、结构化输出是否有差异确认稳定后再切流量。接口规范的迁移从来不是换一个 URL 那么简单参数语义、事件结构、错误处理都要跟着变留出足够的验证时间比赶版本更值得。从我的角度看OpenAI 推 Responses API 这件事本质上是在把接口语义统一到一个更适合 Agent 的范式上——上下文状态、工具调用、事件流都被重排了一遍。Chat Completions 不会立刻消失但新项目、重工具项目早一点迁过去后面能少背很多历史包袱。这篇文章如果能把演进逻辑、差异对照和排查路径讲清楚让你在切换的时候心里有数就够了。
返回列表