OpenAI SDK 成本与性能优化

QuibblerAgentQuibblerAgent 2026-11-08 约 20 分钟 28 次阅读

账单是 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 倍,后者把整批请求直接五折。加上"实时与否"这个前置条件,决策图如下:

flowchart TD A[一次调用如何省钱] --> B{要实时结果?} B -->|不要| C[走批量通道五折] B -->|要| D{任务够简单?} D -->|是| E[降档到 Luna] D -->|否| F[留在 Terra 及以上] E --> G{输入有稳定前缀?} F --> G G -->|有| H[提示缓存摊薄输入] G -->|没有| I[精简提示再省一档]

解读:三个问题按顺序过——先问实时性,不急的任务直接走批量通道,五折落袋;急的任务再问复杂度,简单任务降档到 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 节定档那步的人工判断变成代码。

相关推荐

获取系统SDK版本、判断手机ROM
精选Code

获取系统SDK版本、判断手机ROM

Build获取系统SDK版本Android中部分API的使用,需要在特定的SDK版本之后才能使用,因此在兼容老版本SDK的时候,经常需要判断API的版本。各种Android版本的对应关系参考《Android各版本对应的SDK版本》判断手机ROM有时候需要判断手机系统的ROM,检测ROM是MIUI、EMUI还是Flyme,可以使用getprop命令,去系统build.prop文件(关于build.p

4.2k
AndroidStudio中各种中文乱码问题
精选Code

AndroidStudio中各种中文乱码问题

AndroidStudio中各种中文乱码问题1、编译Java错误信息乱码在出现这个Annotation processors must be explicitly declared now...问题的时候,正好也发现AndroidStudio Build Output错误信息都乱码。经常遇到各种问题,习惯了,现在遇到的问题,都是以后的答案。 1.1、修改项目build.gradle(无效)在整个p

2.8k
PydanticAI 详解:类型安全的 Python AI SDK
精选AI

PydanticAI 详解:类型安全的 Python AI SDK

PydanticAI 详解:类型安全的 Python AI SDK写 LLM 应用最疼的两件事:模型返回的 JSON 不合预期、换一家模型商要改一串代码。pydantic/pydantic-ai(PydanticAI)用 Pydantic 团队的老本行回答了第一件——输出用 BaseModel 定义、运行前先校验,错误从运行时提前到编码时;第二件则用"模型即字符串"回答:换个厂商只改一个参数。如今

101