语义缓存。
你技术栈里其他所有缓存,最坏也不过是慢一点或者没命中;语义缓存却可能答错——它靠相似度得分而非相等判断来决定是否命中,于是一次擦肩而过就会给一个根本没人问过的问题返回一段自信而流畅的答案。仅凭这一条性质就该重排你的优先级:它不是一项缓存功能,而是一套带着误报预算的检索系统,需要你像对待搜索索引那样给它评测、版本化与作用域。
四样东西都叫「缓存」,只有一样会说谎。
这个词把四种失效方式截然不同的机制混作一谈。在争论该开哪个之前,先把它们分清楚。
- 前缀 / 提示词缓存——由提供方在服务端复用已处理过的提示词前缀。键是精确的令牌前缀匹配,所以未命中只是照常付原价。它不可能返回错误答案。这就是提示词缓存,也是最该先打开的那个。
- 精确匹配的响应缓存——你把完全展开后的请求(提示词、模型、参数)做哈希,存下响应。同样不会说谎;收益也同样有限,因为自然语言请求很少逐字节重复。在哈希前统一空白与大小写,通常能白捡一倍命中率。
- 工具结果缓存——用一个确定性的键把昂贵的工具记忆化:未变更文档的嵌入、一次地理编码查询、一次 schema 拉取。它常常是智能体里收益最大、却最少被讨论的一项,因为它省下的压根不是模型调用。
- 语义缓存——把来访请求嵌入,在存有历史请求的向量库里检索,若最近邻的相似度高于阈值,就返回它的答案。这会完全跳过模型调用,这正是它的诱人之处;而它也是四者中唯一可能自信地答错的那个。
前缀缓存与语义缓存既非竞品,也不能相互替代。前者降低每次调用的单价,后者干脆取消这次调用。前者到处都该打开;后者要当成一项你有意投放给特定流量切片的功能来对待。
整个设计的要害都在阈值上。
语义缓存就是最近邻搜索外加一份订在上面的答案。你对嵌入的一切认知都适用,包括那些扎心的部分:余弦相似度衡量的是话题上的相像,而非语义上的等价,而最常让答案反转的那些区别,恰恰是嵌入会压掉的东西。
- 否定。「这些套餐里哪些包含超量计费?」和「这些套餐里哪些不包含超量计费?」在嵌入空间里挨得很近,答案却完全相反。
- 实体与数字。换掉一个账号 ID、产品名、季度或版本号,向量几乎不动,正确答案却完全变了。
- 隐含上下文。两句一模一样的追问(「那第二个呢?」)在不同对话里指的是不同的事。被嵌入的那段文本并不携带用来消歧的对话。
实践中,大家的余弦阈值大多落在 0.85–0.95 这一带;在 FAQ、客服这类重复性流量上,公开报道的生产命中率通常在 30% 到 70% 之间。把这两个数字当作起始坐标,而不是配置项:合适的阈值是你的嵌入模型与你的流量共同决定的函数,你一改动其中任何一个,它就变了。阈值太松,边缘情况会被喂上错误答案;太紧,命中率就塌回精确匹配本来就能给你的水平。
结构上便宜的补救办法,是别再只信阈值。把缓存限制在问题确实会重复的领域,并把命中的条目当作候选——用一个小模型花全额调用零头的价钱验证一句「这条存下来的答案,真的回答了这个问题吗?」。这就把一场无界的相似度赌博变成了有界的。
缓存键比问题本身大得多。
多数语义缓存事故并不是阈值没调好,而是作用域没划好:两个看起来相像的请求,本来就不该被允许共用一个答案。任何可能改变正确响应的东西,都必须写进键里,而不是寄望于运气。
- 租户、用户与权限。如果检索是按权限过滤的,答案也是。一个跨用户共享的缓存,就是一套把甲客户的数据展示给乙客户的机制——而你的授权层里一个 bug 都没有;参见身份与权限。
- 模型、版本与参数。由一个你后来已经替换掉的模型生成的条目,其陈旧程度是任何 TTL 都捕捉不到的。
- 提示词版本与工具集。你改了系统提示词来修一个行为;缓存却继续把旧行为端给那些最需要这个修复的请求。
- 语料版本。对任何检索增强的东西来说,答案都是一组会变的文档的函数。要么把语料修订号放进键里,要么就接受你的缓存是上周知识库的一面镜子。
- 语言。多语嵌入模型会把一个问题和它的译文放得很近——这对搜索很方便,对缓存却是错的,只要答案必须使用提问者的语言。
由同一套逻辑还能推出两条规则。凡是答案涉及个性化或时间依赖的(「我的余额是多少」「昨天发生了什么」),无论措辞多么重复,都绝不缓存。以及,给每个条目一个足够短的 TTL,让坏条目在有人打电话叫你之前就自己过期。
先影子上线,然后度量精确率——而不是命中率。
命中率是那个被拿去汇报、也最会误导人的数字。把阈值调低就能轻易把它拉满,而这样买来的每一个百分点,都是用错误答案换的。真正要紧的指标是命中精确率:在你从缓存里端出去的请求中,有多大比例本来会从模型那里得到等价的答案?
- 先跑影子模式。照常调用模型,同时把缓存本来会返回的内容记下来,再比对两者。这样跑上一天,你就得到一条真实的「精确率对阈值」曲线,代价只有日志量。
- 给分歧打分,别靠肉眼看。用一个裁判模型在几百次影子命中上比较缓存答案与实时答案,这是一次再普通不过的评测,也是你能站得住脚地选定阈值的方式。
- 把缓存状态写进追踪。命中还是未命中、相似度得分、以及被端出去的那个条目的键。一个来自缓存的错误答案和一个由模型生成的错误答案,需要的修复方式完全不同,而没有这个字段你根本分不清它们——这也是可观测性换来的又一项回报。
- 持续盯精确率,而不是只量一次。流量会漂移,语料会变化,一个上线时正确的阈值会悄悄地不再正确。把影子比对排进定期任务。
操作顺序如下,而且很少需要变:打开前缀缓存,加上按归一化键的精确匹配缓存,再把昂贵的确定性工具记忆化。这三件事都不带正确性风险,而在多数系统里,它们已经拿下了可省成本的大头。做完之后再考虑语义缓存,并且只用在确实高度重复的那一小片流量上、以租户与语料版本为键,还要先跑一轮影子、看清它在你打算采用的阈值上的精确率。如果你没法度量这个精确率,那你做的就不是缓存——而是在一个答案分布里抽样,然后祈祷。
延伸阅读:智能体成本控制讲缓存在成本抓手中的位置,小模型与本地模型讲本文一再要求的那个廉价验证器,在循环层面控制成本讲运营视角。