3041 字
约 10 分钟
15
S22-A12 · Agent 的运行成本:一次任务烧多少

上线一个自动处理任务,第二周被问起账单。查下来不是流量涨了:请求量平稳,单次请求的输入长度也平稳,但每个任务内部的模型调用次数从三次变成十几次——有些任务在工具调用之间来回试了七八轮才收敛。同一份流量,成本翻了三倍。

这是 Agent 与普通服务最本质的成本差异:普通服务按请求计费,Agent 按推理步数计费,而步数不可预知——请求数是可预测的输入,步数是运行时的产物。


1. 成本结构与普通服务的差异

成本组成 由什么驱动 可预测性
模型调用 步数 × 每步输入输出 token 差,步数方差最大
工具调用 步数 × 工具单价 × 参数规模 中,回合数受任务影响
检索与重试 查询次数 × 重排深度;失败步数 × 重放成本 检索可缓存,重试尾部极长

关键差异有三条:

  1. 步数由内容决定,不由流量决定。同一个接口、同样大小的请求,任务难度不同则步数差一个量级。
  2. 输入 token 会随步数累积。多轮 Agent 的每一轮都可能带着历史上下文,若不做压缩,第 N 步的输入约等于前 N−1 步之和。
  3. 失败会放大步数。一次失败的尝试通常已经消耗了若干步和若干工具调用,重试是在这个基础上再加一轮。

所以"按请求估算成本"在 Agent 上是失效的。有用的单位是单任务成本分布:p50 与 p99 的差距通常在一到两个数量级,只看平均值会把长尾亏掉的部分藏起来。


2. 步数爆炸的两个原因

步数异常增长的根因只有两类,识别它们的方式不同:

模式 现象 根因 处理方向
任务分解失败 步数阶梯式上升,工具调用重复同一动作 目标不可达或描述含糊,模型反复尝试不同路径 改任务描述,收窄工具集,加前置可行性判断
错误恢复循环 步数在某一对工具之间来回振荡 错误信息没被正确理解,恢复策略固定不变 早停判据,失败两次即换策略或上报

两种模式在日志里的形状不同:分解失败是"缓慢爬升且工具种类发散",恢复循环是"两三个工具的高频往返"。用步数直方图看不出来,要看同一任务内的工具调用序列——把每个任务的工具名按顺序拼起来,统计重复子串即可区分:

─── 按任务聚合工具序列,找振荡模式:同一工具对在单任务内出现 3 次以上
jq -r 'select(.tool != null) | "\(.task_id)\t\(.tool)"' steps.jsonl \
  | awk '{a[$1]=a[$1] " " $2} END {for (t in a) print t a[t]}' \
  | awk '{n=split($0,f," "); for(i=2;i<=n;i++) if(f[i]==f[i-2]) {print $1" osc:"f[i]; break}}'

它的价值是把"成本高"归到两种病因之一,而不是笼统加更多上限——加上限只压成本、不修病因,成功率会一起掉。

限步数是唯一能在运行时止损的手段,但它与成功率直接对冲:

步数上限 成功率的典型影响 失效表现
远低于分布 p50 大量任务半途而废 返回"未完成"但已产生成本
略高于 p50 长尾任务被砍 复杂任务失败率陡升
约等于 p95 只砍真异常 成本上限可控,成功率损失小
不设上限 尾部成本无界 单个坏任务可吃掉当日预算

上限应当定在接近 p95 的位置,而不是"尽量小"。低于 p50 的上限不是节约,是把成本花在了没有产出的任务上——这是最坏的一种花法。同时必须区分两种终止:正常完成与达到上限被截断,前者计成功,后者必须单独计数,否则成功率会被虚高。

─── 成本上限写进任务定义,而不是事后从账单里发现
task:
  name: invoice-reconcile
  max-steps: 24              ── 按历史 p95 定,低于 p50 属浪费
  max-tool-calls: 12         ── 工具调用单独设限,两者互不替代
  max-tokens-per-task: 180000
  on-limit-exceeded: report-partial   ── 截断必须显式上报,不得静默返回
  retry:
    max-retries: 1           ── 重试同时消耗步数与工具配额
    backoff-not-retry-same-error: true

3. 成本控制的四个层次

控制手段的副作用差异很大,应当按顺序使用,而不是一上来就压模型:

层次 手段 省下的 代价
1 收窄工具范围 减少无效尝试步数 任务能力下降,需要更明确的描述
2 限制步数与工具配额 砍掉尾部 复杂任务半成品
3 缓存中间结果 重复子任务的调用 缓存命中率依赖任务同质性
4 分支判断换小模型 每步单价下降 判断错误会把步数推高

顺序必须守住。先换小模型往往把成本推向另一方向:分支判断错一次就多走两三步,单价省下的被步数吃掉,总成本反升。正确顺序是先削无效步数(工具范围与描述质量),再设上限兜底,最后才考虑缓存与模型降级。

缓存的判据是任务同质性:大量任务前几步相同(同一份预处理、同一份检索)时价值高;任务高度独立时命中率低到不值得维护。键的选择决定成败——出现时间戳、请求 ID 或不稳定序列化字段,命中率直接归零:

─── 缓存键只由语义稳定字段构成,并显式暴露命中率
cache:
  scope: step-result          ── 缓存粒度是步骤结果,不是整个任务
  key: [task_type, normalized_input_hash, tool_version]
  exclude: [request_id, timestamp, trace_id]   ── 这三类字段进键必零命中
  ttl_seconds: 3600
  metrics: [cache_hit_total, cache_lookup_total]  ── 输出命中率,不只看调用数

4. 重试与可观测性

重试是最贵的失败模式,它同时消耗三样:步数(重放本身要步)、工具配额(工具调用再算一次)、上下文长度(历史带进下一轮,输入 token 继续累积)。一次失败任务的总成本通常不是单次的两倍,而是三到五倍。

因此重试策略要区分可重试与不可重试:网络超时、限流退避可以重试;同一错误连续两次必须停止,因为第三次大概率得到同样的结果,只是多花一笔钱。

除步数上限外还要一道单任务成本熔断:累计成本超阈值即终止并上报。它与步数上限不重复——步数少但每步都是长上下文的任务,成本可能远高于步数多的小模型任务。阈值按任务价值换算:若人工处理需十分钟,熔断线就该明显低于它,否则任务做成了也是亏的。

可观测性要落到"每步"而不只是"每任务",否则定位不了是模型贵、工具贵,还是步数多:

─── 每步一条结构化记录,任务结束再聚合成任务级
{"task_id":"T-8821","step":7,"model":"small-router","in_tokens":3120,"out_tokens":210,
 "tool":"search_orders","tool_ms":840,"cost_units":0.42,"stop_reason":null,
 "cache_hit":false,"retry_of":null,"elapsed_ms":1180}

任务级看四个分布就够:

指标 用途 异常信号
单任务成本 p50 / p95 成本基线 p95 与 p50 差距扩大
步数分布(直方图) 判断上限是否合理 大量任务堆积在上限处
重试率与重试放大倍数 失败代价 放大倍数超过 2 说明重放太贵
工具调用次数 / 任务 判断工具设计问题 单任务工具调用持续上升
成本归因覆盖率 无归因的成本管不了 低于 95% 时预算判断失真

"让它多想想"是隐性的成本决定。这类优化(增加反思轮次、扩大检索、提高最大步数)在指标上通常表现为成功率小幅提升,而成本提升是数倍的,且发生在长尾上——平均值看不出来,只有 p95 与 p99 会动。任何这类改动都必须同时给出成本增量的授权,否则等于悄悄改了预算。

─── 归因维度:没有归属的成本判断不了哪类请求在亏钱
attribution:
  dimensions: [task_type, tenant_id, entry_point]
  required: true               ── 缺维度视为埋点缺陷,需告警
  bucket: per-task             ── 按任务归集,不按请求归集
budget:
  monthly_limit_units: 90000
  alert_at_ratio: 0.8          ── 到 80% 时告警,而不是到 100%

5. 排错、度量与边界

现象 最可能的原因 处理
成本翻倍但请求量未变 单任务步数上升 看步数直方图与工具序列,先排除循环
单个任务吃掉当日大部分预算 无步数上限或上限过高 设 p95 上限,加单任务成本熔断
成功率提升但账单涨得更快 "多想想"类优化未计入成本 对改动做成本增量评估
重试后成本是原来的五倍 重放带上了完整历史上下文 重放时裁剪上下文,只带必要状态
回答不了"哪类请求在亏钱" 缺归因维度或覆盖率低 补 task_type 与 tenant 维度

归因覆盖率要定量:任务在聚合时缺 task_type 或 tenant_id 就只能进"未归因"桶,这个桶会随埋点遗漏变大,表现为"总账单对得上但看不出谁花的"。未归因占比超过 5% 时先补埋点再谈预算,因为此时任何调整都建立在失真分布上。

边界很清楚:调用量极小(月成本低于一次优化的工时投入,直接看月度总额就够)、任务类型单一且稳定(同质化高,平均成本即可作为预测依据,无需分布)、成本绝对额远低于人工成本(单任务几分钱而人工处理要几分钟,优化的机会成本高于省下的钱)——这三种情况下,正式成本核算可以压缩成一次月度抽查,不需要每步埋点与归因维度。

反过来,一旦出现任一条就该正式化:有按任务或按租户的收费/结算、出现单个任务成本超过该任务价值的情况、调用量月增超过一倍、多个团队共用同一套模型配额。


6. 自测

  1. 为什么"按请求估算成本"在 Agent 上会失效?真正该看哪个统计量?
  2. 任务分解失败与错误恢复循环在日志里的形状差别是什么?
  3. 步数上限定在分布 p50 以下,为什么不算节约?
  4. 为什么"重试"的代价不是单次成本的两倍?它同时消耗了哪三样东西?
  5. 把分支判断换成小模型,为什么可能让总成本上升?
  6. 哪三个指标放在一起,才能判断"成本涨了"是模型贵、工具贵还是步数多?
S22-A12 · Agent 的运行成本:一次任务烧多少
https://www.clxhxhhr.top/posts/6350/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。