LLM 应用成本工程:单价低不等于总账低,Agent 的 token 账怎么算

LLM 系列又一篇。前面讲了 Agent、记忆、评测、安全——但有个每张账单都在问的问题没正面讲过:成本。这篇把它算清楚。

一、结论先放这儿

杠杆省多少成本
提示词缓存输入成本砍 5~10 倍零代码,改 API 参数
模型分级路由单步成本砍 5~20 倍加一个分诊层
上下文瘦身输入 token 砍一半接记忆系统的压缩策略
换便宜模型单价砍 10 倍质量要回归验证

一句话记住:单价低 ≠ 总账低,缓存才是 Agent 场景的第一省钱杠杆。

二、成本从哪来:Agent 一次任务的 token 账

Agent 和聊天机器人的成本结构完全不同。聊天一次一发,Agent 一个任务连发十几次甚至几十次请求,而且每次都重发全量上下文:

一次 Agent 任务的典型构成(20 轮循环):
系统提示词 + 工具定义      ~3000 token × 20 次
对话历史 + 中间结果        ~4000 token × 20 次(越滚越大)
模型输出                   ~500 token × 20 次
─────────────────────────────────────────
输入 ~14 万 token,输出 ~1 万 token

关键事实:Agent 场景输入 token 是输出的 10 倍以上,而输入是按次重发的。 这意味着两件事:

  1. 算账别只看单价:模型 A 单价是 B 的 3 倍,但如果 A 的缓存命中稳定、B 从不命中,跑 Agent 场景 A 反而更省——这就是收藏榜上「Claude Code 换国产大脑」那篇实测出来的结论
  2. 省钱的主战场在输入侧:缓存、上下文瘦身都在动输入,输出那点 token 不值得优化

三、第一杠杆:提示词缓存

主流 API 都支持提示词缓存(prompt caching):相同前缀的内容只算一次全价,命中部分按 1/5 到 1/10 计费。Agent 的系统提示词、工具定义每轮都一样——正是缓存的最佳命中区:

# Anthropic 风格:显式标记缓存断点
system = [{
    "type": "text",
    "text": SYSTEM_PROMPT + TOOLS_DEFINITION,
    "cache_control": {"type": "ephemeral"},   # 这段进缓存
}]

# 对话历史追加时,前缀不变 → 第 2 轮起命中
messages.append({"role": "user", "content": user_input})

验证缓存命中,看返回里的 usage 字段:

usage = resp.usage
print(usage.cache_read_input_tokens)    # 命中了多少 token(按 1/10 价计)
print(usage.cache_creation_input_tokens) # 第一次写入缓存的 token

怎么实测缓存命中率:构造 1 万 token 的固定 system 上下文,一字不差连发 5 轮,记录每轮 cache_read_input_tokens。有的模型第二轮起稳定命中 98%,有的间歇命中,有的全程 0%——同一个网关下不同模型缓存表现天差地别,跑 Agent 前先测这个。测试方法照抄系列评测篇:固定输入、多轮、只信 usage 字段的数。

四、第二杠杆:模型分级路由

不是每一步都值得用旗舰模型。Agent 循环里的步骤分三六九等:

步骤用什么理由
意图分诊、分类、判断小模型/专门模型系列第十篇 Jev 就是干这个的,输出免费
工具参数生成、格式化中档模型格式类任务便宜模型够用
复杂推理、代码生成旗舰模型只有这步配得上单价
def route(task_type: str, prompt: str) -> str:
    if task_type in ("classify", "triage", "check"):
        return call_cheap_model(prompt)      # 分诊类:便宜模型
    if task_type in ("codegen", "reason"):
        return call_flagship_model(prompt)   # 硬活:旗舰
    return call_mid_model(prompt)

分诊判断这类步骤占 Agent 调用量的 60% 以上,把它们挪到便宜模型上,总账直接砍一半起步。具体路由实现(Jev 的 Choice/Noul、或小模型分类)系列第十篇讲过,原样接上。

五、第三杠杆:上下文瘦身

输入 token 越滚越大是 Agent 的常态,瘦身三板斧(都是系列讲过的零件):

  1. 历史压缩:老对话压成摘要(系列记忆篇的 compact,50 轮压 200 字),历史不再线性增长
  2. 工具结果裁剪:read_file 返回 3000 行,喂给模型前截取相关段落——工具结果是最容易被忽略的 token 黑洞
  3. middle-out 截断:超长上下文保头保尾、掐中间,重要约定放 system prompt 和对话开头

这三件事就是系列第六篇 Harness「上下文管理」部件的完整版——Harness 管的不只是权限,还有 token 预算。

六、算账:一个可抄的成本核算脚本

def task_cost(rounds: int, ctx_tokens: int, out_tokens: int,
              price_in: float, price_out: float,
              cache_hit_ratio: float = 0.0, cache_discount: float = 0.1) -> dict:
    """算一次 Agent 任务的 token 账(单位:元/百万token)"""
    hit, miss = cache_hit_ratio, 1 - cache_hit_ratio
    in_cost = rounds * ctx_tokens * (hit * price_in * cache_discount
                                     + miss * price_in) / 1e6
    out_cost = rounds * out_tokens * price_out / 1e6
    return {"input": round(in_cost, 4), "output": round(out_cost, 4),
            "total": round(in_cost + out_cost, 4)}

# 同一个任务:20 轮,每轮重发 7000 token 上下文
no_cache   = task_cost(20, 7000, 500, 8, 28)                 # 不走缓存
with_cache = task_cost(20, 7000, 500, 8, 28, cache_hit_ratio=0.95)
print(no_cache, with_cache)
# {'input': 1.12, ...} → 缓存后输入 0.19:同一任务差 5 倍

把 price_in/out 换成你所用模型的实际定价、cache_hit_ratio 换成实测命中率(第三节的方法),每个任务跑之前先算账,跑完对账单——没有账本的优化和没有评测集的调优一样,都是玄学(系列评测篇的老话)。

七、别省过头:成本回归

省钱优化的最大风险是把质量省没了:换便宜模型、砍上下文、压缩历史,每一步都可能劣化。所以成本优化和功能开发一样要回归验证:

  1. 每次成本改动跑一遍评测集(系列评测篇的 60 行 runner,原样用)
  2. 通过率掉超过 5% 就回滚——省下的钱不够赔返工
  3. 账单和评测曲线放一起看:成本降了、评测没掉,才是真的优化成功

八、踩坑提醒

  1. 缓存前缀必须一字不差:system prompt 改一个标点,整段缓存失效重新计价——动态内容(时间戳、随机 ID)别放进缓存前缀
  2. 历史无脑追加是账单杀手:20 轮任务历史滚到几万 token,每次全价重发——接记忆篇的压缩策略
  3. 输出 max_tokens 别拍脑袋设 8192:按任务实际需要设,超长输出既慢又贵
  4. 别用「感觉这个模型便宜」做决策:单价、缓存命中、每步调用量三个变量一起算总账(第六节的脚本),账本说了算

总结

原则一句话
成本结构Agent 输入是输出的 10 倍+,省钱主战场在输入侧
第一杠杆提示词缓存,输入砍 5~10 倍,零代码
第二杠杆模型分级路由,分诊类步骤占 6 成调用量
第三杠杆上下文瘦身:压缩历史、裁剪工具结果
铁律单价低 ≠ 总账低,先算账再优化,改完跑回归

至此系列的隐形主线浮出水面:Harness 管预算、记忆管压缩、Jev 管分诊、评测管回归——全都是成本工程的零件。 大模型应用的胜负手从来不只是效果,还有每百万 token 的账。觉得有用点个关注。

Logo

一站式 AI 云服务平台

更多推荐