账单是 AI 应用从玩具走向产品的第一道关卡:demo 阶段一次调用几厘钱无人在意,等到日调用十万次,同样的代码每月烧掉几百上千美元。好消息是,OpenAI 把省钱的旋钮都摆在了明面上——换更便宜的模型档位、让重复前缀走缓存、把不急的任务丢进批量通道,三件事都不需要重构架构。本系列第 7 篇讲透了 usage 字段怎么看,本篇补上后半程:token 怎么换成美元,以及这笔钱怎么降。
本篇是《OpenAI Python SDK 从入门到实战》系列第 8 篇,也是第 1 篇路线图"工程落地"阶段的第一站。文中提示缓存与 Batch API 的口径核实自官方 developers.openai.com 文档(2026 年 10 月)。全文按"三杠杆全景 → 账单计算 → 模型降档 → 提示缓存 → 批量通道 → 速度优化 → 千条分类实战 → 总结"组织。
1、成本优化三杠杆
一次调用的成本等于模型单价乘以 token 数,等式两边各有一个旋钮。单价侧是模型档位,第 2 篇的 Astra、Sol、Terra、Luna 四档价差最高 100 倍;数量侧是缓存与批量——前者把重复输入的计费倍率打到常规的 0.05 到 0.1 倍,后者把整批请求直接五折。加上"实时与否"这个前置条件,决策图如下:
解读:三个问题按顺序过——先问实时性,不急的任务直接走批量通道,五折落袋;急的任务再问复杂度,简单任务降档到 Luna;最后问输入形态,有稳定长前缀的让提示缓存自动摊薄。三个杠杆不互斥,降档与批量可以叠加,第 7 节的千条分类会把叠加后的账一起算。还要点破一层关系:省 token 与降延迟常常是同一件事,输入变短、思考变少,钱和时间一起降,第 6 节专门展开。
2、从 usage 到美元账单
第 7 篇讲过 usage 三件套:input_tokens 记全部输入,output_tokens 记全部输出,命中缓存的部分在 input_tokens_details.cached_tokens 里单列。要算美元,把 token 数乘上模型单价再除以一百万,顺手处理缓存折价即可。计费小函数如下:
PRICE = { # 每百万 token 单价:输入、输出、缓存读取
"gpt-6-astra": (10.0, 50.0, 1.0),
"gpt-6.1-sol": (2.0, 10.0, 0.10),
"gpt-6.1-terra": (1.0, 5.0, 0.05),
"gpt-6-luna": (0.1, 0.5, 0.01),
}
def cost(resp, model="gpt-6.1-terra"):
"""按 usage 拆三段计价,返回本次调用的美元成本"""
p_in, p_out, p_cached = PRICE[model]
u = resp.usage
cached = u.input_tokens_details.cached_tokens
fresh = u.input_tokens - cached # 未命中缓存的输入
return round((fresh * p_in + cached * p_cached
+ u.output_tokens * p_out) / 1e6, 6)
解读:缓存读取单价按官方口径取常规输入的 0.1 倍,GPT-6.1 Sol 与 Terra 更低到 0.05 倍;批量任务另有整体五折,第 5 节单独展开。代一组数字进去:Terra 一次调用输入 1240 token、其中 1216 命中缓存,输出 186 token,成本就是 (24 × 1 + 1216 × 0.05 + 186 × 5) / 1000000,约 0.00101 美元。单次调用便宜到没有体感,所以这个函数的真正用途是聚合——累计进会话对象、按天落库,成本曲线才看得见形状。单价以官方定价页为准,表中数字截至 2026 年 10 月。
3、模型降档的适用边界
四档价差最高 100 倍,降档是三杠杆里杠杆率最高的一根。但降档的判断标准不是"任务看起来简单",而是输出能否被机器校验:结构化提取有 schema 校验兜底(第 4 篇),分类打标有标签集合约束,这类任务的错误无处可藏,Luna 完全接得住;反之,开放式写作与多步推理没有便宜的自动评分手段,降档风险只能靠人肉回归来兜,省下的钱常以调试时间还回去。常见任务的对号入座如下:
| 任务形态 | 输出特点 | 建议档位 | 依据 |
|---|---|---|---|
| 多步推理与 Agent 主脑 | 长链决策,错误会放大 | Astra 或 Sol | 降档省小钱,返工花大钱 |
| 日常写作与代码生成 | 单步成文,人可抽查 | Terra | 质量与成本的均衡点 |
| 结构化提取与打标 | 短输出,schema 可校验 | Luna | 校验通过率就是质量护栏 |
| 会话摘要与历史压缩 | 短文改写,损失可容忍 | Luna | 第 7 篇压缩已实践 |
降档的正确姿势是逐级试、数据说话:先用高档位跑出基线准确率,再降一档用同一批数据对比,达标就固化,不达标退回。第 7 篇的摘要压缩是现成例子——压缩任务配 Luna,正是第 2 篇选型结论的直接应用。
4、提示缓存与省钱账
4.1、前缀命中与自动模式
提示缓存(Prompt Caching)的原理一句话讲完:服务端把处理过的输入前缀存成内部状态,下次请求的开头部分逐 token 相同就直接复用,不再重算。它对 GPT-6 家族默认开启、无需任何参数——SDK 侧一个开关都不用碰,官方称隐式模式;要精细控制缓存断点位置时才用 prompt_cache_options 显式标记,日常场景隐式就够。两条硬边界要记牢:前缀达到 1024 token 才可缓存;命中要求前缀完全一致,所以稳定内容放最前、变化内容放最后,多轮对话追加消息而不是重写历史——第 7 篇说过滑动窗口每滑一次前缀就变、命中率随之下降,伏笔在这里兑现。
价格口径:缓存读取按常规输入的 0.1 倍计费,GPT-6.1 Sol 与 Terra 低到 0.05 倍,最多省 95%;写入有溢价,默认 30 分钟存活的写入按 1.25 倍计,12 小时存活按 2.5 倍计。写入溢价听着吓人,算总账依然大赚,下一小节用数字说话。
4.2、命中实测与总账
命中量就记录在第 7 篇 usage 摘录里露过一面的 cached_tokens 字段中。固定前缀连调两次,观察它的变化:
from openai import OpenAI
client = OpenAI()
RULES = "你是评论审核员……(一千多 token 的规则与示例,固定不动)"
COMMENT = "评论:物流太慢了,等了十天!"
for i in (1, 2):
resp = client.responses.create(
model="gpt-6.1-terra",
instructions=RULES, # 稳定内容放最前
input=COMMENT, # 变化内容放最后
)
d = resp.usage.input_tokens_details
print(f"第 {i} 次:input={resp.usage.input_tokens}, cached={d.cached_tokens}")
输出类似:
第 1 次:input=1240, cached=0
第 2 次:input=1240, cached=1216
解读:第一次是写缓存,cached_tokens 为 0;第二次同前缀到达,1216 token 按读取价计费——正是第 2 节代进计费函数的那组数字。再算一笔总账:固定前缀 2000 token、Terra 档连续调用 100 次,无缓存的输入费是 200000 × $1/M 即 $0.20;有缓存则是首次写入 2000 × 1.25 加 99 次读取 198000 × 0.05,折合 12400 个常规输入等效 token,约 $0.0124,省 94%。前提是这 100 次落在 30 分钟存活窗口内,隔夜任务会过期重写;跨更长周期复用的前缀可把存活期设为 12 小时,写入溢价升到 2.5 倍,核算方法相同。细账与进阶玩法见官方提示缓存指南。
5、Batch API 半价批处理
5.1、场景与规则
有一类任务天然不急:每日数据集打标、历史语料向量化、全量评论重审——今天出结果还是明早出结果没有区别。Batch API 为它们而生:把成千上万个请求打包成文件提交,OpenAI 在 24 小时窗口内跑完(多数时候快得多),整批五折,还走独立的限流池、不占同步接口的额度。规则要点如下:
- 输入是 JSONL 文件,每行一个完整请求,带自定义
custom_id - 一个文件只装一个模型的请求,单批至多 5 万行、文件至多 200 MB
- 完成窗口目前只有 24h 一种;到期未跑完转 expired,已完成部分照常计费
- 结果按
custom_id对回原请求,输出行序不保证与输入一致 - 失败请求单独写进错误文件;结果文件在完成后保留 30 天
5.2、三段代码流程
流程固定三段:写文件、传文件建任务、收结果。第一段把千条评论写成 JSONL,每行的 body 就是 responses.create 的参数原样:
import json
def make_request(cid, comment):
return {
"custom_id": cid, # 结果回查的钥匙
"method": "POST",
"url": "/v1/responses",
"body": {
"model": "gpt-6-luna",
"instructions": "把评论分为好评、中评、差评三类,只输出标签",
"input": comment,
},
}
with open("batch_input.jsonl", "w", encoding="utf-8") as f:
for i, c in enumerate(comments, 1):
f.write(json.dumps(make_request(f"req-{i}", c),
ensure_ascii=False) + "\n")
第二段上传文件并创建批量任务,SDK 的入口是 client.batches:
from openai import OpenAI
client = OpenAI()
f = client.files.create(
file=open("batch_input.jsonl", "rb"),
purpose="batch", # 批量专用用途标记
)
batch = client.batches.create(
input_file_id=f.id,
endpoint="/v1/responses", # 与文件内 url 保持一致
completion_window="24h",
)
print(batch.id, batch.status) # batch_xxx validating
第三段轮询状态,完成后下载结果文件解析。状态从 validating 经 in_progress、finalizing 走到 completed,拿不准就隔几分钟查一次:
import time
while (b := client.batches.retrieve(batch.id)).status not in (
"completed", "failed", "expired", "cancelled"):
print(b.status, b.request_counts) # 进度:已完成与总数
time.sleep(300)
results = {}
for line in client.files.content(b.output_file_id).text.splitlines():
r = json.loads(line)
body = r["response"]["body"] # 结构同同步响应对象
results[r["custom_id"]] = body["output"][0]["content"][0]["text"]
解读三段流程:body 里连 instructions 都原样带进批量文件,同步代码几乎零改造就能搬家;custom_id 是唯一可靠的对应关系,别指望行号;每行结果里同样带 usage,收完用第 2 节的计费函数汇总一遍,批量是否真打了五折,自己对账便知。完整规则与状态清单见官方 Batch API 指南。
6、响应速度的朴素优化
省钱与提速经常是同一个动作的两种读法。回顾第 3 篇的拆解:一次等待由排队、首 token 延迟、剩余生成三段构成,提速就是逐段压。提示精简作用于全部三段:输入越长,服务端读入越慢、费用越高,删掉用不上的示例与客套话是零成本优化;结构化任务交给第 4 篇的 schema 约束,比长篇文字说明又短又稳。缓存作用于首 token:命中的前缀不必重算,官方文档还提供 prewarm 预热选项,应用启动时先把公共前缀写进缓存,用户第一问就省掉大半等待。
思考量是常被忽略的一档。GPT-6 家族不传参数时默认 medium 推理档,复杂任务值得,分类提取这类任务纯属浪费——思考 token 按输出计费(第 7 篇的 reasoning_tokens 字段),调低一档钱和时间一起降:
resp = client.responses.create(
model="gpt-6.1-sol",
input=QUESTION,
reasoning={"effort": "low"}, # 默认 medium,简单任务降为 low
)
print(resp.output_text,
resp.usage.output_tokens_details.reasoning_tokens)
解读:effort 常用 low、medium、high 三档,个别模型还有更高档位,支持情况随模型演进,用前查官方模型文档。吞吐侧的朴素手段是并行:相互独立的请求用第 3 篇的 AsyncOpenAI 并发发出,一个进程同时跑几十路,总耗时取决于最慢一路而非全部之和;交互链路再叠加第 3 篇的流式输出,真实等待与体感等待各优化各的。
7、千条评论分类实战
把三杠杆合到同一个任务上算总账。场景:一千条商品评论分成好评、中评、差评,每条输入约 200 token(规则加评论本体),输出约 10 token(一个标签),总量输入 20 万 token、输出 1 万 token。四个方案的价格如下:
| 方案 | 模型 | 通道 | 千条成本 |
|---|---|---|---|
| A | Terra | 同步直调 | $0.25 |
| B | Terra | Batch | $0.125 |
| C | Luna | 同步直调 | $0.025 |
| D | Luna | Batch | $0.0125 |
账很好算:A 是 0.2 × $1 加 0.01 × $5 等于 $0.25;B 在此基础上整体五折;C 换 Luna 后单价降为十分之一;D 两杠杆叠加再五折,从 A 到 D 正好 20 倍差距。工程路径三步走:
- 定档:抽 100 条人工标注做金标准,Luna 跑分达标就定 Luna
- 搬家:同步调用的参数原样填进第 5 节的 JSONL 模板提交
- 复核:按
custom_id对回评论,抽检标签并汇总usage对账
三个步骤里定档是关键,第 3 节的"逐级试"落到真实数据上:Luna 达不到准确率要求就升 Terra 重试,B 与 C 之间选贵的那个,总比上线后返工便宜。
注意:分类的 instructions 是千条共享的固定前缀,同步直调时它会被提示缓存摊薄;但批量执行是否命中缓存官方未作承诺,预算时按全价再五折估算最稳妥。
8、总结
成本三杠杆各管一段:降档动单价,缓存动重复输入的计费倍率,批量动整批折扣。账单从 usage 出发:input_tokens 减去 cached_tokens 是常规输入,各乘单价再除以一百万就是美元。执行顺序建议从便宜的先试——能进批量的任务先吃五折,输出可校验的任务降档 Luna,高频重复的前缀交给自动缓存,最后用计费函数把每笔账算清楚再谈优化。对于刚把应用推向生产的读者,建议先把 usage 落库画成曲线,看清钱花在哪,再按本篇顺序拧旋钮——拍脑袋优化不如一张账单表。
留一道练习:把第 5 节的批量脚本跑在一个真实数据集上,收结果后用第 2 节的函数汇总实际成本,与提交前算的预算对比,验证五折口径;再从错误文件里捞出失败请求,修好后重新组批提交。想加难度,给脚本加上"Luna 准确率不达标自动升 Terra 重跑"的逻辑,把第 7 节定档那步的人工判断变成代码。

