
1. 从 Chat Completions 到 Responses一次接口规范的分水岭聊到 OpenAI 的 API很多人的第一反应还是那个经典的POST /v1/chat/completions。这个接口几乎成了大模型应用的事实标准无论是接 GPT-4 还是本地跑 Llama 3大家默认都是按这个姿势发请求。但到 2025 年再看OpenAI 明显想把重心挪到新的Responses API上也就是POST /v1/responses。我常被问到一个问题这两个接口到底有什么区别开源社区那些兼容层又是怎么做到“让你无感切换”的这篇文章就专门拆这件事。先说结论Completions 是一条服务端生成的、流式返回的文本补全通道Chat Completions 是它针对对话场景的升级版输入 messages 数组模型按角色轮流说话。而 Responses API 则是一个更统一的状态化接口它把工具调用、多轮上下文、内置记忆和文件搜索都揉进了一个响应对象里不再需要你在客户端手搓一大堆逻辑。从名字就能看出来OpenAI 想要的不是“补全一段话”而是“给出一个完整响应”。为什么值得关心这事因为如果你是做 AI 应用开发的你的代码迟早要面对这个迁移。而且更微妙的是开源社区的大量工具和框架仍然死守 Chat Completions 接口这导致了一个非常实际的问题服务商换了、模型换了、SDK 也换了但你的请求体基本没变。这背后的“开源兼容”真相比想象中要复杂也更有意思。1.1 为什么我直到今天还在用 Completions先交代一下我的真实使用情况。我手上有几个线上服务最早用的是text-davinci-003时代的普通 Completions 接口后来 GPT-3.5 Turbo 出来之后就全面切到了 Chat Completions。说实话切得很香因为 Chat Completions 用 messages 数组表达系统提示词、用户消息、历史上下文确实比一条裸字符串要清晰得多。但要说真正让我离不开 Chat Completions 的原因其实是生态兼容。本地跑模型用的 Ollama、vLLM甚至是一些开源的 API 网关默认都实现了/v1/chat/completions路由。这意味着我只要把base_url从 OpenAI 改成局域网里的某个服务SDK 不用换代码几乎不用改就能从云端 GPT 切到本地模型。这种无缝体验直接影响了我的技术选型——模型可以换但接口姿势最好别变。所以当 OpenAI 推 Responses API 时我第一反应不是“好厉害”而是“咱能不能别折腾”。但实际用下来我慢慢理解了这次演进背后的逻辑Chat Completions 本身只是一个“对话文本”的接口它没有原生的 agent 状态管理没有内置的搜索工具也没有统一的工具调用协议。你要做一个能连续处理任务的应用就得自己在外面套一层循环手动拼接上下文、手动解析 tool_calls、手动处理结果。Responses 想把这层循环收编进服务端。1.2 Responses API 到底改了什么用表格来看最直观。我整理了我自己在迁移过程中关注到的差异点维度Chat CompletionsResponses API端点POST /v1/chat/completionsPOST /v1/responses输入结构messages数组每条有role和contentinput数组支持字符串、消息或函数调用结果也支持previous_response_id串联输出结构choices[].message内容是纯文本output数组统一包含message、function_call、web_search等条目工具调用通过tools定义返回tool_calls需手动拆解tools定义后服务端直接把这些调用整理成独立的function_calloutput item多轮上下文客户端必须把历史 messages 全部重新传一遍用previous_response_id引用上一次响应不用重复传历史内置能力不支持原生支持 Web Search、File Search、内置记忆按账号维度流式输出choices[].delta增量output_text.delta增量结构更扁平最核心的变化我觉得是previous_response_id。以前做多轮对话我得把整个消息历史传过去既费 token 又容易超长。现在的做法是先创建一条 response然后用它的 id 作为下一次请求的previous_response_id上下文直接被服务端接管。这在做持续对话和 agentic workflow 时省了很多事。另一个容易被忽略的点是输出结构。Chat Completions 的 tool_calls 是嵌在 message 对象里的解析时要做很多类型判断。Responses API 的输出只有两类核心条目message和function_call清晰得多。我在迁移工具调用逻辑时代码量少了一半不止。1.3 OpenAI 为什么敢动接口规范这里有个很现实的商业问题一个已经被全球开发者用烂了的接口为什么非要改我认为背后的逻辑有三层。第一层是产品形态变了。OpenAI 越来越强调 agent 和助手Assistant而不只是“给你一段生成文本”。Chat Completions 太底层助手需要的是会话、工具、记忆、检索这些高层抽象Responses API 正好是这些能力的统一出口。本质上这是从“AI 补全服务”走向“AI 代理服务”的转型。第二层是生态话语权。谁的接口稳定谁就掌握了开发者迁移成本和工具链指向。OpenAI 当然希望所有第三方框架都围绕它的最新规范来做适配而不是让chat/completions这个老接口变成整个行业的“共同语言”。如果大家都不迁移那它在兼容层上的话语权就会被削弱。第三层是开发者体验。把多轮上下文和工具调用的复杂度收进服务端能减少客户端样板代码降低新手接入门槛。OpenAI 的数据显示Responses API 在同等任务下可以减少大约 40% 的客户端调用逻辑这对推广 agent 类应用很重要。但这里就有意思了Chat Completions 接口已经在开源生态里扎根太深了不是 Oracle 说一句“改用新规范”就能翻篇的。于是我们看到了一种尴尬的并存状态——官方在推新东西社区却在疯狂维护“老模拟层”。2. 接口兼容的真相开源社区是怎么“补位”的很多人误以为“开源兼容”就是直接把 OpenAI 的 API 规范抄一遍。实际上开源社区做的事更像是一种协议翻译。Chat Completions 是全世界用得最多的方言但模型推理引擎各有各的脾气。兼容层的本质是把各家的原生接口映射回这套“共同方言”。我见过不少项目核心业务依赖 OpenAI SDK但底层可能跑的是 Ollama、vLLM、SGLang 甚至是一些国产模型的兼容网关。它们能做到“SDK 不变、请求体不变”靠的就是那些默默无闻的兼容层。这些兼容层有的是官方实现有的是社区用 FastAPI 现写的质量参差不齐。2.1 开源兼容层一个 endpoint 映射的故事以本地模型运行时 Ollama 为例。它在很早的版本里只提供自己的原生接口/api/chat后来为了兼容 OpenAI 生态专门加了一条路由/v1/chat/completions。我当时去翻它的源码发现说白了就是一个适配器把 OpenAI 的messages转成 Ollama 内部的messages再把 Ollama 的生成结果包装成 OpenAI 风格的choices结构。这个转换并不复杂但它解决了一个大痛点——前端用 openai SDK只需改base_url指向http://localhost:11434/v1代码不用动。vLLM 也是类似的思路。它是高性能推理引擎主打吞吐量但它提供的 OpenAI 兼容服务器同样实现了/v1/chat/completions甚至支持多轮对话、工具调用的一部分能力。虽然部分高级特性比如结构化输出做得不够全但日常文本生成完全够用。我自己的经验是评估一个兼容层好不好不要光看它支持多少接口而要看你常用的那几个字段它能不能正确映射。比如logprobs、function_call、stream_options这些边缘功能很多兼容层要么直接忽略要么返回空值。生产环境踩到一个排查起来非常费劲。2.2 本地模型与 openai SDK 的“伪对齐”这里要说一个坑很多本地模型声称“兼容 OpenAI API”但其实只是兼容了最基础的 Chat Completions 文本生成。一旦你用到tools、tool_choice、response_format它们可能直接返回错误或者给出不符合规范的结果。那些模型我没有点名批评的意思这其实是个普遍现实。因为 OpenAI 的工具调用协议本身就是复杂契约包括 JSON Schema 参数、严格模式、并行工具调用等。本地推理框架要做到完全对齐需要同时实现函数匹配、参数约束解码和输出解析难度不小。所以不少框架实际做的是“伪对齐”它们能识别tools字段但只返回一个你传进去的函数的名称参数则靠模型自己瞎猜。这导致了一个非常尴尬的局面表面上是 OpenAI 格式实际上是“半个 OpenAI 格式”。所以我在生产环境里只要涉及工具调用都会先做一轮兼容性冒烟测试而不是信任“支持 OpenAI API”这种模糊的承诺。测试方法很简单用同一个参数列表分别请求 OpenAI 官方和本地服务对比返回结构。不要求你的工具能完全跑通但至少要保证结构能被 SDK 正确解析。2.3 第三方网关与多模型适配的现实方案除了本地推理引擎还有一些独立部署的“API 网关”项目比如 LiteLLM、Higress、One API 这类。它们做的事情更上层把多家模型供应商的接口统一成 OpenAI 格式再暴露给内部应用。这样公司内部可以一个 SDK 接所有模型灵活调度不被单一厂商绑死。LiteLLM 是我用得比较多的。它支持几百种模型提供商核心原理就是每个 provider 写一个 adapter把 OpenAI 格式的请求翻译成各家 API 的原生格式再把返回结果翻译回 OpenAI 结构。它的配置方式很直观一个 config.yaml 就能定义模型列表、密钥、转发策略。但也有要付出的代价。网关层加了适配必然带来额外的延迟和故障点。我自己测过走 LiteLLM 转发比直连官方 API 平均多 20-50ms在大多数场景可以接受。不过一旦网关自身挂掉所有模型的请求都会失败所以生产环境要给它做高可用。另外各家模型的参数并不完全一致比如 Anthropic 的max_tokens和 OpenAI 的max_tokens语义相似但有些模型对temperature支持并不好。网关在这种情况下的做法通常是把不支持的参数悄悄剥离结果可能是你给某个模型传了temperature0但它根本没启用输出随机性反而很大。所以我的建议是网关适合做模型路由和权限管控但要谨慎依赖它的“高级功能映射”。真正关键的业务逻辑尽量直接对接底层厂商 API不要把命都交给网关。尤其是 Responses API 这种状态化接口很多网关还没适配好现阶段强行走网关反而得不偿失。3. 实操从代码迁移到 Responses API聊完兼容说点实操。我最近把一个简单客服系统从 Chat Completions 迁到了 Responses API整个迁移过程比想象中顺利但也有不少值得注意的细节。这里我把核心迁移步骤和代码示例放出来给大家做参考。先说一个总原则不要把 Responses API 当成 Chat Completions 的“升级版”而要用新接口的思路重写调用逻辑。否则你很可能只是换了个端点却还在手动传历史消息白白浪费previous_response_id的优势。3.1 最小可运行的迁移示例先说安装依赖。官方 SDK 从某个版本开始同时支持两个接口我的环境是openai1.40.0Python 3.10。旧代码大概是这样的from openai import OpenAI client OpenAI(api_keysk-...) response client.chat.completions.create( modelgpt-4o, messages[ {role: system, content: 你是客服助手。}, {role: user, content: 我的订单几天能到} ], temperature0.3 ) print(response.choices[0].message.content)迁移到 Responses API 后变成from openai import OpenAI client OpenAI(api_keysk-...) response client.responses.create( modelgpt-4o, input[ {role: system, content: 你是客服助手。}, {role: user, content: 我的订单几天能到} ], temperature0.3 ) print(response.output_text)看起来变化不大对吧但注意几个细节messages变成了inputchoices[0].message.content变成了output_text。如果你的代码里到处都在访问choices迁移时就要全面修改。对于简单问答场景这个迁移只需要换字段名。但真正复杂的是流式响应。Responses API 的流式结构是stream client.responses.create( modelgpt-4o, input[{role: user, content: 讲个笑话}], streamTrue ) for event in stream: if event.type response.output_text.delta: print(event.delta, end)这里的event.delta就是增量文本跟 Chat Completions 里choices[0].delta.content完全不同。SDK 内部已经做了事件解析你只要判断事件类型即可。如果是从旧代码迁移强烈建议先写一个封装函数把流和普通模式都包进去避免业务代码被接口类型污染。3.2 关键字段差异messages 到 input、temperature 到 top_p 的坑我想重点吐槽两个地方。第一个是input的灵活性。Responses API 的input不像messages那样只能传带 role 的消息它还可以传纯字符串、函数调用结果条目甚至可以用previous_response_id串上下文。这大大简化了 agent 状态管理。但问题是很多框架的输入结构还是按messages来的你不能无脑搬运。比如你之前有messages数组包含了assistant角色带tool_calls的消息在 Responses API 里你需要把这种交互改写成多个独立的function_call和messageitem格式不对会直接 400。第二个是 temperature 和 top_p 的语义差别。虽然两个接口都有这两个参数但 Responses API 对它们的处理更严格。OpenAI 官方建议在同一个请求里不要同时修改 temperature 和 top_p否则会报错或不生效。这一点在 Chat Completions 时代其实是“不报错但行为不可预期”到了 Responses API 变成了“直接告诉你别这么干”。我一开始没注意同时传了两个参数结果某个模型直接返回参数冲突错误。这个坑算是白送的教训。另外Responses API 的temperature默认值跟 Chat Completions 不完全一致。在部分模型上默认值从 1.0 变成了 0.8 之类的导致我迁移后感觉输出风格有细微变化。所以如果希望保持一致最好显式传递原来的参数值。3.3 踩坑实录unexpected endpoint or method 的错误排查这个错误估计不少人都见过请求发出去服务端返回一条 JSON 错误提示[error] unexpected endpoint or method. (post /chat/completions)。我第一次遇到时一脸懵因为我的代码明明是标准 Chat Completions 调用。后来定位才发现问题并不在我的代码而在中转服务或兼容层。这种错误通常出现在以下几种情况你配置的base_url指向了某个不支持/chat/completions路径的服务但它依然接收了请求路由没匹配上于是抛出这个错误。你的 SDK 版本太新默认请求路径发生了变化某些网关还在用老路径。你用了 API 密钥配置工具实际请求被转发到了另一个 provider而那个 provider 没有实现该 endpoint。排查步骤我建议按顺序做打印实际请求 URL 和 payload确认base_url、path、method是否符合预期。直接用 curl 发一个最小请求绕过 SDK看服务端返回什么。检查你用的 SDK 版本看它是否把请求发到了其他端点比如 Responses API 的路径被硬编码。如果走了网关查看网关日志看它到底把请求转给了哪个上游服务。我的一个实际案例是公司内部网关把 OpenAI 的 SDK 请求转发到本地 vLLM但网关配置里 vendor 类型写错了把 vLLM 识别成了另一种模型服务导致路径映射失败。改完 vendor 配置就好了。这个错误本身并不难但它提醒我兼容层的排查要从链路视角看不能只盯着应用代码。4. 常见问题与排查技巧这一部分把我在实际迁移和日常使用中遇到的典型问题整理成了一份速查表。很多问题看似是代码问题其实根因在接口规范理解不到位或者兼容层配置不对。4.1 错误速查表错误信息/现象可能原因解决方案unexpected endpoint or method. (post /chat/completions)兼容层/gateway 不支持该路径或 vendor 类型配置错误检查网关路由配置、SDK base_url、使用 curl 直连验证invalid_request_error: messages is a required property向 Responses API 请求时传了旧格式 payload确认 endpoint 是/chat/completions还是/responses切换对应的请求体Unsupported value: messagesSDK 把旧接口请求体发到了新接口更新 SDK 到最新版并按 Responses API 格式重写ResponseError: parameter temperature conflicts with top_pResponses 接口拒绝同时调整两个采样参数只保留其中一个或显式设为相同值流式输出没有内容监听的事件类型不对确认事件名是否为response.output_text.delta本地模型走 OpenAI SDK 时 tool_calls 解析失败兼容层未完整实现工具调用协议降级为文本模式或换用支持工具调用的推理框架多轮对话上下文丢失使用了 Responses API 但忘记传previous_response_id改为传递该字段合并回最近一次响应 ID除此之外还有一个常见问题模型返回 400 但错误信息是空对象。这种情况多半出在请求里带了非法字段比如把 Chat Completions 的function_call参数原样搬到 Responses API。后者不再使用这个字段而是把它拆成了tools下的tool_choice。排查时可以把请求体里的未知字段全部去掉逐项加上很快就能定位。4.2 开源兼容层的正确打开方式给准备用开源兼容层的朋友三个建议。第一明确你的需求边界。如果只是文本生成、简单对话任何兼容层都够用。但如果你要 tool calling、结构化输出、JSON mode就必须先确认兼容层对这些能力的支持程度。很多项目 README 里写“In development”翻译过来就是“别指望”。第二搭建一个兼容性测试矩阵。我第一次接本地模型跑生产时写了一个约 20 个请求的自动化测试脚本覆盖纯文本、流式、多轮、工具调用、超长上下文、错误参数等场景。每次升级兼容层或换模型先跑一遍脚本再上线。这个习惯帮我避开了很多线上事故。第三不要依赖兼容层去“压平”所有差异。比如某些模型不支持system角色消息兼容层可能会把它偷偷拼到第一条 user 消息里结果指令遵循效果变差。这种差异最好在应用层做适配而不是指望兼容层做魔法。4.3 个人经验什么时候该追新、什么时候该守住老接口最后聊聊我的个人选择策略。我现在维护的项目里大概分成三类第一类是稳定运行的老业务上下文逻辑都在客户端历史 messages 自己管理。这类项目我不建议迁到 Responses API因为迁移收益很小还要付出改造和测试成本。守住 Chat Completions 没有任何问题OpenAI 短期内不会砍掉它。第二类是新建的 agent 项目需要多轮工具调用、状态管理、记忆能力。这类项目直接用 Responses API因为官方正在把全部新能力都堆在这里继续用老接口等于绕远路。第三类是深度依赖开源本地模型的项目。这类项目还是要以兼容层为准如果你用的推理框架对 Responses API 支持不到位那响应式接口连用都用不了。现实是 vLLM 这类项目目前主要精力还在完善 Chat Completions 兼容上Responses 的完整实现仍然需要时间。说到底接口规范背后的演进和兼容不只是一个技术话题更是一个生态博弈的问题。OpenAI 想确立新标准开源社区想保留通用协议两边拉扯过程中真正受益的是我们这些开发者——你有选择权可以站在新接口的台阶上看未来也可以守在老接口的舒适区里看热闹。我个人目前的选择是生产环境老业务继续跑 Chat Completions新 agent 项目用 Responses API 小步试点本地实验模型则继续走兼容层。等 Responses API 的工具链完善了再逐步把老业务迁过去也不迟。技术选型这件事最忌盲目追新也最忌固步自封。了解清楚演进方向再决定自己走哪条路这才是最稳妥的做法。