2306 字
约 7 分钟
11
S21-A28 · 调用次数:为什么"多调一次工具"是最贵的那笔钱

一个任务从 4 次工具调用变成 6 次,代码只多了两行 if。上线后成本涨了 40 %,P99 从 4 秒到 9 秒,错误率翻了 2 倍。团队的第一反应是"优化 prompt 省 token",但省下的那点 token 只抵掉了 3 %。

多数人把 Agent 的成本当作"token 的账",而它其实是**"轮次的账"**:每多一次工具调用,你要付的是 一次完整的模型往返(含全部上下文)+ 一次工具执行 + 一次把结果塞回上下文的空间。而这三笔是乘在一起算的,不是加在一起。


1. 一次工具调用的三层成本

层 成本 为什么容易被漏
模型往返 输入 = 当前全部上下文 只看"输出很短"就以为便宜
工具执行 延迟 + 下游压力 + 配额 被算成"后端的事"
结果回填 返回内容进入下一轮输入 体积常被低估(一条搜索结果几千 token)

第一行是全篇的核心机制:每次调用都要把"到目前为止的全部上下文"再发一遍。所以第 6 次调用的输入是第 1 次的数倍,而输出可能都只有几十 token。"输出短"完全不能推出"这次调用便宜"。

// 一个 6 次调用的任务,逐轮输入量的真实形状
轮1 工具调用   输入 1,200
轮2 工具结果+再调  输入 3,400   ← 回填 1,800
轮3 又调         输入 5,900
轮4 又调         输入 8,100
轮5 又调         输入 10,600
轮6 汇总         输入 13,200
// 总输入 ≈ 42,400 token,是"单轮 prompt"的 35 倍
// 而 4 次调用时总输入 ≈ 18,600 —— 多了 2 次,成本翻一倍多

2. 为什么"再加一次调用"不是线性变贵

第 N 次调用的输入包含前面所有内容,所以总成本随调用次数呈近似平方增长。

调用次数 累计输入(相对) 累计延迟(相对)
2 1× 1×
4 2.4× 2×
6 4.2× 3×
10 9.8× 5×
// 同一次任务,"单轮视角"与"累计视角"差 5.9 倍
单轮视角:1,200 × 6 = 7.2k token          ← 严重低估
累计视角:1.2+3.4+5.9+8.1+10.6+13.2 = 42.4k  ← 真实
// 且这个倍数随调用次数上升而变大,不是常数

第三行就是开头那次"多两次调用涨 40 %"的来源——它其实是 4.2/2.4 = 1.75 倍的输入量,只是被"输出很短"和"缓存命中"部分掩盖了。


3. 什么在决定"必须调几次"

因素 影响 能否压缩
任务的固有步骤数 最少调用次数的下界 否(除非拆任务)
工具粒度 一个工具做多件事 → 调用少 能(合并工具)
是否允许并行 并行调用不减少次数但压缩墙钟 能(延迟)
失败重试 每次失败 +1 次调用 能(幂等 + 退避)
探索式查询 一次查询不够、要多次试探 能(改查询策略)

第二行是唯一"结构上"能减次数的手段:把"先查列表、再查详情"合并成一个返回摘要的工具,调用次数直接减半,而输入体积只增加一点点。这与"工具粒度要细"的直觉相反——细粒度省认知负担,粗粒度省调用次数,两者要按任务选。

// 两种工具设计,同一个任务的成本差一倍
细粒度:  search(keyword) → get_detail(id) → get_comments(id)
        3 次调用,累计输入 3 轮全上下文
粗粒度:  search_with_summary(keyword) 一次返回摘要+评论数
        1 次调用,输入 1 轮全上下文
// 代价:粗粒度工具返回体积大,且不灵活;适合"高频固定路径"

4. 三种"看起来省了、实际更贵"的做法

做法 表面效果 实际代价
自动重试 成功率上升 每次重试 = 一次完整往返
每步都验证(再调一次确认) 更可靠 调用次数 ×2
用长上下文减少调用 次数少了 单次输入变巨大,且注意力衰减

第三行是最容易做出错误取舍的地方:把"少调几次"换成"一次塞很多",成本未必降(上下文税),质量还可能降(长上下文里的中段信息被忽略)。

// 一个可用的预算约束(写在编排层,不写在 prompt 里)
agent_budget:
  max_tool_calls: 8              # 硬上限,超了降级为"给已有信息"
  max_input_tokens_per_call: 12000
  retry: { max: 2, backoff: exp, only_idempotent: true }
  on_budget_exhausted: summarize_and_return   # 不是报错,是给部分结果

5. 归因:把成本落到"轮次"上

指标 用途
每任务平均调用次数 最直接的效率指标
每任务累计输入 token 真实成本
调用成功率(按工具) 找出"总在重试"的工具
每工具回填体积 找出"返回一大坨"的工具
// 从日志里算"每任务累计输入",而不是"每调用平均输入"
$ python3 - <<'EOF'
rows = group_by_task(usage_log)
for task, rs in rows.items():
    print(task, len(rs), sum(r.input_tokens for r in rs))   // 次数, 累计
EOF
// 判据:累计输入 / 单轮基准 > 10 → 这个任务值得单独看

第四行常被忽略:某个工具每次返回 5,000 token,它在后续每一轮里都要被重发一次——于是它一次执行的成本,被后面所有轮次放大。"减少回填体积"往往比"减少调用次数"收益更大。


6. 排错:五种"次数不多但很贵"

现象 八成原因 处理方向
调用 4 次成本却像 8 次 某工具回填体积过大 截断/摘要工具结果
某些任务偶尔极贵 重试风暴 只对幂等工具重试
加了一次确认后成本翻倍 确认也是一次完整往返 合并进主调用
长上下文替代多轮后更贵 上下文税 回到多轮 + 摘要
平均次数正常、P99 很高 少数任务在探索 加最大轮次限制

第二行值得拆开:非幂等工具的重试不只是贵,还会产生重复副作用(重复下单、重复发消息)。所以"只对幂等工具重试"既是成本纪律,也是正确性纪律。


7. 边界:什么时候该允许它多调

场景 为什么可以 说明
结果错了代价极高(资金/合规) 多一次校验是划算的 但要接受成本
探索型任务(调研、排障) 调用次数不可预估 设上限而非目标
人工在环、可随时打断 中断能止损 配合取消语义
低频高价值任务 次数不影响总账 先看总量

判断口径一句话:Agent 的成本单位是"轮次",不是"token"——所以优化顺序是减少调用次数 → 缩小回填体积 → 最后才抠 prompt。而"每次调用都重发全部上下文"这个机制决定了:次数与输入体积的乘积才是账单,任何只优化其中一项的做法都只能拿到部分收益。


8. 自测

  1. 一次工具调用有哪三层成本?为什么"输出很短"不能推出"这次调用便宜"?
  2. 为什么调用次数增加会让成本近似平方增长?
  3. 工具粒度"细"与"粗"分别省什么、费什么?什么场景该用粗粒度?
  4. 为什么"减少工具回填体积"的收益可能大于"减少调用次数"?
  5. 为什么只能对幂等工具做重试?这既是成本问题也是什么问题?
  6. 优化顺序应该是什么?判据是什么?

9. 一句话总结

Agent 的成本单位是轮次,不是 token:每次工具调用都要重发"到目前为止的全部上下文",于是次数与输入体积相乘才是账单——多两次调用足以让成本翻倍。优化顺序固定为减少调用次数(合并工具)→ 缩小回填体积(截断/摘要)→ 最后才抠 prompt;重试只对幂等工具开,因为非幂等重试同时放大成本与副作用。判据:先设 max_tool_calls 硬上限,超了给部分结果,而不是继续跑。

S21-A28 · 调用次数:为什么"多调一次工具"是最贵的那笔钱
https://www.clxhxhhr.top/posts/6754/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。