Agent 为什么这么烧钱:成本结构拆解与六个控制手段
有个场景我见过太多次了:团队先做了一个「问答式」的功能,跑了一个月,账单在心理预期之内,大家都很满意。然后有人说「这个我们做成 Agent 吧,让它自己查资料、自己调工具、自己修正」。功能确实变强了,两周后财务那边把账单拍过来——同样的业务量,翻了十几倍。
奇怪的是模型没换,单价一分没涨。涨的是调用次数,以及每次调用要塞进去的上下文长度。这两件事一乘,就是数量级的差距。
这篇把 Agent 的成本结构一块一块拆开,讲清楚每块贵在什么机制上、怎么量化,然后按性价比排一遍控制手段。不谈任何厂商的具体价格,所有算例都用假设参数做纯算术演示——你把自己的实际单价乘进去就是自己的账。
如果你还没搞清楚 Agent 到底是怎么跑起来的,建议先看 Agent 应用模式概览 和 工具调用怎么设计,本文默认你知道「一轮 = 一次完整的模型调用」这件事。
一、先把「一次任务」和「一次请求」分开
Agent 成本失控的第一个认知障碍,就是把这两个词混着用。
一问一答里,一次用户请求 = 一次模型调用 = 一笔账。这两者是同一件事,所以你随便看哪个维度都行。
Agent 里,一次用户任务 = N 次模型调用 = N 笔账。而且这 N 笔的单价虽然一样,token 量却是逐轮递增的。你在计费面板上看到的是 N 条便宜的请求记录,每条都不吓人,但它们属于同一个任务。
这就是为什么很多团队明明在看监控,却完全没预警到成本爆炸——监控的聚合维度错了。这一点后面第五节会展开讲,现在先记住:本文说「成本」时,单位永远是「每任务」,不是「每请求」。
二、成本结构拆解:五块,各有各的机制
2.1 轮次膨胀:每一轮都是完整计费
Agent 的基本循环是「思考 → 调工具 → 看结果 → 再思考」。每次「思考」都是一次独立的、完整的模型调用,按当时的输入 token 加输出 token 计费。没有任何折扣是因为「这是同一个任务的第三轮」。
一个查资料类的任务,轮次通常在 3 到 8 之间;一个要改代码、跑测试、看报错、再改的任务,十几轮很常见。轮次是成本的第一个乘数。
怎么量化:在你的调用日志里给每次调用打上 task_id,然后统计每个任务的调用条数分布。你要看的不是均值,是 P90 和 P99——均值 4 轮、P99 却是 35 轮的系统,钱是被那个尾巴吃掉的。
2.2 上下文累积:这是最大的一块
这块必须讲透,因为它是 Agent 和普通对话在成本结构上的本质区别。
模型调用是无状态的。第三轮要让模型知道前两轮干了什么,唯一的办法是把前两轮的内容重新完整发一遍。所以第 k 轮的输入长度大致是:
输入(k) = 固定前缀 + Σ(前 k-1 轮的模型输出 + 前 k-1 轮的工具返回结果)
注意「工具返回结果」这一项。模型自己每轮输出的 token 通常不多(几十到几百),但工具返回的东西可能非常大:一次搜索返回五条摘要、一次数据库查询返回三十行、一次文件读取返回整个文件。这些观察结果会原封不动地躺在上下文里,被后面每一轮反复重发。
一个 600 token 的搜索结果,如果它出现在第 2 轮,那么第 3、4、5、6 轮的输入里都有它。它被计费了五次。
2.3 工具定义常驻:挂得越多越贵
工具的 schema——名字、描述、每个参数的类型和说明——每一轮都要放在输入里。模型必须看到它才知道自己有什么能力。
单个工具的定义写得规范一点,一两百 token 很正常。挂 5 个工具就是几百到一千 token,挂 20 个就是几千。这几千 token 每一轮都在重发。
我见过一种很常见的偷懒做法:把系统里所有能力做成一个大工具包,所有 Agent 都挂全量。写起来是省事了,代价是每个任务的每一轮都在为它永远用不到的十几个工具付钱。
2.4 失败重试与自我纠错:钱花得静悄悄
这一块阴在「它是自动的」。
工具调用失败了,Agent 会自己再试一次。参数格式错了,模型看到报错会自己改参数重来。这些在功能上是优点,在成本上是没有人按下同意按钮的额外支出。
而且重试是叠加在上下文累积之上的:失败的那一轮不会从历史里消失,它连同错误信息一起留在上下文里,继续被后面每轮重发。一次失败重试的实际代价,是「多一轮调用」加上「所有后续轮次的输入都变长」。
关于重试本身怎么算账,重试的隐性成本 那篇讲得更细,这里不重复。
2.5 跑偏与死循环:预算杀手
前四块是可以估算的,这一块不能——它是长尾的。
典型的失控形态有几种:模型陷入「调工具 → 结果不满意 → 换个参数再调」的循环;模型判断任务已完成又不放心,再调一次工具「确认一下」;两个动作互相触发形成振荡。
没有硬性上限的 Agent,理论上可以一直跑到上下文塞满为止。而上下文快塞满的时候,恰恰是每一轮最贵的时候。一个跑飞的任务,成本可能是正常任务的几十倍,而且它通常是在半夜、在你没盯着的时候发生的。
三、算一遍:一问一答 vs 5 轮 Agent
下面全部是假设参数,用于演示算术结构,不代表任何真实系统的实测值。请把它当成一个可以套自己数字的模板。
假设参数:
| 项目 | 假设值 |
|---|---|
| 系统提示 | 500 token |
| 工具定义(8 个 × 150) | 1200 token |
| 用户任务描述 | 300 token |
| 每轮模型输出(思考 + 工具调用参数) | 150 token |
| 每轮工具返回的观察结果 | 600 token |
| 最终答案输出 | 500 token |
场景 A:一问一答
输入 = 系统提示 500 + 用户问题 300 = 800 token 输出 = 500 token
场景 B:5 轮 Agent
固定前缀 = 500 + 1200 + 300 = 2000 token 每轮结束后,历史增长 = 150(模型输出)+ 600(观察结果)= 750 token
| 轮次 | 输入 token | 说明 |
|---|---|---|
| 第 1 轮 | 2000 | 只有固定前缀 |
| 第 2 轮 | 2750 | +1 轮历史 |
| 第 3 轮 | 3500 | +2 轮历史 |
| 第 4 轮 | 4250 | +3 轮历史 |
| 第 5 轮 | 5000 | +4 轮历史 |
| 合计 | 17500 |
输出 = 前 4 轮各 150 + 最后一轮最终答案 500 = 600 + 500 = 1100 token
对比:
- 输入:17500 ÷ 800 = 21.9 倍
- 输出:1100 ÷ 500 = 2.2 倍
- 若输入输出单价相同(只是为了得到一个总量比值):(17500 + 1100) ÷ (800 + 500) = 18600 ÷ 1300 ≈ 14.3 倍
实际单价通常输出高于输入,具体倍率以你所用服务的官方价格表为准。但结论方向不变:膨胀主要发生在输入侧。
把「重复重发」那部分单独拎出来
17500 token 的输入里,真正的「新信息」只有多少?
- 第 1 轮的固定前缀:2000(首次,必须付)
- 后 4 轮各新产生 750 的历史:750 × 4 = 3000(这些内容首次进入上下文,也必须付)
- 新信息合计 = 5000
剩下的 17500 − 5000 = 12500 token 是重复重发,占总输入的 12500 ÷ 17500 ≈ 71.4%。
拆得更细一点:固定前缀被完整重发了 4 次 = 2000 × 4 = 8000;历史内容的重复重发 = 4500。这 8000 是有办法用缓存前缀打掉的,后面会讲。
为什么是二次方趋势
把轮次记为 N,固定前缀记为 F,每轮历史增量记为 G,总输入是:
总输入 = N × F + G × N(N-1)/2
第一项随 N 线性增长,第二项里的 N(N-1)/2 是 N 的二次项。轮次一多,第二项就会盖过第一项。
代入验证:N=5 时,5 × 2000 + 750 × 10 = 10000 + 7500 = 17500 ✓
看看轮次翻倍会发生什么。N=10:
10 × 2000 + 750 × (10 × 9 ÷ 2) = 20000 + 750 × 45 = 20000 + 33750 = 53750
轮次从 5 到 10 涨了 2 倍,输入从 17500 到 53750 涨了约 3.07 倍。
再看一个失控的例子,N=40(比如你设了一个很宽松的上限,或者根本没设):
40 × 2000 + 750 × (40 × 39 ÷ 2) = 80000 + 750 × 780 = 80000 + 585000 = 665000
轮次从 5 到 40 是 8 倍,输入从 17500 到 665000 是 38 倍。相对最初那个一问一答的 800 token 输入,是 831 倍。
这就是为什么下一节里,硬性上限排在第一位。它不是「一个不错的优化」,它是止损开关。
四、控制手段,按性价比排序
排序依据是「投入的工程量」除以「省下的钱 + 避免的尾部风险」。前两条几乎是白送的,越往后成本收益比越低、实现越麻烦。
4.1 硬性上限:没有它其他都白搭
三个维度都要设,缺一不可:
- 最大轮次:单个任务允许的模型调用次数上限,到了就强制终止并返回当前最好的结果
- 最大总 token:单任务累计输入 + 输出的天花板,防止「轮次不多但每轮巨大」的情况
- 单任务预算:折算成金额的上限,这是给业务方看的那个数
为什么它排第一:前面算过,失控任务的成本是长尾的,一个跑飞的任务能吃掉几百个正常任务的预算。其他所有优化手段都是在优化正常任务的成本,只有硬上限管得住不正常的那部分。而正常任务优化个 30% 是加法,失控任务是乘法。
实现上要注意:上限触发时不要静默失败。要记日志、要报警、要能回溯是哪个任务、卡在哪一步。上限被频繁触发本身就是一个信号——要么任务太难,要么 Agent 的终止判据有问题。
具体的预算折算方法,可以配合 月度预算怎么估 那篇里的口径一起做。
4.2 精简工具集:按任务类型挂载
沿用前面的假设。把工具从 8 个减到 3 个,工具定义从 1200 降到 450,固定前缀从 2000 降到 1250。
N=5 时:5 × 1250 + 7500 = 6250 + 7500 = 13750
相比 17500 省了 3750,降幅 3750 ÷ 17500 ≈ 21.4%。
改一行配置换 20% 出头的输入降幅,这是全场性价比最高的一条。做法是按任务类型分组挂载:查询类任务只给检索工具,写入类任务才给写工具。如果你的系统只有一个入口不好分类,退一步的做法是让第一轮先做一次意图判断再决定挂载哪组——多花一轮,但如果工具池很大,这一轮是划算的。
顺带一个副作用:工具少了,模型选错工具的概率也低了,间接减少了重试轮次。
4.3 压缩历史:只留结论,不留全文
把每轮 600 token 的观察结果,摘要成 150 token 的关键结论再放进历史。每轮历史增量从 750 降到 300。
N=5 时:5 × 2000 + 300 × (5 × 4 ÷ 2) = 10000 + 300 × 10 = 10000 + 3000 = 13000
相比 17500 省 4500,降幅 ≈ 25.7%。
和 4.2 叠加使用: 5 × 1250 + 3000 = 6250 + 3000 = 9250,相比 17500 降幅 ≈ 47.1%。两条加起来,输入砍掉将近一半。
实操上有几个要点:
- 长结果先摘要再入上下文,别等到上下文满了再回头压缩。压缩本身是要花钱的(多一次调用),但摘要用小模型做就行
- 摘要要保留「可追溯的引用」,比如结果 ID、行号、URL,让 Agent 需要时能重新取回全文,而不是把信息彻底丢了
- 有些内容不能压:任务目标、硬性约束、上一轮的错误信息。这些压掉会直接导致 Agent 重复犯错,省下的钱又以重试的形式还回去
- 轮次越多,这条越值钱。3 轮以内的任务基本不用做
关于历史怎么存、怎么取,多轮对话怎么管上下文 那篇有更细的结构设计。
4.4 分层模型:规划用强的,执行用小的
Agent 的轮次里,真正需要强推理能力的通常只有两类:最开始的任务分解,和最后的结果综合。中间那些「拿到工具结果、判断够不够、决定下一步调哪个」的轮次,往往是格式化程度很高的判断,小模型足够。
按前面的假设,第 1 轮(2000)和第 5 轮(5000)用强模型 = 7000 token,占总输入的 40%;中间三轮 2750 + 3500 + 4250 = 10500,占 60%,交给小模型。
省多少完全取决于两档模型的价差,这个以你所用服务的官方价格表为准,我不给数字。但结构上你能看出来:60% 的输入量可以降档。
代价是工程复杂度上来了:要维护两套提示、要处理两个模型输出格式的差异、要在切换点上做兼容。如果你的 Agent 还在快速迭代,这条建议往后放。
4.5 缓存前缀:把稳定部分放在最前面
前面算过,固定前缀在 5 轮里被完整重发了 4 次,共 8000 token,占总输入的 8000 ÷ 17500 ≈ 45.7%。这部分内容每一轮都完全一样,是缓存的理想对象。
要吃到缓存价,有一条铁的排布原则:按「稳定性从高到低」组织上下文。
[ 系统提示 ] ← 几乎不变,最前
[ 工具定义 ] ← 同一类任务内不变
[ 任务描述 ] ← 单个任务内不变
[ 历史动作与观察 ] ← 每轮增长,最后
前缀缓存的匹配是从头开始的,中间任何一个 token 变了,它后面的全部失效。所以最常见的翻车是往系统提示里塞了当前时间戳、随机 ID、或者动态拼进去的用户名——这一下就把整个缓存前缀作废了,你以为在省钱,实际一次都没命中。
另一个坑是工具定义的顺序。如果你的工具是从一个哈希表里遍历出来再序列化的,顺序可能每次都不一样,同样会破坏缓存。序列化前排个序。
折扣比例、缓存有效期、最小可缓存长度这些数值,各家不一样,以官方文档和控制台为准。你要做的是先把排布做对,让缓存有命中的机会,然后从账单上验证命中率是不是真的上去了。
4.6 提前终止:明确成功判据
「再确认一下」是 Agent 最贵的口头禅。
对策不是在提示里写「不要重复确认」——模型不一定听。对策是把成功判据做成可判定的条件,由代码来判,不由模型来判:
- 任务要求「找到某个配置项的值」→ 判据是「已经拿到一个非空的值」,拿到就返回,不再进循环
- 任务要求「修改并验证」→ 判据是「验证步骤返回通过」,通过就停
- 拿不到明确判据的任务,退而求其次:连续两轮没有产生新信息就终止(比如两轮工具调用返回了相同的结果)
这条实现起来不难,收益主要在于砍掉那些「已经完成但还在空转」的尾巴轮次。而尾巴轮次恰好是最贵的——因为它们的上下文最长。
五、可观测性:必须按「任务」聚合
这一节短,但它决定了前面四节能不能落地。
要记的字段,粒度是任务:
| 字段 | 说明 |
|---|---|
task_id | 贯穿一个任务所有调用的唯一 ID,这是一切的前提 |
| 总输入 token | 该任务所有轮次输入之和 |
| 总输出 token | 同上 |
| 轮次数 | 模型调用次数 |
| 工具调用次数 | 分工具名统计,用来发现哪个工具在被反复调 |
| 重试次数 | 因失败或格式错误触发的额外轮次 |
| 终止原因 | 正常完成 / 触发轮次上限 / 触发 token 上限 / 出错 |
| 任务类型 | 用来做分组对比 |
为什么必须按任务聚合,不能按请求:
按请求看,你的面板上是几万条调用,每条 2000 到 5000 token,平均值很好看,没有任何一条触发告警阈值。按任务看,你会立刻发现有 3% 的任务消耗了 40% 的 token。
这两个视图看的是同一批数据,结论完全不同。 按请求聚合的面板永远发现不了「一个任务跑了 60 轮」这件事,因为那 60 轮里每一轮单独看都是正常的。
有了任务维度的数据,你才能算出真正有用的三个数:
- 单任务平均成本:拿来跟业务价值对比,判断这个功能划不划算
- 单任务成本的 P99:拿来设硬性上限,上限一般设在 P99 往上一点
- 成本 ÷ 成功率:一个失败率 30% 的 Agent,它的有效单位成本是平均成本除以 0.7,也就是高出约 43%。这个数才是你该拿去做决策的数
最后一个数经常被忽略。失败的任务同样在烧钱,而且失败前往往烧得更多——它通常是触发了上限才终止的。
六、什么时候压根不该用 Agent
Agent 的溢价买的是运行时的决策自由度。如果你的任务不需要这个自由度,这笔钱就是白花的。
判断标准,满足两条以上就别做 Agent:
- 步骤是固定的。你能提前画出完整流程图,没有分支或者只有两三个可枚举的分支
- 工具调用顺序是确定的。永远是先查 A 再查 B 再汇总,不存在「看了 A 的结果再决定要不要查 B」
- 失败处理是确定的。哪一步失败了该怎么办,规则写得出来
- 输入格式稳定。不需要模型去理解一段自由文本才知道该干什么
满足这些条件的任务,写成固定流水线不仅便宜,而且更稳:
| 维度 | 固定流水线 | Agent |
|---|---|---|
| 模型调用次数 | 确定,通常 1 到 2 次 | 不确定,3 到几十次 |
| 每次输入 | 只带该步骤需要的内容 | 带全量历史 |
| 成本可预测性 | 可以精确算出来 | 只能给分布 |
| 失败可复现 | 是 | 部分不可复现 |
| 出问题排查 | 定位到具体步骤 | 要读完整轨迹 |
有一个折中形态值得考虑:主干用固定流水线,只在真正需要判断的那一步嵌一次模型调用。比如整个流程是固定的七步,其中第三步需要「根据用户描述判断属于哪个类别」,那就只有这一步调模型,其余六步是普通代码。这种做法的成本接近一问一答,能力接近 Agent,是我最常推荐的形态。
反过来说,什么时候 Agent 是值的:任务的步骤数和顺序事先真的不知道,需要根据中间结果动态决定;或者输入的形态太发散,穷举不出流程。这两种情况下,Agent 省下的是工程师的时间,那笔钱花得不冤。
上线前自查清单
- 每个任务有唯一
task_id,所有调用日志都带上它,成本面板按任务聚合而不是按请求聚合 - 最大轮次、最大总 token、单任务预算三个硬上限全部设置并生效,触发时有日志和报警
- 工具按任务类型分组挂载,确认没有出现「所有任务挂全量工具池」的情况
- 上下文按「系统提示 → 工具定义 → 任务描述 → 历史」的稳定性顺序排布,系统提示里没有时间戳、随机 ID 等破坏缓存的动态内容,工具定义序列化前已排序
- 超长的工具返回结果先摘要再入上下文,摘要保留可追溯的引用,任务目标和错误信息不压缩
- 有可由代码判定的成功判据,达成即终止;没有明确判据的任务,配置了「连续两轮无新信息则停」的兜底
- 用自己的实际单价套一遍第三节的算术模板,算出单任务平均成本、P99 成本,以及「成本 ÷ 成功率」的有效单位成本
- 重新过一遍第六节的四条标准,确认这个任务确实需要运行时决策自由度,而不是一条能画出流程图的固定流水线