OpenAI接口演进:从Completions到Responses的兼容性与迁移实践
发布时间:2026/10/7 12:57:57 锦皓数字建站

如果你最近在项目的日志里看到过这么一行英文[error] unexpected endpoint or method. (post /chat/completions). returning 2第一反应多半是我的 base_url 是不是填错了我先说结论这一行报错背后藏着的其实是 OpenAI 接口规范过去三年里最重要的一次演进——从第一批 Completions、到 Chat Completions、再到 2024 年底开始主推的 Responses三个端点看着只是名字不同实际上从入参、出参到底层能力都已经换了代。更巧的是几乎所有本地开源模型网关都选择了只兼容chat/completions这一代。所以这个错误不仅仅是一个配置问题它是一张OpenAI 官方演进路线和开源生态兼容现状在地图上的错位点。这篇文章我从一次真实的排错经历说起整理了三代接口的核心差异、Completions 正式退役后的兼容性余波以及开源项目在适配 Responses 时真正该注意的事情。适合正在做 Agent、工具调用Function Calling、或者维护本地模型后端兼容层的读者参考。看完你至少能分清你的请求到底走的是哪个端点出错时该去看哪一层。1. 一次让项目半夜报警的报错unexpected endpoint or method 的前因后果1.1 现场还原请求链路里到底哪一节断了那是晚上十一点多同事在群里发了这段报错说前端一直返回 502。我让他先把服务器日志翻出来看到完整的一行是[error] unexpected endpoint or method. (post /chat/completions). returning 2注意这不是 OpenAI 云端返回的 JSON 错误。OpenAI 如果收到不存在的路径正常情况下会返回 404并且响应体里有error: {message: ...}这样的结构。这句话更像是某个 SDK 或网关在本地拦截到请求后发现自己的路由表里没有POST /chat/completions这个组合于是直接打印了 unexpected endpoint or method并且用一个内部错误码 2 通知上层调用方。所以第一件事不是去改 API Key也不是去换模型名而是先确认这个请求到底被哪一个组件拦截了。在我这次的情况里有一段老的 Python 服务从 2023 年就在用openaiSDK 直连云端https://api.openai.com/v1后来有人为了统一出口把这个服务的 base_url 改成了一个本地网关的地址。结果就是本地网关实现了POST /v1/chat/completions而老代码里还残留着POST /v1/completions的调用路径。网关一看路径不匹配就相当于你要的接口我这里没有于是返回了这个让人摸不着头脑的错误。另外补充一个容易误导人的小细节returning 2这个数字不是HTTP 状态码它更像是一个内部错误标识。在一些网关实现里错误码 2 表示端点不存在或方法不允许和请求被拒绝、鉴权失败是分开标记的。如果你看到它就别再浪费时间检查授权那一层了。1.2 排查链路先分清楚三层问题现在遇到类似报错我建议按这个顺序排查确认请求实际发出的 URL 和 HTTP 方法。在 SDK 里打开请求日志看它请求的是https://api.openai.com/v1/chat/completions、https://api.openai.com/v1/completions还是https://local-gateway/v1/xxx。这一步能立刻确定问题出在路径前缀还是基础地址。确认是云端还是本地网关。如果 URL 指向 OpenAI 官方这种unexpected endpoint一般不会出现因为官方对不存在的端点会返回明确的 JSON 错误结构如果 URL 指向本地网关、内网中间层那十有八九是网关没有实现对应的路由。确认 SDK 版本和代码调用方式是否匹配。同一个 SDK 的不同版本对base_url的拼接逻辑不同有些会自动加/v1有些不会老版本还可能默认调用/v1/completions新版又可能默认走/v1/responses。别小看这个细节我见过不止一个项目因为升级了 SDK、model字段和入参格式没改结果所有请求全挂。我那次排错最后就是改了一行路由转发规则把/v1/completions老路径也映射到网关内部的chat/completions处理器上服务立刻就恢复了。整个过程看起来很简单但真正要命的是中间那段为什么两边接不上的思考。1.3 为什么这个报错最近变得特别常见这个报错能登上热搜榜本质原因是旧教程 新版底层的冲突。网上大量的教程、开源项目 README、甚至企业内部公共组件还停留在 Chat Completions 这一代把messages数组、chat/completions当成了唯一正确的调用方式。但另一方面OpenAI 官方从 2024 年开始高调推行 Responses 接口新模型也越来越多地围绕 Responses 提供额外能力。两套文档同时存在新引入的人很容易读一篇旧文章、用一个新 SDK配置上两边对不上于是这类奇奇怪怪的 endpoint 报错就开始集中冒出来。提示当你看到 unexpected endpoint or method 时先别急着改密钥或换模型。优先打印请求的完整 URL 路径确认它到底在调哪个端点。大多数情况下问题不在凭据而在路线。2. 三代接口Completions、Chat Completions、Responses 到底差在哪2.1 Completions 时代的思维模型模型就是一个文本续写器先回到第一代 Completions。它做的事情非常简单给你一段prompt模型继续输出后面的文本。没有 role没有 system没有工具调用。一个典型的请求长这样import openai # 第一代 Completions 的典型写法现在已经跑不通了 resp openai.Completion.create( modeltext-davinci-003, promptQ: 什么是 OpenAPI?\nA:, max_tokens100 ) print(resp[choices][0][text])这段代码如果现在拿去跑大概率会直接报错因为text-davinci-003本身已经被冻结/v1/completions端点也在 2025 年正式退役。但它帮助我们理解了为什么后来一定要引入 Chat Completions纯文本续写很难规范地表达系统人设和多用户对话所有上下文都得开发者手工拼进 prompttoken 上限很容易被撑爆更别说做结构化的工具调用了。2.2 Chat Completions 时代消息数组的大统一Chat Completions 的核心理念是把对话建模成一个消息数组。每条消息有rolesystem/user/assistant有content调用方只需要把历史消息和当前提问一起传上去模型自己理解对话结构。这看上去只是一个小改动但它把许多原本要开发者手工处理的逻辑变成了内置能力system 消息让模型行为设定有了标准位置不用再靠 prompt 前缀拼凑assistant 历史消息让多轮对话不用再手工拼接长文本function calling的出现让模型可以在回复中输出结构化的函数调用请求应用层再去执行真正的函数。POST /v1/chat/completions { model: gpt-4o-mini, messages: [ {role: system, content: 你是一个助手。}, {role: user, content: 帮我查一下北京的天气} ], tools: [...] }正因为接口简单、语义清晰它迅速成了事实标准。后来 Ollama、vLLM、llama.cpp、LM Studio 在做OpenAI 兼容接口时优先实现的基本都是它而不是更早的 Completions。可以说Chat Completions 用一个非常克制的抽象定义了对话式 LLM API 该长什么样。2.3 Responses API面向 Agent 的重构Chat Completions 做普通聊天完全够用但做 Agent 时开发者必须在循环里手动保存历史消息、手工解析 function call、执行完再拼接回去。这套流程重复几遍之后你会发现大部分代码都不是在调模型而是在伺候对话状态。Responses API 就是为了把这个过程变得更顺。它的核心变化可以这样概括input替代messages既可以是消息数组也可以直接给字符串instructions从 message 列表里单独拆出来语义上更接近系统级操作手册tools不再是单纯的function还包含 OpenAI 内置的web_search、code_interpreter、file_search返回结构更结构化output是一个数组里面明确区分message、function_call、reasoning等类型支持previous_response_id可以在部分场景下省去重复传历史消息的麻烦。这一代接口的定位很清楚给 Agent 场景用的。它不是简单地把chat/completions改名而是把对话升级成了任务。2.4 一张表说清楚三者差异用表格直接对比会更直观维度CompletionsChat CompletionsResponses端点路径/v1/completions已下线/v1/chat/completions/v1/responses顶层入参promptmessagesinput/instructions角色概念无system/user/assistant有且 output item 类型更细工具调用无tools 手动解析内置工具类型返回更结构化上下文管理开发者自己拼接开发者维护 messagesprevious_response_id等方式典型应用文本补全对话、Function CallingAgent、多步工具调用、内置查询这张表是我个人整理的经验版不完全等于官方文档口径但用来做迁移判断足够了如果只是做一个聊天机器人Chat Completions 完全够用如果你正在写 Agent 框架Responses 的价值才会体现出来。3. 2025年大限已至Completions 的正式退役与兼容性余波3.1 官方时间线退役是怎么一步步来的回到从 Completions 到 Responses这条时间线。官方其实很早就开始铺垫了2023 年 3 月推出 ChatGPT API也就是chat/completions端点此后官方明确建议新项目一律走 Chat Completions并陆陆续续冻结了一批第一代文本模型包括text-davinci-003、text-curie-001等。到了 2024 年官方在发布公告中正式提出要弃用早期的 Completions 端点给了开发者一段过渡期。最终在 2025 年年中之前/v1/completions这个老端点被正式关闭。如果你现在再去请求它基本会得到 404 或明确的模型不可用错误。这次下线主要影响的其实是三类人还在用旧模型名如text-davinci-003的项目连模型通道都不存在了还在请求/v1/completions的老 SDK 调用比如第一代openai.Completion.create企业私有化部署的一些老网关内部路由还硬编码了这个路径。如果你一直在使用chat/completions和gpt-4o-mini这类模型这次下线对你几乎没有任何影响。3.2 关停之后谁还在踩雷实际踩雷的人比想象中多。最常见的情况是翻出 2023 年的项目模板里面恰好用了Completion.create而不是ChatCompletion.create。当时两种写法共存后来 SDK 升级旧的Completion类被移除项目一跑就报错。另一些情况发生在本地网关的兼容区里。有些网关为了照顾老项目会在路由层保留一个/v1/completions到内部chat/completions处理器的转换但一旦网关没做这个映射就会暴露类似的 unexpected endpoint or method 错误。更麻烦的是这类网关往往自己对 OpenAI 新端点的适配也是滞后状态所以新老代码混在一起时排查的复杂度会成倍增加。3.3 Responses 会不会淘汰 Chat Completions这是很多人关心的问题我的判断是短期不会。官方目前明确让两者并存Chat Completions 依然是一个被广泛支持的基础端点。真正需要注意的地方是一些新模型能力比如内置的 web search、更完整的推理字段可能只出现在 Responses 里而 Chat Completions 不会第一时间获得。对开源界和中间层服务来说这就意味着一个跷跷板如果只做 Chat Completions可能错过新能力如果只做 Responses又和现有生态脱节。两端都要维护又确实有成本。提示如果你在维护开源兼容层请把Chat Completions 仍会长期存在作为默认假设但把 Responses 当作一个并行端点持续跟踪而不是等到它完全替代了再行动。4. 开源兼容的真相别把长得很像当成就是4.1 为什么本地推理引擎全都选了 Chat Completions本地推理引擎需要一套通用、好学的 HTTP 协议来暴露能力。Chat Completions 的messages结构足够表达绝大部分任务返回结构也简单choices[0].message.content直接拿字符串。相比之下Completions 太老Responses 又太复杂——后者不仅有instructions、input、tools的多种组合响应里的output还包含多种 item 类型对引擎作者来说光是测试矩阵就很头疼。所以你会发现一个现象几乎每一个本地引擎都宣布自己兼容 OpenAI API但仔细看文档底下的示例基本都是POST /v1/chat/completions。这就是开源生态集体投票的结果。4.2 兼容也有三个层次兼容这个词其实可以拆成三层路径层只实现了POST /v1/chat/completions、GET /v1/models参数层能传temperature、top_p、max_tokens等采样参数语义层system 提示、角色切换、工具调用、流式事件顺序都和云端一致。很多网关只做到第二层。我遇到过最典型的情况是本地引擎跑普通对话完全正常一旦传入tools字段就报 400。原因是引擎虽然开放了/v1/chat/completions但并没有真正实现 function calling 的语义。这时你其实不是在调 OpenAI你只是碰巧用了一个长得像 OpenAI 的接口。所以在选型前一定先看两件事它对tools的支持是否完整它对流式tool_calls增量事件的处理是否符合官方行为只看支持 Chat Completions这句话远远不够。4.3 unexpected endpoint 背后其实是两套标准的错位回到标题想表达的核心矛盾。从 Completions 到 Responses 的演进过程中不同项目的代码习惯可能停在任何一个版本代码习惯网关实现结果老的/v1/completions只实现/v1/chat/completionsunexpected endpointSDK 默认走/v1/responses只实现/v1/chat/completions404 / unexpected endpointSDK 走/v1/chat/completions网关完整实现正常这个表格解释了为什么最近半年unexpected endpoint和chat completions会同时成为热搜大家照着新教程改代码、用各类中间层转发结果反复卡在路径对不上这一层。本质上不是某一个人的配置水平问题而是整个生态在迁移期的必然摩擦。4.4 开源项目适配 Responses 的难度被低估了我不认为开源项目适配 Responses 只是多写一个路由那么简单。Responses 的响应里包含了reasoning、function_call、内置工具执行结果等不同类型的 output item如果要完整支持还需要把previous_response_id这类状态管理映射到底层模型。对于只做单次补全的引擎来说它需要把 Responses 请求翻译成内部格式再把响应重新拼装成官方结构这个过程中不可避免地会丢东西。比如官方内置的web_search工具背后是一套完整的检索服务开源网关无法凭空模拟搜索出结果并组织成引用的过程。所以我的判断是短期内开源生态的主流仍是 Chat Completions。除非 Agent 框架们大面积转向 Responses否则开源引擎不会优先投入资源补齐这块适配。这就是标题里开源兼容真相最需要被理解的部分开源兼容是成本驱动的哪个标准简单、用户多生态就聚在哪边。4.5 但我仍然建议你关注 Responses不是让你立刻把线上代码全部切过去而是建议在写新项目时把 Responses 和 Chat Completions 都纳入选型视野。特别是当你明显需要某类新能力——比如内置搜索、更完整的工具输出时先查一下这个能力是不是只在 Responses 里提供。如果是就直接以 Responses 为基准写代码再做一个很薄的转换层把消息结构翻译一下。这样做的好处是主逻辑不受端点切换影响未来迁移的成本被压缩在一个函数里。5. 从 Chat Completions 迁到 Responses动手改一次就知道的区别5.1 入参结构的差异messages 变成 input instructions最直接的差异在请求体。Chat Completions 里你需要把所有历史消息放进messagessystem 设定也混在其中Responses 里instructions被单独拆出来input可以是字符串也可以是消息数组。以最简单的场景为例from openai import OpenAI client OpenAI() # Chat Completions 风格 resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是客服助手。}, {role: user, content: 我的订单为什么还没发货} ], ) print(resp.choices[0].message.content) # Responses 风格 resp client.responses.create( modelgpt-4o-mini, instructions你是客服助手。, input我的订单为什么还没发货, ) print(resp.output_text)从可读性来说Responses 其实更简洁尤其适合一次提问、一次回答的场景。如果业务需要维护多轮消息input也可以传消息数组迁移成本并不高。关键是把原来塞在messages里的 system 内容拆到instructions这个思维转换要早点完成。5.2 工具调用省掉最恶心的一段解析代码这是我最喜欢 Responses 的地方。在 Chat Completions 里Function Calling 的返回长这样{ choices: [{ message: { role: assistant, content: null, tool_calls: [{ id: call_abc, function: { name: get_weather, arguments: {\city\: \北京\} } }] } }] }注意arguments是一段 JSON 字符串你不仅要二次解析还要小心它被截断或转义出问题。Responses 的返回结构则清楚得多{ output: [ { type: function_call, name: get_weather, arguments: {city: 北京}, call_id: call_abc } ] }差别显而易见前者需要手写一段从字符串里抠 JSON的胶水代码后者从一开始就是结构化对象。而且output是数组多个并行工具调用可以直接平铺在同一个数组里不用再自己拼接。5.3 流式输出事件体系完全重做流式输出是迁移中工作量最大的地方。Chat Completions 的流式事件以choices[0].delta.content为主你需要在客户端做增量拼接。Responses 的事件体系更细常见的如response.output_text.delta一类事件每一段的语义更明确。如果你还在手写 SSE 解析迁移到 Responses 时建议直接用官方 SDK 的stream模式让 SDK 替你处理事件。不要试图只改 URL 就保留原来的解析逻辑那样会踩很多隐蔽的坑。5.4 最小迁移清单给你一份可以照着执行的清单替换 SDK 调用方法chat.completions.create→responses.create拆分 messagessystem 内容 →instructions其余内容 →input内容读取choices[0].message.content→output_text或output数组工具调用不再手动解析tool_calls改为读取output中typefunction_call的条目流式处理重写基于delta的解析 → 基于事件类型的解析回归测试重点覆盖工具调用、多轮对话、流式三种场景。6. 迁移适配中的常见坑与个人建议6.1 升级 SDK 不等于迁移完成很多团队以为把openai包升到最新版就是迁移了。实际上新版 SDK 同时提供chat.completions和responses两个属性你调用方法的名字没换就不会真的走到 Responses 逻辑。真正的迁移是代码层面的调用方式切换而不只是依赖版本号变了。6.2 中间层的选择决定你会不会踩 unexpected endpoint无论你用的是云厂商托管网关、开源网关、还是自己写的一层转发都要先确认它到底实现了多少个端点。如果网关只认/v1/chat/completions而新版 SDK 里你用了responses.create网关很可能不知道/v1/responses是什么于是返回 404或者就是你开头看到的 unexpected endpoint。如果不确定最土但最有效的办法是打一次原始 curl直接看到响应内容。不要依赖文档上说支持这种话。6.3 开源框架的适配进度现在还不用焦虑LangChain、LlamaIndex 这类框架对 Responses 的集成正在逐步完善但很多底层封装默认仍然把ChatOpenAI映射到chat.completions。如果你要跑的是需要新能力的场景直接在代码里调官方 SDK 会更可控别让框架的抽象层再遮一层纱。这也意味着如果你维护的是内部组件最好自己做一层薄薄的 Adapter把messages - input、instructions之类的转换写成纯函数。这样上层业务不关心底层走的是哪个端点未来的迁移成本就被限制在一个文件里。6.4 我的实际建议两条腿走路最后说说我在项目里的处理方式线上稳定的对话和工具调用继续留在 Chat Completions不为了追新而冒险新模型的新能力内置搜索、富工具调用单独封装一个服务走 Responses 端点在团队内部维护一份端点能力矩阵文档谁用的哪个端点、支持哪些参数一目了然不要把 API Key 硬编码进代码仓库也不要用别人的 Key 做实验。安全习惯比任何接口迁移都重要。另外在动手迁移前先在本地写一个对比脚本把同样的对话分别用chat.completions.create和responses.create调一次观察返回结构的差异。这个方法虽然老但对团队里没接触过 Responses 的同事来说比看十页文档都管用。我自己在迁移过程中最大的体会是接口演进从来不只是 URL 变一变背后是一整套对话管理心智的替换。Completions 时代靠拼 promptChat Completions 时代靠 messages 数组Responses 时代靠输入输出对象。每走一步开发者手写的胶水代码都在减少但前提是你得理解每个端点设计的初衷。一句话收束兼容是有时效的架构上多点冗余总比被热搜报错追着跑要好。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。