第二季第 9 篇 · 收官 。拆解对象:DeepFlux 里所有跟"少花钱"有关的设计------从选模型、走缓存、砍上下文,到给失控调用装天花板。第 51 篇讲过账单怎么看(token 从哪来、怎么写进数据库),这一篇讲怎么减:同样一件事,账单能差出一百倍,差的不是运气,是设计。
先交代真实状态。今天我写了个成本测算脚本,用仓库里的真实价目 (configs/llm/profiles.yaml)和真实公式 (provider.go 的 Pricing.Cost)算了五个场景。为了证明没算错,脚本第一步先把公式复刻一遍,拿仓库自己的单元测试期望值对表:
ini
═══ 对表:仓库 pricing_test.go 期望值 ═══
[OK] input+output: got=0.00028 want=0.00028
[OK] zero pricing = free: got=0.0 want=0.0
[OK] reasoning priced separately: got=0.006 want=0.006
[OK] reasoning free when 0: got=0.002 want=0.002
→ 公式复刻与仓库一致
四条全中,后面的测算才可信。然后是结论最刺眼的那个场景:
bash
═══ 场景 0 · 基准:claude-prod 跑 1000 轮 ═══ 合计 $16.5000
═══ 场景 1 · 换模型:路由到 deepseek-v3 ═══ 合计 $0.5600 → 降 96.6%
同一段对话、同样的 1000 轮,只是换了模型,账单差 29 倍。 这就是本篇要说的事:省钱的第一杠杆不在"省着用",在"用对的东西"。
一、先弄懂三个词
Token :大模型处理文本的计量单位,约等于"一个字/半个词"。它是计费单位------API 按 token 收钱,分两笔:输入(你说给模型的)和输出(模型生成的),输出通常贵 2--5 倍。
上下文窗口(context window) :模型单次能"看到"的 token 上限(DeepSeek 128k、Claude 200k)。它同时是账单放大器 :ReAct 智能体每轮都要把历史消息重新发一遍------第 10 轮发 10 轮的历史,第 40 轮发 40 轮的历史,token 用量随轮数平方级增长。
前缀缓存(prompt cache) :模型厂商提供的折扣机制。如果这次请求的开头跟上次一模一样(比如固定的系统提示词 + 工具清单),厂商可以复用上次的计算结果,命中部分按约十分之一的价格计费。省的是"重复劳动"的钱。
二、阶梯一:选对模型(省 96%)
最贵和最便宜的模型差多少钱?看仓库里的真实价目(USD / 1K token):
| profile | 用途 | 输入价 | 输出价 |
|---|---|---|---|
claude-prod |
Anthropic 代表 | 0.003 | 0.015 |
deepseek-v3 |
国内主力 | 0.00014 | 0.00028 |
ollama-local |
本地推理 | 0 | 0 |
输入差 21 倍、输出差 53 倍。但问题来了:不能所有任务都用便宜模型------复杂推理用便宜模型,答错了重来更贵。
DeepFlux 的答案是按意图分流(2026-07-16,迁移 000093,人工拍板)。流程写在一个文件的头注释里:
每轮用户消息先用 cheapest profile 跑一次 ~几 token 的分类调用 → 标签命中
intent.*routing 键 →ChatRequest.Profile改写为该键
翻译:用户发来一句话,先花几个 token 让最便宜的模型做一次"这是在问什么类型的问题"的分类,然后按分类结果把这一轮交给对应的模型------简单问答走便宜模型,复杂推理走贵模型。分类成本相对主调用可以忽略(注释里专门限了 2000 字符输入和 2 秒超时,"不能拖慢主回路")。
这个设计的工程品格体现在它的兜底上,注释原文:"fail-open:分类超时/出错/标签越界/未装配 routes → 一律回落 cfg.LLMProfile(静态绑定=现状),意图路由只增益不阻断。"------省钱的功能出故障时,退回"按原样走"而不是拒绝服务。省钱永远不能让可用性买单。
三、阶梯二:让缓存命中(省 34%,但账上看不见)
前缀缓存的难点不在"开不开",在各家触发方式不同。DeepFlux 把它做成数据驱动的四种模式(2026-05-28 落地):
| 模式 | 怎么触发 | 谁在用 |
|---|---|---|
none |
不缓存 | ollama / mock |
auto-detect |
厂商自动识别 | OpenAI、DeepSeek |
auto-flag |
请求体加一个布尔开关 | 通义千问 |
explicit-mark |
在内容块上打标记 | Anthropic |
选哪种写在 profile 配置里(一行 cache_policy: {mode: auto-detect}),适配器读配置决定请求怎么改。还有一种模式最麻烦:explicit-mark 模式下,得由调用方告诉适配器"哪段内容是稳定前缀" ------于是有了 CacheHints 结构:系统提示词是否稳定、工具清单是否稳定、前几条消息是稳定前缀。Anthropic 适配器据此在对应位置插 cache_control 标记(系统块末尾、最后一个工具、第 N 条消息)。
我的测算里,70% 前缀命中按 0.1 倍计价,1000 轮从 16.50降到10.83(省 34.4%)。但这里必须诚实 ------仓库代码里有一行注释把真相钉在墙上(anthropic.go:81):
ponytail: cache_read 折扣(Anthropic 缓存读约 0.1× input)未建模,按全价 input 计;上线再补
也就是说:系统知道 命中了缓存(响应里有 cache_read_input_tokens 字段,采集并记录了),但算钱时仍按全价 。真实账单会比这个模型算出来的便宜------省是真省了,只是账面上看不出来。把这个缺口写进注释而不是假装省了,是这套代码一贯的诚实(第 104 篇的假绿、第 106 篇的 ADR,同一种气质)。
四、阶梯三:砍掉装不下的历史(省 47%)
历史越长,每轮重复发送的 token 越多。DeepFlux 的做法叫滑窗截断 (trimHistory),策略写得像口诀:
- 保留头部 N 条(默认 2)------通常是最初的任务说明,丢了模型就不知道要干什么;
- 保留尽量多的最近消息------ReAct 循环的局部一致性(最近的推理和工具结果)最要紧;
- 中间被砍掉的部分,用一条 system 提示替代------让模型知道"这里有省略",而不是让它以为对话本来就是断的。
token 估算用 字符数 ÷ 4 + 50 的粗略上界------注释说明取 4 是"上界友好值"(中文实际更省),宁可高估早截断,也不低估导致超窗口报错。截断的数学效果:我的测算里 40 条历史、10000 token 被压到 4065 token,单轮成本降 47.5%。
第 82、83 篇详细拆过 Eino/ADK 那套更复杂的裁剪中间件(清工具结果、截断大结果、摘要压缩历史)------那是框架层 的方案;这里讲的 trimHistory 是 DeepFlux 自己在构建请求入口处做的最后一道保险,两者不冲突:框架拆不动的时候,本地还有一刀。
五、阶梯四、五:给失控调用装天花板
省钱清单的另一半不是"少花",是"别失控"。两类失控各有闸门:
单次跑飞的护栏(LoopBudgetTracker) 。ReAct 主循环可能陷入"调工具→不满意→再调"的长跑,一轮对话烧掉预算。这个护栏的注释坦白了它的局限:"主循环跑在 Eino react agent 内部,无法逐 step 拦截 (Eino callbacks 无 per-iteration 钩子)"------所以是 best-effort:每轮 LLM 返回后累计真实 token,超限就 cancel 上下文、优雅收尾并记录停止原因。它不能"事前拦",但能保证跑飞有上界。
日账单的天花板(ResLLMCostDaily) 。套餐表里除了月度 token 配额,还有独立的一列日上限,Free/Team/Business 分别是 1 万 / 20 万 / 200 万 token,注释写明用途:"防 LLM 账单爆破 "。这一列跟月度配额量纲相同但用途正交 :月配额管"这个月还能不能用",日上限管"今天最坏也就能花这么多"。看测算------一次失控的 1000 万 token 调用(Claude 价)理论成本 37.50,被日上限钉住后,Free套餐当天最多0.055、Team 1.10、Business11.00:
bash
Free 日上限 10,000 token → 最坏日账单被钉在 $0.0550
Team 日上限 200,000 token → 最坏日账单被钉在 $1.1000
Business 日上限 2,000,000 token → 最坏日账单被钉在 $11.0000
钱的护栏必须是硬上限,不能是"提醒"。 这是账单爆炸类事故与普通性能事故的本质区别:后者慢一点,前者金额无上界。
六、两个"别多付":计价的暗坑
省钱清单的最后一节反过来讲------别在计价上多花冤枉钱。两个真实坑都写进了注释:
坑一:推理 token 的重复计费。 推理模型(OpenAI o 系列、DeepSeek-R1)会先"想一想"再回答,这些思考 token 叫 reasoning token。陷阱在于:厂商一般已把 reasoning 计入 completion token 并按输出价收费了 ,如果系统再按独立的"推理单价"加一遍,就是收两次钱。仓库的规避方式是配置里把 cost_per_1k_reasoning 恒设为 0,并在字段说明里逐行警告:"此处必须留 0,否则重复计费。"我的测算演示了后果:某轮 1000 个 completion token 全是推理,正确计费 0.0020,重复计费0.0060------一轮多付 200%。
坑二:CostUSD 从恒 0 到真实计算。 2026-06-24 的 commit(01fa5401)标题就叫"LLM 成本计价管线 ------ CostUSD 从恒 0 到真实计算"。在此之前,账单里每一笔成本字段都是 0------计量在跑、数字在涨、钱数是假的 。这个坑的教训不是"代码写错了",而是:一个恒为 0 的字段,比一个缺失的字段更危险------它看起来"有数据",报表上是一排整齐的 0,没人会怀疑。第 104 篇的"假绿"、第 106 篇的"幽灵事件",都是这一族。
小结:省钱的优先级
把五道闸门按杠杆大小排一遍(这是本篇最实用的一张表):
| 优先级 | 手段 | 量级 | 关键设计 |
|---|---|---|---|
| 1 | 选对模型(意图路由) | 省 ~96% | 分类用最便宜模型 + fail-open 回落 |
| 2 | 前缀缓存 | 省 ~34% | 四模式数据驱动;折扣未建模=账上偏贵 |
| 3 | 滑窗裁剪 | 省 ~47% | 保头保尾 + 中间插省略提示 + 高估早截 |
| 4 | 日额度封顶 | 钉死最坏情况 | 与月配额正交;硬上限非提醒 |
| 5 | 循环预算 | 跑飞有上界 | best-effort,注释坦白了局限 |
三条跨场景的原则,值得带走:
- 省钱的第一问是"这活儿配哪个模型",不是"怎么让它少说两句"。 96% 和 30% 的差距不在一个数量级------先动模型,再谈技巧。
- 省钱的工程不能牺牲可用性。 意图分类失败回落静态绑定、裁剪高估宁可早截------每一个省钱机制都配了"失效时的退路",没有一条是"省不了就报错"。
- 账要算得准,也要算得诚实。 reasoning 重复计费(多收)与缓存折扣未建模(少算优惠)是两个方向的错------前者要代码规避、后者要注释披露。一个成本系统最大的价值不是显示一个数,是这个数可以拿来对账。
第二季小结(101--109)
九篇走完,第二季的主题是"主库之外的世界"------把平台拆开看它的边缘:
- 交付 (101 装进断网机房 / 108 命令行)
- 前端 (102 微前端 / 105 浏览器 SSE)
- 代码治理 (103 充血重构 / 104 DDD 铁律门禁)
- 后台机制 (106 Outbox + Cron / 107 WASM 沙箱)
- 成本(109 本篇)
加上第一季的 100 篇,这个系列累计 109 篇散文,拆的是同一套代码、同一个判断标准:跑过 demo 才写,设计标设计、演练标环境、没验证的点名。第二季的九篇里,103/105/106/107 各有一个亲手跑通的 demo,101/102/108 是只读实跑与代码勘察。