news 2026/10/7 18:22:42

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
OpenAI接口演进:从Chat Completions到Responses API迁移指南

最近后台至少有四五位朋友问过我同一个报错:[error] unexpected endpoint or method. (post /chat/completions). returning 2。有人明明用的是最新的 OpenAI SDK,代码却还在写client.chat.completions.create,结果网关直接甩了这么一句不明不白的话回来。更常见的情况是本地接了一个开源兼容服务,路径、SDK、base_url 三者之间互相打架,最后卡在一个小小的端点配置上。

这件事往小了说是配置问题,往大了说其实是 OpenAI 接口规范正处在一个微妙的分水岭。老牌的 Completions 已经退场,Chat Completions 统治了几乎整个开源生态,而官方又在全力推新一代 Responses API。问题是,社区并没有跟着官方一起转身,大量模型服务、代理网关、开源框架都还虔诚地盯着/chat/completions。这篇文章我就把这次接口演进的前因后果、字段差异、开源兼容的真实情况,以及我在迁移过程中踩过的坑一次讲清楚,希望对正在做模型接入的同学有帮助。

1. 接口演进的底层逻辑:从文本补全到对话,再到 Agent 执行层

1.1 初代 Completions 接口解决了什么,又留下了什么坑

OpenAI 最早的商用接口就是 Completions,路径是POST /v1/completions,请求体里核心参数只有一个prompt,模型拿到这段文本,然后续写下去。那个年代最有代表性的模型是text-davinci-003,用法也直白:

import openai openai.Completion.create( model="text-davinci-003", prompt="写一句关于上海的文案", max_tokens=100 )

返回结构里嵌着一个choices数组,每个元素里有text字段,那就是续写出来的内容。这套设计在 2022 年前后是非常自然的,因为 GPT-3 系列本质上就是一个"文本续写器",你给前缀,它补后缀。

但用着用着问题就出来了。最典型的是多轮对话,如果你想实现一个聊天机器人,就必须把整个对话历史手动拼进prompt里。今天的系统提示、几轮一问一答、用户当前问题,全用换行符和特殊标记串在一起。这会导致几个实际痛点:

第一,Token 消耗非常浪费。每一轮都要把全部历史塞进去,语义没有结构化,很多 token 浪费在重复的边界标记上。第二,上下文管理全靠手写,用户说一句"刚才说的那个方案改一下",程序得自己在字符串里找"刚才"指代什么,非常痛苦。第三,模型的角色区分只能靠文字暗示,比如在 prompt 里写"你是客服"和"用户说",一旦历史长了模型很容易跟丢角色。

所以 Completions 接口本质上是为"续写"设计的,不是为"对话"设计的。当 ChatGPT 这样的产品引爆市场后,这套以字符串拼接为核心的接口已经撑不起更复杂的交互范式。OpenAI 自己也清楚,与其让开发者继续在字符串里打滚,不如把对话结构内置到接口里,于是 Chat Completions 来了。

1.2 Chat Completions 如何成为事实标准

Chat Completions 的路径是POST /v1/chat/completions,核心是把一段文本prompt换成了结构化数组messages。每个消息带role和content,角色分为system、user、assistant,规则也清晰:system 负责设定人设和边界,user 是用户输入,assistant 是模型回复。

我第一次从 Completions 迁到 Chat Completions 的时候,最大的感受不是参数变了,而是"语义终于对上号了"。多轮对话不再需要手工拼字符串,代码里维护一个数组,对话结束就把新消息 push 进去,然后整体发过去。这套设计直接降低了接入门槛,也让函数调用(Function Calling)有了安放的位置:模型可以输出结构化的工具调用指令,而不是在文本里瞎猜。

后来开源社区大规模跟进,几乎所有的本地推理框架、模型网关、SDK 都默认兼容/v1/chat/completions。OpenAI 官方文档更新了一版又一版,Chat Completions 依然是全世界接入语言模型最常见的姿势。这很容易给人一个错觉:这就是不可动摇的事实标准了。当然,从结果看,OpenAI 自己并不打算让它永垂不朽。

1.3 Responses API 到底新在哪

2024 年底,OpenAI 推出了 Responses API,路径是POST /v1/responses。官方口径很明确:这是面向 Agent 场景的统一入口,把对话、工具调用、联网搜索、文件检索、记忆等多模态能力收编到一个执行管道里。你要是只做一次普通的聊天,Chat Completions 仍然能跑,但你要是做一个会反复调用工具、需要维护多步上下文的 Agent,Responses 的设计才更贴合。

Responses API 引入了两个我很在意的概念。一个是input,它取代了messages,既可以传字符串,也可以传消息数组;另一个是previous_response_id,用它把上一次 response 的 ID 传进来,就能延续上下文,不用再把历史消息一股脑重发一遍。这种"服务端管理上下文"的思路,和之前每次请求都要带上完整历史完全不同,设计上更接近数据库的游标,而不是邮件全文转发。

另一个新点是输出结构。Chat Completions 返回一个choices数组,每个 choice 里嵌 message 和 finish_reason。Responses 的返回顶层的output是一个数组,里面每一项都带type,可能是message、function_call、web_search_call、file_search_call等。也就是说,响应被做成了"事件流",不同的工具调用是不同类型的事件,模型的一次执行过程可以被拆成多个可观测的步骤。这个变化对调试 Agent 非常有用,你能清楚地看到模型先调了什么函数、拿到了什么结果、下一步做了什么,而不用从一段文本里猜它的内部推理。

2. 从请求字段到返回结构:Chat Completions 与 Responses 的逐项对比

2.1 请求体的差异,远不止换名字

很多人以为 Responses 只是把messages改成了input,其实差异远不止如此。我整理了一张对比表,方便你一眼看出核心字段的变化:

维度Chat CompletionsResponses API
请求路径POST /v1/chat/completionsPOST /v1/responses
对话历史messages数组,每次都全量提交input字符串/数组,可配合previous_response_id增量提交
系统提示messages中 role=system 的消息独立字段instructions
工具定义tools,类型包含 functiontools,类型包含 function、web_search、file_search 等
上下文交接无标准方案,需要自己拼历史previous_response_id直接引用上一条执行结果
输出项choices[].messageoutput数组,按类型区分事件

第一眼就能看出,Responses 把"系统提示"提升为独立字段instructions,这算是对 chat 时代习惯的一种纠偏。以前写 system 消息,总有人忘记它的优先级,搞不清楚它和 user 消息的边界。现在变成了显式指令,语义清楚,甚至支持用数组传入多段指令,对复杂 Agent 编排更友好。

previous_response_id是我觉得最核心的改动。Chat Completions 时代,每轮对话必须把全部历史消息发过去,一旦历史很长,token 消耗直线上升,而且可能被上下文长度限制卡住。Responses 只靠一个 ID 就能串联前后两次执行,服务端替你保管上下文,客户端只需要维护最近一次 response 的 ID。这种设计对多轮 Agent 场景是颠覆性的,交互次数降了一个数量级,请求体体积也小了很多。

还有一个字段值得注意:store。Responses API 支持把 response 存在服务端,后续可以通过 ID 查询和复用。默认是true,如果不希望服务端存,可以显式传store=false。这个选项在 Chat Completions 里是没有的,背后是 OpenAI 在把接口往"执行记录"的方向推,让你能追踪模型做了哪些操作。

2.2 返回结构的变化,直接影响可观测性

先说 Chat Completions 的返回结构,典型长这样:

{ "choices": [ { "message": { "role": "assistant", "content": "你好,有什么可以帮你?" }, "finish_reason": "stop" } ] }

简单直接,一个完整回复块。Responses API 的返回就完全不一样了。我刚切换的时候看到output数组里的type字段,第一反应是:这不就是消息事件流吗?它的最小结构类似这样:

{ "output": [ { "type": "message", "role": "assistant", "content": [ { "type": "output_text", "text": "你好,有什么可以帮你?", "annotations": [] } ] } ] }

output里的每一项都有一个type。如果模型调用了工具,会出现type: "function_call"的事件,里面带着函数名和参数;如果模型触发了联网搜索,会出现type: "web_search_call"。这带来的好处是:你的程序不需要再把模型回复的文本强行分词、正则提取工具调用意图,因为工具调用已经被结构化成了独立事件。

调试体验也随之变化。以前排查 Agent 问题时,要打印完整响应、手动找tool_calls,再去关联工具返回结果,链路又长又容易断。现在 Responses 把每次执行的中间产物都结构化地暴露出来,你可以按type过滤、跟踪每一步。如果你在做复杂 Agent,这种可观测性会让你少掉很多头发。

2.3 官方迁移工具与 SDK 支持

面对这么大改动,OpenAI 也给了迁移路径。官方 SDK 从某个版本开始同时支持client.chat.completions.create和client.responses.create,并且提供了一套迁移工具,可以帮你把旧代码中的chat.completions替身切换到responses。这个迁移工具本身并不神秘,核心是做了字段映射:

  • messages拆成input和instructions
  • temperature、top_p等采样参数直接对应
  • tools中 type=function 的工具基本可以平移
  • max_tokens改叫max_output_tokens

我在一个 CRUD 机器人项目上试过官方迁移提示,大部分基础调用改起来很快,但涉及流式、异步、工具调用和系统消息拼接的地方要格外小心,表面上是同名迁移,实际行为有一些细微差异。我的建议是:不要盲目相信自动迁移,先把迁移后的请求体和响应体打印出来,逐个字段核对,尤其是边界情况。

3. 开源兼容的真相:为什么大家还在折腾 /chat/completions

3.1 生态的现实:Chat Completions 是开源模型的通用语言

OpenAI 高调推 Responses,但开源世界并没有立刻跟上。今天你打开任意一个主流本地推理服务的文档,几乎清一色写着"兼容 OpenAI Chat Completions API"。vLLM、Ollama、LocalAI、FastChat、LiteLLM、One API 这些项目,优先支持的都是/v1/chat/completions,而支持/v1/responses的屈指可数。

这不是这些开源项目技术实力不够,而是生态惯性太强。过去两年里,围绕着 Chat Completions 长出了海量的第三方工具、教程、SDK、示例代码。开发者应聘一个岗位,面试官看的是你会不会调chat.completions;一个开源项目要快速扩张用户群,最简单的手段就是兼容chat.completions。当一套接口成了社区默认通信协议,它的价值就已经超出了 OpenAI 官方文档的边界。

更现实的原因是,很多开源模型框架本身并不提供"Agent 原生"的能力。Responses API 里的web_search_call、file_search_call依赖 OpenAI 服务端的托管工具,而本地部署的模型服务往往只提供基础推理能力,真正跑搜索和检索需要自己挂外部服务,所以它们选择继续用 Chat Completions 作为统一出口,反而是更务实的设计。

3.2 兼容层是怎么工作的

所谓"开源兼容",本质上就是做一个协议翻译器。客户端还是用 OpenAI SDK 的经典姿势发POST /chat/completions,网关或推理框架收到请求后,把它翻译成内部数据结构,丢给模型引擎,再把模型输出恢复成标准的choices[].message格式。如果是本地推理引擎,这个过程中还需要完成 prompt 模板拼接、角色映射、停止词处理等工作。

我在本地用 Ollama 接入过 LangChain 和自写 Python 脚本,它提供的/v1/chat/completions端点体感非常接近官方接口。当时我检查了一下它的兼容细节,发现messages全部转成了对话模板,system角色也会被正确处理,返回里的finish_reason也模拟得很像。不过要达到这种兼容性,不是简单的字符串转发,而是要在框架层面实现对 OpenAI 协议子集的精确理解。

但兼容层也有边界。很多开源实现只做到"能用",做不到"完全一致"。比如流式输出的 chunk 格式、usage字段的完整性、logprobs支持、tools的写法差异,这些细节经常是埋雷的地方。所以如果你要在开源兼容服务上跑生产任务,最好先做一轮冒烟测试,把下面几项逐个验证:单轮对话、多轮对话、流式输出、工具调用、异常输入。

3.3 开源项目支持状态速查

我自己维护过几个调用 OpenAPI 风格接口的小工具,也测过不少开源兼容方案,给你一张通用的支持状态参考表(具体请以各项目最新文档为准):

项目兼容端点备注
vLLM/v1/chat/completions主流开源模型服务,覆盖较全
Ollama/v1/chat/completions自带 OpenAI 兼容层,适合本地快速验证
LocalAI/v1/chat/completions本地多模型支持,需注意版本差异
LiteLLM/chat/completions + 其他模型供应商偏向多厂商路由,映射规则更丰富
One API/v1/chat/completions国内常见的聚合网关,走转发和计费
OpenRouter/v1/chat/completions云端聚合,兼容层稳定

这张表想说明一件事:现在开源世界的主流策略仍然是"统一兼容旧标准"。Responses API 想要在开源生态里普及,还有一段很长的路。这也意味着,你如果今天开发一个面向企业的中间层,最好同时保留两套出口:对外继续广播 Chat Completions 的兼容能力,对内允许新项目逐步试用 Responses。两边都接得住,才不会在接口交替期被生态淘汰。

4. 迁移实操:代码级对比与决策指南

4.1 同一功能的两种写法

为了让你直观感受差异,我拿最简单的"带系统提示的单轮对话"举例。用 Chat Completions 的经典写法:

from openai import OpenAI client = OpenAI() resp = client.chat.completions.create( model="gpt-4o", messages=[ {"role": "system", "content": "你是一个简洁的助手。"}, {"role": "user", "content": "用一句话介绍 Responses API"} ] ) print(resp.choices[0].message.content)

用 Responses API 的写法变成了:

from openai import OpenAI client = OpenAI() resp = client.responses.create( model="gpt-4o", instructions="你是一个简洁的助手。", input="用一句话介绍 Responses API" ) print(resp.output_text)

注意第二个示例里我直接用了resp.output_text。这是官方 SDK 为 Responses 提供的一个便捷属性,它会把output中的文本内容拼起来返回,省去手工遍历output数组。如果你是刚接触新接口,这个属性用起来非常顺手。

再对比一个多轮对话场景。Chat Completions 要维护一个 messages 数组:

messages = [ {"role": "system", "content": "你是资深技术顾问。"}, {"role": "user", "content": "我需要了解接口迁移。"}, ] resp = client.chat.completions.create(model="gpt-4o", messages=messages) messages.append(resp.choices[0].message) messages.append({"role": "user", "content": "继续说说风险"}) resp = client.chat.completions.create(model="gpt-4o", messages=messages)

Responses API 可以这样写:

resp1 = client.responses.create( model="gpt-4o", instructions="你是资深技术顾问。", input="我需要了解接口迁移。" ) resp2 = client.responses.create( model="gpt-4o", previous_response_id=resp1.id, input="继续说说风险" ) print(resp2.output_text)

看到区别了吗?第二次调用只需要传previous_response_id,不需要把对话历史完整重发一遍。在长对话场景里,这种写法的请求体积会小很多,网络传输和上下文处理压力都显著下降。

4.2 迁移时的几个关键注意事项

迁移不是把chat.completions四个字改成responses就完事,有几个坑我在项目里真实遇到过。

第一个是instructions的优先级。Responses 里的instructions字段等价于 Chat Completions 的 system message,但如果你在同一请求里既传了instructions,又在一个input数组里塞了 role 为system的消息,行为可能会让你意外。我测试下来,input里的 system 消息会被视为普通用户会话里的历史消息,而不是指令,所以必要的时候把系统提示统一放到instructions,避免歧义。

第二个是工具调用的结构。Chat Completions 的 function calling 可以这么写:

tools = [ { "type": "function", "function": { "name": "get_weather", "description": "获取指定城市天气", "parameters": { "type": "object", "properties": { "city": {"type": "string"} }, "required": ["city"] } } } ]

Responses API 也接受类似的 tools 定义,但事件输出变了。在 Chat Completions 里,模型会输出message.tool_calls;在 Responses 里,工具调用变成了output数组中的function_call事件。处理逻辑要相应调整,不能再用老代码去读choices[0].message.tool_calls。

第三个是流式输出差异。Chat Completions 的流式 chunk 每一片可能只包含一个字节增量,Response 的流式结构也类似,但字段名和事件类型不同。如果你以前用for chunk in client.chat.completions.create(...):处理流式,现在切到 Responses 需要改用响应事件迭代的方式。最好在正式迁移前写一个小 demo 验证流式协议是否满足你的业务需求。

第四个是max_tokens改名。Responses 把输出 token 上限改成了max_output_tokens,语义更明确,但初迁移的人很容易忘记改,结果报参数错误的尴尬我经历过不止一次。

4.3 新项目用 Responses,存量项目怎么平滑过渡

我的建议是:新项目如果有 Agent 化趋势,直接用 Responses;存量项目如果只是简单对话、兼容性和稳定优先,暂时留在 Chat Completions 完全没问题,不用为了追新而强行迁移。过度迁移本身是一种风险:工具调用、流式、Prompts 模板都要跟着改,回归测试成本很高。

如果实在要过渡,可以采用"双客户端"策略。代码里同时创建使用不同接口的调用方法,老接口保持原样,新功能模块走 Responses,通过配置开关切换默认行为。我在一个客服机器人项目上就是这么干的:核心对话链路保持 Chat Completions,新增的联网检索与多步工具操作走 Responses。这样既不影响现有用户,又能逐步积累新接口的使用经验。

另外要注意,Responses API 的计费、上下文存储、数据留存策略和 Chat Completions 不一定完全相同。涉及企业合规场景,务必先核对官方文档中关于数据存储和隐私的说明,再决定是否开启store=true。

5. 常见错误与报错排查实录

5.1 unexpected endpoint or method 的完整排查路径

回到文章开头那个报错:[error] unexpected endpoint or method. (post /chat/completions). returning 2。这句话本身不是 OpenAI 官方 API 的标准报错,更多是网关、反向代理或兼容服务返回的。

我见过好几次这种报错,基本都是三个原因。第一个是 base_url 配错了。比如你本地跑了一个开源网关,它只暴露了/v1/responses端点,但 SDK 默认在 base_url 后面追加/chat/completions,于是请求打到不存在的路径,网关就抛了这个错误。第二个是路径重复拼接。假设 base_url 已经写成了https://api.example.com/v1,SDK 又在内部拼出/v1/chat/completions,最终实际请求会变成/v1/v1/chat/completions,各种网关都会直接拒掉。第三个是兼容服务本身没有启用 Chat Completions 路由,只支持自家格式,客户端却按 OpenAI 协议发请求。

排查顺序我建议这样来:

  1. 确认你调用的服务到底支持哪些端点。看官方文档或直接curl试一下,比如:
curl -X POST https://api.example.com/v1/chat/completions \ -H "Authorization: Bearer YOUR_KEY" \ -d '{"model":"gpt-4o","messages":[{"role":"user","content":"hi"}]}'
  1. 检查 base_url 是否包含/v1。如果包含,确保 SDK 配置里不要再重复追加版本路径。
  2. 在你自己的服务端日志里查看实际收到的请求路径和 method,确认是不是多了/v1或少了/chat/completions。
  3. 如果用的是内网网关,确认网关路由规则有没有把/chat/completions转发到对应的上游服务。

5.2 一个典型线上问题的排查过程

我有一次帮朋友排查一个内部 API 代理的报错,现象是任何请求都会返回unexpected endpoint or method. (post /chat/completions)。一开始我以为是路径拼接问题,后来发现 base_url 配的是https://gateway.internal/v1,SDK 发的请求是https://gateway.internal/v1/chat/completions,从路径看完全正常。

接着我去看网关的配置文件,发现这个网关只注册了一个路由:/v1/responses,没有注册/v1/chat/completions。也就是说,它压根就没打算兼容旧接口。问题根源不是 SDK,而是网关能力集与客户端协议不匹配。

最后的解决办法是在网关前再加一层轻量适配,或者让团队统一改用client.responses.create并配置 base_url 指向正确的响应端点。这个案例给了我很深的印象:接口兼容是一个双向工程,客户端要按协议走,服务端也要明确自己支持哪些端点。两边只要有一边不动,报错就是家常便饭。

5.3 其他高频问题速查表

问题典型原因处理建议
401 unauthorizedAPI Key 未配置或已失效检查环境变量、密钥权限
404 endpoint not foundbase_url 多带/漏带 /v1用 curl 验证完整路径
unexpected endpoint or method网关未启用对应路由检查网关路由配置,开启兼容路由
tool_calls 读取不到响应结构仍为 Chat Completions确认是否切到了 Responses API
max_tokens 不被识别新接口参数名变了改用 max_output_tokens
上下文不连续忘记传 previous_response_id记录上一条 response.id 并回传
流式返回格式异常对 Responses 流式结构不熟悉打印原始 chunk,按输出类型解析

另外提一嘴,OpenAI 官方最近大力推广的 Codex CLI 其实已经走的是 Responses API 风格,它作为一个命令行编码代理,需要反复调用模型、工具和文件操作,这种场景恰好是 Chat Completions 最吃力的地方。如果你要在自己的工具链里接入类似 agent 能力,尽早理解 Responses 的执行模型会很有帮助。

我个人的真实感受是:接口规范演进从来不是单纯的技术升级,Chat Completions 的普及让整套生态围绕对话格式形成了深度绑定,而 Responses API 更像是 OpenAI 为下一代 Agent 应用铺的路。现阶段完全没必要把存量代码急着重写,但新项目、新能力,完全值得在 Responses 上投入。

最后分享一个很实用的小技巧:无论你用的是哪种接口,务必在项目入口处集中封装一个"模型网关模块",把请求构造、响应解析、错误重试全部收口。这样将来接口迁移、模型切换,你只需要改一个模块,而不是满项目搜chat.completions.create。我在多个项目上吃过程序里散落几百处 chat 调用的亏,后来花了整整两天统一封装。早封装早省心,这比纠结选哪个接口重要得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 18:22:36

Spring AI对接阿里云实战:ReactAgent工程化落地指南

1. 这不是“第九掌”,而是Spring AI在阿里云生态里的一次真实落地尝试 “降SpringAI阿里第9掌-或跃在渊-ReactAgent”——这个标题乍看像武侠小说里的秘籍名,实则是一线Java工程师在真实项目中踩坑、调试、重构后留下的技术笔记代号。“或跃在渊”出自《…

作者头像 李华
网站建设 2026/10/7 18:22:32

Spring AI React Agent对接阿里云服务实战指南

1. 项目概述:这不是一个“掌法”,而是一次Spring AI与阿里系基础设施的深度耦合实践 “降SpringAI阿里第9掌-或跃在渊-ReactAgent”——这个标题乍看像武侠小说里的秘籍名,但实际是当前Java生态中一个极具现实张力的技术实践代号。它不是玄学…

作者头像 李华
网站建设 2026/10/7 18:21:53

Java工程师转型AI Agent:Spring思维拆解原理与工程落地手册

前阵子有位做了五年 Java 后端的读者私信我,说看了不少 AI Agent 的帖子,越看越焦虑:满屏的 Python、LangChain、研究型论文,感觉 Java 工程师已经被踢出牌桌了。我当时回了他一句:你看到的是 Python 写 Demo 的人多&a…

作者头像 李华
网站建设 2026/10/7 18:21:18

DDR4高速信号设计实战:SIwave仿真流程与避坑指南

做硬件做到DDR4这个阶段,很多人都会发现,光靠PCB布线经验和一堆layout设计规则,已经压不住信号完整性问题了。DDR4的数据速率轻松上到2400MT/s、2666MT/s,甚至3200MT/s,信号上升沿已经来到几十皮秒量级,反射…

作者头像 李华
网站建设 2026/10/7 18:18:32

PyTorch CUDA多stream内存管理:record_stream与wait_event协同机制详解

1. 这个“掉坑”记录到底在讲什么?——一个让无数PyTorch CUDA开发者深夜挠头的真实问题你写好了一个多GPU、多stream的PyTorch训练脚本,模型跑得飞快,显存占用看着也合理,一切似乎都很完美。直到某天你把模型导出做推理&#xff…

作者头像 李华
网站建设 2026/10/7 18:17:31

游戏引擎渲染架构核心解析:线程模型、剔除与GPU-Driven

这两年我和团队在做一款自研引擎的渲染系统重构,期间踩了不少坑,也把Unreal、Unity、CryEngine那套渲染架构来回翻了好几遍。说实话,网上聊渲染管线的教程很多,但大部分都停在API层面——怎么调DrawCall、怎么写Shader、怎么用Com…

作者头像 李华