语义缓存是什么?有什么风险?

进阶原理场景题约 6 分钟读完

一句话回答

语义缓存用 Embedding 判断新问题和历史问题的意思是否相近,相似度超过阈值就直接返回之前的答案,不再调用模型,能省成本、降延迟。阈值高则安全但命中少,阈值低则命中多但容易答错。它适合 FAQ 这类答案稳定、和用户无关的场景。最大的风险是意思相近但关键细节不同(日期、地点、否定)时返回了错误的答案;个性化和时效性的内容不能缓存;缓存必须按租户或用户隔离,否则会把别人的数据返回出去。此外还要设计好失效策略。

详细解析

流程

  1. 先查精确缓存:问题归一化(去掉多余空格、统一大小写和标点)后算哈希,命中就直接返回,成本最低
  2. 没命中时,计算问题的 Embedding,在缓存里找最相似的历史问题,同时按租户、Prompt 版本、模型版本过滤
  3. 相似度超过阈值,并且通过了关键信息校验,返回缓存的答案
  4. 否则调用模型,把问题、向量、答案和元数据(租户、版本、引用的文档、过期时间)写入缓存

阈值怎么取舍

阈值 命中率 风险
高 低,只有几乎相同的问法才命中 答错的少,但省得也少
低 高 把不同的问题当成相同的问题,答非所问

阈值没有通用的值:不同 Embedding 模型的相似度分布不同,换了模型就要重新调。做法是准备一批标注好的问题对("可以用同一个答案"和"不可以"),在不同阈值下统计命中率和误命中率,按业务能接受的误命中率来选。

风险

  • 关键细节不同:"怎么开通会员"和"怎么取消会员","北京明天的天气"和"上海明天的天气","能退款吗"和"不能退款怎么办",向量可能非常接近,答案却完全不同
  • 个性化内容:"我的订单到哪了"这类答案依赖用户自己的数据,不能缓存
  • 时效性内容:价格、库存、新闻、天气,缓存的答案很快就过期了
  • 多轮对话:"那它多少钱?"依赖上文,单看这句话没有意义。要先结合上下文把它改写成完整的问题,再查缓存
  • 数据泄露:缓存的答案里如果带了某个用户的信息,再返回给别人就是泄露。缓存要按租户或用户隔离,只缓存不含个人数据的答案

降低误命中的办法:命中后再校验关键信息(抽取日期、数字、实体、否定词,要求一致);用重排模型或小模型判断"这两个问题能不能用同一个答案回答";只在明确的场景(FAQ、产品说明)启用;只缓存用户评价好的答案。

失效策略

  • 过期时间:按内容的时效性设置
  • 版本:缓存的键和过滤条件里带上 Prompt 版本和模型版本,改动后旧缓存自然失效
  • 数据源变化:记录答案引用了哪些文档,文档更新或删除时,清掉相关的缓存
  • 用户反馈:被点踩的答案立即移出缓存

和其他缓存的区别

响应缓存要求请求完全相同,不会答错,但命中率低;Prompt Caching 只复用输入前缀的计算,模型照样生成新的回答(见 Prompt Caching 是什么);语义缓存的命中率最高,但只有它会答错。穿透、雪崩这些缓存的通用问题同样存在,见缓存穿透、击穿、雪崩。

代码示例

JavaScript
const THRESHOLD = 0.92 // 示例值,要用标注数据针对自己的 Embedding 模型调整
const VERSION = 'faq-v3' // Prompt 或模型变化时更新,旧缓存自然失效

async function answerWithCache(question, { tenantId }) {
  const text = normalize(question)
  const exactKey = `qa:${tenantId}:${VERSION}:${sha256(text)}`
  const exact = await redis.get(exactKey)
  if (exact) return exact

  const vector = await embed(text)
  const [hit] = await vectorStore.search({ vector, topK: 1, filter: { tenantId, version: VERSION } })
  // 相似度够高,还要求关键信息一致(日期、数字、实体、否定词)
  if (hit && hit.score >= THRESHOLD && sameKeyFacts(text, hit.question)) return hit.answer

  const answer = await generateAnswer(question)
  // 不含个人数据、不涉及时效性内容的答案才缓存
  if (isCacheable(question, answer)) {
    await redis.set(exactKey, answer, 'EX', 86400)
    await vectorStore.insert({ vector, question: text, answer, tenantId, version: VERSION, expiresAt: Date.now() + 86400_000 })
  }
  return answer
}

面试官可能追问

命中率很低怎么办?

先看问题的分布:如果大部分问题本来就不重复,语义缓存的收益有限,每次请求还多了一次 Embedding 和向量检索。可以先用意图识别把问题归到标准问题上,再缓存标准问题的答案;或者把高频问题整理成 FAQ,直接走知识库。上线前用历史日志模拟一遍,估算命中率和误命中率,再决定值不值得做。

在 RAG 系统里,缓存放在哪一步?

可以缓存最终答案,也可以只缓存中间结果,比如问题改写的结果、检索结果。缓存最终答案省得最多,风险也最大;缓存检索结果时,模型仍然会针对原问题生成回答,答错的风险小一些,但知识库更新后同样要失效(RAG 的链路见 RAG 的完整链路)。

怎么评估语义缓存的效果?

看三个指标:命中率(省了多少次调用)、误命中率(抽样人工检查,或者用模型判断缓存的答案是否回答了新问题)、命中后的用户反馈(点踩、追问有没有增加)。误命中的代价通常比省下的钱更高,阈值宁可保守一些。

易错点

  • 把语义缓存当成和精确缓存一样安全,不做误命中的校验
  • 照搬别人的相似度阈值,不同的 Embedding 模型分布不同
  • 缓存不按租户隔离、不过滤个人数据,把 A 的答案返回给了 B
  • 知识库更新了,缓存里还是旧答案

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。