吃透 AI Agent 开发 · 第 18 篇 · 第四章 · Context Engineering
Prompt Cache、KV Cache、Context Collapse 不是一回事。理解缓存边界,才能解释延迟和成本为什么突然上升。
Prompt Cache、KV Cache、Context Collapse 不是一回事。理解缓存边界,才能解释延迟和成本为什么突然上升。
先画一张成本账
Agent 的一次请求不只花输出 token。稳定 system prompt、项目指令、动态上下文、工具结果、重试和摘要都有成本;缓存可能降低其中一部分输入费用,却不会让输出、工具或外部服务免费。没有账本时,团队只能用“感觉变慢了”讨论优化。
flowchart LR
Prefix[稳定前缀] --> Cache[Prompt Cache]
Dynamic[动态上下文] --> Input[输入 token]
Cache --> Model[模型请求]
Input --> Model
Model --> Output[输出与 reasoning]
Output --> Retry[重试/压缩/工具]
Retry --> Cost[总成本账本]
缓存优化的第一步不是把所有内容冻结,而是找出既稳定又值得重复发送的部分。把日期、Git 状态和当前任务混进前缀,会让每轮都变成新 key。
三个“缓存”不要混叫
Prompt Cache 关注请求前缀是否可复用;KV Cache 是模型推理过程里的中间状态;Context Collapse 是上下文增长后被压缩、丢弃或污染的现象。它们的指标和修复动作不同:前者看命中和 token,第二个看 provider 行为,第三个看信息生命周期。
先用数据而不是直觉做预算
interface UsageSample {
inputTokens: number
cachedInputTokens: number
outputTokens: number
retries: number
toolCalls: number
costUsd: number
}
function cacheRatio(sample: UsageSample): number {
return sample.inputTokens === 0 ? 0 : sample.cachedInputTokens / sample.inputTokens
}
真实系统还要记录模型、provider、请求类型和时间窗口。平均值很容易掩盖长任务的尾部成本,应该分别看首轮、工具轮、重试轮和压缩轮。
一张成本分摊表
| 成本来源 | 观测指标 | 可以优化什么 |
|---|---|---|
| 稳定前缀 | cached / input tokens | 调整 Prompt 层次和顺序 |
| 动态上下文 | 注入字符、命中来源 | JIT、预算、过期策略 |
| 输出与 reasoning | output tokens、TTFT | 输出样式、推理强度、停止条件 |
| 重试 | retry count、失败类别 | 幂等性、超时、provider 路由 |
| 工具 | 次数、耗时、外部费用 | 工具合并、结果摘要、缓存 |
q-code 中寻找成本证据
| 阅读顺序 | 路径 | 追踪内容 |
|---|---|---|
| 1. 用量归一化 | src/usage/tracker.ts |
不同 provider 的 token 如何统一 |
| 2. 价格表 | src/usage/pricing.ts |
成本如何按模型和缓存区分 |
| 3. Prompt 组装 | src/context/prompt-builder.ts |
哪些片段最容易破坏前缀稳定 |
| 4. Trace 导出 | src/observability/langfuse.ts |
延迟和成本如何进入观测 |
失败反例:看到缓存命中就继续塞内容
Prompt Cache 命中后,团队可能误以为上下文可以无限增长。实际上命中只说明前缀可复用,动态尾部、输出和模型推理仍然会消耗预算;过长的稳定前缀还会让每轮都背着历史负担。
优化应该有停损线:当缓存命中提升但总延迟、上下文占用或输出长度变差时,说明冻结了不该冻结的内容。把稳定规则、项目说明和按需资料分开测量,才能知道收益来自哪里。
做一周的成本实验
练习:为同一个任务采集三组运行数据:全部前缀稳定、动态字段混入前缀、使用 JIT 上下文。每天记录缓存比例、TTFT、总 token、重试次数和工具耗时。不要只比较单次价格,还要看失败后重试是否抵消了缓存收益。
实验结束后给每项优化写上回滚条件,例如“缓存提高但命中率低于某阈值就撤回”。成本控制不是追求一个漂亮数字,而是让每个数字都能解释。
先算一次任务成本,不要只算一次请求
一个任务可能发出 6 次模型请求、2 次重试、1 次压缩,调用 8 个工具。供应商账单按请求计费,用户感受却是整项任务。成本追踪需要从 step 聚合到 run,再聚合到任务类型。
task=fix-login
model input uncached $0.082
model input cached $0.011
model output/reasoning $0.047
compression $0.009
external search API $0.014
retries $0.021
total $0.184
若只看主请求 $0.14,会漏掉重试和外部 API。优化后主请求下降 $0.02,但工具搜索多了四轮,总成本可能反而上升。
价格归一化要保留 provider 原始 usage
不同模型报告 input、output、cache read、cache write 和 reasoning token 的方式不一。有的 usage 在流末尾才到,有的失败请求没有完整 usage。Tracker 可以映射成统一字段,同时保留原始摘要和计价版本。
价格表也会变化。run artifact 记录当时使用的价格版本或单价,历史报表才不会因为今天更新配置而重算成另一个数字。估算值明确标为 estimated,不能和供应商最终账单混为一谈。
没有 usage 的失败请求也不是零成本。可以记 unknown 并单独统计,或按已知输入估算下限。静默填 0 会让最不稳定的 provider 看起来最便宜。
Cache 命中率要按前缀位置解释
总 input 100K,cached 80K,命中率 80% 看起来不错;如果前一版是 input 60K、cached 48K,同样 80%,这次每轮仍多发送 40K。比例没有说明绝对负担。
同时看 cached tokens、uncached tokens、稳定前缀长度和总输入。一个动态字段插在 5K 位置,后面 75K 稳定内容都无法复用;把它移到尾部后,命中可能立刻改善。Context manifest 的 section hash 能指出第一个变化位置。
缓存边界由 provider 实现,应用不要假设任意短前缀都可缓存。最低 token、TTL、cache write 成本和模型切换都会影响结果,实验要在真实接口上采样。
TTFT 下降不代表总延迟下降
稳定前缀复用通常改善首 token,但输出过长、工具调用过多和串行重试仍决定总耗时。一个研究任务 TTFT 从 2 秒降到 700 毫秒,随后多做 5 次检索,总时间从 20 秒变 35 秒,用户不会觉得更快。
延迟账本按阶段拆:排队、首字节、首有效 part、模型完成、工具执行、重试等待、压缩。p50 反映常态,p95/p99 暴露偶发长尾。优化一个阶段时,观察是否把成本转移到另一个阶段。
Reasoning 强度应该跟任务风险匹配
简单格式转换使用高 reasoning effort,往往增加延迟和输出 token;复杂代码迁移一味降低推理,则可能造成更多重试和错误工具调用。按任务类别或显式用户需求选择模型与 reasoning,不能只用全局最强配置。
模型路由的验收是任务级成功率、总成本和风险,而不是单次 token。便宜模型若多走三倍步骤,未必便宜;强模型若一次完成高风险计划,可能更划算。
路由失败要可解释。记录选择依据和 fallback,provider 不可用时切换模型不能偷偷改变工具能力或 reasoning 协议。
Keepalive 也有成本和边界
为保持 Prompt Cache 定期发送短请求,只有在 provider cache TTL、任务间隔和命中收益明确时才可能划算。默认开启的 keepalive 会在用户空闲时持续花费,还可能造成隐私和审计困惑。
开启后只用当前稳定 system prefix,不带用户历史、文件和动态任务;会话或模型切换时取消;小于合理间隔要钳制;cache 模式关闭时不发送。后台请求在 usage 中单列为 keepalive,不能混入用户任务成本。
一个简单决策是比较:预期下次请求节省金额 × 在 TTL 内发生概率,是否大于 keepalive 费用与额外复杂度。没有数据时,关闭更合理。
成本预算是运行闸门,不是月底报表
Eval 和长任务可以设置最大总 token、最大成本、最大工具数和 deadline。接近上限时先阻止新的昂贵分支,保存当前进度和产物;不要耗尽后只返回一个没有证据的错误。
预算要为收尾预留空间。剩余 2K token 时仍启动一个预计返回 20K 的子 Agent,必然把主流程挤死。调度器在分配子任务时扣除局部预算,完成后按实际 usage 归还或记账。
面向用户的提示不用展示复杂计费公式,只说已用多少、为何暂停、有哪些可继续选项。审计与 artifact 保留完整数字供分析。
把成本回归放进发布检查
固定一组 deterministic case,记录输入 token、工具数和步骤,不必每次调用真实模型。Prompt 或工具 schema 变更时,先检查稳定前缀增量和工作集大小;定期真实运行再校准缓存、TTFT 和价格。
设阈值时用业务容忍度:普通任务 token 增长 5% 可接受,高频启动请求增加 20K 不可接受;成功率提升可以允许一定成本增长,但安全退化不能用便宜抵消。成本是质量维度之一,不是唯一目标。
一次成本告警如何找到真正变化
周一部署后,平均任务费用上涨 38%,缓存命中率只下降 3 个百分点。团队最初怀疑 provider 调价,按 run 分摊后发现 input 未缓存 token 变化不大,工具轮数从 4.1 增到 7.6,主要是模型反复调用 tool search。
Context manifest 显示新版本把常用 read_file 从常驻工作集移到 deferred 目录;每次读取前都多一次搜索,且搜索结果摘要又触发确认轮。修复不是继续调 Prompt Cache,而是把三项高频只读工具恢复常驻,并给重复搜索加指纹。
成本告警因此同时携带 task type、steps、tool calls、uncached/cached input、output/reasoning、retry 与外部费用。只看总美元无法定位,只有分项变化能导向动作。
修复上线后比较同一 Case:成功率不降,平均步骤回落,稳定前缀只增加少量 schema token,总费用下降。成本优化以任务质量为约束,不通过少做必要验证获得漂亮账单。