把 Agent 的账单砍掉 88.9%
一、背景:Agent 上线三个月,账单涨了 14 倍
1.1 一条真实到有点尴尬的账单曲线
先说结论,再说过程。这是某家中型 SaaS 公司(下称 T 公司,约 420 名员工,核心产品是面向制造业的供应链协同平台)在 2026 年 5 月到 8 月之间的月度模型调用支出:
| 月份 | 月支出(美元) | 活跃任务数 | 单任务成本 | 环比 |
|---|---|---|---|---|
| 2026-05(内测) | $2,180 | 4,100 | $0.53 | — |
| 2026-06(小范围公测) | $9,640 | 12,700 | $0.76 | +342% |
| 2026-07(全量上线) | $28,900 | 26,300 | $1.10 | +200% |
| 2026-08(峰值) | $46,700 | 31,800 | $1.47 | +62% |
三个月,支出从 $2,180 涨到 $46,700——21 倍。而同期任务数只涨了 7.7 倍。
也就是说,单任务成本从 $0.53 涨到了 $1.47,涨了 177%。用户没变多多少,模型没换,价格没涨(期间还降过一次价),成本却在涨。
这是 2026 年所有 Agent 项目都会撞上的那堵墙,而且它有一个很反直觉的特点:它不是一次性爆炸的,是慢慢渗出来的。每个月涨 60%–200%,每次涨都有理由,每个理由都合理,直到某天财务把账单甩到技术负责人桌上。
1.2 钱到底花在哪:把一次任务拆开看
T 公司的核心 Agent 任务是「供应链异常处置」:系统检测到某个物料的到货延迟,Agent 需要判断影响范围、找出替代供应商、评估成本差异、生成处置方案,最后提交给人工复核。
我们把它的一次完整执行拆开,看看 40 次模型调用分别是什么:
| 阶段 | 调用次数 | 用途 | 输入 token(均值) | 输出 token(均值) | 使用模型 |
|---|---|---|---|---|---|
| 任务理解与分解 | 1 | 读异常事件、拆成子任务 | 3,200 | 480 | Opus |
| 影响范围分析 | 3 | 遍历 BOM 树、查在途订单 | 8,600 | 320 | Opus |
| 供应商检索 | 6 | 查询供应商库、资质校验 | 5,400 | 180 | Opus |
| 成本模型计算 | 4 | 比价、运费、关税测算 | 7,100 | 260 | Opus |
| 风险评估 | 5 | 逐供应商打分 | 6,800 | 210 | Opus |
| 方案生成 | 2 | 汇总成处置方案 | 9,200 | 1,100 | Opus |
| 自我校验 | 4 | 检查方案一致性、查漏 | 8,900 | 340 | Opus |
| 人工摘要 | 1 | 生成给复核人的摘要 | 4,100 | 620 | Opus |
| 重试与修正 | 9 | 上一步不达标重跑 | 7,300 | 300 | Opus |
| 工具结果重读 | 5 | 上下文被截断后重读 | 6,700 | 150 | Opus |
| 合计 | 40 | — | ~273,000 | ~4,000 | — |
按 2026 年 8 月的 Opus 级定价粗算(输入 $12/M、输出 $60/M,未计缓存折扣):
输入成本 = 273,000 / 1,000,000 × 12 = $3.28
输出成本 = 4,000 / 1,000,000 × 60 = $0.24
单次任务成本 ≈ $3.52
但实际账单上单任务成本是 $1.47——因为大约 60% 的任务没跑满 40 次(简单异常 3 步就结束了)。跑满的复杂任务,实际成本是均值的 2.4 倍。
这就是第一个关键发现:均值掩盖了长尾。你看到的 $1.47 是「3 次调用的便宜任务」和「40 次调用的昂贵任务」混在一起的加权平均,而真正吃掉预算的是那 20% 的复杂任务——它们贡献了约 68% 的支出。
1.3 三个被忽略的成本放大器
把账单拆解完之后,团队识别出三个此前完全没被监控的放大器:
放大器一:重试的隐性成本
上表里「重试与修正」占了 9 次调用,是单项最大的一块。为什么会有 9 次重试?
| 重试原因 | 占比 | 是否可避免 |
|---|---|---|
| 输出格式不合规(JSON 缺字段 / 多了 markdown 围栏) | 34% | ✅ 完全可避免 |
| 工具调用参数错误(日期格式、ID 写错) | 26% | ✅ 可避免 |
| 上下文被截断,模型「忘了」前面的约束 | 22% | ✅ 可避免 |
| 任务本身确实难,第一次没做对 | 18% | ❌ 合理重试 |
82% 的重试是工程问题,不是模型能力问题。 这部分成本是纯浪费。
放大器二:上下文重复搬运
一次任务的 40 次调用里,有大约 31 次的输入包含了同一份「任务背景包」(异常事件详情 + 物料主数据 + 供应商白名单 + 公司处置规则)。这个背景包约 6,800 token。
重复搬运的 token = 6,800 × 31 ≈ 210,800 token
占总输入的 77%
也就是说,接近八成的输入 token 是同一个东西被反复发了 31 遍。这在 2026 年有一个专门的名字:缓存未命中。如果这些都能命中 Prompt Cache,输入成本可以直接降到 1/10。
放大器三:模型能力错配
40 次调用里,我们逐条人工标注了「这一步真的需要前沿模型吗」:
| 判断 | 调用次数 | 占比 |
|---|---|---|
| 必须前沿模型(复杂推理、长程规划) | 7 | 17.5% |
| 中等模型足够 | 11 | 27.5% |
| 小模型甚至规则就能做 | 22 | 55% |
超过一半的调用,用的是杀鸡的牛刀。 尤其是「供应商资质校验」(查表比对)、「工具结果重读」(提取字段)、「格式校验」(JSON schema 检查)这类步骤——它们本来就不该调模型,或者至少不该调最贵的模型。
1.4 为什么「换个便宜模型」是最先想到也最先失败的办法
团队的第一反应非常典型:全部换成便宜模型。
第一次尝试的结果:
| 方案 | 单任务成本 | 质量通过率 | 结论 |
|---|---|---|---|
| 全量 Opus(基线) | $1.47 | 99.1% | 太贵 |
| 全量换 Haiku 级小模型 | $0.19 | 71.3% | ❌ 不可用 |
| 全量换中等模型 | $0.58 | 92.6% | ❌ 仍不达标 |
全量降级把成本砍了 87%,但质量从 99.1% 崩到 71.3%——这个质量水平下,人工复核的成本反而涨了 4 倍(复核员要花 3 倍时间修正 AI 的错误)。总账算是亏的。
原因也很清楚:Agent 任务的质量由最弱的那一环决定,不是由平均值决定。40 步里只要第 7 步(复杂推理)做错,后面 33 步全对也没用。而「全量降级」把最弱的那一环从「99 分」拉到了「70 分」。
真正的解法不是「全量降级」,而是分层:识别出哪些步骤是决定性步骤,这些用最贵最好的;剩下的用便宜的甚至不用模型。这就是后面要讲的模型路由。
一句话总结这一节:Agent 的成本问题,80% 不是「模型太贵」,而是「把贵的模型用在了不需要它的地方」+「同样的内容反复付费」+「工程 bug 导致的重试」。降本的正确入口是架构,不是采购。
二、目标定义:什么叫「成本降 88.9% 但质量不掉」
2.1 先定义质量,再谈降本
这是整个项目里最重要、也最容易被跳过的一步。如果不能在改造之前把「质量」量化成一个数字,那么改造之后你无法证明自己没把产品做坏——你只能凭感觉,而凭感觉的降本项目最后一定会被推翻。
T 公司花了整整一周做这件事。最终定义如下:
质量 = 在 1,200 条标注评测集上的「任务级通过率」。
注意几个关键设计:
| 设计点 | 选择 | 理由 |
|---|---|---|
| 单位 | 任务级,不是步骤级 | 用户关心的是「这事办成了没」,不是「第 7 步对不对」 |
| 判定 | 复合判定(结构化 + 语义) | 单独用结构化判定会放过语义错误,单独用语义判定不稳定 |
| 规模 | 1,200 条 | 要能检测出 0.5% 的质量差异,1,200 条是统计上的下限 |
| 构成 | 按真实流量分布采样 | 不能均匀采样,否则长尾复杂任务被稀释 |
复合判定的具体规则(这是全项目最核心的一段代码):
def judge(task, prediction, golden):
# 第一层:结构化判定(硬约束,不满足直接判失败)
structural = check_schema(prediction) # JSON schema 合法
if not structural.ok:
return Verdict(False, reason="schema", detail=structural.error)
required = check_required_fields(prediction, task) # 必填字段齐全且类型正确
if not required.ok:
return Verdict(False, reason="missing_field", detail=required.missing)
# 第二层:事实性判定(关键数字必须与标准答案一致)
facts = compare_facts(prediction, golden, tolerance=0.02) # 金额/数量允许 2% 误差
if facts.mismatch_count > 0:
return Verdict(False, reason="fact_error", detail=facts.mismatches)
# 第三层:语义判定(由评审模型打分,阈值 4.0 / 5.0)
semantic = llm_judge(task, prediction, golden)
if semantic.score < 4.0:
return Verdict(False, reason="semantic", detail=semantic.rationale)
return Verdict(True, score=semantic.score)
三层判定缺一不可:
- 只做第一层:会放过「字段都对但金额算错」——这是供应链场景最危险的错。
- 只做第三层:评审模型会因为格式漂亮而给高分,漏掉硬错误;且评审本身有随机性。
- 三层全做:通过率从「看起来 97%」降到「真实 99.1%」——等等,为什么变高了?
因为基线里的 99.1% 是人工抽查 200 条得出的,抽样偏差让它虚高。改用 1,200 条全量 + 三层判定后,最初的真实基线是 98.7%。后面所有的「99.96%」都是相对这个口径说的。
踩坑提醒:降本项目开始前的第一件事,是把「我们现在的真实质量是多少」测准。T 公司原本以为自己是 99.1%,实测 98.7%——这 0.4% 的差距,在项目后期差点被误判为「改造导致质量下降」。
2.2 三层指标体系
除了质量,还有两组指标必须同时监控。只降成本不看这两组,等于把问题转移到别处。
┌─────────────────────────────────────────────────┐
│ 第一层:成本指标(我们要优化的对象) │
│ · 单任务成本(P50 / P95 / P99) │
│ · 输入 token / 输出 token 分解 │
│ · 调用次数分布 │
│ · 缓存命中率 │
│ · 模型分层占比(前沿 / 中等 / 小 / 规则) │
├─────────────────────────────────────────────────┤
│ 第二层:质量指标(不能退化的约束) │
│ · 任务级通过率(1,200 条评测集) │
│ · 分层通过率(简单 / 中等 / 复杂 / 极端) │
│ · 事实错误率(金额、数量、日期) │
│ · 人工复核介入率 │
│ · 复核平均耗时 │
├─────────────────────────────────────────────────┤
│ 第三层:体验指标(成本转嫁的警报器) │
│ · 任务端到端延迟(P50 / P95) │
│ · 用户主动重试率 │
│ · 用户放弃率(任务中途退出) │
│ · 用户满意度评分 │
└─────────────────────────────────────────────────┘
第三层是很多团队会漏掉的。成本降下来但延迟变长,用户会用脚投票;成本降下来但复核变多,成本只是从模型账单转移到了人力账单。这两条必须钉死。
2.3 评测集怎么建
1,200 条评测集是项目里最费人工的部分(约 3 人周),但它决定了后面 8 周所有决策的可信度。
构成设计:
| 分层 | 条数 | 来源 | 说明 |
|---|---|---|---|
| 简单(≤5 步) | 360 | 真实流量随机采样 | 占真实流量 30% |
| 中等(6–15 步) | 420 | 真实流量随机采样 | 占真实流量 35% |
| 复杂(16–40 步) | 300 | 真实流量定向采样 | 占真实流量 25%,但占成本 68% |
| 极端(>40 步 / 多异常并发) | 90 | 人工构造 + 历史事故 | 占流量 7.5%,最容易崩 |
| 对抗样本 | 30 | 人工构造 | 缺失数据、矛盾指令、越权请求 |
为什么会需要「极端」和「对抗」这两层?
因为前三层来自真实流量,而真实流量里没有那些「模型做错了所以没走到最后」的任务——它们已经被系统性地过滤掉了。如果只用真实流量建评测集,你会得到一个「模型本来就擅长的子集」,改造后在这个子集上跑分一定很好看,但线上会翻车。
极端层的 90 条里,有 41 条直接来自过去三个月的人工复核记录(即:AI 做错过、被人改过的任务)。这 41 条是整个评测集里最有价值的部分。
标注规范(这部分必须写成文档,否则多人标注会不一致):
## 标注规范 v1.2
### 一条评测样本包含
1. `task_input`:异常事件原始 payload(完整,不要截断)
2. `context_snapshot`:当时的物料主数据、供应商库快照(版本号必须记录)
3. `golden_answer`:人工给出的标准处置方案
4. `critical_facts`:必须完全正确的关键事实列表(金额、数量、供应商 ID、日期)
5. `difficulty_label`:人工标注的难度分层
6. `failure_history`:该任务历史上是否被人工修正过(bool)
### 标注红线
- `golden_answer` 必须是「一个合格的高级供应链专员会给出的方案」,不是「最优解」
- `critical_facts` 必须穷举,漏标会导致评测集放过高事实错误率的系统
- 禁止用 AI 生成 golden_answer 后不审核直接入库
- 每条样本至少 1 人标注 + 1 人复核,争议样本由第三人裁决
### 版本管理
- 评测集版本号写入每条样本的 `eval_version` 字段
- 任何修改必须开新版本,禁止原地改(否则历史跑分不可比)
2.4 回滚红线:什么情况下立刻停
改造是灰度进行的,必须有明确的「熔断」条件。T 公司定的红线:
| 红线 | 阈值 | 动作 |
|---|---|---|
| 评测集通过率下降 | > 0.5 个百分点 | 立即暂停当前灰度批次 |
| 事实错误率上升 | > 0.2 个百分点 | 立即全量回滚 |
| P95 延迟上升 | > 30% | 暂停,排查后再继续 |
| 人工复核介入率上升 | > 3 个百分点 | 立即回滚(说明成本转移到人力了) |
| 单任务成本下降 | < 30% | 继续观察,不回滚(成本目标未达成但不影响用户) |
五条红线里,第四条是后加的。第一版灰度时没监控它,结果第二批灰度(模型路由激进版)把成本砍了 61%,但复核介入率从 8.2% 涨到 19.7%——模型账单省了 $1.9 万,复核人力多了 $3.4 万。总账是亏的,但因为没监控这条指标,直到第 9 天才通过人工反馈发现。
这是全项目最贵的一个教训:成本指标必须和「下游人力成本」一起看。省了模型钱但让人多干活,是净亏损。
三、工具分工:谁负责什么
3.1 工具矩阵
| 层 | 工具 | 职责 | 为什么选它 | 备选 |
|---|---|---|---|---|
| 观测 | Langfuse | 链路追踪、token 计费归因、调用树可视化 | 开源、支持嵌套 span、能把成本归因到单次调用 | LangSmith / Phoenix |
| 路由 | LiteLLM | 统一网关、模型路由、重试、fallback | 支持 100+ 模型、可以按 tag 路由、自带预算限制 | 自研网关 / OpenRouter |
| 缓存 | Redis + 语义缓存层 | Prompt 前缀缓存、工具结果缓存、语义近似命中 | 需要精确 + 近似两级缓存 | GPTCache |
| 评测 | 内部评测平台(自研) | 跑 1,200 条评测集、跑分对比、回归卡口 | 已有的,与 CI 打通 | promptfoo / Braintrust |
| 监控 | Prometheus + Grafana | 成本/延迟/质量的时序监控与告警 | 已有基础设施 | Datadog |
| 兜底 | vLLM 自托管 | 小模型自托管,进一步压低单位成本 | 规模化后单 token 成本可到 API 价的 1/5 | SGLang / TGI |
| 模型 | Opus 5.5 / 中等模型 / Haiku 5 / GPT-6 Sol·Luna | 分层供给 | 前沿层双供应商避免单点 | — |
3.2 观测层:没有数据就不要优化
这是第 0 周唯一的工作,也是最容易被视为「没产出」而被砍掉的工作。不要砍它。
T 公司在改造前的观测状态:
| 观测能力 | 改造前 | 改造后 |
|---|---|---|
| 能否看到单次任务的完整调用树 | ❌ 只有日志 | ✅ 嵌套 span,40 步全可见 |
| 能否把成本归因到单次调用 | ❌ 只有月度总额 | ✅ 每步的 in/out token 与单价 |
| 能否看到重试次数与原因 | ❌ 埋在日志里 | ✅ 结构化字段 retry_reason |
| 能否看到缓存命中率 | ❌ 完全没有 | ✅ 按前缀分组统计 |
| 能否按任务难度分层看成本 | ❌ | ✅ difficulty tag 全链路透传 |
埋点的最小字段集(这是可以直接抄的):
{
"trace_id": "任务唯一 ID,贯穿 40 步",
"span_id": "当前步骤 ID",
"parent_span_id": "父步骤 ID(构成调用树)",
"step_name": "supplier_risk_scoring",
"step_index": 17,
"difficulty": "complex",
"model": "opus-5.5",
"model_tier": "frontier",
"input_tokens": 8420,
"output_tokens": 210,
"cached_tokens": 6800,
"cache_hit": true,
"cache_key_prefix": "bg:v3:incident:88231",
"latency_ms": 4120,
"retry_count": 0,
"retry_reason": null,
"tool_calls": ["query_supplier_db", "check_certification"],
"estimated_cost_usd": 0.0312
}
15 个字段,其中 cache_key_prefix 和 retry_reason 是后面所有优化的决策依据。
一个反直觉的发现:埋点上线第一天,团队就发现了两件此前完全不知道的事——
- 有 11% 的调用在超时后自动重试,但第一次的结果其实已经写入了上下文,等于同一件事做了两遍还付了两次钱。
- 有 6.3% 的调用返回后从未被下游读取(写了个变量然后被覆盖)。这是纯浪费。
这两项加起来是 17.3% 的支出,在没有任何架构改动的情况下,光靠修 bug 就能省下来。
3.3 路由层:Harness 的核心
模型路由是整个项目贡献最大的一环(后面数据显示占降本的 52%)。
路由的本质:在每次调用之前,先回答一个问题——这一步需要什么级别的智能?
T 公司最终采用的不是「按步骤名静态配置」,而是两级路由:
第一级:静态路由表(按 step_name 配置,覆盖约 80% 调用)
↓ 命中 → 直接路由
↓ 未命中 / 标记 needs_dynamic
第二级:动态路由(由一个 Haiku 级小模型做分类,约 40 token 成本)
↓ 输出 tier: frontier | balanced | small | rule
第三级:规则兜底(纯正则/查表,0 模型成本)
静态路由表(节选,实际 43 条):
| step_name | 静态 tier | 依据 |
|---|---|---|
task_decomposition | frontier | 决定全局,错一步全错 |
bom_traversal | rule | 纯图遍历,代码做更快更准 |
supplier_query | rule | 数据库查询 |
certification_check | rule | 查表比对 |
cost_model_calculation | rule | 确定性计算,不该用模型 |
risk_scoring_simple | small | 单供应商、规则清晰 |
risk_scoring_multi | balanced | 多供应商权衡 |
plan_generation | frontier | 核心产出 |
self_verification | balanced | 需要推理但不需前沿 |
human_summary | small | 格式化的摘要生成 |
format_validation | rule | JSON schema 校验 |
注意这张表里最大的变化:bom_traversal、supplier_query、certification_check、cost_model_calculation 这四个原本占 17 次调用的步骤,全部从模型改成了代码。
这一步的贡献极大:
改造前:17 次调用 × 均值 6,800 token × $12/M ≈ $1.39
改造后:4 次函数调用 × 约 0 模型成本 ≈ $0.001
「不要把确定性的事交给模型」是本次降本 ROI 最高的一条原则,但它需要有人愿意写代码——这也是为什么纯 Prompt 工程师做不了成本治理,必须有后端工程参与。
动态路由的分类 Prompt(这是全项目复用率最高的一个 Prompt):
你是模型路由分类器。判断下面这次调用需要什么级别的模型能力。
## 可选级别
- frontier:需要长程规划、多约束权衡、创造性方案设计、或错误的代价极高
- balanced:需要多步推理,但约束明确、有清晰的正确标准
- small:单一明确任务,格式转换、摘要、分类、简单抽取
- rule:完全确定性,可以由代码或查表完成(数据库查询、算术、schema 校验、图遍历)
## 输入
步骤名:{{step_name}}
步骤描述:{{step_description}}
输入内容摘要(前 300 token):{{input_head}}
本次调用之后是否还有依赖它的下游步骤:{{has_downstream}}
如果这次调用出错,是否能被后续的自检步骤捕获:{{caught_by_verification}}
## 判断规则
1. 能由代码确定性完成的,一律 rule——不要用模型做算术、查表、遍历
2. 有下游依赖 且 不会被自检捕获 → 至少 balanced(错误会传播且不可逆)
3. 涉及金额、法律条款、安全边界 → 至少 balanced,涉及多方案权衡 → frontier
4. 只是格式转换、摘要、抽取、分类 → small
5. 拿不准时选高一级,成本差异远小于质量损失
## 输出
只输出一行 JSON,不要任何解释:
{"tier": "...", "confidence": 0.0-1.0, "one_line_reason": "..."}
这个分类器本身用 Haiku 级模型跑,单次成本约 $0.0004,而它每次决策影响的调用成本在 $0.003 – $0.09 之间。也就是说路由器的成本是被路由调用成本的 1/10 到 1/200,这是划算的。
路由器的准确率实测:
| 指标 | 数值 |
|---|---|
| 与人工标注 tier 的一致率 | 94.1% |
| 偏保守(选高了) | 4.3% |
| 偏激进(选低了) | 1.6% |
| 偏激进且导致任务失败 | 0.08% |
1.6% 的激进错误里只有 0.08% 真的影响了任务——因为大部分被自检步骤兜住了。这也是为什么「后面有没有自检」要作为路由器的输入特征之一。
3.4 缓存层
缓存分三级,命中率和收益差别很大:
| 级别 | 机制 | 命中率 | 成本节省 | 风险 |
|---|---|---|---|---|
| L1:前缀精确缓存(Provider 原生 Prompt Caching) | 相同前缀直接命中 | 78% | 输入成本 -90%(命中部分) | 低 |
| L2:工具结果缓存 | 相同参数的工具调用结果复用 | 41% | 省掉重复的调用与重读 | 中(数据新鲜度) |
| L3:语义近似缓存 | 向量相似度 > 0.97 复用答案 | 12% | 省掉整次调用 | 高(可能答非所问) |
L1 是主力,也是改造重点。 要让 L1 命中率从改造前的 7% 提到 78%,核心是前缀稳定性——这正好对上了本站另一篇《Cache-Friendly Prompting》讲的四层结构(L0 系统 / L1 项目 / L2 任务 / L3 变量)。
关键改造就一条:把重复的 6,800 token 背景包做成固定前缀,放在最前面,且逐字节稳定。
改造前的 Prompt 组装方式(❌ 缓存杀手):
prompt = f"""当前时间:{datetime.now()}
任务ID:{task_id} # ← 每次都不同,放在最前面
用户:{user_name}
==== 任务背景 ====
{background_package} # ← 6,800 token 固定内容,被上面三行"污染"了前缀
==== 当前步骤 ====
{step_instruction}
"""
改造后(✅):
prompt = f"""{background_package_v3} # ← 固定前缀放最前,内容逐字节稳定
==== 当前步骤 ====
{step_instruction}
==== 元信息 ====
时间:{datetime.now()} # ← 变量全部沉到尾部
任务ID:{task_id}
用户:{user_name}
"""
仅仅这一行顺序调整,L1 命中率从 7% 提到 71%。再配合三件小事提到 78%:
| 改造 | 命中率提升 |
|---|---|
| 背景包内容排序固定(不用 dict 遍历顺序) | +4% |
| 背景包内容裁剪到稳定子集(去掉实时字段) | +2% |
版本号写入前缀(bg:v3:),改版时整体失效而非静默错配 | +1% |
L3 语义缓存的教训:T 公司上了 L3,命中率 12%,但因为近似命中导致的错误占到了总错误的 6.8%——远超它带来的成本收益。最终把阈值从 0.97 提到 0.995,命中率掉到 3.2%,错误率降到 0.4%。
结论:语义缓存在 Agent 场景基本不值得做。Agent 任务的输入复杂、输出影响大,「差不多一样的问题」往往有关键差异。精确缓存才是主力。
3.5 评测层
评测层不是「跑一次分」,而是卡在 CI 里的常态化闸门。
| 触发时机 | 跑分规模 | 卡口阈值 |
|---|---|---|
| 每次 Prompt 改动 | 200 条(快速子集) | 通过率下降 > 1% 则阻断 |
| 每次路由表改动 | 1,200 条全量 | 通过率下降 > 0.5% 则阻断 |
| 每次模型版本升级 | 1,200 条全量 + 对抗集 | 任何红线触发则阻断 |
| 每日凌晨 | 300 条(轮转子集) | 趋势监控,不阻断 |
跑一次 1,200 条全量的成本和时间:
时间:约 22 分钟(并发 32)
成本:约 $41
这个成本是可以接受的——它比「线上翻车一次」的代价低得多。
四、策略设计:五把刀,按 ROI 排序
这是本文的核心。五把刀按实测降本贡献占比排序(不是按实施难度):
| 刀 | 措施 | 降本贡献占比 | 实施周期 | 质量影响 | 风险 |
|---|---|---|---|---|---|
| 刀 1 | 模型路由 + 确定性步骤代码化 | 52% | 2 周 | -0.03% | 中 |
| 刀 2 | Prompt Caching(前缀稳定化) | 23% | 2 周 | 0 | 低 |
| 刀 3 | 上下文预算与压缩 | 9% | 2 周 | -0.01% | 中 |
| 刀 4 | 批处理与并发优化 | 3% | 1 周 | 0 | 低 |
| 刀 5 | 早停与去重(修 bug) | 2% | 1 周 | +0.3% | 低 |
| — | 其他(单价谈判、预留容量等采购侧) | 11% | — | 0 | 低 |
(合计超过 100% 是因为分母是「改造前的单任务成本」,各项叠加时有交互效应,此处为归因近似值。)
刀 1:模型路由(52%)
已经在 3.3 讲过机制,这里补充分层后的实际调用分布变化:
| 层级 | 改造前调用占比 | 改造后调用占比 | 单次成本(相对) |
|---|---|---|---|
| frontier | 100% | 14% | 1.00× |
| balanced | 0% | 23% | 0.28× |
| small | 0% | 19% | 0.06× |
| rule(代码) | 0% | 44% | ~0 |
44% 的调用变成了不花钱的函数调用,这是最大的一块。
关键经验:
- 先做「代码化」再做「降级」。把确定性步骤改成代码,收益最高且零质量风险;直接把非确定性步骤降级,收益低且质量风险高。
- 路由决策要能解释。每次路由都要记录 tier + 理由(前面那个 Prompt 的
one_line_reason),出问题时能快速定位是哪一步被误判。 - 保留人工 override。有些任务就是需要全 frontier(比如客户投诉处置),要能在任务级别强制指定。T 公司有 6 类任务被永久钉在 frontier 白名单上。
刀 2:Prompt Caching(23%)
机制在 3.4 讲过。补充两个实战细节:
细节一:缓存 TTL 与版本失效
Provider 的 Prompt Cache 通常有 TTL(5 分钟到 1 小时不等)。Agent 任务一次跑 40 步、耗时 3–8 分钟,TTL 是够的;但如果任务被排队等待(高峰期排队 20 分钟),缓存就失效了。
解法:把同类型任务聚合成批,让同一前缀在时间上密集出现。T 公司加了一个简单的「背景包亲和调度」——把共享同一背景包的子任务排在相邻时间窗执行,缓存命中率又提了 +6%。
细节二:缓存的最小长度门槛
大多数 Provider 有最小缓存长度(如 1,024 或 2,048 token)。T 公司有约 19% 的调用输入不足 2,048 token,这些调用无论怎么优化都享受不到缓存。对它们,正确的做法是:
- 要么合并步骤(把两个小步骤合成一个,让输入过门槛)
- 要么接受不缓存,把优化精力放在别处
合并这一步让可缓存调用占比从 81% 提到 93%。
刀 3:上下文预算与压缩(9%)
上下文预算:给每个步骤设定输入 token 上限,超了就压缩或裁剪。
| 步骤类型 | 预算(token) | 超限处理 |
|---|---|---|
| 分解/规划 | 8,000 | 裁剪历史,只留摘要 |
| 检索/查表 | 4,000 | 只传必要字段 |
| 评分/判断 | 6,000 | 截断 + 标注「已截断」 |
| 生成/汇总 | 12,000 | 分层压缩 |
压缩策略(不是简单截断):
1. 丢弃:已完成的步骤的完整输出(只留结论 + 关键数字)
2. 摘要:超过 800 token 的工具输出压缩成结构化摘要(用 small 模型,成本 $0.0002)
3. 保留:所有 critical_facts(金额、ID、日期)——永不压缩
4. 标注:任何被压缩的位置插入 [已压缩:原 N token,摘要 M token]
第 4 条的「标注」很关键:让模型知道这里被压缩过,它才不会把「没看到」当成「不存在」。加了标注后,因压缩导致的事实错误从 0.9% 降到 0.2%。
刀 4:批处理与并发(3%)
- 批处理 API:对非实时的后台任务(如批量风险评估、历史数据回放),走 Batch API,单价约 -50%。T 公司约 22% 的任务可以走批处理。
- 并发控制:原来 40 步是串行的,其中 15 步无依赖可以并发。并发后端到端延迟从 4.2 分钟降到 1.7 分钟,虽然省的钱不多(批处理那部分),但体验指标大幅改善。
刀 5:早停与去重(2%,但质量 +0.3%)
这一刀省的钱最少,但唯一一个让质量上升的——因为它修的是 bug:
| Bug | 影响 | 修复 |
|---|---|---|
| 超时重试但旧结果已入上下文 | 11% 的调用重复付费 | 重试前清理上下文,幂等键去重 |
| 结果写入后从未被读取 | 6.3% 的调用纯浪费 | 静态分析 + 运行时检测未读变量 |
| 自检步骤与生成步骤用同一个 Prompt | 自检发现不了同类错误 | 自检改用独立视角 Prompt |
| JSON 解析失败后整步重跑 | 34% 的重试源于此 | 加结构化输出约束(schema 强制),解析失败率从 12% 降到 0.3% |
其中「结构化输出约束」这一条单项就让重试次数从 9 次降到 2.4 次,同时质量因为「少折腾」而上升了 0.3%。
五、实施步骤
5.1 第 0 周:埋点与基线
| 天 | 任务 | 产出 |
|---|---|---|
| D1–D2 | 接入 Langfuse,全链路埋点(15 个字段) | 调用树可见 |
| D3 | 补齐成本归因(单价表、token 统计) | 单次调用成本可查 |
| D4–D5 | 建立 1,200 条评测集 v1.0 | 基线跑分:98.7% |
| D6–D7 | 建立成本/质量/体验三层看板 | 基线:成本 $1.47、P95 延迟 4.2min、复核介入率 8.2% |
第 0 周不做任何优化。这五天的产出是后面所有决策的依据。
5.2 第 1–2 周:任务分层与路由表
| 天 | 任务 |
|---|---|
| D8–D9 | 人工标注 40 个 step 的 tier(3 人独立标注,取多数) |
| D10–D11 | 实现 LiteLLM 路由网关 + 静态路由表(43 条) |
| D12–D14 | 把 17 个确定性步骤改成代码(最大工作量,3 名后端) |
| D15–D17 | 实现动态路由分类器 + Prompt |
| D18–D19 | 离线回放:用历史 500 个任务回放,对比改造前后 |
| D20–D21 | 评测集 v1.1 跑分 + 灰度 5% |
第 12–14 天是全项目人力投入最密集的 3 天,也是收益最高的 3 天。
5.3 第 3–4 周:缓存友好化改造
| 天 | 任务 |
|---|---|
| D22 | 梳理所有 Prompt 模板,识别固定前缀与变量区 |
| D23–D25 | 重构 Prompt 组装:固定前置 + 变量沉尾(涉及 31 个模板) |
| D26 | 加背景包版本号 + 内容排序固定化 |
| D27–D28 | 实现「背景包亲和调度」 |
| D29–D30 | 小步骤合并(让输入过缓存门槛) |
| D31–D32 | 命中率监控上线 + 灰度提到 20% |
5.4 第 5–6 周:上下文预算
| 天 | 任务 |
|---|---|
| D33–D34 | 定义各 step 的 token 预算 |
| D35–D37 | 实现三级压缩(丢弃/摘要/保留/标注) |
| D38–D39 | critical_facts 保护机制 |
| D40–D41 | 灰度提到 50% |
5.5 第 7–8 周:批处理 + bug 修复 + 全量
| 天 | 任务 |
|---|---|
| D42–D44 | 非实时任务走 Batch API |
| D45–D46 | 无依赖步骤并发化 |
| D47–D49 | 修 bug:重试幂等、未读检测、结构化输出约束 |
| D50–D52 | 灰度提到 100% |
| D53–D54 | 稳定观察期 |
5.6 第 9–10 周:固化
| 天 | 任务 |
|---|---|
| D55–D57 | 把评测卡口接入 CI(4 种触发 + 阈值) |
| D58–D60 | 成本告警(日环比 > 15% 告警) |
| D61–D63 | 文档化 + 团队培训 |
| D64–D65 | 采购侧:预留容量谈判(额外 -11%) |
| D66–D70 | 长稳观察(14 天无红线触发即结项) |
六、效果数据
6.1 总表
| 指标 | 改造前(08 月) | 改造后(10 月) | 变化 |
|---|---|---|---|
| 单任务成本(均值) | $1.47 | $0.163 | -88.9% |
| 单任务成本(复杂任务 P95) | $3.52 | $0.41 | -88.4% |
| 月支出 | $46,700 | $5,180 | -88.9% |
| 任务级通过率(1,200 条) | 98.7% | 99.96% | +1.26pt |
| 事实错误率 | 1.1% | 0.31% | -0.79pt |
| 人工复核介入率 | 8.2% | 5.4% | -2.8pt |
| 复核平均耗时 | 4.1 min | 2.6 min | -37% |
| P95 端到端延迟 | 4.2 min | 1.7 min | -60% |
| 单任务调用次数 | 40 | 22.4 | -44% |
关键对照:开头提到的行业数据「保留 99.96% 任务质量的同时降低成本 88.9%」,与本次实测高度吻合(质量口径略有差异,行业数据的 99.96% 是「相对基线的保留率」,本次是「绝对通过率」)。
6.2 分层数据
| 难度层 | 改造前成本 | 改造后成本 | 降幅 | 改造前通过率 | 改造后通过率 |
|---|---|---|---|---|---|
| 简单(≤5 步) | $0.18 | $0.031 | -82.8% | 99.6% | 99.9% |
| 中等(6–15 步) | $0.94 | $0.104 | -88.9% | 98.9% | 100% |
| 复杂(16–40 步) | $3.52 | $0.398 | -88.7% | 97.4% | 99.8% |
| 极端(>40 步) | $7.81 | $0.902 | -88.5% | 91.2% | 99.1% |
| 对抗样本 | $0.61 | $0.072 | -88.2% | 84.3% | 96.7% |
最有价值的一行是「极端」:通过率从 91.2% 提到 99.1%。原因是路由让极端任务的关键步骤全部走 frontier(改造前它们和简单任务共用同一套 Prompt 和模型,被平均掉了)。
6.3 质量为什么反而提升了
这是很多人不信的一点。四个原因:
| 原因 | 贡献 |
|---|---|
| 重试次数从 9 → 2.4,减少了「越改越乱」 | +0.5pt |
| 确定性步骤改代码,消除了模型的算术/查表错误 | +0.4pt |
| 极端和复杂任务的关键步骤走 frontier,不再被平均 | +0.3pt |
| 自检改用独立视角 Prompt,能抓到同类错误 | +0.2pt |
| 其他(压缩标注、结构化输出等) | +0.1pt |
| 路由误判等负面因素 | -0.24pt |
| 净变化 | +1.26pt |
结论:降本和质量不是对立关系。 大部分 Agent 的质量问题,根源是「该用代码的地方用了模型」和「不该重试的地方重试了」——这两个问题同时也是成本问题。修好了,两边一起改善。
6.4 延迟为什么也降了
| 因素 | 延迟贡献 |
|---|---|
| 44% 的调用变成函数调用(毫秒级) | -1.8 min |
| 无依赖步骤并发 | -0.5 min |
| 缓存命中减少 prefill 时间 | -0.2 min |
| 合计 | 4.2 → 1.7 min |
七、Prompt 模板集
模板 A:路由分类器(3.3 已给出完整版)
模板 B:上下文压缩器
你是上下文压缩器。请把下面的内容压缩,供后续步骤使用。
## 铁律
1. 所有数字(金额、数量、ID、日期、比例)必须原样保留,一个字符都不能改
2. 被判定为 critical_facts 的字段必须原样保留
3. 去除:重复描述、客套话、推理过程、中间尝试、被否决的方案
4. 保留:结论、决策依据、关键数字、未解决的问题
5. 压缩后的文本必须以 [已压缩:原 N token] 开头,让下游知道这里被压缩过
## critical_facts(必须原样保留)
{{critical_facts}}
## 待压缩内容
{{content}}
## 目标长度
不超过 {{max_tokens}} token
## 输出
直接输出压缩后的内容,不要任何前言。
模板 C:独立视角自检器
你是独立复核员。你**没有参与**前面方案的生成,请用怀疑的眼光审查。
## 你要审查的方案
{{plan}}
## 可用的原始事实(这些是权威的,方案的任何数字都必须与之一致)
{{ground_truth_facts}}
## 审查清单(逐条给结论)
1. 方案中的每个数字,是否都能在原始事实中找到依据?不一致的请列出
2. 方案是否遗漏了 {{required_aspects}} 中的任何一项?
3. 方案是否存在内部矛盾(前后不一致、自相冲突)?
4. 方案是否依赖了任何未经证实的假设?列出所有假设
5. 如果执行这个方案,最可能出问题的三个环节是什么?
## 输出格式
{
"fact_mismatches": [...],
"missing_aspects": [...],
"internal_contradictions": [...],
"unstated_assumptions": [...],
"top_risks": [...],
"verdict": "pass" | "needs_revision",
"revision_instructions": "..." // 仅在 needs_revision 时填写
}
关键点:这个 Prompt 明确告诉模型「你没有参与生成」。去掉这句话,自检的抓错率从 31% 掉到 12%——模型会倾向于为自己之前的输出辩护。
模板 D:回归对比评审器
你在对比同一个任务的两个输出,判断改造后是否变差。
## 任务
{{task}}
## 输出 A(改造前基线)
{{output_a}}
## 输出 B(改造后)
{{output_b}}
## 权威事实
{{golden_facts}}
## 判断规则
1. 先分别检查 A 和 B 的事实正确性(对照权威事实)
2. 再比较完整性:B 是否遗漏了 A 中的重要内容
3. 再比较可用性:作为一份给供应链专员的处置方案,哪个更好用
4. 如果 A 和 B 都正确且完整,判 tie,不要为了区分而区分
## 输出
{"winner": "A" | "B" | "tie", "reasoning": "...", "critical_diff": "..."}
只输出 JSON。
模板 E:难度分层器
判断这个 Agent 任务的难度分层,用于路由和监控。
## 分层定义
- simple:≤5 步,单一数据源,无权衡
- medium:6–15 步,多数据源,有简单权衡
- complex:16–40 步,多约束权衡,有不确定性
- extreme:>40 步 或 多异常并发 或 存在数据缺失需推断
## 任务
{{task_input}}
## 输出
{"difficulty": "...", "estimated_steps": N, "has_uncertainty": true/false, "reason": "..."}
八、踩坑清单
| # | 坑 | 表现 | 解法 |
|---|---|---|---|
| 1 | 用人工抽查当基线质量 | 以为 99.1%,实测 98.7% | 建全量评测集,别用抽查 |
| 2 | 评测集只采样真实流量 | 遗漏「模型做错所以没走完」的任务 | 从人工复核记录里补极端样本 |
| 3 | 没监控人工复核介入率 | 模型省 $1.9 万,人力多花 $3.4 万 | 成本指标必须与下游人力成本联看 |
| 4 | 语义缓存阈值太低 | 12% 命中率带来 6.8% 的错误率 | 阈值提到 0.995,命中率降到 3.2% |
| 5 | 变量放在 Prompt 最前面 | 缓存命中率只有 7% | 固定前缀前置,变量沉尾 → 71% |
| 6 | dict 遍历顺序不稳定 | 同样内容每次字节不同,缓存失效 | 固定序列化顺序 + 版本号 |
| 7 | 压缩时截断关键数字 | 事实错误率 0.9% | critical_facts 永不压缩 + 压缩标注 |
| 8 | 自检与生成用同一 Prompt | 抓错率 12% | 明确「你没有参与生成」→ 31% |
| 9 | JSON 解析失败整步重跑 | 34% 的重试源于此 | 结构化输出约束 → 失败率 0.3% |
| 10 | 超时重试但旧结果已入上下文 | 11% 调用重复付费 | 重试前清理上下文 + 幂等键 |
| 11 | Provider 缓存有 TTL | 高峰期排队 20 分钟导致缓存失效 | 背景包亲和调度,+6% 命中率 |
| 12 | 小步骤输入不足缓存门槛 | 19% 调用永远享受不到缓存 | 合并小步骤,可缓存占比 81%→93% |
| 13 | 路由误判后无法定位 | 出问题不知道哪一步被降级 | 每次路由记录 tier + 理由,可回溯 |
| 14 | 评测集原地修改 | 历史跑分不可比,趋势失真 | 版本号写入样本,修改开新版本 |
九、经验总结
9.1 五条原则
原则一:先测量,再优化。 第 0 周的 5 天埋点是全项目 ROI 最高的投入。没有它,后面 8 周所有的决策都是猜。埋点上线第一天就发现了 17.3% 的纯浪费。
原则二:确定性的事,不要交给模型。 这是单条收益最高的一条。44% 的调用(图遍历、查表、算术、schema 校验)改成了代码,零成本、零错误、毫秒级。它需要后端工程参与,不是纯 Prompt 工作。
原则三:降本的正确粒度是「步骤」,不是「任务」。 「这个任务用便宜模型」几乎总是失败(71.3% 通过率);「这个任务的第 7 步用前沿模型、第 12 步用代码」才是正解(99.96%)。
原则四:缓存是结构问题,不是开关问题。
开一个 cache=true 参数只能拿到 7% 命中率;把 Prompt 重构成「固定前缀 + 变量沉尾」才能拿到 78%。这需要改代码,不是改配置。
原则五:质量指标必须包含下游。 只看模型账单会做出「全量降级」这种净亏损的决策。人工复核介入率、复核耗时、用户放弃率,这三条必须和成本放在同一张看板上。
9.2 什么情况下不该做这件事
诚实地说,这套改造不是所有团队都该做:
| 情况 | 建议 |
|---|---|
| 日调用成本 < $50 | 不值得。直接买更便宜的模型,或者接受成本 |
| 任务数还在快速增长(月增 > 50%) | 先做埋点,暂不做架构改造——需求还在变 |
| 没有后端工程资源 | 只能做刀 2(缓存)和刀 5(bug),放弃刀 1 的 52% |
| 没有人工复核环节 | 先建复核环节,否则你没有质量基线的来源 |
| 任务是创意类(写作、设计) | 模型路由的空间很小,大部分步骤都需要前沿模型 |
最重要的前提:你必须已经有人工复核或某种形式的质量反馈。如果你连「AI 做错了多少次」都不知道,那么你要做的第一件事不是降本,是建立质量反馈回路。
9.3 下一步
T 公司在完成这轮之后的三个方向:
- 小模型自托管:把占比 19% 的 small 层调用迁到 vLLM 自托管,预计再降 3–4%(绝对值不大,但能消除 API 限流风险)。
- 路由策略学习化:用历史数据训练一个更准的路由分类器(现在 94.1% 一致率,目标 98%),把 1.6% 的激进误判压到 0.5% 以下。
- 成本预算硬约束:给每个业务线设月度成本上限,超限自动降级到 balanced 层并告警——把成本治理从「项目」变成「机制」。
9.4 一句话
Agent 的成本问题,本质上是架构问题在账单上的投影。你看到的 $46,700,是「把确定性计算交给模型」+「同样的内容付费 31 次」+「工程 bug 导致的重试」这三件事的货币化结果。修架构,账单自己会下来——顺便,质量还会上去。
附录 A:成本归因脚本
下面这段是 T 公司实际在用的归因脚本的简化版,输入是 Langfuse 导出的调用链 JSON,输出是归因表。可以直接改改就用。
import json
from collections import defaultdict
# 各档模型的单价(美元 / 百万 token),按自己的实际合同改
PRICE = {
"opus-5.5": {"in": 12.0, "out": 60.0, "cached_in": 1.2},
"balanced": {"in": 3.0, "out": 15.0, "cached_in": 0.3},
"small": {"in": 0.8, "out": 4.0, "cached_in": 0.08},
}
CACHE_MIN_TOKENS = 2048 # Provider 的缓存最小长度门槛
def step_cost(s):
p = PRICE.get(s["model"], PRICE["balanced"])
fresh_in = max(0, s["input_tokens"] - s["cached_tokens"])
return (
fresh_in / 1e6 * p["in"]
+ s["cached_tokens"] / 1e6 * p["cached_in"]
+ s["output_tokens"] / 1e6 * p["out"]
)
def attribute(traces):
"""traces: [{trace_id, difficulty, steps:[{...}]}]"""
buckets = defaultdict(float)
total = 0.0
for t in traces:
for s in t["steps"]:
c = step_cost(s)
total += c
# 归因顺序很重要:先判"根本不该调模型",再判"浪费",最后才是"错配"
if s["step_name"] in DETERMINISTIC_STEPS:
buckets["确定性步骤误用模型"] += c
elif s.get("never_read"):
buckets["结果未被读取"] += c
elif s.get("retry_count", 0) > 0 and s.get("retry_reason") in ENGINEERING_BUGS:
buckets["工程 bug 导致的重试"] += c
elif s["cached_tokens"] == 0 and s["input_tokens"] >= CACHE_MIN_TOKENS:
# 本可缓存但没缓存:把"若能命中"的差额算作浪费
buckets["上下文重复搬运(未命中)"] += c * 0.9
elif s["model_tier"] == "frontier" and s["step_name"] not in FRONTIER_REQUIRED:
buckets["模型能力错配"] += c * 0.7
else:
buckets["合理且必要的开销"] += c
return {k: (v, v / total if total else 0) for k, v in buckets.items()}, total
def long_tail(traces):
"""返回成本最高的 20% 任务贡献了多少支出"""
costs = sorted((sum(step_cost(s) for s in t["steps"]) for t in traces), reverse=True)
n = max(1, int(len(costs) * 0.2))
return sum(costs[:n]) / sum(costs)
def below_cache_threshold(traces):
steps = [s for t in traces for s in t["steps"]]
below = [s for s in steps if s["input_tokens"] < CACHE_MIN_TOKENS]
return len(below) / len(steps), len(below)
# —— 需要人工维护的三张表(这是脚本的核心输入,没有它们归因就是瞎猜)——
DETERMINISTIC_STEPS = {"bom_traversal", "supplier_query", "certification_check",
"cost_model_calculation", "format_validation"}
ENGINEERING_BUGS = {"json_parse_failed", "tool_param_error", "timeout_retry"}
FRONTIER_REQUIRED = {"task_decomposition", "plan_generation", "multi_supplier_tradeoff"}
脚本之外更重要的三张表:DETERMINISTIC_STEPS、ENGINEERING_BUGS、FRONTIER_REQUIRED。这三张表是人工标注的结果,脚本只是执行归因。没有标注就没有归因——这是很多团队想跳过的一步,跳过了就只能得到一句「Prompt 可能可以优化」。
附录 B:完整路由表(43 条)
这是 T 公司最终定稿的静态路由表,可以直接作为起点。
| # | step_name | tier | 理由 | 改成代码需要的接口 |
|---|---|---|---|---|
| 1 | task_ingest | rule | 解析事件 payload | parseIncident(payload) |
| 2 | task_decomposition | frontier | 决定全局,错一步全错 | — |
| 3 | priority_classification | small | 明确规则的分类 | — |
| 4 | bom_traversal | rule | 图遍历 | traverseBOM(sku, depth) |
| 5 | inventory_query | rule | 数据库查询 | queryInventory(skus) |
| 6 | in_transit_query | rule | 数据库查询 | queryInTransit(skus) |
| 7 | supplier_query | rule | 数据库查询 | querySuppliers(criteria) |
| 8 | certification_check | rule | 查表比对 | checkCert(supplierId) |
| 9 | capacity_check | rule | 查表 + 简单算术 | checkCapacity(supplierId, qty) |
| 10 | lead_time_estimate | small | 需要一点推断 | — |
| 11 | cost_model_calculation | rule | 确定性计算 | calcLandedCost(...) |
| 12 | tariff_lookup | rule | 查表 | lookupTariff(origin, hsCode) |
| 13 | freight_estimate | rule | 查表 + 插值 | estimateFreight(...) |
| 14 | currency_conversion | rule | 外部 API | fx.convert(...) |
| 15 | cost_comparison | rule | 排序与差值 | compareOptions(list) |
| 16 | risk_factor_extract | small | 抽取结构化要素 | — |
| 17 | risk_scoring_simple | small | 单供应商、规则清晰 | — |
| 18 | risk_scoring_multi | balanced | 多供应商权衡 | — |
| 19 | risk_scoring_extreme | frontier | 多异常并发 | — |
| 20 | contract_term_check | rule | 条款查表 | checkTerms(contractId) |
| 21 | contract_term_judgement | balanced | 条款需要解释 | — |
| 22 | legal_redline_detect | frontier | 高风险,需谨慎 | — |
| 23 | alternative_generation | frontier | 方案设计 | — |
| 24 | plan_generation | frontier | 核心产出 | — |
| 25 | plan_formatting | small | 格式转换 | — |
| 26 | fact_extraction | small | 从 plan 抽关键数字 | — |
| 27 | fact_verification | rule | 对照 ground truth 比对 | verifyFacts(plan, facts) |
| 28 | schema_validation | rule | JSON schema 校验 | validate(schema, obj) |
| 29 | self_verification | balanced | 独立视角审查 | — |
| 30 | consistency_check | balanced | 前后一致性 | — |
| 31 | completeness_check | small | 清单式检查 | — |
| 32 | uncertainty_flagging | small | 标出不确定项 | — |
| 33 | human_summary | small | 摘要生成 | — |
| 34 | human_summary_translate | small | 翻译 | — |
| 35 | escalation_decision | balanced | 是否升级人工 | — |
| 36 | notification_compose | small | 模板化文案 | — |
| 37 | audit_log_write | rule | 写日志 | audit.log(...) |
| 38 | context_compression | small | 压缩历史 | — |
| 39 | context_budget_check | rule | 计数对比 | countTokens(ctx) |
| 40 | retry_decision | rule | 重试策略 | shouldRetry(err, n) |
| 41 | tool_result_cache_lookup | rule | 缓存查询 | cache.get(key) |
| 42 | final_review_gate | balanced | 终审 | — |
| 43 | fallback_to_human | rule | 转人工 | escalate(taskId) |
分布统计:
| tier | 条数 | 占比 |
|---|---|---|
| frontier | 5 | 11.6% |
| balanced | 7 | 16.3% |
| small | 11 | 25.6% |
| rule | 20 | 46.5% |
近一半的 step 根本不该调模型。 这是整张表最值得记住的一行。
附录 C:灰度批次计划模板
| 批次 | 流量 | 观察时长 | 进入下一批的条件 | 回滚触发 |
|---|---|---|---|---|
| 批次 0 | 内部账号 100% | 3 天 | 无红线触发 | 任一红线 |
| 批次 1 | 5% | 3 天 | 通过率下降 < 0.2pt 且 P95 延迟下降 > 10% | 任一红线 |
| 批次 2 | 20% | 5 天 | 通过率下降 < 0.3pt 且 复核介入率未上升 | 任一红线 |
| 批次 3 | 50% | 7 天 | 全部指标稳定 | 任一红线 |
| 批次 4 | 100% | 14 天 | 结项 | 任一红线 |
每一批都要跑全量评测集,不要只在灰度流量上跑。灰度流量的任务分布与评测集不同,只在灰度上看等于没有对照组。
每批次的检查清单:
□ 全量评测集跑分完成,通过率记录在案
□ 事实错误率对比基线
□ 人工复核介入率与复核耗时
□ P95 / P99 延迟
□ 缓存命中率
□ 路由 tier 实际分布 vs 预期分布(偏差 > 10% 要查)
□ 成本(单任务 P50 / P95)
□ 用户反馈(工单、满意度)
□ 无 P0/P1 事故
附录 D:给管理层的汇报模板
技术方案做得再好,拿不到预算也是白搭。这个模板解决「怎么跟非技术的人说清楚」。
## Agent 成本治理(第 X 期汇报)
### 一句话
在不降低质量的前提下,把 AI 单任务成本从 $1.47 降到 $0.16(-88.9%),
月支出从 $46,700 降到 $5,180。质量从 98.7% 提升到 99.96%。
### 三个数字
- 年化节省:约 $49.8 万(按当前任务量)
- 质量变化:+1.26 个百分点(不是下降)
- 用户侧:平均等待从 4.2 分钟降到 1.7 分钟
### 钱花在哪、省在哪
| 类别 | 占比 | 性质 |
|---|---|---|
| 把本该代码做的事交给了模型 | 31% | 工程错误 |
| 同样的内容重复付费 | 27% | 架构问题 |
| Bug 导致的无效重试 | 23% | 质量问题 |
| 模型档位用高了 | 15% | 配置问题 |
| 必要开销 | 4% | 保留 |
### 投入
- 周期:10 周
- 人力:3 名后端 + 1 名算法 + 0.5 名 QA
- 一次性投入约 4.5 人月
### 回报
- 静态回收期:约 1.4 个月
- 且质量与延迟同步改善
### 风险与对策
| 风险 | 概率 | 对策 |
|---|---|---|
| 长尾任务质量下降 | 中 | 五条回滚红线 + 全量评测集卡口 |
| 成本转移到人工复核 | 中 | 复核介入率纳入红线(发生过一次) |
| 后续需求变化导致返工 | 低 | 路由表与业务解耦,改配置不改代码 |
汇报的关键:不要讲「模型路由」「Prompt Caching」这些技术名词。讲三个东西——钱花在哪(浪费占比)、省了多少、质量有没有变差。管理层关心的是这三个。技术细节放在附录里,有人问再看。
附录 E:一页纸速查
┌────────────────────────────────────────────────────────┐
│ Agent 成本治理 速查 │
├────────────────────────────────────────────────────────┤
│ 五把刀(按 ROI 排序) │
│ 1. 模型路由 + 确定性步骤代码化 52% │
│ 2. Prompt Caching(前缀稳定化) 23% │
│ 3. 上下文预算与压缩 9% │
│ 4. 批处理与并发 3% │
│ 5. 早停与去重(修 bug) 2% ← 唯一提升质量 │
├────────────────────────────────────────────────────────┤
│ 三条铁律 │
│ · 确定性的事不要交给模型 │
│ · 固定前缀在前,变量沉尾 │
│ · 成本指标必须和"下游人力成本"一起看 │
├────────────────────────────────────────────────────────┤
│ 不要做 │
│ × 全量换成便宜模型(通过率 99% → 71%) │
│ × 语义近似缓存(12% 命中换来 6.8% 错误) │
│ × 人工抽查当质量基线 │
│ × 省了模型钱但让人多干活 │
├────────────────────────────────────────────────────────┤
│ 前置条件(没有就先补) │
│ 1. 全链路埋点(15 个字段) │
│ 2. 1,200 条评测集(含极端 + 对抗) │
│ 3. 人工复核环节(质量基线的来源) │
└────────────────────────────────────────────────────────┘
附录 F:术语表
读这篇文章(以及 2026 年大部分 Agent 工程资料)时会反复遇到的词,一次讲清。
| 术语 | 含义 | 本文语境 |
|---|---|---|
| Harness | 包裹在模型外面的执行框架:编排、工具调用、上下文管理、路由、重试 | 模型路由就属于 Harness 层能力,本文刀 1 的载体 |
| 模型路由(Model Routing) | 每次调用前先判断需要什么档位的模型,再分发 | 本文贡献最大的一把刀(52%) |
| Prompt Caching | 相同前缀的 KV 缓存复用,命中部分输入按折扣计价 | 本文刀 2,命中部分约为原价 1/10 |
| 前缀(Prefix) | 输入中最靠前、需要逐字节稳定的那一段 | 稳定性决定缓存命中率 |
| 缓存门槛 | Provider 要求的最小可缓存长度(1024 / 2048 token) | 低于它的调用永远享受不到缓存 |
| 缓存 TTL | 缓存的有效时长(5 分钟 ~ 1 小时) | 任务排队过久会导致失效 |
| 上下文预算 | 给每类步骤设定的输入 token 上限 | 超了走压缩或裁剪 |
| critical_facts | 绝不能被压缩或改写的关键事实(金额、ID、日期) | 压缩策略里唯一永不压缩的部分 |
| tier | 模型档位:frontier / balanced / small / rule | rule 表示由代码完成,零模型成本 |
| 任务级通过率 | 以「整个任务是否办成」为单位的成功率 | 本文的质量主指标,区别于步骤级 |
| 事实错误率 | 关键数字与标准答案不一致的比例 | 比「通过率」更敏感的指标 |
| 人工复核介入率 | 需要人工修改 AI 产出的任务占比 | 必须和成本一起看,否则会做出净亏损决策 |
| 评测集(Eval Set) | 带标准答案的任务集合,用于跑分与回归 | 本文 1,200 条,含极端层与对抗层 |
| 对抗样本 | 刻意构造的、容易让模型出错的输入 | 30 条,用于检测降级是否被「跑分好看」掩盖 |
| 灰度切流 | 按比例逐步放量新方案的发布方式 | 本文 5 批,每批有进入条件与回滚红线 |
| 回滚红线 | 触发立即停止/回滚的指标阈值 | 5 条,其中复核介入率是吃了亏才加的 |
| 亲和调度 | 把共享同一前缀的任务排到相邻时间窗执行 | 提高缓存命中率 +6% |
| 批处理 API | 非实时任务走异步通道,单价约五折 | 本文刀 4,覆盖约 22% 任务 |
| 幂等键 | 用于识别重复请求的唯一键 | 修「超时重试但旧结果已入上下文」的关键 |
| 结构化输出约束 | 强制模型输出符合给定 schema | 把 JSON 解析失败率从 12% 降到 0.3% |
| 归因(Attribution) | 把总成本拆到各类开销上并给出占比 | 附录 A 的脚本就是干这个的 |
| 长尾任务 | 成本最高、步骤最多的那 20% 任务 | 本文贡献了约 68% 的支出 |
十、相关阅读
- 《Claude Opus 5.5 接入 GitHub Copilot 完整配置指南》——本文路由层涉及的前沿模型选型与配置
- 《Cache-Friendly Prompting:四层结构与逐字节前缀稳定》——本文刀 2 的方法论展开
- 《GitHub MCP:让 Agent 直接操作仓库与 Issue》——本文刀 1 中「工具调用代码化」的配套方案
- 《模型迁移实战:从闭源前沿到开放权重》——当路由还不够时的下一步