Appearance
第20章 成本控制与性能优化
"Premature optimization is the root of all evil — but ignoring cost at scale is the root of all bankruptcies."
本章要点
- Agent 的主要成本来源:LLM API 调用(按 token 计费)
- 成本优化三板斧:减少 token、用更便宜的模型、缓存重复请求
- 延迟优化:流式输出、并行工具调用、Prompt Caching
- 必须设置预算上限——失控的 Agent 可以在几分钟内消耗数百美元
20.1 Agent 的成本结构
一次典型的 Agent 交互成本构成:
用户: "帮我修复这个 bug"
Agent 执行过程:
LLM Call #1: 分析需求 2K input + 500 output = $0.03
Tool: Read 3 files (免费,本地操作)
LLM Call #2: 带文件内容思考 15K input + 2K output = $0.19
Tool: Edit 1 file (免费)
LLM Call #3: 运行测试 8K input + 200 output = $0.09
Tool: Bash npm test (免费)
LLM Call #4: 测试失败,修复 12K input + 1K output = $0.14
Tool: Edit 1 file (免费)
LLM Call #5: 再次验证 10K input + 300 output = $0.11
Tool: Bash npm test (免费)
LLM Call #6: 总结 8K input + 500 output = $0.09
─────────────────────────────────────────────────
总计: 55K input + 4.5K output ≈ $0.65看起来不多?如果一天处理 1000 个这样的任务:$650/天,$19,500/月。
多 Agent 场景更恐怖——每个子 Agent 都是独立的 LLM 调用链路,成本倍增。
20.2 Token 优化
Token 是成本的直接来源。减少 token = 减少成本。
精简系统提示词
Before: 5000 tokens 的系统提示词
× 20 次 LLM 调用
= 100K tokens 的系统提示词开销
After: 3000 tokens(移除冗余、精简描述)
× 20 次 LLM 调用
= 60K tokens
节省: 40K tokens ≈ $0.40/任务每个系统提示词中的冗余词都在每次 LLM 调用中重复计费。
工具定义精简
工具的 description 和 parameter description 也是 token:
json
// ❌ 冗长的工具描述 (120 tokens)
{
"description": "This tool reads a file from the local filesystem. You can access any file directly by using this tool. It reads up to 2000 lines starting from the beginning. When you already know which part you need, only read that part..."
}
// ✅ 精简版 (40 tokens)
{
"description": "Read a file. Use offset/limit for large files. Supports images and PDFs."
}40+ 工具每个省 80 tokens = 省 3200 tokens/请求。
延迟加载工具
Claude Code 的 Deferred Tools 机制——不是一次性发送所有工具定义,而是按需加载:
typescript
// 初始只发送核心工具
const coreTools = [Read, Write, Edit, Bash, Grep, Glob]
// 用户提到 notebook 时才加载
if (userMentionsNotebook) {
tools.push(NotebookEdit)
}
// 用户提到 web 搜索时才加载
if (taskNeedsWebSearch) {
tools.push(WebSearch, WebFetch)
}工具结果截断
上一章提到的工具结果管理在这里也是成本优化:
typescript
// 一个 5000 行文件 = ~20K tokens
// 截断到关键的 200 行 = ~800 tokens
// 节省 19.2K tokens ≈ $0.19/次读取20.3 模型选择策略
不是每个 LLM 调用都需要最强的模型。
分级调用
typescript
async function chooseModel(task: TaskType): string {
switch (task) {
case 'complex_reasoning':
case 'architecture_design':
return 'claude-opus-4-6' // $15/M input, 最强推理
case 'code_editing':
case 'general_tasks':
return 'claude-sonnet-4-6' // $3/M input, 性价比最优
case 'simple_formatting':
case 'summarization':
return 'claude-haiku-4-5' // $0.25/M input, 快速便宜
}
}实践中,80% 的 Agent 工具调用用 Sonnet 级别就够了。只在复杂推理、架构决策时升级到 Opus。
一个实际的成本分布案例:某团队将 Agent 从全部使用 Opus 改为分级调用后,月成本从 $19,500 降至 $5,200——降低 73%,任务成功率仅下降 2%。
路由模型
用一个便宜的模型来决定该调用哪个模型:
python
# 用 Haiku 做路由决策 (成本极低)
routing_decision = haiku.classify(
"这个任务需要 opus 级别的推理能力吗?",
task_description
)
if routing_decision == "complex":
response = opus.complete(task)
else:
response = sonnet.complete(task)20.4 缓存策略
Prompt Caching
Anthropic 的 Prompt Caching 对 Agent 场景极度友好——系统提示词在多次调用间几乎不变:
typescript
const response = await client.messages.create({
system: [{
type: 'text',
text: STATIC_SYSTEM_PROMPT, // 3000 tokens,缓存后几乎免费
cache_control: { type: 'ephemeral' }
}],
messages: conversationHistory
})
// 首次调用: 3000 tokens 写缓存
// 后续调用: 缓存命中,成本降低 90%
// 一个 20 轮对话: 从 60K → 3K + 19 × 0.3K = ~8.7K tokens工具结果缓存
相同文件在短时间内被多次读取——缓存结果:
typescript
const fileCache = new LRUCache<string, string>({
max: 100, // 最多缓存 100 个文件
ttl: 60_000, // 60 秒过期
})
async function readFileCached(path: string): Promise<string> {
const cached = fileCache.get(path)
if (cached) return cached
const content = await fs.readFile(path, 'utf-8')
fileCache.set(path, content)
return content
}20.5 延迟优化
用户体验的关键指标:首 token 延迟(TTFT) 和 总完成时间。
流式输出
不要等 Agent 全部完成再显示——边做边展示:
typescript
for await (const chunk of agent.stream(userMessage)) {
if (chunk.type === 'text') {
process.stdout.write(chunk.content) // 实时输出文本
}
if (chunk.type === 'tool_call') {
showToolProgress(chunk.tool, 'executing...')
}
if (chunk.type === 'tool_result') {
showToolProgress(chunk.tool, 'done')
}
}并行工具调用
当模型请求多个独立的工具调用时,并行执行:
typescript
// 模型同时请求读 3 个文件
const toolCalls = response.toolCalls // [Read a.ts, Read b.ts, Read c.ts]
// 并行执行而非串行
const results = await Promise.allSettled(
toolCalls.map(tc => executeTool(tc))
)
// 串行: 3 × 100ms = 300ms
// 并行: max(100ms, 100ms, 100ms) = 100ms预取
在用户打字时预测下一步可能需要的信息:
typescript
// 用户打开了一个文件——预先读取相关文件
async function onFileOpened(filePath: string) {
const imports = parseImports(filePath)
// 后台预读取依赖文件,缓存结果
imports.forEach(dep => readFileCached(dep))
}20.6 预算熔断
这是最重要的安全机制——Agent 可能因为 bug 或无限循环消耗天量 token。
typescript
class BudgetBreaker {
private totalCost = 0
private readonly maxCostPerTask: number
private readonly maxCostPerDay: number
constructor(config: { perTask: number; perDay: number }) {
this.maxCostPerTask = config.perTask // 如 $5/任务
this.maxCostPerDay = config.perDay // 如 $100/天
}
recordCost(inputTokens: number, outputTokens: number, model: string) {
const cost = calculateCost(inputTokens, outputTokens, model)
this.totalCost += cost
if (this.totalCost > this.maxCostPerTask) {
throw new BudgetExceededError(
`Task cost $${this.totalCost.toFixed(2)} exceeds limit $${this.maxCostPerTask}`
)
}
}
}Claude Code 通过限制最大轮次(--max-turns)间接控制成本。生产环境应该有更精细的 token/金额级别的熔断。
20.7 成本可视化
让用户/运维能看到成本分布:
本月 Agent 成本报告:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
总成本: $1,234.56
总 token: 412M (input 380M + output 32M)
总任务: 3,421
平均每任务: $0.36
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
按模型分布:
Opus: $890 (72%) ← 考虑更多用 Sonnet
Sonnet: $312 (25%)
Haiku: $32 (3%)
按任务类型:
代码修改: $456 (37%)
代码审查: $312 (25%)
Bug 修复: $234 (19%)
文档生成: $132 (11%)
其他: $100 (8%)
成本异常:
⚠️ task-4521: $12.34 (平均的 34 倍) — 无限循环导致
⚠️ task-3987: $8.67 (平均的 24 倍) — 读取了巨大的日志文件20.8 本章小结
成本控制和性能优化的核心策略:
- 减少 token——精简提示词、截断工具结果、延迟加载工具
- 选对模型——80% 任务用 Sonnet,只在必要时用 Opus
- 缓存一切——Prompt Caching、文件缓存、结果缓存
- 降低延迟——流式输出、并行工具调用、预取
- 预算熔断——必须有 per-task 和 per-day 的成本上限
- 成本可视化——看得见才管得住
下一章是全书的最后一章——我们将提炼贯穿全书的设计模式和架构决策。