提到 Responses API,不少人的第一反应是:这不就是官方把 Chat Completions 换了个名字吗?其实差别非常明显。我最近把一个问答服务从旧的补全接口迁到 Responses API,同样的业务量,成本账单比原来低了不止一半,而且不是通过任何灰色渠道,完全是官方接口自己的定价机制和用法差异带来的。这篇文章就专门拆一拆这两件事:Responses API 到底该怎么用,以及怎么把它用得比“默认调用”便宜一半以上。
Responses API 是官方在补全接口之后推出的新一代生成接口,把普通对话、工具调用、知识检索、代码执行这些能力统一到了同一套协议里。对于做 Agent、RAG、自动化流程的开发者来说,它算是目前接入成本和后期维护成本都比较低的选择。如果你正在写一个需要连续对话、调用外部工具或者做结构化输出的项目,这篇文章可以直接当入门手册用。
1. 为什么“Responses API”自带省钱潜质
1.1 这一代接口本质上是在整合“补全+工具+状态”
要理解省钱逻辑,先得看懂这一代接口到底改了什么。旧的 Chat Completions 接口只解决一件事:传入一串消息,返回一条补全结果。工具调用要靠开发者手动拼消息、手动塞 function_call 结果,连续对话要把整段历史反复重发,业务一复杂,代码里全是拼接逻辑。
Responses API 直接把“会话状态”这个概念做进了接口里。你可以把多轮对话、工具返回结果、检索片段统一交给接口自己去维护上下文。这不是简单换皮,而是把很多原本需要前端或后端自己负责的状态管理、并发控制、上下文压缩工作收编了。
从省钱角度看,这一点非常关键。因为它允许你只用一条 previous_response_id 就把整个历史带上,省去了自己把历史消息全部序列化再塞进 input 的环节。而历史消息一旦由官方服务端管理,输入 token 的重复发送量就能得到明显控制。很多项目成本高,不是模型输出贵,而是每轮请求都在重复发送大量不变的上下文,这部分钱非常隐性。
1.2 便宜一半的机制来自哪里
很多人看到“比官方便宜一半以上”这几个字,第一反应是去找第三方渠道或者拼车号。这里先表个态:完全不需要,也不建议。官方定价机制里本身就藏着两个稳定折扣源——缓存和批量接口。
缓存是自动生效的。当你的请求前缀和之前请求的前缀一致时,这部分输入 token 会按缓存价计费。不同模型的缓存折扣不一样,老模型可能只有五折,新的高性价比模型缓存价能低到原价的四分之一甚至更低。批量接口则是把一段时间内不急着要结果的任务打包提交,官方给大约五折的价格。两个折扣叠加,再加上模型档位的选择,成本降到默认直连方式的一半以下是非常现实的。
但折扣只是基础。真正决定账单大小的,是你怎么设计请求结构。比如把固定系统提示词放在 input 最前面、把动态变化的内容放在后面,缓存命中率就会明显提高。再比如有状态会话虽然方便,但如果一个会话长期不清理,后续每次请求的输入 token 会越滚越大,反而比无状态更贵。这些用法层面的差异,才是“同一套官方接口,有人用得便宜、有人用得贵”的根本原因。
2. 动手前先吃透四个核心概念
2.1 输入项、响应与会话状态
Responses API 里最基础的两个概念是 input 和 response。input 可以是纯文本字符串,也可以是一个数组,数组里的每个元素叫 item。item 的类型包括消息、函数调用、函数调用结果、检索结果等。response 是模型返回的结果块,里面同样是一组输出 item。
可以这么理解:过去你发请求给服务员点菜,每次都要把“我姓什么、坐在哪桌、口味偏好”重复说一遍。Responses API 则是你办了一张会员卡,第一次把信息登记好,之后直接报卡号就行。卡号就是 previous_response_id,登记表就是官方服务端维护的会话状态。
这套机制对写业务代码的影响很大。原来你需要自己设计一套消息历史结构、自己做 tool 循环的上下文拼接,现在接口帮你存了一部分。但千万别以为有了它就可以无脑依赖,服务端帮你存状态不代表不花钱,这么大的历史每次参与计算都会产生输入 token。所以后面我会专门讲,什么时候该用有状态,什么时候该用无状态。
2.2 有状态模式与无状态模式
Responses API 支持两种调用姿势。第一种是无状态:store 设为 False(默认),每次请求都在 input 里明确告诉模型所有需要的信息。第二种是有状态:store 设为 True,配合 previous_response_id,让官方服务端把之前的会话内容自动拼到这次请求里。
有状态模式对开发者非常友好,尤其是做客服机器人、多轮问答时,代码量能少一大截。但代价是,上下文会随着轮次逐渐变长。一个 20 轮的长会话,前面 19 轮的完整内容都可能被当作历史带进来。假设模型上下文窗口是 128K,后面每轮几乎都在按 100K 以上的输入量计费。
我的经验是:有状态模式适合轮次少、上下文变化快的场景;无状态模式适合那些上下文可以在本地被压缩、截断或摘要的业务。很多高成本账单,问题就出在把有状态会话当成无限记忆来用,导致越往后越贵。
2.3 内置工具与函数调用
Responses API 的第二大改进是 tools 参数。你可以直接在请求里声明要使用哪些工具,比如网页搜索、文件搜索、代码解释器,也可以是自定义的函数调用。模型会根据用户问题自动决定要不要调用工具,以及怎么组织参数。
内置工具的价值是省事。以前要实现联网问答,你得自己抓网页、清洗正文、拼进上下文,现在用 web_search 类型工具就能让模型自己完成检索与引用。文件搜索类似,适合挂载一批文档做知识库问答。代码解释器则适合需要计算、绘图、跑数据脚本的场景。
自定义函数调用则用于接入你自己的业务系统。模型只负责生成调用参数,真正执行还是由你的后端完成。Responses API 里最需要注意的是工具结果回传的格式:函数执行完,要把结果包装成对应 item,再作为 input 传给同一个会话,模型才会继续生成最终回答。
2.4 输出控制参数:几个最容易被忽略的细节
很多开发者习惯了老接口的风格,把 temperature、top_p 一股脑塞给所有模型。但在 Responses API 里,新一代推理模型对 temperature 的支持非常有限,甚至有些模型直接不支持,传了就会报参数错误。reasoning 类模型建议通过 reasoning.effort 来控制思考深度,而不是调节温度。
max_output_tokens 这个参数值得重视。我见过不少项目因为没设上限,模型在一个离谱的循环里生成了上千 token,费用翻了几倍。设置一个合理的输出上限,能很自然地限制成本边界。另一方面,如果业务需要稳定 JSON 格式,官方也提供了结构化输出能力,只要把输出格式声明为 JSON Schema,模型就会严格按结构返回,省去了解析失败重试的钱。
3. 从零跑通:环境、代码与参数
3.1 环境准备
整个过程只需要两个前置条件:一个官方 API Key,以及装了 Python 3.9+ 的环境。安装 SDK 用一行命令:
pip install -U openai然后设置环境变量。Linux 或 macOS 可以这样:
export OPENAI_API_KEY="你的Key"Windows PowerShell 则用:
$env:OPENAI_API_KEY="你的Key"建议不要把 Key 写死在代码里。无论你是个人项目还是团队协作,环境变量方式都更安全,也方便在不同环境之间切换。
3.2 最小可用示例(无状态)
安装好之后,先跑一个最简单的无状态调用。这一步能验证 Key、网络和基本调用链是否正常。
from openai import OpenAI client = OpenAI() response = client.responses.create( model="gpt-4o-mini", input="用两句话说明白:什么是缓存计费?", ) print(response.output_text)这里的client.responses.create就是 Responses API 的入口。input 直接传字符串,SDK 会自动包装成消息项。output_text是便捷属性,用来拿模型输出的纯文本内容。
如果这步通了,说明基础链路没问题。我在这一步还建议顺手打印一下response.id,因为后面带历史、做多轮会话都要用到这个 ID。
3.3 带历史的有状态示例
接下来模拟一个连续对话场景。第一次请求让模型记住“今天天气适合跑步吗”,第二次请求带着上一次的 ID 追问。
first = client.responses.create( model="gpt-4o-mini", input="今天天气适合跑步吗?", store=True, ) first_id = first.id second = client.responses.create( model="gpt-4o-mini", previous_response_id=first_id, input="那如果是雨天呢?", store=True, ) print(second.output_text)注意两个关键点。第一,传previous_response_id时必须同时设store=True,否则官方服务端没有保存历史,这条参数就失效了。第二,第二次请求里我没有再重复“今天天气适合跑步吗”这句话,但模型应该能理解“那如果是雨天呢”指的是同一个话题。这就是有状态会话的价值。
我不建议一上来就把所有工具和状态都打开。先把最简单的无状态、有状态跑通,再逐步加复杂度。这样出问题时,你能很快定位是业务问题还是接口用法问题。
3.4 带内置工具的示例
接下来加工具。最典型的是联网搜索,需求很简单:让模型自己去查资料并总结。
response = client.responses.create( model="gpt-4o-mini", input="帮我查一下今天的行业新闻,并用三点总结。", tools=[ { "type": "web_search" } ], ) print(response.output_text)当 tools 参数里声明了 web_search,模型就会在需要时发起搜索。从效果上看,它相当于把“搜索、读取、总结、引用”打包成一个动作。文件搜索和代码解释器的用法类似,只是 type 不同。需要注意的是,这些内置工具通常有单独的使用费用,官方文档里会有明确说明。对于高频率调用的场景,我一般会先估算工具使用费在整体账单里的占比,再决定要不要接自定义函数代替。
3.5 输出结构稳定化与容错
做业务接口时,我强烈建议让模型输出固定结构,而不是返回一段话再让代码去解析。一种是官方提供的结构化输出能力,直接声明 JSON Schema;另一种是代码层面做校验。如果你暂时不想深挖官方结构化参数,也可以先这样处理:
import json response = client.responses.create( model="gpt-4o-mini", input="给我一个JSON,包含三个学习建议,每个建议有标题和描述。", ) text = response.output_text try: data = json.loads(text) print(data) except json.JSONDecodeError: print("解析失败,需要重试或调整提示词")这段代码的意义在于:让异常暴露在明处,而不是让脏数据流到业务下游。实际开发中,解析失败的处理比大多数人想象得常见。我在项目里遇到过模型多输出了一句解释文字导致 JSON 解析失败的情况,后来改成结构化输出声明之后,这种问题基本绝迹。
4. 把费用压下一半以上的实操清单
4.1 模型档位:先选对再谈省钱
模型选型是成本控制的第一道关卡。Responses API 支持不同档位的模型,从高能力模型到高性价比模型,再到 mini 规模模型,每档价格差距很大。这不代表越便宜越好,而是要根据任务复杂度来选。
如果一个任务只是简单的分类、改写、抽取,用 mini 档模型完全够用;如果任务涉及复杂推理、长文写作、多步工具调用,再用回高能力模型。一个常见做法是“路由”:先用轻量模型做初筛,判断任务难度,把简单问题直接解决掉,只有困难问题才转发给更贵的大模型。这个策略能把整体成本压低一大截,而用户几乎感知不到差异。
另外要警惕“顺手调贵模型”的惯性。我之前有个项目一直默认用旗舰模型,后来发现其中 70% 的请求根本不需要那么强的大脑,换成性价比档位后,账单直接降了一个数量级。
4.2 让缓存稳定命中:前缀顺序决定成本
缓存是官方自动行为,不需要手动开关,但你可以通过请求结构来影响命中率。核心规律是:缓存的粒度是前缀匹配,如果两次请求的前缀完全一致,前缀部分就会命中使用缓存价格。
所以请求结构设计就有讲究了。系统提示词、固定工具定义、固定示例段落,这些内容要尽量放在 input 的前面。用户问题、时间戳、随机 token 等动态内容,要尽量放在后面。如果你把用户问题放在前面,后面大段固定指令每次都会跟着变化,缓存就很难命中。
还有一点:不要频繁改动系统提示词。哪怕只是改了一个标点,都会导致前缀分叉,让之前积累的缓存全部失效。我在实际项目中,会把提示词的版本号固化到应用配置里,需要调整时就整体升级一版,而不是每轮动态拼接。
下面用表格对比一下缓存友好型和非友好型的请求结构:
| 请求结构 | 具体示例 | 缓存命中影响 |
|---|---|---|
| 固定前缀 + 动态后缀 | 系统提示 + 工具定义 + 用户问题 | 前缀稳定,命中率高 |
| 动态前缀 + 固定后缀 | 用户问题 + 系统提示 | 前缀每次都变,命中率低 |
| 频繁微调提示词 | 同一系统提示每隔几句改一次 | 前缀分叉,缓存失效 |
4.3 历史管理:别让上下文越长越贵
上下文管理是省钱库里最大的一块。很多人觉得有状态模式把所有历史交给官方管理就万事大吉,但每轮请求的输入 token 不仅包含新问题,还包含全部旧历史。
我的做法是:对长会话设置一个“水滴”,比如最近 N 轮完整保留,再往前的历史用模型生成摘要,摘要本身作为上下文继续传递。这样一来,模型仍然能理解早期对话的意图,但当轮请求的输入 token 被显著压缩了。
另一个技巧是主动截断。对于那些已经完成、不再影响后续对话的冗长内容,比如大段工具返回结果、搜索原文,就没必要一直保留在上下文中。只保留结论,把原始文本从历史里摘掉。这个动作单独看可能只省几百 token,但在高频调用下,积少成多,一个月能省下的费用相当可观。
4.4 批量接口:再拿五折
如果业务里有不需要立即返回结果的任务,批量接口是必须知道的省钱手段。像批量生成摘要、离线数据标注、定时报表、大批量内容分类这些场景,完全可以走批量任务。
批量任务的核心流程是:把多个请求体按行写入 JSONL 文件,然后通过官方接口提交。每个请求体都是一次完整的 responses.create 调用参数。提交后异步执行,完成后统一取回结果。这种方式对应模型的标准价格大约打五折。
举个例子,假设你要跑一万条商品描述分类,每条请求约 2K 输入 token、500 输出 token。如果同步逐条调用,费用按全价算;如果打包成批量任务,光这一项就能省一半。代价只是不能立刻拿到结果,一般需要等一段时间。
4.5 一个成本模拟计算
空谈省钱不直观,我做一个模拟账本。假设一个常见问答场景,平均每轮请求带 12K 输入 token,输出 1.5K token,一个月跑 1000 轮,模型按某档常见官方价格估算:输入约 2 美元/百万 token,输出约 10 美元/百万 token。
默认方式下:
- 输入费用:12K × 1000 = 12M token,费用 24 美元
- 输出费用:1.5K × 1000 = 1.5M token,费用 15 美元
- 总计:39 美元
优化后,我做了三件事:把固定系统提示放最前,缓存命中率提升到约 70%;改为只回传最近两轮上下文,每轮输入从 12K 压到 6K;缓存命中部分按原价的四分之一计费。
- 未命中输入:6K × 1000 × 30% = 1.8M,费用 3.6 美元
- 缓存命中输入:6K × 1000 × 70% = 4.2M,费用约 2.1 美元
- 输出费用不变:15 美元
- 总计:约 20.7 美元
如果这个场景允许异步处理,再走批量接口拿五折,总费用还能再压到约 10.35 美元。对比最初的 39 美元,相当于省了七成以上。这里的具体价格只是示例,每个模型的官方单价不同,你需要以自己的定价页为准,但这个计算思路是通用的。
5. 常见问题与排查技巧实录
5.1 响应格式不符合预期
最常见的问题之一,是模型返回的 JSON 里混入了额外文字,或者干脆输出 Markdown 代码块。这类问题在纯文本输入、未声明结构化输出时尤其明显。排查思路不是反复调提示词,而是优先考虑官方结构化输出能力,把格式约束交给接口。
另外,max_output_tokens 设置太短也可能导致输出被截断,JSON 不完整。如果发现输出总是戛然而止,先检查是不是输出上限过小。调参时注意,输出上限不是越大越好,要结合业务真实需要来定。
5.2 状态会话失效或历史丢失
有人会碰到这种情况:第二天继续对话,传了 previous_response_id,模型完全忘了之前聊过什么。大概率是服务端保存的会话有有效期限制,或者你在中间换过模型、改过 store 参数。Responses API 的会话状态不是永久存储,不能当成数据库用。
我的建议是:重要业务信息不要只依赖 previous_response_id。要么在无状态模式下自己把关键信息持久化,要么在每次调用时把核心背景重新写进 input。对长期记忆来说,外部向量库或数据库仍然是更可靠的方案。
5.3 推理模型报参数错误
换用推理模型后,偶尔会看到一些参数不支持的报错。最典型的是 temperature、top_p 这类采样参数。推理模型的思路是让模型自己决定思考深度,直接用温度控制不一定生效,甚至会被拒绝。
遇到这种情况,先看官方文档里该模型支持哪些参数。通常在 responses.create 参数里传 reasoning.effort 来控制思考强度就够了。不要从老接口把整套参数原封不动搬过来。
5.4 缓存命中率远低于预期
缓存是省钱的隐形引擎,但它也有脾气。前缀匹配要求很高,以下情况都会让缓存失效:每次请求都带时间戳;系统提示词里有随机内容;工具定义顺序不固定;请求之间前缀不一致。另外,缓存也有长度限制,超长内容不一定能完整命中。
排查时,先看响应对象里有没有返回缓存命中的 token 数量,用来判断实际命中情况。然后重点检查请求前缀是否“每次都长一样”。如果发现了动态内容在前缀区,把它挪到可变区间里。
5.5 工具调用循环跑飞
启用函数调用后,模型可能陷入“调用工具、回传结果、再调用工具”的死循环。尤其当工具返回结果不理想时,模型会尝试反复调用,token 消耗一路狂飙。
解决方案是限制轮数。在前端代码里维护一个最大迭代次数,比如 5 轮,达到上限就强制返回兜底回答。同时,工具返回结果要尽可能精简明确,如果返回的是大段原始数据,模型很容易在理解上跑偏,导致反复调用。我会在工具返回结果前做一次摘要,只把关键信息回传给模型,效果立竿见影。
6. 最后分享几个我反复踩过的坑
Responses API 用下来,我个人体会最深的一点是:它确实更省事,但“省事”不等于“放任不管”。第一次我把所有请求都切到有状态模式,结果一个长会话跑到三十几轮之后,每轮请求的输入 token 已经膨胀到接近上下文窗口上限,费用比原先无脑重发整段历史还要夸张。后来我学会了一个最简单的习惯:每轮调用前问自己一句,这轮对话真的需要带全部旧历史吗?
第二个坑是提示词版本控制。有一段时间我为了调效果,几乎每天改系统提示词,每次改动都让缓存前缀分叉,缓存命中率掉得厉害。后来我把提示词都收敛到固定版本,只在发布新版本时整体切换,成本才真正稳下来。
如果你准备把现有项目迁到 Responses API,我的建议是不要一次全量替换。挑一个非核心模块先跑起来,把有状态、无状态、工具调用、缓存命中这些基础能力都摸透,再逐步扩大范围。这套接口能帮你省钱,但它更像个需要磨合的搭档,你用对了地方,成本优势才会真正显现出来。