拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从Completions到Responses:OpenAI接口演进与迁移实战指南

从Completions到Responses:OpenAI接口演进与迁移实战指南

这两年做AI应用开发的朋友,估计都有一种共同体验:OpenAI的接口说变就变。我最早写第一行调用代码时,用的还是openai.Completion.create,传入一句prompt,拿回一段补全文本;没过多久,官方开始主推Chat Completions,把prompt改成了messages数组,所有人都跟着改;最近又冒出个Responses接口,官方文档已经把它标成“最新一代”推荐方案。很多群里老哥直接开喷:“又改?我代码才刚跑通。”

吐槽归吐槽,接口规范演进背后是有明确逻辑的。这篇文章我想把三件事聊透:Completions到Responses到底变了什么,为什么要变,以及开源社区天天喊的“兼容OpenAI接口”到底兼容的是哪一层。特别是最后一点,很多人其实被“兼容”这两个字误导了。不管你是刚入门的小白,还是正在维护老项目的老手,把这条演进线弄明白,后面接新接口、做技术选型、切换本地模型,都会省很多事。

1. 先搞清楚Completions到底是什么

1.1 从“补全”到“对话”:接口形态的一次转舵

早期OpenAI的接口语义是“补全”。你给一段文本,模型接着往下写,像输入法预测下一个词一样,只不过规模大了很多。这个阶段对应的模型是GPT-3那一代,请求体核心就几个字段:prompt(输入文本)、max_tokens(生成长度)、temperature(随机性)。理解起来非常直白,就是“模型续写”。

后来GPT-3.5开始做对话优化,ChatGPT产品火了,底层模型经过指令微调和对话训练,能力重心从“补全文本”变成了“理解多轮对话”。这时候老的Completion接口就不够用了——你不能让开发者把整个聊天历史拼成一段超长文本扔给模型,那既不优雅,又没法准确表达哪句话是系统设定、哪句话是用户说的。

所以Chat Completions接口顺势而出。它的核心是messages数组,每条消息带一个role,system负责设定人设和行为准则,user是用户输入,assistant是模型之前的回复。这套设计把“聊天”这件事从接口层面固定下来。

1.2 Chat Completions为什么能成为事实标准

Chat Completions接口在2023年到2024年这段时间,几乎统治了整个AI应用开发生态。原因很简单:

第一,足够简单。构造一个HTTP请求就能调用,messages数组用任何编程语言都能拼出来,不挑框架。第二,足够通用。不管是聊天机器人、内容生成工具,还是早期的Agent原型,都可以基于这个接口搭。第三,生态沉淀太厚了。LangChain里的ChatOpenAI、LlamaIndex里的OpenAIAgent、各种开源项目里的默认适配器,全是按Chat Completions写的。

更重要的是,大量开源推理框架也选择兼容Chat Completions协议。比如你在本地部署一个开源模型,然后启动vLLM或Ollama提供的API服务,客户端只需要把base_url从https://api.openai.com/v1改成http://localhost:11434/v1,原本用OpenAI SDK写的代码就能直接跑通。这种“协议兼容”的魔力在于:它不要求用户改代码,而是让服务去适配用户的习惯。久而久之,Chat Completions变成了事实上的行业标准。

1.3 用了这么久,它的痛点其实很明显

Chat Completions好用归好用,但在Agent这种复杂场景下,问题越来越明显。

最典型的就是工具调用。过去你想让模型调用一个函数,比如查天气或者搜索知识库,你得走一遍“工具调用循环”:模型先返回一个tool_calls字段,里面写着它想调用的函数名和参数;你的代码解析这个字段,执行对应函数;拿到结果后,再作为tool角色的消息塞回messages数组,重新发给模型。这个过程调试起来非常烦躁,任何一个字段格式不对,模型下一轮就“失忆”。

另一个痛点是状态维护。用Chat Completions做多轮应用,你要自己管理历史上下文,每轮请求都把完整的messages传上去。对话短还好,一旦变成几轮工具调用加多轮问答,上下文体积一路膨胀,带宽和费用都跟着涨。

再有就是结构化输出和内置能力的问题。虽然OpenAI后来在Chat Completions上补了JSON Mode、结构化输出这些东西,但能明显感觉到它们是打补丁打上去的,不是最初设计的一部分。至于联网搜索、代码执行这类能力,早期根本没有官方工具接口,开发者只能自己在外面封装。

也就是说,Chat Completions本质上是为“对话”设计的,不是为“任务”设计的。而AI应用的主旋律正在从聊天走向干活。

2. Responses接口来了:OpenAI想解决什么

2.1 从“生成一条消息”到“完成一个任务”

Responses接口被官方定位为“新一代接口”,它最大的变化不是字段改名,而是设计视角变了。

Chat Completions的视角是:我发一段消息,你回一段消息,每次调用是一个回合。Responses的视角则变成了:我提出一个请求,你完成一个任务,每次调用是一个带状态的过程。

你去看官方文档,请求体里不再强制要求拼一个巨大的messages历史,而是可以用input字段描述当前输入,用instructions字段写系统指令,同时声明需要用到的工具。模型返回的不再是一个简单的“完成消息”,而是一个结构更完整的response对象,里面包含模型生成的文本、调用了哪些工具、是否有中断、附带了哪些外部信息。这些数据组合起来,更适合让程序判断下一步该怎么办,而不是让开发者从一段对话文本里“猜”模型意图。

2.2 核心差异:工具、状态与内置能力

Responses接口在工具调用上做了大幅度的简化。开发者在请求里声明tools,模型在输出里直接给出需要调用的工具项以及调用参数,开发者执行完之后,用function_call_output这样的角色把结果回传,就能进入下一轮。相比之前手动维护tool消息和tool_calls循环,代码逻辑清晰了很多。

更值得关注的是它对内置能力的整合。Responses接口支持官方托管的一系列工具,比如联网搜索、文件检索、代码执行。对普通开发者来说,这意味着很多通用能力不用自己搭基础设施了。比如你做一个问答应用,希望模型能检索你上传的文档,不再需要自己去接向量数据库和切片流程,直接用文件搜索工具就行。

另外Responses引入了更明确的状态管理思路。Chat Completions时代,所有状态都在开发者手里,每个请求都要带上完整历史。Responses的生态里开始出现“会话状态”这种设计,可以在多次请求之间保持上下文,减轻客户端维护历史消息的压力。这对Agent类应用尤其有价值——Agent一次任务可能要调用十几次模型,每次都把所有消息原样搬过去,效率太低了。

2.3 老接口不会立刻消失,但方向已经改变

很多开发者一看到新接口就焦虑,生怕第二天老接口就下线。目前来看,Chat Completions仍然是官方支持的接口,大量存量应用还在稳定运行,官方也没有强制大家马上迁移。这点要安心。

但方向上需要看清:OpenAI现在把更多新能力优先给到Responses。新工具、新参数、新的状态管理机制,通常都先在这个接口上落地。Chat Completions更多是维持兼容,而不是增长。你如果从现在才开始做一个新项目,尤其是Agent方向,直接选Responses是更理性的选择,省得未来再做一次迁移。

3. 开源兼容的真相:大家都在“假装”OpenAI

3.1 开源模型为什么抢着“兼容OpenAI”

打开任何一款主流开源推理框架的文档,几乎都能看到一句话:提供OpenAI兼容API。vLLM、Ollama、llama.cpp、FastChat,甚至一些商业化的私有化部署方案,都把“OpenAI兼容”当成核心卖点。

原因很现实。现在企业的AI应用,前端早就写好了,调用逻辑统一走OpenAI接口,跑得好好的。如果要切换成开源模型,最理想的情况是只改一个base_url和api_key,业务代码一行不动。哪家开源框架能提供这种无缝迁移能力,哪家就更容易被企业采用。

这本质是生态卡位。模型能力只是一部分,接口兼容性决定了你能否进入现有应用体系。谁兼容得好、兼容得稳,谁就能吃到开发者默认选择的那批流量。

3.2 兼容层到底做了什么

但很多人对“OpenAI兼容”有误解,以为OpenAI把自己的接口实现开源了,或者开源模型背后跑的就是OpenAI的代码。真相是:OpenAI的API服务本身是完全闭源的,开源的是它的SDK和客户端生态。

所谓的兼容层,其实是开源推理框架做了一层“翻译+包装”。它对外暴露一个/v1/chat/completions路由,接收到OpenAI格式的请求后,把model字段映射到本地模型名,把messages数组转为内部推理格式,最后再把模型生成的文本包装成OpenAI风格的JSON响应返回。

这层兼容的覆盖范围是有边界的。兼容层做得再好,通常也只覆盖Chat Completions的基础能力,比如文本生成、多轮对话、最简单的工具调用。至于OpenAI服务里那些复杂托管能力,兼容层几乎都实现不了。开源社区和闭源服务之间,天然存在一道能力分界线。

3.3 真的能无缝对接Responses吗

现在的问题来了:官方都开始推Responses了,开源兼容层能跟得上吗?

我的判断是:短期很难,而且这里藏着“开源兼容”最大的真相。

Responses里的很多能力是绑定到OpenAI官方托管服务上的。比如联网搜索,背后是一整套网页抓取、内容清洗、质量评分的基础设施;文件检索,背后是向量索引和权限管理;代码执行,背后是安全的沙箱环境。这些能力不是单靠一个开源模型能替代的。开源框架就算给你开一个/v1/responses路由,它的实现方式大概率是把Responses请求转换成Chat Completions逻辑,在内部走一遍老流程,然后套一个Responses格式的响应壳子。

所以你会看到一种很有趣的现象:很多框架声称“支持OpenAI API”,但细看支持的版本,几乎都停留在Chat Completions时代。Responses一出来,兼容层集体沉默了。这也给开发者提了个醒——如果你的项目深度依赖开源兼容层,短期内别指望它能原生支持Responses;反过来,如果你直接用Responses开发新应用,也得接受它没法随便切换回本地模型的现实。

4. 实操迁移指南:从Chat Completions切到Responses

4.1 环境准备:注册、API Key与依赖安装

先讲环境。要调用官方Responses接口,你得有一个OpenAI账号,进开发者后台创建一个API Key。这一步很多人卡在账号注册和密钥获取上,其实流程不复杂:注册账号,登录后台,找到API Keys页面,点创建,复制保存。密钥只显示一次,丢了就只能重新生成。

拿到Key之后,推荐用官方Python SDK。直接:

pip install openai

SDK版本要求比较新,老版本里可能还没有responses相关的客户端方法。装完后在代码里通过环境变量注入密钥,别硬编码在源码里,更别把密钥提交到Git仓库。我见过太多把API Key直接写在代码里然后传到GitHub上的案例,分分钟被扫号工具薅走。

你可以在命令行先验证一下密钥是否有效:

curl https://api.openai.com/v1/models \ -H "Authorization: Bearer $OPENAI_API_KEY"

能看到模型列表,就说明账号和网络链路都通了。

4.2 一个最简的Responses调用示例

先用Python SDK发一个最简单的请求:

from openai import OpenAI client = OpenAI() response = client.responses.create( model="gpt-4o-mini", input="用一句话解释什么是接口规范", ) print(response.output_text)

这里client.responses.create就是新的入口,input可以直接传字符串,也可以传消息列表。如果你需要设定系统指令,加上instructions参数:

response = client.responses.create( model="gpt-4o-mini", instructions="你是一个擅长用通俗例子讲解技术概念的老师。", input="什么是接口规范?", ) print(response.output_text)

response.output_text是官方提供的一个便捷属性,直接拿到模型生成的文本内容。如果你想知道更完整的输出结构,可以打印整个response对象,里面包含output数组,数组中每一项可能是文本、函数调用或其它类型的输出项。

如果你更习惯原生HTTP调用,请求会是这样:

curl https://api.openai.com/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $OPENAI_API_KEY" \ -d '{ "model": "gpt-4o-mini", "input": "用一句话解释什么是接口规范" }'

注意路径是/v1/responses,不是/v1/chat/completions,后面会单独说这个坑。

4.3 关键参数变化对照

从Chat Completions迁移到Responses,最直观的变化是请求体字段。我整理了一份对照表,方便你快速定位:

功能Chat CompletionsResponses
用户输入messages数组(role/content)input字段,支持字符串或列表
系统指令messages中 role=system 的消息instructions参数
最大生成长度max_tokensmax_output_tokens
工具调用结果回传role=tool 的消息function_call_output项
流式输出choices[].deltaresponse_output_item.delta 事件
输出文本choices[0].message.contentoutput_text / output 数组
采样参数temperature/top_p保留,语义基本一致
终止条件finish_reasonresponse.status / incomplete

max_tokens改成max_output_tokens这条,坑了不少人。直接把老请求体复制过去,改个URL,然后收到一个Unknown parameter的报错,很多人第一反应是“API坏了”,其实是参数名变了。至于为什么改,是因为在Chat Completions时代,max_tokens的含义有点模糊,到底包不包括推理的思维链长度、包不包括工具调用参数,不同模型表现不一致。改成max_output_tokens之后,语义更明确,就是限定最终输出文本的长度。

流式输出这块也要特别留心。Chat Completions的流式返回是不断追加delta内容,最后拿全部文本拼接;Responses的流式事件结构不一样,它会把“正在推理”“正在调用工具”“生成文本片段”“完成”这些不同阶段都作为独立事件吐出来。如果你之前写了流式解析逻辑,这部分基本要重写。

4.4 迁移时最容易踩的坑

我实际操作下来,有几个坑是几乎每个人都会踩的。

第一个坑是请求路径和SDK版本混用。老代码里写着client.chat.completions.create,你只是把模型名字从gpt-4o换成了别的,但没有切换到client.responses.create,那走的还是老接口。有些框架的SDK版本太老,里面压根没有responses入口,直接报AttributeError。解决办法很简单:先升级SDK到最新版,再确认调用的是responses客户端方法。

第二个坑是input历史消息的格式。Responses的input字段虽然也支持带role的消息列表,但工具结果回传的方式变了。Chat Completions里你用role: "tool"+tool_call_id,Responses里则要用type: "function_call_output"+call_id+output。很多人在这一步反复报“tool call not found”,十有八九就是类型没写对。

第三个坑是上下文重复累积。Responses如果启用了会话状态管理,SDK会在后台帮你维护上下文。这时候你如果还像以前一样手动把历史messages塞进input,模型看到的上下文就会重复,回答变得又慢又乱。我自己就遇到过类似问题,排查了半天,最后发现是“自动状态 + 手动历史”叠加了。

第四个坑是响应解析逻辑。老接口里内容统一放在choices[0].message.content,新接口里output是个数组,每一项类型不同,文本项、工具调用项、思考项都混在里面。如果直接按老路径取数据,轻则拿到None,重则抛索引错误。建议先打印一次完整响应结构,看清楚再写解析。

5. 常见问题与排查技巧实录

5.1 高频报错速查表

我在测试Responses接口时,把最容易碰到的报错和排查方法整理成了表格,方便你直接对照:

报错现象可能原因解决办法
404 Not Found/v1/responses请求路径打错,或SDK版本太老确认使用新版SDK,检查代码里调用入口
Unknown parameter: prompt把Chat Completions请求体原样发给了Responses改成input字段,注意参数名变化
Unknown parameter: max_tokens还在用老参数名换成max_output_tokens
Invalid API key密钥错误、环境变量没生效检查OPENAI_API_KEY,或重新创建密钥
context_length_exceeded传入的上下文太长截断历史消息,或改用会话状态管理
Tool call not found工具结果回传格式错误使用type: "function_call_output"并带上正确的call_id
流式返回解析报错还在按老delta格式解析改为按response_output_item.delta等事件处理

遇到问题不要急着怀疑模型能力,先用最小请求排除接口本身的问题,再逐步增加参数和工具,往往很快能定位到是哪一层出错。

5.2 调接口的一点实战经验

调试这块我有个习惯:先用curl打一个最原始的请求,确认接口能通,再上SDK。这样做的好处是快速隔离问题——如果curl能通但SDK报错,那问题基本出在SDK版本或代码调用方式上;如果curl也报错,那就是请求体或密钥的问题。

再一个建议是把完整响应结构打出来看。很多人依赖官方文档里的示例代码,上来就写response.output_text,拿到结果就觉得没问题。但一旦需要解析工具调用、流式事件,就得面对真实的JSON结构。建议在代码里加一行:

print(response.model_dump_json(indent=2))

把整个响应对象漂亮地打印出来,亲眼看看output数组里每一项的type是什么,不同的type对应哪些关键字段。这样写过一轮之后,你写解析逻辑会非常快,比看文档猜字段高效得多。

最后一点,参数配置尽量显式声明。temperature、top_p这类采样参数虽然语义和老接口差不多,但不同模型的默认行为有差异。生产环境里我建议把所有关键参数都显式写上,避免因为默认值变化导致输出风格突变。

5.3 现在做技术选型该怎么选

聊完了实操,最后说下选型建议。如果你在维护成熟的老项目,业务稳定,Chat Completions可以继续用,没必要为了追新而强行迁移。但如果你的系统频繁踩到状态维护、工具调用编排这类痛点,Responses确实值得认真考虑。

新项目我更倾向于直接上手Responses,尤其是Agent方向的应用。原因前面也说过:新能力优先落地在Responses上,工具调用和状态管理的体验好很多。但有一个前提——评估你的生产环境能不能依赖官方服务。有些项目要求私有化部署,或者有数据合规方面的约束,这时候Responses内置的那些托管工具基本用不上,反而不如Chat Completions配合开源兼容层灵活。

技术选型没有绝对的对错,关键是想清楚接口后面的能力能不能落到你的场景里。官网说的“推荐”是给大多数人的方向,不是替你做的决策。

最后分享一点个人的体会

从最早的Completion,到中间的Chat Completions,再到现在的Responses,我算是一路踩着迁移过来的。每次接口一改,社区都会闹腾一波,但冷静下来看,OpenAI这几次演进都在往同一个方向走:把开发者从繁琐的底层编排中解放出来,把通用的复杂能力收编成标准化的接口能力。对于开发者来说,真正可怕的不是变化,而是代码结构写得太死,一个字段变动就要全链路修改。

我在做项目时有个习惯——不管用哪套接口,都会在代码里单独抽象一层“调用网关”。上游模型接口变化,我只改网关内部逻辑,业务层完全不用动。这轮从Completions迁移到Responses,我的业务代码几乎没有改动,所有适配都收敛在网关层里。如果你现在打算做AI项目,或者正在为下一次迁移头疼,强烈建议也试试这个思路。

返回列表