AI 秒回了,为什么却拿别人的答案回答我?
问法相近,不代表旧答案还能用。用一个虚构的课程退款助手,讲清提示缓存与语义缓存的区别,以及用户边界、多轮上下文、规则版本为什么比相似度分数更重要。
假设一个课程平台给客服助手加了缓存。过去用户问一次,它要查订单、读规则,再组织回答;现在遇到熟悉的问题,几乎立刻就能回复。
甲问:“我这门课还能退吗?”助手说:“可以,你购买的是录播课,还在期限内,也没有开始观看。”过一会儿,乙也问:“我这门课还能退吗?”屏幕上出现了同样的答案。
可乙买的是已经开课的直播课。
这个平台和退款规则都是为说明机制构造的例子,不对应任何真实产品。问题也不一定出在模型推理上:这一次,系统可能根本没有让模型重新回答,而是把甲的旧答案取出来交给了乙。
我觉得这比一句明显的胡话更容易漏过检查。答案流畅、理由完整,甚至完全符合某个人的情况,只是不符合眼前这个人。
都叫缓存,省下的却不是同一件事
先把两个容易混在一起的概念分开。
提示缓存,也就是 prompt caching,复用的是模型处理重复输入前缀时已经做过的工作。比如助手每次都要读同一份课程说明、同一组任务规则,这些共有部分可以复用;新的问题仍然要被处理,答案仍然要生成。Anthropic 的文档把它描述为从已经缓存的提示前缀继续处理。
这更像读书时留下书签和读过部分的笔记。下次翻到相同段落,不必从第一页重来,但老师问了新问题,你还是要作答。
来源:提示缓存怎样复用前缀
语义缓存可以省得更彻底:把过去的问题和完整回答一起保存,把新问题转成向量,找意思相近的旧问题;符合复用条件时,直接返回旧答案。Redis 的说明明确区分了这种完整响应复用和检索文档片段:前者命中后可以跳过模型,后者通常还要把材料交给模型生成回答。
来源:语义缓存复用的是什么

如果问题是“怎样重置播放器的字幕大小”,而界面版本、设备类型都相同,复用一份已经检查过的操作说明,确实可能很合适。换个说法问同一件事,不必每次重新编排那几句话。
这种公共说明也有边界。假设播放器升级后,把字幕设置从播放页移到了个人偏好页,旧答案就会让新版本用户找不到按钮。此时不必给每个人单独存一份说明,却需要把界面版本作为共享条件。可见合理复用既不等于所有人共用,也不等于每个人隔离;关键是找到真正决定步骤的那组条件。
但“我的课程能不能退”包含另一类工作:判断这个人的实际条件。即使句子逐字相同,订单也可能不同。省掉重复措辞的生成,和省掉本次事实核对,不能当成同一笔优化。
相似度回答的是“像不像”,不是“能不能替换”
向量模型把文本映射到一组数字,方便寻找语义上接近的内容。它擅长把不同问法拉到附近,却不会自动知道应用里哪些差异具有决定性。
“还没播放,可以退款吗”和“已经播放,可以退款吗”,都围绕课程、播放、退款展开。一个词就可能改变结论,整句话的主题却没有变化。它们究竟有多近,要看模型与输入,不能凭直觉编出一个相似度数字;但这足以说明,主题接近并不构成复用答案的证明。
更极端的是刚才那两个“我这门课还能退吗”。假如缓存只保存这一句,任何更精细的文本比较都无从分辨甲乙。这里缺的不是更聪明的匹配算法,而是压根没进入比较的订单事实。
我会把问题拆成两问:新问题是否在问同一类事情?旧回答中的每个关键判断,对新请求还成立吗?语义搜索主要帮助回答前一问。把前一问的高分直接当成后一问的答案,才是危险的跨步。
调高阈值能减少一些勉强匹配,但不能补回缺失条件。还要看工具给的是“相似度”还是“距离”:RedisVL 的这一接口使用余弦距离,值越小才越严格。不同系统里的一个小数,不能脱离定义照搬。
同一句“那现在呢”,可能接着完全不同的故事
多轮聊天会把这个问题放大。
甲先说:“买了三天,还没看过。”助手确认了他问的是录播课。甲接着问:“那现在能退吗?”
乙先说:“我参加的是直播班,第一节已经上完。”助手查到了另一套处理规则。乙接着也问:“那现在能退吗?”
如果只拿最后一句去搜,两次请求几乎没有区别;真正决定回答的东西,恰好藏在前面的对话里。

把所有历史原封不动塞进向量,也不是必然正确。对话可能很长,用户聊过播放设备、课程内容、作业安排,关键的“已开课”只占很小一部分。更实际的做法,是整理当前问题所依赖的事实,并保留它们来自哪次查询、是否还有效。
AWS 的建议也是把当前消息与相关关键事实、近期消息结合起来,而不是直接对整段原始对话做缓存匹配。这仍然需要应用负责判断哪些上下文重要,不能把整理上下文当成已经验证了答案。
对于这个虚构助手,我会把“课程类型、适用规则、订单状态”从闲聊里拿出来,形成明确的判断条件。订单状态来自有权限的查询结果,不能因为用户一句“就当我还没上课”而改变。用户换了讨论对象,从甲课程切到乙课程,先前那份判断条件也要随之更新。
这里还要留一个“信息不足”的出口。如果系统尚未确定用户指哪门课,它应该补查或追问。缓存里恰好有一份写得很完整的答案,不能成为替用户补全未知条件的理由。
先划边界,再找相似
回到最初的错误,第一件该改的事,是把候选答案的范围收窄。
公开的课程操作说明可以在明确范围内共享;包含具体订单、观看记录的个人答复,则需要按身份与权限隔离。哪些内容允许共享,应该由应用规则决定。不能先从所有用户的答案里找到最像的,再希望模型识趣地避开个人信息。
之后再核对产品、语言、适用规则版本等条件。只有进入了正确的候选集合,相似搜索才有意义。Redis 将这种做法称为把软性的相似匹配与硬性的元数据边界结合起来。

在课程例子里,我更愿意分开缓存两样东西:一份经过审核的通用规则说明,以及本次订单查询后得到的判断。前者的有效期可能比较长,后者随订单变化失效。这样即使不能复用完整答复,也可能复用稳定的那一部分。
还有一种容易被忽略的混合答案:“你符合退款条件,款项将在若干工作日内退回。”前半句是资格判断,后半句又涉及是否已经申请、是否处理完成。仅仅看到“可以退款”,不足以复用整个段落。缓存的颗粒度太粗,会让原本不该绑在一起的事实一起过关。
真正执行退款则是另一回事。缓存里“已提交申请”的文字不是这次操作的收据,旧答复也不能替代新的授权、业务处理和结果查询。对于带动作的助手,我会把“解释怎么做”和“已经替你做了”从一开始就分开。
五分钟没到,不代表答案还有效
缓存通常会设置 TTL,也就是保存多久后过期。它解决的是“最迟什么时候停止使用”,不等于保证这段时间里事实一直不变。
继续用假设场景:十点整,系统确认用户尚未观看;十点零一分,用户开始播放;十点零二分,他又问退款资格。如果缓存五分钟后才过期,那么它还很新,却已经不适用了。
规则变化也类似。平台切换政策版本后,一小时前的通用说明可能就需要重新核对。把版本放进复用条件,或者在数据变更时主动使相关缓存失效,才能对应这种变化。TTL 可以作为兜底,但不能代替对变化的理解。
还有一层是答案本身的质量。一次模型回答错误,如果未经检查就被缓存,错误会从偶发生成变成稳定输出。反过来,缓存一份审核过的标准答复,也可能比每次自由生成更可靠。缓存没有天然的质量方向,取决于存进去的是什么、允许它在什么条件下再次出现。
我会让每条可复用内容带着依据和适用范围,而不只带一段漂亮的文字。以后用户指出错误,能找到相关条目并停止复用,比只改提示词更直接。否则模型已经变好了,缓存仍可能持续播放过去的错误。
验收时,专门找那些“很像,却不能共用答案”的问题
如果只用十种说法询问同一条规则,很容易证明缓存命中率不错。更有价值的检查,是让问法尽量不变,只改变一个真正影响答案的条件。
针对这个例子,我会准备三组题。第一组换措辞、不换事实,确认合理复用不会全被拒绝。第二组保留问法,换用户、课程或观看状态,确认系统能够拒绝复用。第三组先得到正确答案,再修改规则或订单,检查旧答案是否退出候选范围。
多轮场景再加一道:保持最后一句相同,交换前文。如果结果依然总是相同,就要回头看当前条件究竟有没有被送进判定过程。这些都是可以构造的测试,不需要等真实用户撞错后才发现。
观察指标也要拆开。“命中率”表示有多少请求拿到了缓存;“命中后的正确率”表示拿到的那些答案中有多少真正适用。两者不能互相替代。缓存拒绝了一个不安全的候选,有时恰恰是系统正常工作。
这里甚至有一个容易误读的结果:更换策略后命中率下降,整体体验反而变好。因为减少的正是那些原本会立即答错的请求。复用的价值应该按省下的生成成本、增加的检验成本和错误代价一起算,而不是只追着一个百分比走。
还有个容易误判的区别:答复里的个人名字删掉了,不代表它就成了公共答案。“你的课程可以退款”虽然没有姓名,仍然携带对某个订单的判断。决定共享范围时,应该看这句话依赖了谁的事实,而不是只扫描有没有姓名和编号。
同样,按用户隔离也不是终点。一个用户可以同时买两门课,上一轮还在问录播,下一轮已经切到了直播。如果缓存只认用户、不认当前对象,问题会从“拿别人的答案”缩小成“拿自己另一件事的答案”,错误仍然存在。权限边界和事实条件各有职责,补上其中一个,不能代替另一个。
上线前还可以先旁路观察:照常生成用户答案,同时记录“缓存本来会选哪一条”,抽查两者是否可替换。新的模型输出也不是绝对正确的裁判,关键条件仍需要按事实核对;这种做法的价值,是先看见误命中,再决定放开哪些类别。
我并不觉得语义缓存一定不值得做。稳定的公共问答、明确版本下的操作说明、经过审核的标准解释,都是合理起点。越接近个人事实、实时状态和实际动作,就越需要谨慎界定复用范围。
下次看到助手突然秒回,我会先确认一件事:它省掉的是重复劳动,还是这一次本来必须重新核对的条件。
评论