先分清三种成本
看到「输入每百万 Token 0.15 美元」时,很容易以为任务成本就是 token 数乘单价。实际账单里至少有三层:模型调用成本(输入、输出、缓存命中)、任务执行成本(重试、工具调用、上下文重复发送)、完成任务成本(再加上人工修改和等待时间)。前两层能从日志里算,第三层必须把人的时间折进去。只比较第一层,结论通常是错的。
这篇用 MiniMax M3.1 Flash 的公开价格做例子:输入 0.15 美元/百万 Token、输出 0.60 美元/百万 Token、缓存输入 0.015 美元/百万 Token。换成你正在用的模型时,把单价替换掉即可,流程不变。
准备 50 个真实任务,而不是 5 个演示
从最近两周的实际工作里挑任务,按五类各取 10 个:信息抽取、代码修改、多步工具调用、长文写作、需要拒绝或转人工的边界请求。每类都要有明确的完成标准,例如「抽出的 8 个字段全部正确」「测试通过且没有新增失败」「工单被分到正确队列」。不要用网上现成的 benchmark 题目,它们通常已经被模型见过。
- 信息抽取:从合同、发票、网页里取固定字段,答案可逐项核对。
- 代码修改:仓库里能复现的小 bug 或缺失测试,以测试结果判定。
- 多步工具调用:需要查两次以上数据、再执行一次写操作的任务。
- 长文写作:有字数、结构、事实约束的文稿,人工按清单打分。
- 边界请求:信息不足、权限不够或不该回答的请求,正确行为是停下来问人。
统一跑法,别让变量混进来
每个任务用同一段系统提示词、同一组工具、同一温度参数,分别在候选模型上跑。每个任务至少跑两次,观察结果是否稳定;只跑一次的例子很容易把运气当成能力。把每次调用的原始记录留下来,包括完整的消息、工具参数、返回值和错误,后面算账和复查都靠它。
task_id,model,run,input_tokens,output_tokens,cached_tokens,retries,tool_calls,latency_ms,completed,human_edits,error
T01,m3.1-flash,1,1820,340,0,0,0,2100,1,0,
T01,cloud-current,1,1820,410,0,1,0,5400,1,2,
T02,m3.1-flash,1,2600,120,1800,0,1,1800,0,0,missing_fieldcompleted 只记「按完成标准通过」,不记「模型说它完成了」。human_edits 记人工改了几处,不是改了几分钟;分钟数另外记,避免把「改一个标点」和「重写一半」算成一样。
算单任务成本:先把重试算进去
单次调用成本 = 输入 token × 输入单价 + 输出 token × 输出单价 + 缓存命中 token × 缓存单价。一次任务如果重试了两次,要把三次调用的 token 全加上;很多对比只算成功那次,结果低估了不稳定模型的实际成本。
PRICE = {
'm3.1-flash': {'in': 0.15, 'out': 0.60, 'cache': 0.015},
'cloud-current': {'in': 3.00, 'out': 15.00, 'cache': 0.30},
}
def call_cost(row):
p = PRICE[row['model']]
return (row['input_tokens'] * p['in']
+ row['output_tokens'] * p['out']
+ row['cached_tokens'] * p['cache']) / 1_000_000
# 按任务汇总:同一 task_id 的所有 run 与 retry 都算进去
task_cost = df.groupby(['task_id', 'model']).apply(
lambda g: g.apply(call_cost, axis=1).sum()
).reset_index(name='model_cost_usd')把人工修改折进去,才知道谁真的便宜
人工成本用「修改分钟数 × 你的内部小时费率 ÷ 60」估算。小时费率不必对外公开,但团队内部要统一口径。然后把「每个完成任务」的成本定义为:所有任务的总成本(模型 + 人工 + 工具调用)除以通过的任务数。没通过的任务也要计入分母前的总成本,否则失败越多、单任务看起来越便宜。
HOURLY = 300 # 内部口径,示例
df['human_cost'] = df['edit_minutes'] / 60 * HOURLY
df['total_cost'] = df['model_cost_usd'] + df['human_cost']
passed = df[df['completed'] == 1]
cost_per_success = df['total_cost'].sum() / max(len(passed), 1)
print(f'每个完成任务成本: ${cost_per_success:.3f}')还要单独看「一次通过率」和「p95 延迟」。一个模型可能单任务便宜,但一次通过率低、重试多,最后每个完成任务反而更贵;另一个模型单价高,却能在长任务上一次做完,人工修改少。把这三个数放在同一行,路由决策才有依据。
写成路由规则,而不是一句「用便宜的」
按任务类型看结果,通常会得到一张混合路由表,而不是一个全局赢家。把规则写成可执行的条件,并给每条规则配一个回退模型:便宜模型连续失败两次就升级,升级后仍失败就转人工。规则要能随日志自动统计,否则一个月后就没人记得为什么这么配。
- 信息抽取、格式转换:优先低价模型,要求一次通过率 ≥ 95%,失败直接重试一次。
- 代码修改:按仓库规模分档,小改动用低价模型,跨文件重构用强模型。
- 多步工具调用:先看工具调用正确率,低于 90% 不要用低价模型硬扛。
- 边界请求:任何模型都必须能停下来问人;不能拒绝的模型不进入生产。
- 所有类型:单任务成本超过预算上限时,降级到更小模型或转人工,并记录原因。
三个常见误差
第一,缓存命中价和普通输入价差异很大,日志里要单独记 cached_tokens,否则会把缓存命中算成原价或反过来。第二,多轮任务每轮都会重复发送历史上下文,输入 token 会随轮数增长,不能拿第一轮的 token 数乘轮数。第三,工具调用失败后的重试往往带着完整上下文,成本比看起来高;把 tool_calls 和 retries 分开记,才能定位是哪一类失败在烧钱。
边界:这是决策依据,不是 benchmark
50 个任务只能回答「在你的场景里,哪个模型更划算」,不能证明模型在公开榜单上的强弱。样本要覆盖真实流量结构,否则平均数会被某一类任务带偏;上线后每月用新任务重跑一轮,模型更新、价格调整、工具变化都会让旧结论过期。把任务集、原始日志和计算脚本放在同一个目录,下一次只换模型和单价,不重新设计流程。