ARTICLE DETAIL

资讯详情

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

OpenAI API演进:从Completions到Responses的迁移实践与开源兼容

OpenAI API演进:从Completions到Responses的迁移实践与开源兼容 Completions、Chat Completions、ResponsesOpenAI 的接口规范在短短几年里经历了三轮大版本演进。每次版本更迭社区里都会出现两种声音一种说官方又在制造迁移成本另一种说这是为了长期体验而必须付出的代价。我自己的项目从 2023 年接入 Completions到后来全面转向 Chat Completions再到最近开始评估 Responses中间踩过不少坑也逐渐看清楚了这次演进背后的技术逻辑以及开源社区在接口兼容层上的真实处境。这篇就把我从 API 使用者角度观察到的演进路径、迁移细节、开源兼容真相一次说清楚。1. Completions 时代一个只懂接龙的接口为什么能火1.1 本质就是文本接龙很多人第一次接触 OpenAI 接口用的其实是后来居上的 Chat Completions对最早的 Completions 接口反而很陌生。这个接口的设计思路非常朴素你给它一段prompt它帮你把后面的文本补全。模型内部做的事情本质上就是在大规模预训练阶段学会的文本接龙。一个标准的 Completions 请求长这样curl https://api.openai.com/v1/completions \ -H Authorization: Bearer $OPENAI_API_KEY \ -H Content-Type: application/json \ -d { model: text-davinci-003, prompt: 给我写一封请假邮件主题是感冒需要休息, max_tokens: 200 }返回结果也很直白一个choices数组里面带着补全出来的text字段。没有角色概念没有对话历史结构就是纯粹的上文接下文。在那个 GPT-3 还占据主流的年代这种设计是够用的因为大家的玩法本来就很简单写邮件、做翻译、生成文案一次请求就是一次完整的生成任务。1.2 为什么它最终被官方冷落用久了你会发现 Completions 有几个硬伤放到现在的智能体应用场景里几乎没法忍受。第一多轮对话要自己拼上下文。你想做客服机器人就必须把用户前几轮的问题和 AI 的回答拼成一个超长字符串塞进prompt顺序错了、分隔符混淆了模型的表现就会明显变差。第二没有系统提示词和用户角色的区分。系统指令只能靠字符串拼接硬塞进 prompt 的开头稍微复杂一点的业务逻辑提示词就变成了一锅粥。第三函数调用的支持非常丑陋。在 Completions 时代你想让模型结构化输出只能靠在 prompt 里用自然语言描述函数签名这种 hack 方式解析结果更是全凭正则拼运气。我记得 2022 年底在做一个简历解析项目时为了让模型返回 JSON 格式的候选人信息费尽心思在 prompt 里规定输出模板结果模型偶尔还是会多输出一句解释性文字导致 JSON 解析直接失败。这类问题不是调参能解决的而是接口设计层面缺少约束机制。1.3 这段历史的技术遗产不过 Completions 时代并不是毫无意义。它验证了一个重要假设大规模语言模型确实能靠补全完成大量实际任务而且 API 化调用是产品化的正确路径。没有这段积累Chat Completions 发布时社区不会那么快接受 OpenAI 的接口规范。换句话说Completions 是这个生态的第一个锚点后来所有接口设计都是在它身上打补丁、做演进。2. Chat Completions 的统治算法messages 结构如何赢得所有人的心2.1 消息结构是一次正确的抽象2023 年 3 月OpenAI 推出 Chat Completions 接口/v1/chat/completions最大变化是把一段文本换成了一个消息数组。每条消息带有role字段分成system、user、assistant三类。这个设计看似简单实际上精准解决了 Completions 时代的三大痛点系统指令有专门的位置了多轮对话有自然的结构了助手的历史输出也有独立记录了。用代码看更直观from openai import OpenAI client OpenAI(api_keysk-...) response client.chat.completions.create( modelgpt-4o, messages[ {role: system, content: 你是简历解析助手只输出 JSON}, {role: user, content: 请解析这段简历张三5年Python经验}, {role: assistant, content: {name: 张三, years: 5}}, {role: user, content: 再补充一栏技能标签}, ] )你不用再自己拼历史、记分隔符了接口把对话状态显式化了。这也让 OpenAI 后续的微调、指令跟随能力的优化有了统一的发力方向。可以说Chat Completions 的成功不只是模型的成功更是交互原语设计上的成功。2.2 函数调用终于规范化了Chat Completions 真正让我觉得接口开始懂开发者的是tools参数和函数调用的标准化。模型可以根据系统提示和用户问题自己决定调用哪个函数并输出结构化的调用参数然后你执行函数、把结果塞回对话模型再基于结果生成最终回答。也许你会觉得这套机制在 Completions 时代也能靠 prompt 硬凑出来但规范化之后完全不一样了tools [ { type: function, function: { name: get_weather, description: 查询指定城市天气, parameters: { type: object, properties: { city: {type: string} }, required: [city] } } } ]模型返回的tool_calls有标准 ID、函数名、参数 JSON你再也不需要猜测模型的输出格式了。当时我做的第一个函数调用版本解析成功率从 80% 左右直接跳到了 99% 以上因为失败的原因从模型输出格式飘忽不定变成了模型选错了参数——后者简单得多。2.3 统治期留下的判断惯性从 2023 年到 2025 年Chat Completions 成为事实上的行业标准接口。国内外的模型厂商、开源推理框架、中间层网关几乎都选择兼容/v1/chat/completions。这也导致很多开发者的心智被深深固化了一提到调用大模型 API脑子里浮现出的就是messages数组就是max_tokens就是choices[0].message.content。这种固化带来的问题在于当 OpenAI 推出 Responses API 时很多人第一反应是抗拒——好好的 Chat Completions 不用为什么要换事实上如果你的产品只是做聊天机器人、内容生成Chat Completions 完全够用并不需要主动迁移。但如果你想做更复杂的智能体应用或者想省掉自己封装工具、上下文管理的代码Responses 提供的东西是 Chat Completions 再怎么打补丁也补不出来的。3. Responses API 到底改了什么从 messages 到 input 的底层逻辑3.1 定位不再是聊天补全而是任务执行Responses API/v1/responses的定位和 Chat Completions 有本质区别。Chat Completions 的核心抽象是对话而 Responses 的核心抽象是一次任务的完整执行。它不再要求你把每一条消息都自己传一遍而是允许你从零开始构建一个响应也可以基于上一次响应的 ID 继续对话还能在请求里直接声明要使用哪些内置工具。拿一个最简单的例子对比一下。Chat Completions 的请求是response client.chat.completions.create( modelgpt-4o, messages[{role: user, content: 介绍一下 Vue 3}] )Responses 的请求变成response client.responses.create( modelgpt-4o, input介绍一下 Vue 3 )messages变成了input连数组都可以不传直接传字符串。看起来改动不大但在这个简化背后隐藏着一整套新的执行模型。3.2 内置工具改变了集成方式Chat Completions 时代你要给模型接入网络搜索、代码执行、文件分析能力得自己去找第三方服务、自己封装工具调用流程。Responses API 把这部分能力变成了声明式的{ model: gpt-4o, input: 帮我搜索一下今天开源社区的新闻并总结要点, tools: [ { type: web_search } ] }你不需要自己实现搜索函数、不需要解析搜索结果、不需要把结果再塞回对话里。Responses API 会在内部完成工具调用闭环搜索 → 拿到结果 → 让模型基于结果生成回答。这一类内置工具还包括file_search、code_interpreter以及对开发者来说很有价值的computer_use。对于做智能体的团队来说这套机制省掉的不是一星半点的代码量。我不否认这种内置工具策略带有明显的厂商锁定意味但对多数开发者来说快速交付价值远比纠结锁不锁定更重要。等业务真正跑起来了再去评估是否迁移到开源模型也不迟。3.3 状态、事件与令牌开销的重新设计Responses API 还引入了显式的响应状态机制。一个响应对象会经历in_progress、completed、failed、rejected等状态。其中rejected表示请求被安全策略拦截failed表示执行环节出现异常。对稳定性要求高的生产系统来说这个状态机比从前只靠 HTTP 状态码猜错误要直观得多。流式输出也做了重新设计。Chat Completions 的流式返回是choices[].delta每个 chunk 只是一个增量片段你还要自己拼装。Responses 的流式输出改用事件模型不同类型的事件区分了响应生命周期中不同阶段stream client.responses.create( modelgpt-4o, input说一个关于程序员的笑话, streamTrue ) for event in stream: if event.type response.output_text.delta: print(event.delta, end)不同 SDK 版本里事件对象的属性名可能略有差异但事件类型本身是稳定的。你可以按response.created、response.output_text.delta、response.completed这套流程精确控制自己的 UI 展示逻辑。还有一个容易被忽略的改进支持传入previous_response_id来延续对话上下文。之前每次多轮对话都要全量把历史消息重新传一遍token 开销和时延都上去了。现在如果对话是基于上一次响应的延续只需要带上响应 ID 即可。官方在发布时提过一个数字在典型的连续多轮会话场景下相较传统方案可以节省约 25% 的 token 消耗。我实测下来短会话场景改善不明显但长会话场景确实能省出不少成本。4. 动手迁移从 chat.completions 到 responses 的代码改造实录4.1 环境准备与版本确认迁移前先确认 SDK 版本。不管是 Python 的openai包还是 Node 的openainpm 包老版本不一定支持 Responses 接口。建议直接升级到最新稳定版pip install --upgrade openainpm install openailatest升级之后先跑一个最小请求验证 API Key 和网络连通性import openai client openai.OpenAI(api_keyyour-key) resp client.responses.create(modelgpt-4o, inputping) print(resp.output_text)能输出pong之类的回复环境就算没问题了。4.2 参数映射哪些变了哪些没变我整理了日常开发中最常用的参数映射关系维度Chat CompletionsResponsesHTTP 端点/v1/chat/completions/v1/responses消息入口messages[]input[]系统提示messages 中 rolesysteminput 中 rolesystem 或字符串开头模型参数modelmodel采样参数temperature, top_ptemperature, top_p流式输出streamTrue choices[].deltastreamTrue 事件类型工具调用tools tool_calls roletool 消息tools function_call function_call_output 消息历史延续手动传全部历史消息传 previous_response_id从表格能看出来改动不是天翻地覆的model、temperature、tools这些核心参数仍然保留真正需要动手改的是消息结构和工具结果的回传方式。这样迁移的阻力其实比想象中小。4.3 工具调用链路改造工具调用是迁移时最大的工作量来源。Chat Completions 的工具调用流程是模型返回tool_calls你执行函数再把结果作为新的roletool消息追加进 messages 数组然后再次发起请求。Responses 的流程变成了这样# 第一步发起请求带上工具定义 resp client.responses.create( modelgpt-4o, input北京今天天气怎么样, tools[ { type: function, name: get_weather, parameters: { type: object, properties: { city: {type: string} }, required: [city] } } ] ) # 第二步从响应中提取函数调用 fc resp.output[0] if fc.type function_call: # 自己实现 get_weather 并拿到结果 result run_weather_function(fc.arguments) # 第三步把函数执行结果回传 resp2 client.responses.create( modelgpt-4o, input[ { type: function_call_output, call_id: fc.call_id, output: result } ] )注意这里有两个明显的不同第一工具执行结果不再是一条普通消息而是有明确类型的function_call_output第二回传时必须携带call_id把结果和之前的函数调用对应起来。这套设计让工具调用链路的追踪清晰了不少调试多工具协作场景时尤其有用。4.4 流式与结构化输出的迁移技巧如果你需要流式输出迁移时的改动主要是把解析 delta 文本改成遍历事件。Chat Completions 的代码是stream client.chat.completions.create( modelgpt-4o, messages[{role: user, content: 写一段长篇故事}], streamTrue ) for chunk in stream: delta chunk.choices[0].delta if delta and delta.content: print(delta.content, end)Responses 事件流风格不同但逻辑同样简洁。如果暂时不想大改代码官方也提供了带include参数的机制来过滤事件比如只关心response.output_text.delta。结构化输出方面text参数的format字段仍然可以使用json_schema方式约束输出格式这一点和 Chat Completions 的response_format很接近迁移成本几乎为零。5. 开源兼容的真相大家都在仿 OpenAI但仿到什么程度5.1 兼容是从哪里来的只要接触过开源大模型生态你就一定见过OpenAI 兼容 API这个说法。vLLM、llama.cpp、Ollama、LocalAI以及国内大量开源推理框架几乎无一例外提供/v1/chat/completions端点。背后的原因很简单OpenAI 通过 Chat Completions 定义了一套事实标准开发者已经在用这套接口写业务了如果开源框架不能兼容它推广成本就非常高。本质上这是一种聪明的追随策略模型能力可以在开源世界自由竞争但交互层必须先对齐主流生态用户才有迁移的动力。所以开源兼容不是口头上的致敬而是生态准入的门票。5.2 兼容的层次分析开源兼容要分三个层次看。第一层是 HTTP 协议和路径兼容。你向http://localhost:8000/v1/chat/completions发请求返回的 JSON 形状和 OpenAI 官方基本一致。这是最常见、也最容易做到的兼容。第二层是 SDK 层兼容。开发者使用openaiPython 包把base_url改成自定义地址代码尽量不用改或只改很少几行就能接入。这一层比 HTTP 层更考验框架作者对字段细节的把握因为 SDK 会自动做类型解析、错误处理、流式解析任何字段命名偏差都会立刻暴露。第三层是行为兼容属于最难的层面。模型能不能理解同一个messages结构会不会正确输出tool_callsJSON 格式的输出是否稳定流式事件是不是按预期顺序到达这些不是靠返回 200 状态码就能糊弄过去的需要大量测试和调优。很多标榜兼容的框架到第三层就开始露馅尤其是工具调用往往是半天调不通一个函数。5.3 开源兼容的边界Responses 的推进明显滞后当 OpenAI 把主推方向从 Chat Completions 转向 Responses 时开源社区陷入了某种追赶式兼容的尴尬。大多数框架至今仍集中火力做好/v1/chat/completions的成熟度对/v1/responses的支持要么完全没有要么只是草草实现了最基本的功能。原因也不难理解开源社区资源有限你不可能要求每个项目都把 OpenAI 每次接口演进的边边角角都无缝跟上。这就带来一个很现实的问题如果你的业务迁移到了 Responses API你就失去了大部分开源框架的即时兼容性。想从 OpenAI 切换到某个开源模型推理服务原来的responses.create调用很可能直接报 404 或者 501因为对方根本没有实现这个端点。这就是开源兼容真正的边界所在——兼容的是历史事实标准而不是正在变动中的未来标准。5.4 兼容是动态博弈我在实际项目中体会到开源兼容更像一场动态博弈。OpenAI 负责制定新规则开源社区负责追平时差。Chat Completions 用了几年时间成为业界公认的通用语言Responses API 要走到同样地位可能需要更长时间也可能因为智能体生态的爆发而加速。对于创业团队我的建议是如果你依赖开源模型做私有化部署现阶段不要轻易把全链路迁到 Responses保持 Chat Completions 作为主要接口如果你做的是在线产品并且需要智能体能力快速落地那可以考虑在 Responses 上做增量开发集中精力吃透新接口的红利。6. 实操踩坑记录Codex 安装、API Key 与 Windows 可选依赖6.1 Codex 安装时的 optional dependency 报错Contrary to expectation但这就是实际开发中的常态我在一次环境准备中遇到了一个跟接口演进没有直接关系、却非常典型的安装报错missing optional dependency openai/codex-win32-x64. reinstall codex: npm i这个报错出现在 npm 安装 Codex CLI 工具时。原因是 Codex 在不同平台上有各自的原生二进制可选包比如 Windows 对应openai/codex-win32-x64。npm 在处理 optional dependency 时如果网络原因或镜像源问题导致某一个平台包没装上就会抛出这条提示。我的处理方式是先执行清理再重新安装npm uninstall -g codex npm cache clean --force npm install -g codex如果还报同样的错说明 npm 源和该平台包的同步有问题。这种情况我建议换个镜像源再试一次。顺带一提拿到 Codex 后你还需要一个可用的 OpenAI API KeyCLI 启动时会引导你配置也可以在环境变量里直接设置好。6.2 API Key 的获取与轮换经验很多新手在申请 API Key 时会被官网页面的节奏绕晕。简单说登录 OpenAI 平台之后进入 API Keys 页面创建一个新的 key创建后立刻复制保存因为 key 只在生成时刻完整展示一次关掉页面就再也看不到了。我自己通常会给不同环境创建不同 Key比如开发环境、生产环境各用一个并在项目里通过环境变量注入而不是硬编码。这样做的好处是一旦某个 Key 疑似泄露只需在后台删除它再重新生成其他环境不受影响。另外生产环境建议开启用量限制防止异常流量导致费用飙升——这个教训我吃过一次亏。6.3 Responses API 在各个开源生态中的支持现状回到 Responses 接口我目前观察到的开源支持现状是主流推理框架多半还没把 Responses 列为一等公民。在 GitHub 上搜/v1/responses的支持情况大多数项目还处于 open issue 状态或者只有最基本的路径转发没有实现事件流和内置工具语义。这意味着当你选择 Responses API 时你的代码和 OpenAI 官方平台的耦合度是显著提高的。如果你在意后续模型可替换性建议在业务代码之上再包一层抽象比如自己定义一个轻量级 client 接口把 Responses 的调用封装在里面将来切换模型或接入其他兼容层只需要替换这一层即可。这个习惯我一直建议身边的朋友保持它不会增加多少代码量但能极大降低未来被接口锁定带来的痛苦。6.4 回归测试是迁移的底线最后特别提醒一点从 Completions 到 Chat Completions 再到 Responses每次接口迁移最容忽视的环节是回归测试。不要只验证一条最简单的 prompt 能返回内容就觉得没问题了工具调用、流式输出、超长上下文、并发访问、安全策略拦截这五类场景必须全部覆盖。我自己的做法是准备一组固定的测试用例包括正常问答多轮追问函数调用流式输出违禁内容拦截五类在每次迁移后跑一遍用脚本对比响应中的关键字段是否正常。有了这套基线迁移的恐慌感会小很多因为你知道哪些行为变了哪些行为没变心里有数。7. 我从这轮接口演进中提炼出的几点判断先说结论OpenAI 一定会继续演进接口规范Chat Completions 不会立刻消失但新特性会越来越集中在 Responses 生态里。站在开发者的角度最理性的策略不是马上大规模迁移也不是永远不迁移而是把迁移当作一次能力升级的机会。我的建议是新项目直接用 Responses API。既然官方已经明确把新工具、新模型能力优先集成到 Responses 生态新项目再抱着 Chat Completions 不放等于主动放弃了免费的前进动力。旧项目则不要急着推倒重来只有当确实需要内置工具、需要减少长对话 token 消耗、需要更流畅的事件流时才考虑迁移。我在实际项目里的体会是接口演进本身不可怕可怕的是把接口当成本质的心智怠惰。Completions 教会我们补全Chat Completions 教会我们对话Responses 教会我们把模型放进更完整的任务闭环里。每轮演进其实都在削减开发者的重复劳动让你把精力从怎么把模型回调格式整理干净逐渐挪到怎么用模型能力解决业务问题上。最后分享一个小技巧如果你暂时不想大改代码可以在自己的工具函数层做一层响应解析适配把 Responses 的输出转成旧的 Chat Completions 消息格式这样既能尝到新接口的甜头又能沿用旧的业务逻辑。我在一个原型项目里就是这么干的新旧代码混跑了两周最后才平滑地完全切过去。整个过程没有一次服务不可用也没有一次输出格式崩溃。这就是我认为对待接口演进最健康的方式跟上变化但带着护栏去跟。
返回列表