Day 56 / 共 68 天 · 阶段 10 可信可观测

成本治理 & 缓存:别让账单吓一跳

昨天(Day 55)学会了「看表」——盯住 token、延迟、错误率。今天解决下一个现实问题:怎么少花钱。大模型是按 token 计费的,一个没管好的 Agent 一个月能烧掉几万块。今天四招:先把账算清(预算归因,搞清谁花的)、把活分级(模型路由,贵活派贵模型、便宜活派便宜模型)、重复的问题直接查缓存(prompt/结果缓存)、能攒一批的用批处理打折。学完你就能把成本砍掉一大半,还能给老板讲清「钱花哪了」;这也是阶段 10 的收官,明天(Day 57)进入部署篇,学 Docker 打包。

📍 你在阶段 10(可信可观测 D53-56)的位置
D53 防幻觉 Critic D54 失败闸门&护栏 D55 可观测 D56 成本治理&缓存
💡 用一个类比兜住今天(今天全程沿用「过日子管家用钱」的世界观) 运营一个 Agent = 当一个精打细算的家庭管家token 计费 = 家里的水电费(用多少算多少);预算归因 = 记账本(搞清这个月钱花在谁身上、哪个功能最烧钱);模型路由 = 会持家的智慧(买菜去菜市场、办大事才请专家,不能顿顿下馆子);缓存 = 昨天熬的汤留着今天热一热(同样的问题别花两次钱);批处理 = 超市大包装比零买便宜(能攒一起买就不零买)。今天你从「大手大脚」升级成「会过日子的管家」。
L01

成本为何会悄悄失控

🤔 痛点开发时随便测测没几个钱,一上线用户量一涨,月底账单直接翻十倍。更糟的是你还说不清「这钱到底花哪了」——是某个功能太费,还是有人恶意刷,完全一头雾水。
💡 本质大模型按 token 计费,输入和输出分开算价,越贵的模型单价越高。成本 ≈ 请求量 × 每次 token 数 × 单价。三个数任何一个失控,账单就爆。就像家里水费:用水次数、每次用量、水价,任何一个涨了都心疼。治理成本 = 盯住这三个乘数,逐个压下去。
账单 = 请求量 × 每次 token × 单价(三个乘数,逐个砍) 请求量 缓存↓ / 限流 × 每次 token 精简 prompt ↓ × 单价 路由到便宜模型↓
图注:今天的每一招,都对应砍这三个乘数中的某一个。心里有这个公式,就不会瞎优化。
👶 为什么输入 token 也要钱?我又没让它生成那么多。因为模型每次都要「读一遍」你给它的所有内容(系统提示 + 历史对话 + 检索到的文档),读进去的每个字都算输入 token。所以一个塞了超长 prompt、超长历史的 Agent,哪怕只回一句话,输入这头也很烧钱。Day 18 学的「上下文工程」不只是为了效果,也是为了省钱。
L02

预算归因:先记账,才知道砍谁

🤔 痛点老板说「成本太高,砍一半」。可你连「钱花在哪个功能、哪个用户、哪个模型」都不知道,怎么砍?乱砍可能把最有价值的功能砍没了。
💡 本质预算归因 = 给每一笔 token 花费打上标签(谁花的、哪个功能、哪个模型),这样能按标签汇总,一眼看出「大头在哪」。就像家庭记账本按「买菜/交通/教育」分类,月底一看就知道哪项超支。这一步直接接上昨天的可观测——你已经在日志里记了 token,现在只要加几个标签再汇总。
# 复用 Day55 的日志思路,但多打几个「归因标签」
def log_cost(feature, user_id, model, in_tok, out_tok):
    # 各家单价不同,这里用「每百万 token 多少美元」的示意价自己算
    price = {"cheap": (0.25, 1.25), "pro": (3.0, 15.0)}[model]  # (输入价, 输出价)
    cost = in_tok / 1e6 * price[0] + out_tok / 1e6 * price[1]
    print({"feature": feature, "user": user_id, "model": model,
           "cost_usd": round(cost, 5)})   # 带标签,事后能按 feature/user 汇总
    return cost

# 一天下来把所有记录按「功能」汇总,就知道谁最烧钱
log_cost("摘要", "u1", "pro", 3000, 800)     # 贵模型 + 长输入 → 最烧
log_cost("闲聊", "u2", "cheap", 200, 100)    # 便宜活
📝 举个例子:归因一下就抓到「元凶」 汇总一个月的日志,按功能分类:
文档摘要功能:占总成本 68% ← 元凶!(因为每次塞整篇长文档进去)
问答功能:22% 闲聊:10%
结论清晰:优化重点就是「文档摘要」——先做检索只塞相关段落、或换便宜模型。没有归因,你会平均用力;有了归因,你精准打击。
👶 归因标签打哪几个够用?三个最实用:feature(哪个功能)、user_idtenant(哪个用户/客户,SaaS 里要按客户分摊成本)、model(哪个模型)。再加 trace_id 就能顺藤摸瓜查到具体那一次。
L03

模型路由:便宜活别请贵专家

🤔 痛点很多人图省事,所有请求都用最强(最贵)的模型。可「今天天气怎么样」这种简单活,用顶级模型是杀鸡用牛刀,钱哗哗流。
💡 本质模型路由 = 先判断这活难不难,简单活派给便宜/小模型,难活才派给贵/强模型。回顾 Day 15 学的分流思想。就像家里办事:买瓶酱油自己去菜市场(便宜),打官司才请律师(贵)。关键是要有个「分诊台」先判断难度。
分诊台:先看难度,再决定派哪个模型 请求进来 → 分诊 简单活 → 便宜小模型 闲聊/分类/改写,省钱 难活 → 贵强模型 复杂推理/写代码,花钱值
图注:分诊本身可以用规则(关键词/长度)或一个便宜小模型来判难度,成本极低,收益很大。
def route(question):
    # 最朴素的分诊:用规则判难度(真项目里也可用小模型判)
    hard_signals = ["为什么", "分析", "写代码", "对比", "推理"]
    is_hard = len(question) > 60 or any(k in question for k in hard_signals)
    return "pro" if is_hard else "cheap"   # 难→贵模型,简单→便宜模型

print(route("你好呀"))               # → cheap(便宜活)
print(route("分析这三个方案的优劣并给建议"))  # → pro(难活值得花钱)

👶 小白:那我全用便宜模型不就最省了吗?

👨‍🏫 老师:省是省,但便宜模型难活会答砸,砸了要么用户跑了、要么你得重试反而更贵。目标不是「最便宜」,是「在满足质量的前提下最便宜」——这就要靠昨天学的可观测:一边路由省钱,一边盯着质量分别掉。省钱和质量是跷跷板,可观测就是那把尺。还有个进阶招叫「便宜模型先试,不行再升级到贵模型」(级联),但要小心别把两次都花了。

L04

缓存:一样的问题别花两次钱

🤔 痛点「你们的退货政策是什么?」这个问题一天被问 800 次,答案几乎一模一样。每次都真的去调一遍模型,等于同一道菜天天从头做——又慢又费钱。
💡 本质缓存 = 把「问过的问题 → 生成的答案」存起来,下次一样的问题直接端出来,不再调模型。省钱又秒回。就像昨天熬好的汤放冰箱,今天热一热就能喝,不用重新熬。两个层次:① 结果缓存(整个答案存起来,命中就 0 花费);② 提示词缓存 prompt caching(很多模型 API 支持:把不变的长前缀——如系统提示、固定文档——缓存起来,重复部分打折计费)。
缓存类型存什么省多少
结果缓存「问题 → 完整答案」映射(自己用字典/Redis 存)命中就完全不花钱,还秒回
提示词缓存不变的长前缀(系统提示/固定文档),模型侧缓存重复读的那部分 token 大幅打折
cache = {}   # 最简单的结果缓存:一个字典(真项目用 Redis,能设过期时间)

def ask_with_cache(question):
    key = question.strip().lower()      # 归一化:去空格、转小写,提高命中率
    if key in cache:                    # 命中缓存
        print("💰 命中缓存,0 花费,秒回")
        return cache[key]
    answer = call_model(question)       # 未命中才真花钱调模型
    cache[key] = answer                 # 存起来,下次直接用
    return answer
📝 举个例子:缓存命中率的威力 客服 Agent 上线后统计:40% 的问题是重复的高频问题。加上结果缓存后,这 40% 请求成本直接归零、响应从 2 秒变 10 毫秒。一行字典就能让账单立减近四成——缓存是性价比最高的一招。
👶 缓存有什么坑要小心?三个:①时效——政策改了,旧答案还在缓存里会误导人,所以要设「过期时间」或改动时清缓存;②个性化——「我的订单到哪了」这种因人而异的问题不能缓存;③命中率——问法千变万化(「退货」vs「怎么退」)会降低命中,可用「语义缓存」(问题也 embedding,相似就算命中)提升,但那要多花点检索成本,权衡着用。
L05

批处理:能攒一起买就别零买

🤔 痛点有些活不急着马上要结果——比如「把一万条用户评论都分个类」「给一批文章各写个摘要」。一条一条实时调,又慢又按原价算,没必要。
💡 本质批处理(batch) = 把大量「不着急」的任务攒成一批,一次性提交,模型厂商通常给明显折扣(代价是结果不是实时,可能等几分钟到几小时)。就像超市大包装比零买便宜、批发比零售便宜。关键判断:这活是「用户在等着的」(实时)还是「后台慢慢跑的」(可批)?
实时 vs 批处理:按「用户等不等」来选 ⚡ 实时 in-line 用户正盯着屏幕等 聊天/问答/客服 原价,但必须快 📦 批处理 batch 后台慢慢跑,不着急 批量分类/打标/摘要 明显打折,可等
图注:一个成熟系统往往「两条腿走路」——实时通道保体验,批处理通道降成本。
各家批处理 API 的用法、折扣、最长等待时间不一样,别背具体数字。记住判断标准:用户在实时等 → 走实时;后台离线任务 → 走批处理省钱。再加一招:把不急的活攒到低峰期跑,也能错开限流。
L06

预算闸 + 组合拳:把四招串起来

🤔 痛点就算前面都做了,万一有人恶意刷接口、或代码 bug 死循环调模型,一夜之间也能烧穿预算。省钱之外,还得有个「保险丝」。
💡 本质预算闸(budget guardrail) = 给花费设上限,快超了就报警、降级或直接拦。回忆 Day 54 的「护栏/闸门」思想——这是它在「钱」这个维度的应用。就像家里设的信用卡限额,刷爆自动停。把「归因 + 路由 + 缓存 + 批处理 + 预算闸」串起来,才是完整的成本治理。
DAILY_BUDGET = 50.0        # 今天最多花 50 美元
spent_today = 0.0

def guarded_call(question):
    global spent_today
    if spent_today >= DAILY_BUDGET:                 # 预算闸:超了就拦
        return "⚠️ 今日预算已用尽,请稍后或联系管理员"
    # 组合拳顺序:先查缓存 → 再路由选模型 → 记账归因
    ans = ask_with_cache(question)                  # 1) 缓存:命中就 0 花费
    model = route(question)                         # 2) 路由:难度决定模型
    cost = log_cost("qa", "u1", model, 500, 200)    # 3) 归因:记这笔账
    spent_today += cost                             # 累加今日花费
    if spent_today > DAILY_BUDGET * 0.8:            # 到 80% 先预警
        print("🔔 预算已用 80%,注意!")
    return ans
📝 举个例子:组合拳能省多少 一个客服 Agent 优化前后对比(示意):
优化前:全用贵模型、无缓存 → 月账单 ¥30000
加缓存(命中40%) → ¥18000 再加路由(60%走便宜模型) → ¥8000
四招叠加,成本降到约四分之一,质量几乎不变。这种「我把成本砍了 70%」的量化结果,写进简历、面试讲出来,极其加分。
🔗 想看成本闸、预算护栏在真实多 Agent 系统里怎么落地?去看《gov-agents 多 Agent 框架教程》→ 学完回来收官 Day 56
L07

今日小结 + 动手 10 分钟

🧠 今天你应该能回答

  • 账单的成本公式是什么?(请求量 × 每次 token × 单价)
  • 预算归因是什么?该打哪几个标签?为什么先归因再优化?
  • 模型路由的思路?为什么「全用便宜模型」不一定最省?
  • 结果缓存和提示词缓存分别省什么?缓存有哪三个坑?
  • 什么活适合批处理?怎么判断实时还是批处理?
  • 预算闸解决什么问题?四招怎么串成组合拳?

✋ 动手 10 分钟:给一个「小管家」加上缓存 + 路由 + 记账

新建 day56.py,把今天几招串起来跑一遍,直观看到「命中缓存 0 花费、便宜活走便宜模型」:

cache = {}
spent = 0.0
PRICE = {"cheap": 0.001, "pro": 0.02}   # 示意:每次调用的粗略成本

def route(q):                            # 分诊:难活走 pro
    return "pro" if len(q) > 20 or "分析" in q else "cheap"

def ask(q):
    global spent
    key = q.strip().lower()
    if key in cache:                     # 命中缓存
        print(f"💰 [缓存命中] {q}  本次花费 0")
        return cache[key]
    model = route(q)                     # 未命中:选模型
    cost = PRICE[model]
    spent_before = spent
    cache[key] = f"[{model}模型的回答] {q}"
    globals()['spent'] += cost
    print(f"🛒 [{model}] {q}  本次花费 {cost}")
    return cache[key]

# 故意有重复问题、有简单有难,观察花费
for q in ["你好", "你好", "分析下这个方案的优缺点", "你好", "退货政策"]:
    ask(q)
print(f"\n总花费 = {round(spent,4)}(比全走 pro、无缓存省了多少?自己算算)")

加分题:把「每次调用」也用 Day55 的结构化日志打出来(带 feature/model/cost 标签),再按 model 汇总——你就同时做完了「可观测 + 成本归因」,这正是线上运维的完整闭环。

明日预告 · Day 57:阶段 10「可信可观测」到此收官——你已经能让 Agent 可信(Critic/护栏)、看得见(可观测)、花得省(成本治理)。明天进入阶段 11 部署篇,第一课是 Docker:把你的 Agent 服务连同它的所有依赖打包成一个「集装箱」,做到「在我电脑能跑,在服务器上也一模一样能跑」,彻底告别「在我这儿是好的呀」这句噩梦。
← Day 55 · 可观测 Day 57 · Docker 打包 →