从Chat Completions到Responses API:迁移要点与开源兼容真相
发布时间:2026/10/31 16:20:01 锦皓数字建站

Chat Completions 接口用了两年多OpenAI 在 2024 年底正式把 Responses API 推到台前我的技术交流群里讨论度立刻上来了。大家最关心的不是“多了个新接口”而是“我现在的代码要不要改”“开源生态到底跟不跟得上”。这篇我打算从实际开发者视角把 Completions 与 Responses 的差异、迁移时的动作清单、以及开源兼容这件事的真实状态一次讲清楚。先说结论Responses API 不是 Chat Completions 的简单改名它对请求结构、响应结构、工具调用流程和流式事件做了整体重构同时官方 SDK 是开源的旧接口也仍然在用但“OpenAI 开源了”和“开源生态完全兼容新接口”是两码事。下面逐个拆。1. 先聊清楚Completions 和 Responses 到底差在哪1.1 从一次函数调用说起先看一段最常见的 Chat Completions 调用from openai import OpenAI client OpenAI(api_key你的key) resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是一个订单助手。}, {role: user, content: 帮我查一下订单 12345 的状态} ] ) print(resp.choices[0].message.content)这段代码代表了过去几年绝大多数 OpenAI API 应用的形态把系统提示词和历史消息全部塞进messages服务端按线性消息列表理解上下文最后从choices[0].message.content取文本。功能上没问题但如果你做的是 Agent会很快撞到几个痛点多轮对话要由你手动把整段历史传给接口token 重复计算工具调用之后必须严格按user - assistant tool_calls - tool的顺序续接漏一步模型就“失忆”。再看同样场景下 Responses API 的写法resp client.responses.create( modelgpt-4o-mini, instructions你是一个订单助手。, input帮我查一下订单 12345 的状态 ) print(resp.output_text)服务端返回的不再是choices数组而是一个output数组模型返回的文本直接用output_text字段就能取到。instructions独立出来历史上下文由previous_response_id维护你不需要反复拼完整消息数组。初次接触会不习惯但确实更贴近 Agent 场景。我做了一张对比表方便你直接对照关键差异维度Chat CompletionsResponses API请求入口/v1/chat/completions/v1/responses系统提示messages里的 system 角色instructions参数用户输入messages数组中的 user 消息input参数可传字符串或结构化数组多轮维护调用方手动传完整历史previous_response_id自动续接返回结构choices[].message.contentoutput数组 output_text函数调用tools 回传tool角色消息toolsfunction_call_output类型流式事件choices[].delta单一数据流response.output_text.delta等语义化事件上下文存储默认不存储默认storetrue可关闭输出上限参数max_tokensmax_output_tokens1.2 设计动机为什么要聚合与统一Chat Completions 最大的优势是简单但它把“上下文管理”和“工具调度”的责任全推给了开发者。早期做聊天机器人还好一旦上 Agent开发者要自己维护历史队列、处理函数调用的多轮回传、还要为每一次请求重复传输完整上下文。Assistants API 解决了部分存储问题但引入了 thread、run、message 这套复杂的对象模型用起来并不轻松。Responses API 的思路是把 Agent 场景里的公共能力收编进协议层。上下文用 ID 代替完整消息数组工具调用和工具结果用显式类型表达服务端负责状态维护。对开发者来说从“自己拼历史”变成“报一个会话号”。可以类比成以前去办事要自己复印一堆材料现在窗口工作人员说“把你的档案号给我后台自己调”。这套设计对两类人意义最大一类是正在做多函数工具链的 Agent 开发者另一类是维护统一网关、需要适配多种模型服务的人。前者能少写大量状态管理代码后者则面临新的协议适配压力。1.3 谁最受影响如果你只是写个一次性脚本调模型取文本Responses 上线对你的冲击不大Chat Completions 依然可用。真正被影响的是这些群体生产环境跑着大量 Chat Completions 请求、需要评估是否迁移的业务开发者依赖函数调用做自动化流程的工具链开发者回传方式变了排查逻辑也要重写维护内部网关、开源 LLM 服务框架、监控平台的工程团队需要决定要不要实现/v1/responses路由依赖usage字段做成本核算和缓存统计的人字段结构变了理解“谁受影响”比理解“接口多了个端点”更重要因为这直接决定你要投入多少改造资源。2. Responses API 核心变化拆解2.1 请求结构从消息数组到“指令状态”模式Chat Completions 时代system消息是放在messages[0]里的。Responses API 把它提升为独立的instructions参数含义更清晰这组指令指导模型整个会话期间的行为和普通用户消息层级不同。input参数比messages更灵活。简单场景下直接传字符串比如input你好复杂场景下传结构化数组数组里的元素类型包括message普通对话消息里面再分role和contentfunction_call_output函数执行结果的回传computer_call_output计算机操作类工具的输出多轮对话的关键是previous_response_id。第一次请求返回一个response.id第二次请求带上这个 ID再传入新的input服务端会在上一次上下文的基础上追加新输入而不是让你把所有历史重新拼一遍。注意store参数默认是true也就是说 OpenAI 默认会保存响应历史方便后续使用previous_response_id续接如果你有数据合规要求记得显式设置为false。一个典型的请求体长这样{ model: gpt-4o-mini, instructions: 你是一个订单助手。, input: [ {role: user, content: 帮我查一下订单 12345 的状态} ], tools: [ { type: function, name: query_order, description: 查询订单状态, parameters: { type: object, properties: { order_id: {type: string} }, required: [order_id] } } ], tool_choice: auto, store: true }这里要提醒一个变化点Chat Completions 的 function tools 结构是{type: function, function: {name, parameters}}Responses API 里直接平铺成{type: function, name, parameters}。迁移时如果直接复制旧请求体网关层可能直接报找不到function字段。max_tokens改为max_output_tokens含义更精确限制模型生成输出 token 的上限而不是上下文总长度。老代码如果直接透传max_tokens到新端点轻则参数不识别重则被网关当作非法字段拦截。接口文档里提到的一些老参数名官方 SDK 有部分兼容处理但新代码应该直接按新规范写。2.2 响应结构output 数组取代 choicesChat Completions 的响应里内容嵌套在choices[0].message.content工具调用在choices[0].message.tool_calls。Responses API 把所有输出统一放到output数组数组元素按类型区分message模型生成的普通回复content数组内是内容块function_call一次函数调用包含name、arguments和唯一的call_idweb_search_call内置 web search 工具的调用记录computer_call计算机操作类工具的调用一个简化的响应结构示例{ id: resp_xxx, output: [ { type: message, role: assistant, content: [ {type: output_text, text: 正在为您查询订单状态。} ] }, { type: function_call, name: query_order, arguments: {\order_id\:\12345\}, call_id: call_xxx } ], usage: { input_tokens: 120, output_tokens: 100, total_tokens: 220, input_tokens_details: {cached_tokens: 0}, output_tokens_details: {reasoning_tokens: 10} } }注意usage字段也在这次演进中重排了。老结构是prompt_tokens和completion_tokens的分法新结构统一叫input_tokens和output_tokens缓存的 token 被收进input_tokens_details.cached_tokens思考模型的推理 token 被归到output_tokens_details.reasoning_tokens。做成本核算和缓存统计的团队必须同步更新解析逻辑。还有一个非常实用的字段output_text。Chat Completions 需要你自己遍历content数组拼接文本Responses 直接给你一个字符串纯文本场景一步到位。我自己实测下来这个字段在流式结束时尤其省事。2.3 事件流与内置工具真正的重头戏流式接口是这次演进里变化最彻底的模块。Chat Completions 的流式返回只有choices[0].delta里面可能带content也可能带tool_calls开发者要自己判断当前增量属于哪种类型再手动拼接成完整文本或完整参数。Responses API 把事件语义化了事件类型包括但不限于response.created响应创建response.in_progress响应生成中response.output_item.added输出项开始response.output_text.delta文本增量response.function_call_arguments.delta函数参数增量response.output_item.done单个输出项结束response.completed响应完成response.failed响应失败对开发者来说事件类型本身就是“状态机”你可以根据事件名精确处理不同阶段的数据而不是在一个裸delta里猜内容。内置工具也是回应 Agent 场景的一步。Responses API 把web_search、file_search、code_interpreter这类能力直接变成 tools 配置项接口层面的集成成本降低。需要注意这些内置工具的实际可用性取决于账号权限和模型支持范围不是每个模型每个账号都能开全套。动手前先查官方文档的可用矩阵。3. 迁移实操把已有代码从 Chat Completions 搬到 Responses3.1 手工迁移的五个动作如果你决定把现有服务迁到 Responses API官方 SDK 升级之后代码改动基本围绕五个动作升级 SDK 到支持responses.create的版本把messages里的 system 消息抽出来放到instructions把首轮用户输入放进input参数多轮场景改成传previous_response_id取文本的位置从choices[0].message.content改为resp.output_text一个最简单首轮请求的迁移# 迁移前 resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是助手。}, {role: user, content: 你好} ] ) text resp.choices[0].message.content # 迁移后 resp client.responses.create( modelgpt-4o-mini, instructions你是助手。, input你好 ) text resp.output_text纯对话场景迁移成本确实不高。麻烦的是带工具调用、带多轮状态的服务下面单独说。3.2 函数调用与多轮工具返回的处理这是最值得细看的部分。Chat Completions 里工具调用结果要回传一个role: tool的消息并且必须按 assistant、tool 的顺序严格排好否则模型会认为上下文断裂。Responses API 的做法是定义一个function_call_output类型直接放进下一轮的input里。我项目里的处理逻辑大致如下import json from openai import OpenAI client OpenAI() # 第一轮让模型决定是否调用函数 resp client.responses.create( modelgpt-4o-mini, instructions你有查询订单状态的工具。, input帮我查一下订单 12345 的状态, tools[ { type: function, name: query_order, description: 查询订单状态, parameters: { type: object, properties: { order_id: {type: string} }, required: [order_id] } } ], storeTrue ) for item in resp.output: if item.type function_call: args json.loads(item.arguments) # 执行本地业务逻辑 result {order_id: args[order_id], status: 已发货} # 第二轮把函数结果回传并带上 previous_response_id 续接上下文 resp client.responses.create( modelgpt-4o-mini, previous_response_idresp.id, input[ { type: function_call_output, call_id: item.call_id, output: json.dumps(result) } ], tools[ { type: function, name: query_order, description: 查询订单状态, parameters: { type: object, properties: { order_id: {type: string} }, required: [order_id] } } ], storeTrue ) print(resp.output_text)几个细节我踩过坑分别说明call_id必须原样传回去不能自己编一个。模型靠它对上“这次函数调用”的上下文。第二轮我仍然带上了完整的tools避免模型在续推理时“忘了自己有哪些函数可用”。用previous_response_id时新input会追加到上一个响应之后如果你的业务要求必须关闭store那就得改成手动把上一轮message和function_call_output按顺序放进input数组工作量会大不少。arguments是字符串解析前记得json.loads别直接当对象用。3.3 迁移策略什么时候迁、什么时候不迁不是所有服务都值得立刻迁。我的建议分三类新项目直接上 Responses API尤其是要做 Agent、工具调用、多轮上下文的场景。协议设计本来就为这些准备没必要再学一遍老写法。老的纯聊天服务如果只是文本问答、没有复杂工具链可以继续跑 Chat Completions。官方至今仍然维护这个端点线上稳定优先没必要为了追新而承担不必要的回归风险。折中方案是做“端点抽象层”。在业务代码和 OpenAI SDK 之间加一层自己封装的服务接口统一内部用开关控制走 Chat Completions 还是 Responses。灰度时先切 5% 流量到新端点观察错误率、延迟、token 用量再逐步放大。这样即便迁移过程出问题也能一键切回旧端点而不是整个服务停摆。4. 开源兼容的真相协议、SDK 与网关4.1 开源与兼容两个字要拆开看很多讨论把“OpenAI SDK 开源”和“新接口兼容”混在一起。OpenAI 的 Python SDK、Node SDK 源码确实放在 GitHub 上代码是开放的Responses API 的协议文档也是公开的任何团队都可以照着文档实现。但模型本身的权重和推理服务不开放Responses API 里的很多托管能力依赖官方后端。这意味着“开源”主要体现在客户端 SDK 和接口文档层面而不是服务端能力开放。你在本地跑一个开源模型框架不能天然就获得 Responses API 的全部体验。这也是“开源兼容真相”里最容易产生误解的地方。4.2 开源生态为什么以 Chat Completions 为事实标准现在几乎所有主流开源推理框架和模型网关第一优先实现的都是/v1/chat/completions兼容层。原因不复杂这个协议出现最早、生态最成熟LangChain、LlamaIndex、各种 Agent 框架的 OpenAI 适配器全部围绕它对接。对开源项目来说先兼容 Chat Completions 就是先接入最大存量市场。Responses API 作为一个新协议在开源生态里的普及要慢一拍。不是技术难度多高而是投入产出比的问题实现一个基本的路由转发不难但要把流式事件、function_call_output回传、previous_response_id的状态续接都做对需要成体系的改动这对很多维护者来说优先级不高。如果你在评估某个开源网关对 OpenAI 的“兼容性”一定要问清楚支持的是 Chat Completions 兼容还是 Responses API 兼容这俩是不同协议不是同一协议的两个版本。4.3 怎么验证一个网关是否真的支持 Responses API网上很多项目 README 写着“Support OpenAI API”但实际只兼容了老的聊天补全接口。我教大家三个最直接的验证方法第一看路由。打开网关代码搜索/v1/responses搜不到基本就不用期待原生支持。有的网关会把/v1/responses反向代理到官方服务这算半支持请求能通但响应格式和事件流是否按要求实现了要看代码。第二看文档和 changelog。Responses API 发布之后凡是在版本更新里专门提到responses字样的通常做了针对性适配。第三直接发一个最小请求实测curl -X POST https://你的网关地址/v1/responses \ -H Content-Type: application/json \ -d { model: gpt-4o-mini, input: 测试 }返回 JSON 里如果有output数组说明至少响应结构对了如果没有说明网关可能只是把请求转发到了老协议或者压根没处理这个路由。我曾经用过一个宣称兼容 OpenAI 的开源网关Chat Completions 用得挺顺一切到 Responses客户端直接报错404。查日志才发现网关根本没注册/v1/responses路由。这个教训告诉我README 写“兼容”之前最好自己先做接口清单核对。4.4 关于 OpenAI CLI 依赖的一个小插曲聊到“开源交付体验”补一个配套工具里的真实情况。用 npm 安装 OpenAI 官方命令行工具openai/codex时Windows 平台上偶尔会看到这样的提示missing optional dependency openai/codex-win32-x64. reinstall codex。原因是这个 CLI 通过 npm 的 optionalDependencies 为不同平台分发原生二进制包Windows 环境对应的是openai/codex-win32-x64。但当 npm 因为缓存、网络波动或权限原因没有成功安装这个平台包时CLI 就会提示依赖缺失。我的处理办法是清理 npm 缓存后重新安装或者手动补装对应平台的 optional 包。这类问题不是接口协议演进引起的但它反映了开源生态“交付链路”里的真实摩擦包发布策略越复杂环境差异带来的坑就越多。做内部工具的时候遇到这种报错先查平台二进制别急着怀疑自己的代码。5. 常见问题与避坑实录5.1 多轮对话出现上下文丢失我见过最多的问题是把previous_response_id用成了“每次都传历史第一轮的 ID”。它必须指向最近一次响应而不是第一轮响应。多轮场景的正确做法是维护一个“最新 response_id”变量每轮请求后更新。如果你关闭了storefalse那就不要依赖previous_response_id而是手动把上一轮的message输出和function_call_output按顺序放进input。我这个顺序容易搞反模型一旦在时间线上看到乱序的工具结果回复内容就会变得奇怪。5.2 流式解析逻辑对不上从 Chat Completions 迁到 Responses 的流式模式最常见的 bug 是还在等choices[0].delta.content。Responses 的流式事件结构完全不同你要监听的是response.output_text.delta函数参数增量在response.function_call_arguments.delta。一个最小示例with client.responses.stream( modelgpt-4o-mini, input写一段话, ) as stream: for event in stream: if event.type response.output_text.delta: print(event.delta, end)实测下来事件驱动模型对前端的渲染逻辑更友好因为你可以把“开始生成”“文本增量”“生成完成”映射到 UI 状态而不是自己从裸数据里推导。5.3 max_tokens 参数不生效有同事直接在应对 Responses 请求里继续用max_tokens结果发现上限完全不生效。原因很简单新协议对应参数是max_output_tokens。SDK 版本不同对老参数的宽容度也不同为了不踩边界新代码直接按新协议字段写别图省事复用老配置。另外一个相关坑是temperature这类采样参数的语义在不同模型上表现有差异切换模型时留意官方文档里的参数说明不要盲改数字。5.4 网关层的兼容性排查以下是常见网关问题速查表现象可能原因排查建议请求返回 404网关未注册/v1/responses路由查网关源码路由表请求成功但返回空内容网关透传到旧端点响应结构不匹配看响应 JSON 是否含output字段流式事件解析失败网关没有按 SSE 语义转换新事件抓包看事件名是否为response.output_text.delta工具调用回传失败网关不识别function_call_output类型检查请求体类型字段是否被透传排查这类问题最有效的手段是“抓原始 HTTP 响应”。SDK 的报错信息被包装过反而看不出问题在哪个环节。5.5 上线前的回归测试清单把服务迁移到 Responses API 之后我建议至少跑一遍这个清单多轮对话连续三到五轮确认上下文不丢工具调用让模型查询至少两种不同的函数确认call_id回传正确长文本输出验证max_output_tokens的截断效果流式解析确认流式文本拼接后与完整响应一致用量统计核对input_tokens、output_tokens、cached_tokens的字段解析错误上报模拟故意传错参数确认异常能捕获并记录降级路由确认开关切回 Chat Completions 后服务能继续运行5.6 回归测试里的隐藏细节跑回归测试时我额外关注了一个点output_text和output数组里的内容是否完全一致。多数情况一致但个别流式场景下如果代码一边用output_text、一边又遍历output做数据统计可能出现重复计数。避免的方式就是统一数据源纯展示全部走output_text逻辑分析按output数组里的内容块逐项处理。还有一个容易被忽略的点新版 SDK 对许多参数增加了额外校验一些在 Chat Completions 里被默默忽略的异常参数在 Responses API 里会直接抛错。上线前最好在测试环境把配置项全部排查一遍别等生产报警。6. 最后再分享一点我的实际体会Responses API 刚发布时我的第一反应也是“又要改签名了”。可真动手把几个 Agent 服务迁过去之后我才体会到它的价值多轮对话不用再手动拼历史工具调用回传有了清晰类型流式事件也好解析多了。很多以前要靠状态机硬扛的东西协议层直接帮你消化了。我现在的建议是新写 Agent 相关功能直接上 Responses API别在新代码里继续堆老写法存量服务则按风险分层迁移纯聊天可以先不动涉及工具链的模块逐步灰度切换。不要在“必须立刻全量迁移”和“坚决不迁”之间二选一把端点封装成配置随时能切比任何立场都稳妥。Chat Completions 不会一夜之间消失它的存量生态太厚了开源网关们也会继续维护相当长一段时间。但如果你把项目周期拉长到两三年Responses API 这套更接近 Agent 理想形态的协议大概率会成为新的默认选项。早一点把核心链路跑通后面其他团队来问“迁移坑不坑”的时候你能直接扔给他们几张截图而不是一句“听说很难”。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。