接完大模型 API,第一周账单往往比预估高好几倍。原因不是单价贵,是计费口径和直觉不一样。
一、计价的基本单位是「每百万 token」,不是一个请求
所有厂商的定价页长这样:
| 项 | 计价方式 |
|---|---|
| 输入(input / prompt) | 每百万 token 多少钱 |
| 输出(output / completion) | 每百万 token 多少钱,通常是输入的 2~5 倍 |
| 缓存命中输入 | 输入价的 10%~50% |
| 批量接口(batch) | 常规价的 5 折左右 |
具体数字各家不同且经常调整,不要看博客里的价格,直接看官方定价页。真正要记住的是结构:
- 输出比输入贵得多。让模型写 1000 字,比让它读 1000 字贵 2~5 倍。
- 缓存命中的折扣很猛。重复的长系统提示词,第二次开始可能只要十分之一的价。
- 批量接口便宜一半,代价是几小时内返回,适合离线任务。
二、一次对话到底多少钱:把公式写出来
设:
N_turn = 对话轮数
S = 系统提示词 token
H_i = 第 i 轮用户消息 token
A_i = 第 i 轮模型回答 token
P_in = 输入单价(每 token)
P_out = 输出单价(每 token)
第 i 轮的输入是:
input_i = S + H_1 + A_1 + H_2 + A_2 + ... + H_i
注意这里:每一轮都要把全部历史重新发一遍。所以 N 轮对话的总输入 token 不是 S + ΣH,而是近似:
总输入 ≈ N × S + Σ (N - i + 1) × (H_i + A_i)
这就是第一个烧钱点:历史是平方级增长的。
举个具体例子。假设系统提示词 800 token,用户每轮问 100 token,模型每轮答 400 token,聊 20 轮:
| 项 | 计算 | 结果 |
|---|---|---|
| 系统提示词 | 20 × 800 | 16,000 |
| 用户消息(加权) | 100 × (20+19+...+1) | 21,000 |
| 模型回答(加权) | 400 × (20+19+...+1) | 84,000 |
| 总输入 | 121,000 token |
也就是说,20 轮对话处理了 2,000 token 的新内容,却消耗了 12 万 token 的输入。倍率 60 倍。
如果开了缓存,系统提示词那 16,000 能压到 1,600 左右;但历史消息每轮都在变,缓存命中不了。这就是为什么长对话的成本降不下来。
先用字数统计把每部分的实际字数算出来,再乘换算系数,比拍脑袋准得多:
三、三个烧穿预算的地方
1. 输出单价高,而且没人限制长度
一次请求输出 3,000 token 和输出 300 token,成本差十倍。很多项目在提示词里写「请详细说明」,结果每次回答都写满,一天几千次调用就是几十倍差价。
做法:提示词里给长度上限(「不超过 150 字」),接口层设 max_tokens,两处都卡。
2. 重试是乘法的
for (let i = 0; i < 5; i++) {
try { return await call() } catch { /* 重试 */ }
}
失败重试 5 次,最坏情况成本 ×5。如果失败原因是「输出被截断」,那么每次都会重新消耗完整输入——输入成本 ×5,输出成本也 ×5。
做法:重试上限 2 次,且失败后先判断原因。超时重试有意义,schema 不合法重试意义有限,额度不足重试纯浪费。
3. 没人用的定时任务
「每天凌晨把所有文章重新生成一遍摘要」,上线时觉得不多,跑一个月才发现它比真实用户流量贵十倍。AI 任务特别容易变成这种情况,因为单次便宜、无感、容易加。
做法:每个 AI 任务都记 tokens × 单价,按任务维度出日报。看到哪个任务在榜首,就知道该优化谁。
四、五个降本手段,实测压缩比例
| 手段 | 典型节省 | 代价 |
|---|---|---|
| 压缩系统提示词(删例子、删客套) | 20%~40% | 效果可能轻微下降 |
| 历史对话滚动摘要(只留最近 3 轮 + 摘要) | 50%~70% | 早期细节会丢 |
| 输出长度硬限制 | 30%~60% | 回答变简短 |
| 小模型分流(简单任务不用大模型) | 40%~80% | 要分类判断 |
| 开启缓存(长固定前缀) | 系统提示词部分省 50%~90% | 需前缀稳定 |
| 批处理接口 | 40%~50% | 非实时 |
最值得先做的两个:历史摘要 + 小模型分流。 前者省得最多,后者省得最稳。
小模型分流怎么做?用一个极便宜的判断:消息长度、是否含代码块、是否要计算。短的、闲聊的、格式转换的,全部走小模型;只有长的、要推理的才走大模型。这类规则能覆盖 60% 以上请求。
五、上线前必须做的估算
月成本 = 日调用量 × 30 × (平均输入 token × 输入单价 + 平均输出 token × 输出单价)
填数之前先用真实请求测三次,拿到 usage 里的实际值,别用估的。算的时候直接算:
👉 在线计算器
再乘一个 1.5 的安全系数。原因:上线后提示词会变长、用户输入会比测试长、重试率会高于预期。
一个参考量级(仅示意,不代表任何厂商现价):如果每次请求输入 2,000 token、输出 500 token,一天 1,000 次调用,那么一个月的输入量是 6,000 万 token、输出 1,500 万 token。按「输出 = 4 倍输入价」算,输出那部分贡献了总成本的一半。输出才是成本大头,不是输入。
六、几个容易忽略的计费细节
失败请求也可能计费。 输出到一半被中断、超时断开,只要已经产生了 token,多数厂商照样收。所以「超时重试」是双倍成本。
图片按块算,不按文件大小。 同一张图压缩成 200KB 还是 2MB,计费一样,因为计费看分辨率和切块数。要省就把分辨率降下来。
微调模型的调用价高于基座。 训练是一次性成本,调用是长期的,量大时算总账才知道划不划算。
上下文缓存要求前缀完全一致。 系统提示词里加一个当前时间戳,缓存立刻全废。这类「动态字段放在提示词开头」的写法,是缓存失效最常见的原因。把变化的内容放末尾,固定的放开头。
七、成本监控的最小实现
不需要上监控系统,先做这三件事就够了:
// 1. 每次调用都记账,落到一个表里
await db.insert('ai_usage', {
ts: Date.now(),
task: 'article_summary', // 任务名,必须区分
model: resp.model,
in: resp.usage.prompt_tokens,
out: resp.usage.completion_tokens,
cached: resp.usage.prompt_tokens_details?.cached_tokens ?? 0,
})
// 2. 每天按 task 汇总一次,看排名
// 3. 设日预算告警,超了就降级到小模型或直接停
有了 task 这个维度,才能回答「钱花在哪」。没有它,账单只是一堆数字。
八、小结
- 输出比输入贵 2~5 倍,输出才是成本大头
- 历史对话每轮重新计费,20 轮能让 2,000 token 的新内容消耗 12 万 token
- 重试是乘法,上限 2 次
- 降本优先级:历史摘要 > 小模型分流 > 输出限长 > 缓存 > 批处理
- 预算按实测值 × 1.5 估,别按字数估
最后一条:先测三笔真实请求,再谈成本优化。没有 usage 数据的所有估算,误差都在一倍以上。