site logo

Marico's space

代理型 LLM 的 Prompt Caching 攻略:成本与延迟

AI技术与应用 2026-09-08 20:55:49 10

最近折腾了代理型 AI(人工智能)系统的 Prompt Caching,踩了几个坑,这篇把问题说清楚。

如果你在做 Agent(代理)开发,需要调用工具、等待外部任务、或者处理多轮推理,那八成会盯着 Token 数量看。这很正常——但如果你的云服务提供商已经有缓存机制,那"精简 Token"就不再是首要原则了。

这篇文章聊聊为什么代理型系统的 Prompt Caching 改变了成本和工程的权衡,为什么过度压缩或者事后剪枝会毁掉缓存命中,以及应该怎么正确处理。文章里有个简短的代码示例,还有一个 5 步检查清单,你可以这周就拿一个 Flow 试起来。

Prompt Caching 底层原理(快速科普)

现代 LLM 提供商会把模型处理完一个 Prompt 前缀后的内部 Key/Value(KV)状态存下来。当后续请求以相同前缀开头时, provider 可以复用这段 KV 状态,不用重新计算这些 Token——这样就降低了首 Token 延迟(TTFT)和输入计费。

有几个你必须知道的 provider 特性:

  • 精确前缀匹配:缓存通常按字面前缀或显式断点来匹配。前缀一旦有任何变化,缓存就失效了。
  • 最小可缓存长度:很多模型要求前缀达到一定长度(近期模型通常约 1,024 Token)才能写入缓存。
  • 计费不对称:缓存写入可能按稍高的费率计费(比如某些新模型家族是 1.25 倍),而缓存读取则按输入价格的极小比例计费(通常约 0.1 倍)。所以当前缀会被复用时,写入是值得的。
  • 路由和粘性:请求按前缀哈希(以及可选的 prompt_cache_key)来路由。每台机器对每个前缀处理的吞吐量有限(大约 15 请求/分钟)。超出流量会打到其他机器上,产生缓存未命中。

这些特性使得缓存成为一个高杠杆的优化手段——前提是你把 Prompt 结构设计对了。但如果你不必要地破坏精确性,缓存就会变得脆弱。

为什么过度压缩或重排序会适得其反

团队在激进地压缩、重排序、或者事后编辑历史记录来省 Token 时,往往会改变缓存的前缀。这会导致:

  • 缓存未命中,需要重新完整预填充前缀。
  • 额外的诊断轮次和重传,因为丢失了锚点或上下文。
  • 对那些永远不会被复用的内容反复写入缓存。

一个实际例子:一项研究中,把工具输出缩小 38.4%,反而让计费成本增加了 6.8%——因为压缩破坏了缓存命中,强制重新运行。另一个编程 Agent 测试里,激进压缩破坏了代码锚点,补丁成功率从 27/40 掉到 15/40。省出来的那点 Token 完全被额外的 Agent 轮次、延迟和开发时间吃掉了。

教训是:一个 Token 的货币价值和延迟收益,取决于它是从缓存里提供的还是重新计算的。

核心原则:保留可缓存锚点,隔离易变内容

一个简单且可扩展的规则:把可缓存的锚点(系统 Prompt、工具定义、稳定指令)原封不动地放在 Prompt 最前面。把易变内容(工具输出、临时运行时状态、时间戳)放到缓存前缀之外。

在各种 provider 上都管用的保守策略:

  • 在缓存写入之前预注入压缩摘要,而不是事后剪枝已缓存的历史。
  • 使用显式断点(如果有的话)来声明缓存前缀的结束,避免把易变后缀写进去。
  • 避免在缓存区域内放内联时间戳、UUID 或会话特定字符串。

代码示例(概念版)

这个伪代码展示的是 relocation 模式:计算并存储一个静态前缀,然后在请求时动态追加一块内容。

cache_key = hash(static_prompt)
store_cache(cache_key, static_prompt)
prefix = get_cache(cache_key)
// dynamic_blob contains tool outputs, fresh observations, etc.
prompt = prefix + "\n" + dynamic_blob
send_request(prompt)

下面是一个 OpenAI 风格的显式断点示例(JSON 片段),避免缓存动态后缀:

{ "messages": [ {"role": "system", "content": "\n"}, {"role": "user", "content": ""} ], "prompt_cache_options": {"mode": "explicit", "ttl": "30m"}, "prompt_cache_key": "my-agent-shard-42"
}

(Provider 的字段名称不同;用你对应 API 的等价控制参数。)

运维参数和推荐初始值

  • TTL(缓存有效期):保守起步。很多 provider 有默认值(OpenAI 约 30 分钟,Anthropic 约 5 分钟)。对于交互式会话,根据会话流转情况在 10–60 分钟之间调优。
  • Keepalive / 热保活:当 Agent 因外部工作(构建、人工审批等)暂停时,用低成本的 keepalive 读取来刷新缓存前缀,避免被淘汰。选择一个 ping 间隔,确保在 provider 的淘汰时间点之前(比如约 240–480 秒,视 provider 而定)——但先测量,避免 ping 一个已经死掉的缓存,那就要付全额的写入费用了。
  • 路由 Key:用 prompt_cache_key 保持一致,让相关前缀的路由粘性更好。不要让单个 Key 超过约 15 RPM 的负载,必要时做分片。
  • 预注入阈值:不要在累计到 1k Token 时就激进压缩工具输出,考虑只在块很大时(比如 >10k)才预注入压缩摘要。一个运维 Flow 里,我们把 1k 摘要阈值改成了保守的 10k 预注入策略;延迟下降了,准确率上升了。之前的 1k 设置让端到端延迟增加了约 177%,因为产生了额外的重新运行。
  • 隐私护栏:在缓存前脱敏或 Token 化 PII(个人信息),并在权限变更时强制失效。

检查清单:这周可以试的 5 个步骤

1) 断点——区分静态和动态:缓存系统 Prompt 和工具锚点;把运行时状态放在前缀之外。
2) TTL——用保守的 TTL 起步(10–60 分钟),通过测量缓存命中率和陈旧度来调优。
3) Keepalive 间隔——用轻量级 keepalive 在暂停期间维持热 Key。从约 60 秒开始,根据 provider 行为调整。
4) Relocation 技巧——把频繁变化的数据作为独立的动态块或元数据头注入,让缓存前缀保持字节级一致。
5) 隐私护栏——在缓存前脱敏或 Token 化敏感数据,并在权限变更时失效缓存。

测量然后迭代

别猜:测量 cached_tokens、cache_write_tokens、TTFT、命中率、端到端延迟和计费读取。在一个 Flow(比如最贵的那个 Agent 路径)上做 A/B 测试,记录前后的数据。重点关注:

  • 命中率:有多少比例的请求读取了缓存 Token。
  • 缓存读取比例:输入中有多少是从缓存提供的。
  • 重跑次数:每个会话的诊断或重传轮次。
  • 补丁/操作成功率(对于编程 Agent)。

如果省 Token 导致命中率下降,净账单或延迟反而可能增加。优化的是净成本和延迟,而不是原始 Token 数量。

结语:Cache-First 思维,不是 Token-First

代理型系统的 Prompt Caching 奖励的是稳定性和刻意的边界控制。激进地做 Token 手术、改变缓存前缀,往往会毁掉你本来想利用的那个杠杆。保留锚点,把易变内容移到动态块,在暂停期间保持热 Key 温热,测量命中率、延迟和计费读取的真实影响。

这周拿一个高成本 Flow 试一下 5 步检查清单。如果用在了编程 Agent、运维自动化或交互式助手场景,跟踪命中率、TTFT 和总计费读取——你通常会发现,省下的那点边际 Token 根本抵不上失去的缓存命中。

你打算先从哪个 Flow 下手?