ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s

This commit is contained in:
2026-08-20 13:12:50 +00:00
commit b119135836
10275 changed files with 3284984 additions and 0 deletions
+325
View File
@@ -0,0 +1,325 @@
# Agent End-to-End Cost Analysis / Agent 任务端到端成本分析(实验 7-9)
## English
This project performs a full cost decomposition for a multi-turn agent workflow (refund handling), including input/cache/output tokens, latency, and cost distribution. It enables practical measurement of where costs come from and how optimization strategies affect total spend.
### What it does
The benchmark runs a fixed 8-turn customer refund scenario and records every LLM call with a lightweight tracing layer:
- token usage (prompt, cached prompt, output)
- latency
- model cost by pricing table
It then reports:
- per-step cost breakdown
- cost component breakdown (non-cached input / cached input / output)
- p50/p95/p99 for per-step cost
- full 2×2 A/B comparison between optimization levers
The two levers are:
- **KV-cache friendliness**: keep a stable prefix to maximize cache hits
- **Context compression**: summarize long tool outputs for earlier turns
### Default model and API behavior
- Default model: `gpt-5.6-luna`.
- Preferred credentials: `OPENAI_API_KEY`, fallback to `OPENROUTER_API_KEY` (`gpt-*` remapped to `openai/*`).
- If OpenRouter keys exist for `gpt-5.x`, it is preferred due to authentication requirements.
- Offline mode is supported via `sample_trace.json` so all tables can be recomputed without API calls.
### Files
| File | Purpose |
|---|---|
| `config.py` | pricing model definitions and pricing presets |
| `tracer.py` | tracing helper and cost decomposition/aggregation |
| `agent.py` | 8-turn refund agent with `run_scenario(kv_cache, compress)` |
| `demo.py` | CLI entry for online/offline runs |
| `sample_trace.json` | captured 2×2 scenario token records for offline recomputation |
| `tests/` | offline pytest regressions for trace parsing and usage accounting |
| `requirements.txt` / `env.example` | dependencies and environment templates |
### Run
```bash
# From the repository root: use the shared Chapter 6 environment
uv sync --locked --python 3.12 --extra ch6
# Activate it before changing directories:
# macOS/Linux:
source .venv/bin/activate
# Windows PowerShell: .\.venv\Scripts\Activate.ps1
# Windows cmd: .venv\Scripts\activate.bat
# pip fallback when uv is not installed:
# python -m pip install -e ".[ch6]"
cd chapter7/agent-cost-analysis
# Single-project compatibility path, still supported during migration:
# python -m pip install -r requirements.txt
export OPENAI_API_KEY=your-openai-api-key # or OPENROUTER_API_KEY=your-openrouter-api-key
python demo.py
python demo.py --offline --scenario all
```
### Tests
Automated tests are offline and do not require API keys.
```bash
# From the repository root, include the dev extra for pytest:
uv sync --locked --python 3.12 --extra ch6 --extra dev
# pip testing fallback:
# python -m pip install -e ".[ch6,dev]"
cd chapter7/agent-cost-analysis
python -m pytest tests
```
### CLI options
| Argument | Meaning |
|---|---|
| `--live` / `--offline` | call real model (default) / recompute from trace |
| `--scenario` | `ab` (naive+both), `all` (four scenarios), or subset list |
| `--trace` | trace file for offline mode |
| `--save-trace` | persist observed token usage from online runs |
| `--model` | model name for price preset |
| `--price-input` / `--price-cached` / `--price-output` | override per-million-token prices |
| `--no-warmup` | disable prefix warmup for KV-cache scenario |
| `--output` | export full result JSON |
### A/B scenarios (2×2)
| Scenario | KV-cache | Compression | Context design |
|---|---|---|---|
| `naive` | no | no | random session header + full tool returns |
| `kv` | yes | no | stable long prefix |
| `compress` | no | yes | only keep last 2 turns full; older turns summarized |
| `both` | yes | yes | stable prefix + compressed history |
The task logic is identical across scenarios so differences isolate optimization effects.
### Interpretation
Empirical results show:
- KV-cache can produce large improvements when prefixes are stable.
- Compression lowers prompt growth while keeping functional behavior.
- Joint optimization usually gives best total cost, though cache gains and compression gains are not simply additive.
### Offline recomputation
Offline mode reads `sample_trace.json` and re-runs only cost arithmetic, enabling:
- quick replication without keys
- quick “what-if” with different model prices
### Notes
- Observed numbers can vary due to real API behavior and cache timing.
- Prompt cache is best-effort and may miss in some turns.
- Tool-return token estimate uses tokenizer counts against current model encoder.
- Key precedence: prefer `OPENAI_API_KEY`; fallback is automatic via OpenRouter for supported paths.
---
## 中文
# 实验 7-9:Agent 任务的端到端成本分析
配套《深入理解 AI Agent》第 6 章「实验 7-9 ★: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 用量(离线模式的输入,成本按当前单价重算)|
| `tests/` | 离线 pytest 回归测试:trace 解析与 usage 计费容错 |
| `requirements.txt` / `env.example` | 依赖与环境变量示例 |
## 运行
```bash
# 在仓库根目录使用统一的第 6 章环境
uv sync --locked --python 3.12 --extra ch6
# 切换目录前先激活环境:
# macOS/Linux
source .venv/bin/activate
# Windows PowerShell.\.venv\Scripts\Activate.ps1
# Windows cmd.venv\Scripts\activate.bat
# 未安装 uv 时可用 pip 兜底:
# python -m pip install -e ".[ch6]"
cd chapter7/agent-cost-analysis
# 迁移期间仍支持单项目兼容路径:
# python -m pip install -r requirements.txt
# 在线(真实调用模型,需要 key):默认跑 A(朴素)+B(优化) 两组
export OPENAI_API_KEY=your-openai-api-key # 或 export OPENROUTER_API_KEY=your-openrouter-api-key(自动回退)
python demo.py
# 离线(无需 key):用内置 canned trace 复算全部表格
python demo.py --offline --scenario all
```
在线模式会真实调用 OpenAI`--scenario all` 约几十次 chat completion,运行一两分钟。
## 测试
自动化测试均为离线回归测试,不需要 API Key。
```bash
# 在仓库根目录安装 pytest 所需的 dev extra
uv sync --locked --python 3.12 --extra ch6 --extra dev
# pip 测试兜底路径:
# python -m pip install -e ".[ch6,dev]"
cd chapter7/agent-cost-analysis
python -m pytest tests
```
### 命令行参数(`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 即可复现全部表格,还能一键换算到其它模型定价:
```bash
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.py``PRICING_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。