跳转到主要内容

当源数据已更改,你的 Prompt 缓存仍在提供旧的工具执行结果

阅读需 1 分钟Tian PanTian Pan

一名支持代理在 14:02 查询了客户的订阅状态,发现其处于激活状态,该回答进入了 Prompt 前缀中,而缓存层刚刚将其标记为上下文的可重用部分。在 14:14,计费系统取消了该订阅。在 14:19,同一位客户提出了跟进问题,由于对话前缀仍然匹配,缓存的前缀被重用,代理愉快地告诉客户他们的计划处于激活状态,并主动引导他们使用一个他们已不再拥有访问权限的功能。下游系统是正确的。模型与上下文保持了一致。但用户被缓存命中(Cache Hit)欺骗了。

这是 Prompt 缓存为原本对数据陈旧度(Staleness)保持诚实的系统引入的失效模式。在引入缓存之前,工具调用是对单一事实源(Source of Truth)的请求,并遵循该源所声明的任何新鲜度契约。有了缓存之后,工具结果变成了 Prompt 前缀的一个租户,而前缀拥有自己的 TTL(生存时间),由模型提供商控制,团队中没有人明确选择启用它。

诱人之处在于,Prompt 缓存确实做到了文档所承诺的一切。Anthropic 的缓存会将前缀保留 5 分钟(或支付额外费用保留一小时),OpenAI 的自动缓存会重用超过 Token 阈值的最长匹配前缀,成本降低了,首个 Token 延迟(Time-to-first-token)降低了,命中率仪表盘也变绿了。这些都没有错。错误存在于缓存对“前缀匹配”的承诺与你的应用对“数据新鲜度”的隐含承诺之间的鸿沟中,而这个鸿沟是由你的用户的任何假设填补的。

Prompt 缓存命中是你未签署的新鲜度契约

缓存命中不仅仅是一项性能优化。它是对模型的一份声明:“此前缀中的 Token 是此请求的当前上下文。”如果工具结果存在于该前缀中,模型就会将其视为当前的真实情况。模型没有办法询问“这是 5 秒前还是 5 分钟前获取的?”——它只能看到 Token。

这使得 Prompt 前缀成为了流入其中的任何数据的“事实上的缓存”,其 TTL 由推理提供商的缓存层控制。在 14:02 获取并在 14:19 重用的客户记录正作为 14:19 的新鲜数据被提供,而不是 14:02 的新鲜数据。没有人故意这样设计;这是将易变数据放在缓存分界线(Cache Cut)之上的后果。

标准的生产建议——系统提示词在前,然后是工具,接着是静态文档,最后是用户消息——是为了“命中率优化”而编写的。它恰好在新鲜度方面也是正确的,但原因却是大多数团队没有内化的:缓存分界线之上的层被隐式地断言为时间不变的。当你把动态内容放在那里时,你不仅是在内容变化时损失了缓存命中率,你还在内容本身未变但底层源数据已变时,交付了陈旧的答案。

这与分布式缓存设计者几十年前学到的关于 TTL 的教训是一样的:TTL 限制了从缓存角度看你的数据可能有多陈旧,但它无法告诉你在此期间源数据是否发生了变化。区别在于,HTTP 缓存对其本质是诚实的。而吸收了工具结果的 Prompt 前缀看起来像是对话的一部分,而不是缓存的一部分。新鲜度契约已经通过 LLM 上下文被“洗白”了。

失去一致性的两个缓存层

在任何生产环境的智能体中,调用路径通常存在两个缓存,它们由不同的团队拥有,并具有不同的 SLO(服务水平目标)。

第一个是工具结果缓存:CRM 前的 Redis 查询、记忆化的 HTTP 响应、数据库查询计划。它的 TTL 由负责数据层的人员选择,该选择通常反映了底层记录实际变化的频率——库存为 60 秒,产品目录为 15 分钟,静态 FAQ 为 24 小时。契约是明确的。通常在源数据发生变化时,会有某个地方触发失效钩子(Invalidation Hook)。

第二个是 Prompt 前缀缓存:模型提供商的 KV 缓存或其等效物,保存了生成模型第一个隐藏状态(Hidden States)的 Token 化前缀。它的 TTL 取决于提供商给你的值——自今年早些时候的静默回归以来,Anthropic 的默认值为 5 分钟——并且它完全不知道这些 Token 最初来自哪里。它无法被你的数据层作废,因为你的数据层甚至不知道它的存在。

这两层之间的漂移就是 Bug 所在。一个工具结果被获取,并以 60 秒的 TTL 存储在工具结果缓存中,同时流入 Prompt,被提供商缓存 5 分钟。60 秒后,数据层作废了它的副本。下一个调用者将重新获取并得到新鲜数据——但 Prompt 前缀仍然持有陈旧的值,并且在剩余的 4 分钟内任何重用该前缀的请求都会得到旧答案。工具缓存履行了职责。Prompt 缓存也履行了职责。这种组合模式失效了。

这种情况之所以难以察觉,是因为这两个 TTL 是由不同的人商定的。设置 Redis TTL 的数据工程师从未听说过 cache_control 代码块。选择缓存断点位置的应用工程师不知道上游的失效契约是什么。没有一个单一的负责人来掌控“这个事实端到端可以有多陈旧”。

会员专享

余下内容仅对会员开放。

会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。

  • 完整文章,包含未公开存档的部分
  • 可落地的工作框架,附带权衡与决策依据
  • 新文章抢先看,先于公开发布

随时取消 · 一次订阅,畅读全部

参考资料

保持联系,关注我获取更多内容

阅读需 10 分钟

被反向代理剥离的 SSE Keep-Alive,以及你支付了两次费用的 Prompt

LLM 流式响应看起来像是从模型到用户的通畅管道 —— 直到一次 35 秒的工具调用导致反向代理断开连接,你的客户端针对无状态 API 重试整个 Prompt,最终用户的账单为同一个响应支付了两次费用。

insider
ai-engineering
阅读需 9 分钟

Agent 流水线的分布式追踪:为什么你的 APM 工具形同虚设

标准 APM 工具在多步骤 Agent 流水线上会失效。以下是 AI Agent 专用可观测性的真正需求——以及三个能在用户察觉之前预判 Agent 劣化的关键指标。

insider
ai-engineering
阅读需 9 分钟

你从未构建过的智能体反馈闭环

每一个点踩、每一次无声的放弃、每一次重新表述的问题,都是一个免费的带标签失败案例 —— 而大多数团队却将其丢弃。本文介绍如何构建从用户信号到分诊失败再到永久评估案例的流水线。

insider
ai-engineering
阅读需 10 分钟

Agent 调试器没有断点:为什么追踪优先工作流正在取代单步执行

当输入具有随机性时,单步调试就会失效。替代方案是基于追踪和重放的工作流,它具有四种功能——时间轴拖动、分支对比、扰动重放以及每一步的意图恢复——这些功能与 IDE 的调试工具栏完全不同。

insider
ai-engineering
阅读需 9 分钟

滑动窗口税:为什么 30 轮对话的成本远超单次对话的 30 倍

多轮 AI 功能在仪表板上按调用次数计费,但在实际支出中却遵循对话增长曲线。其长尾效应呈超线性增长,而这正是账单激增的根源。

insider
ai-engineering