LLMNotes

第 25 章:缓存与成本

zjc 于 2026-01-25 发布

这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 LLM 成本来自输入 token、输出 token、工具调用、检索和存储。成本优化的前提是不显著降低质量,并把预算、监控和限流纳入系统设计。

25.1 成本构成

总成本
  = 模型输入 token
  + 模型输出 token
  + embedding
  + rerank
  + 向量存储
  + 工具和 API
  + 重试
  + 人工审核

常见问题:

  1. 历史消息无限累积;
  2. 检索上下文过长;
  3. 输出过度冗长;
  4. 重复问题没有缓存;
  5. 重试风暴;
  6. Agent 循环;
  7. 用大模型处理简单任务;
  8. 日志保存过多。

25.2 缓存层级

缓存对象
结果缓存 最终答案 规范化问题 + 权限 +版本
检索缓存 chunk ID 和分数 query + filter + index version
Embedding 缓存 查询向量 text + model
工具缓存 外部结果 参数 + 时间窗口
对象缓存 解析结果 doc version

缓存失效条件必须包含模型、Prompt、索引、策略和数据版本。

25.3 语义缓存

语义缓存用向量相似度判断问题是否相近。

Query
  -> normalize
     -> embedding
        -> find similar cached question
           -> threshold
              |-- hit: return cached answer
              +-- miss: run pipeline

风险:

  1. 相似问题条件不同;
  2. 时间敏感;
  3. 用户相关;
  4. 权限相关;
  5. 引用文档已更新。

适合缓存:

“如何修改登录密码?”
“公司的年假政策是什么?”

不适合缓存:

“我的订单什么时候到?”
“现在库存多少?”
“帮我总结这个文件。”

25.4 上下文压缩

def build_context(chunks, max_chars=12000):
    parts = []
    total = 0
    for chunk in chunks:
        text = f"[{chunk['id']}] {chunk['title']}\n{chunk['content']}"
        if total + len(text) > max_chars:
            break
        parts.append(text)
        total += len(text)
    return "\n\n".join(parts)

压缩策略:

  1. 历史改摘要;
  2. 工具结果改摘要;
  3. 检索只保留 rerank 后片段;
  4. 表格保留必要列;
  5. 长文档分阶段处理;
  6. 引用正文按需读取。

25.5 输出控制

手段 示例
max_tokens 限制上限
格式约束 JSON、要点
字数限制 不超过 200 字
拒答模板 固定话术
分段生成 先大纲后细节
缓存模板 常见答案

输出不能无限压缩。关键解释、引用和风险提示应保留。

25.6 模型分级

简单分类 -> 小模型 / 规则
普通问答 -> 中档模型
复杂推理 -> 高能力模型
低风险失败 -> 自动降级

路由判断:

  1. 任务类型;
  2. 输入长度;
  3. 风险等级;
  4. 用户价值;
  5. 历史成功率;
  6. 成本预算;
  7. 延迟要求。

不要让所有请求都走最强模型。

25.7 预算控制

class Budget:
    def __init__(self, max_tokens=20000, max_money=0.20):
        self.max_tokens = max_tokens
        self.max_money = max_money
        self.used_tokens = 0
        self.used_money = 0.0

    def spend(self, tokens=0, money=0.0):
        self.used_tokens += tokens
        self.used_money += money
        if self.used_tokens > self.max_tokens or self.used_money > self.max_money:
            raise RuntimeError("budget exceeded")

预算维度:

  1. 单请求;
  2. 单会话;
  3. 单任务;
  4. 单用户;
  5. 单租户;
  6. 全局服务;
  7. 每日成本。

25.8 成本观测

日志字段:

{
  "trace_id": "t-1",
  "model": "chat-mini",
  "prompt_version": "v3",
  "input_tokens": 2400,
  "output_tokens": 180,
  "cached_input_tokens": 1200,
  "cost_usd": 0.0021,
  "cache_hit": false,
  "retry_count": 0
}

看板维度:

  1. 按业务场景;
  2. 按模型;
  3. 按租户;
  4. 按用户;
  5. 按成功失败;
  6. 按缓存命中;
  7. 按时间。

25.9 优化流程

统计成本分布
  -> 找高成本低质量场景
     -> 归因
        -> 压缩 / 缓存 / 换模型
           -> 评测
              -> 灰度
                 -> 验证指标

避免只看单价,不看解决率。便宜但转人工率上升,总成本可能更高。

本章小结

成本控制要贯穿请求、上下文、输出、模型选择和重试。缓存必须绑定权限和版本,语义缓存要排除时间敏感和用户相关请求,所有优化都通过质量指标验证。

思考题

  1. 哪些问题不适合语义缓存?
  2. 上下文压缩会带来什么风险?
  3. 如何设计单任务预算?
  4. 模型降级时如何评估质量损失?
  5. 成本下降但转人工率上升说明什么?