跳转至

实验 6-7:Agent 任务的端到端成本分析

配套《深入理解 AI Agent》第 6 章「实验 6-7 ★:Agent 任务的端到端成本分析」。

对一个典型的多轮 Agent 任务(客服退款)做全链路成本拆解,用自建的轻量 tracing / 可观测系统记录每次 LLM 调用的输入/输出/缓存 token、时延与成本:按步骤聚合出「哪一步最贵」,按成本构成拆出「未缓存输入 / 缓存输入 / 输出各占多少、工具返回注入了多少 token」,并给出单步成本分布(p50/p95/p99);再做完整 2×2 A/B 对比,量化 KV-cache 复用上下文压缩 两个杠杆各自及叠加后的真实成本差异。

  • 默认模型 gpt-5.6-luna(当前廉价旗舰),通过 openai Python SDK 调用。首选 OPENAI_API_KEY;未设置时自动回退到 OPENROUTER_API_KEY(走 OpenRouter 兼容端点,gpt-* 映射为 openai/*)。由于 gpt-5.x 直连 OpenAI 需组织实名认证,只要存在 OPENROUTER_API_KEY 就优先走 OpenRouter。
  • KV-cache 的节省是真实的:利用 OpenAI 的自动 prompt caching(前缀 ≥ 1024 token 且命中近期相同前缀时,usage.prompt_tokens_details.cached_tokens > 0,这部分输入按缓存价 5 折计费)。
  • 提供离线模式:不打模型,读入一份此前真实运行录下的 token 用量(sample_trace.json,canned token counts),用可配置单价重新计算成本/成本构成/A/B 对比表——无需 API key 即可复现全部表格,也可一键换算到其它模型定价。

文件

文件 说明
config.py 模型与价格:Pricing 单价对象 + 常见 OpenAI 模型单价预设,token→成本换算
tracer.py 自建轻量 tracing:包裹每次 LLM 调用记录 token/缓存/时延/成本;成本构成拆解、单步成本分布、按步骤拆解表;支持从录制用量离线复算(from_records
agent.py 多轮客服退款 Agent 任务;run_scenario(kv_cache, compress) 把两个开关正交组合成 2×2 场景,并用 tiktoken 估算「工具返回注入」token
demo.py 命令行入口(argparse):在线跑真实模型 / 离线复算;选择 A/B 场景、模型单价、输出文件
sample_trace.json 一次真实运行录下的四个场景逐步 token 用量(离线模式的输入,成本按当前单价重算)
requirements.txt / env.example 依赖与环境变量示例

运行

pip install -r requirements.txt

# 在线(真实调用模型,需要 key):默认跑 A(朴素)+B(优化) 两组
export OPENAI_API_KEY=sk-...           # 或 export OPENROUTER_API_KEY=sk-or-...(自动回退)
python demo.py

# 离线(无需 key):用内置 canned trace 复算全部表格
python demo.py --offline --scenario all

在线模式会真实调用 OpenAI,--scenario all 约几十次 chat completion,运行一两分钟。

命令行参数(python demo.py --help

参数 说明
--live / --offline 在线真实调用(默认)/ 离线从 trace 文件复算(无需 key)
--scenario NAME ab(默认=naive+both) / all(2×2 四组) / 逗号分隔子集 naive,kv,compress,both
--trace FILE 离线读取的 canned trace,默认 sample_trace.json
--save-trace FILE 在线跑时把真实 token 用量落盘,供之后 --offline 复算
--model NAME 模型名(决定默认单价预设:gpt-4o-mini/gpt-4o/gpt-4.1-mini/gpt-4.1
--price-input/-cached/-output 覆盖三档单价(每百万 token 美元)
--no-warmup 关闭 KV-cache 组的前缀预热(默认预热以稳定命中缓存)
--output FILE 把成本拆解结果(含成本构成/分布/逐步用量)写成 JSON

不改任何参数直接 python demo.py,行为与之前一致:在线跑 A(朴素) 与 B(优化) 两组并打印拆解 + A/B 对比表。

A/B 四种策略(完整 2×2)

同一个 8 轮客服退款任务(查订单 → 查物流 → 查退款政策 → 查知识库 → 风控 → 发起退款 → 通知 → 关单),四组做的是同样的逻辑工作,只在上下文构造上不同——因此成本差异纯粹来自「是否 KV-cache 友好」与「是否压缩上下文」两个正交开关:

场景 KV-cache 压缩 上下文构造
naive A 朴素 每轮 system 前塞随机 session 头(破坏前缀)+ 历史工具返回原样带全
kv 仅缓存 稳定长前缀(system 逐字节不变)+ 历史不压缩
compress 仅压缩 前缀不稳定 + 仅最近 2 轮保留完整工具返回、更早压成一句话摘要
both B 优化 稳定长前缀 + 上下文压缩(两个杠杆叠加)

为聚焦「输入侧」两个杠杆,四组都用 temperature=0 且限制输出长度(max_tokens=160),让输出 token 成本近似为四组相等的固定项,避免模型生成长度的随机波动干扰对比。

工具环境是「受控」的(工具返回内容预设,真实系统里来自订单/物流/知识库后端),但每一次 LLM 调用、每一份 token 用量、每一分成本都是真实打到 OpenAI 得到的,保证可复现。

真实运行输出(gpt-4o-mini)

以下为一次真实运行(python demo.py --scenario all)的输出,sample_trace.json 即由该次运行落盘、供 --offline 复现。

(a) 单次任务成本拆解:按步骤 + 按成本构成 + 分布

===== 成本拆解: A 朴素(无缓存/无压缩)(单次任务全链路拆解) =====
步骤       工具/动作                   输入tok    缓存tok    工具tok    输出tok    时延(s)        成本($)
---------------------------------------------------------------------------------------
turn-1   query_order              1113        0      276      104     3.15     0.000229
turn-2   query_logistics          1807        0      829       99     2.09     0.000330
turn-3   check_refund_policy      2154        0     1046      139     2.69     0.000406
turn-4   query_knowledge_base     2564        0     1287      160     2.92     0.000481
turn-5   query_user_history       2863        0     1389      136     2.69     0.000511
turn-6   issue_refund             3123        0     1490      160     3.07     0.000564
turn-7   send_notification        3408        0     1579      160     3.09     0.000607
turn-8   close_ticket             3668        0     1648      160     2.50     0.000646
---------------------------------------------------------------------------------------
合计                               20700        0     9544     1118    22.20     0.003776

最贵的一步 → turn-8 / close_ticket: $0.000646(占总成本 17.1%)
成本构成:
  未缓存输入     20700 tok  $0.003105  (82.2%)
  缓存输入           0 tok  $0.000000  (0.0%)
  输出            1118 tok  $0.000671  (17.8%)
  其中「工具返回注入」累计输入 9544 tok (同一份工具返回在后续每轮被反复计费)
单步成本分布(n=8): 均值 $0.000472  p50 $0.000481  p95 $0.000646  p99 $0.000646

===== 成本拆解: B 优化(KV缓存+压缩)(单次任务全链路拆解) =====
步骤       工具/动作                   输入tok    缓存tok    工具tok    输出tok    时延(s)        成本($)
---------------------------------------------------------------------------------------
turn-1   query_order              1056     1024      276      139     2.41     0.000165
turn-2   query_logistics          1781        0      829      112     2.18     0.000334
turn-3   check_refund_policy      2143     1024     1046      151     2.56     0.000335
turn-4   query_knowledge_base     2310        0     1052      160     3.03     0.000442
turn-5   query_user_history       2060     1024      635      160     2.48     0.000328
turn-6   issue_refund             2143     1024      551      160     2.71     0.000341
turn-7   send_notification        2188     1024      430      160     2.79     0.000347
turn-8   close_ticket             2354     1024      429      122     2.38     0.000349
---------------------------------------------------------------------------------------
合计                               16035     6144     5248     1164    20.54     0.002643

最贵的一步 → turn-4 / query_knowledge_base: $0.000442(占总成本 16.7%)
成本构成:
  未缓存输入      9891 tok  $0.001484  (56.1%)
  缓存输入        6144 tok  $0.000461  (17.4%)
  输出            1164 tok  $0.000698  (26.4%)
  其中「工具返回注入」累计输入 5248 tok (同一份工具返回在后续每轮被反复计费)
单步成本分布(n=8): 均值 $0.000330  p50 $0.000335  p95 $0.000442  p99 $0.000442

可以看到:朴素组 A 的输入 token 随轮次从 1113 一路涨到 3668(上下文累积效应,最后一步最贵),「工具返回注入」累计吃掉 9544 输入 token;优化组 B 的输入 token 被压缩策略压住(末轮 2354 而非 3668,工具注入累计降到 5248),且多数轮次持续命中 1024 缓存 token,缓存输入把整块费用打了 5 折。

(b) 完整 2×2 A/B 对比

===== A/B 成本对比(同一个 8 轮客服退款任务)=====
方案                             总输入tok      缓存tok      缓存率    输出tok       总成本($)       vs基线
------------------------------------------------------------------------------------------
A 朴素(无缓存/无压缩)                   20700          0     0.0%     1118     0.003776         基线
KV 仅缓存(稳定前缀/不压缩)                20386      13568    66.6%     1112     0.002707     -28.3%
仅压缩(前缀不稳定/摘要)                   16177          0     0.0%     1147     0.003115     -17.5%
B 优化(KV缓存+压缩)                   16035       6144    38.3%     1164     0.002643     -30.0%
------------------------------------------------------------------------------------------

重点对比:A 朴素(无缓存/无压缩)  →  B 优化(KV缓存+压缩)
  总 token:   A=21818  →  B=17199   减少 4619 (21.2%)
  缓存 token: A=0  →  B=6144   (B 靠稳定前缀命中缓存)
  总成本:     A=$0.003776  →  B=$0.002643   降低 $0.001133 (30.0%)
  成本倍率:   A 是 B 的 1.43 倍

结论(读出两个杠杆各自与叠加的贡献): - 仅 KV-cache:不改上下文长度,只靠稳定前缀让重复的系统提示/工具定义/历史轮次按缓存价计费,缓存率冲到 66.6%,端到端成本就降了 28.3%——本例中它是单个最有效的杠杆。 - 仅压缩:把旧轮次工具返回压成摘要,总输入 token 从 20700 降到 16177(约 −22%),端到端成本降 17.5%;但因为前缀不稳定,缓存率为 0。 - 两者叠加(B 优化):输入 token 最少、又能命中缓存,端到端总成本降 30.0%(A 是 B 的 1.43 倍)。单看输入侧成本,降幅落在书中「KV Cache 可降低 30%-60% 输入 token 成本」的经验区间内。

注意 KV 与压缩并非简单相加:压缩缩短了历史,可被缓存的「历史轮次」也随之变少,所以 B 的缓存 token(6144)反而少于「仅 KV」(13568)。这正是评估的价值——两个优化叠加时要实测协同效应,而不是把各自的收益直接相加。

离线复算(无需 API key)

sample_trace.json 存的是上面这次真实运行的逐步 token 用量(实测值);离线模式只做「按单价重算成本」这一步纯离线数学,因此无需 key 即可复现全部表格,还能一键换算到其它模型定价:

python demo.py --offline --scenario all                 # 用 gpt-4o-mini 单价复算
python demo.py --offline --scenario all --model gpt-4o  # 同一份 token 用量,换 gpt-4o 单价重算
python demo.py --offline --price-input 0.20 --price-cached 0.10 --price-output 0.80

gpt-4o 单价后,同一批 token 的四组总成本等比放大(结论与占比不变,A=\(0.062930 → B=\)0.044047,仍是 −30.0%)——这说明成本优化的相对收益由 token 结构决定,与绝对单价无关

注意事项

  • 具体数字每次在线运行会有小幅波动:OpenAI 的 prompt cache 是尽力而为的(按约 128 token 的块缓存、约 5–10 分钟过期、偶尔未命中),因此某些轮次可能出现 cached_tokens=0(如上面 B 组的 turn-2/turn-4)。demo.py 在正式计量前会先对 KV-cache 组跑一次「预热」把稳定前缀写入缓存,让命中更稳定;--no-warmup 可关闭。
  • 价格写在 config.pyPRICING_PRESETS),默认是 gpt-4o-mini 的公开单价(输入 $0.15 / 缓存 $0.075 / 输出 $0.60 每百万 token)。换模型:用 --model(如 --model gpt-4o)或 --price-* 直接覆盖单价即可;缓存命中要求稳定前缀 ≥ 1024 token,换更强模型不影响该机制。
  • 「工具返回注入」token 用 tiktoken 按当前模型的编码器离线估算(统计每轮输入里工具返回文本占多少 token),用于回答书中「一次工具返回可能占 2000-5000 token,且在后续每轮被反复计费」这一放大因素。
  • 凭据:首选 OPENAI_API_KEY;未设置时自动回退到 OPENROUTER_API_KEY(走 OpenRouter,gpt-* 映射为 openai/*)。gpt-5.x 直连需组织实名认证,故有 OPENROUTER_API_KEY 时优先走 OpenRouter。离线复算(--offline)无需任何 key。