跳到主要内容

RAG 新鲜度问题:过时的 Embedding 是如何悄悄破坏检索质量的

· 阅读需 15 分钟
Tian Pan
Software Engineer

你的 RAG 系统在三个月前上线,检索准确度令人印象深刻。如今,它对用户提问中三分之一的内容都给出了“自信的错误”回答——而你的监控系统完全没有察觉到这种变化。没有错误日志,没有延迟激增。语义相似度得分看起来很正常。但检索到的文档已经过时,而模型却充满了信心地回答,因为检索到的上下文看起来非常权威。

这就是 RAG 的新鲜度问题:语义相似度并不关心时间。一个已弃用的 API 参考文档的 Embedding 得分可能与当前最新的文档一样高。上个季度的政策文档可能会排在更新版本之前被检索到。系统不知道,也无法分辨。大多数团队只有在收到用户投诉后,才发现他们的索引已经过时了数周甚至数月——而到那时,用户已经悄然失去了对系统的信任。

为什么新鲜度失效是无声无息的

标准 RAG 系统中根本性的架构缺陷在于向量相似度没有时间维度。当你对一个文档进行 Embedding 并将其存储在向量数据库中时,该 Embedding 捕捉的是某个固定时间点的语义。在检索流水线中,没有任何机制可以将昨天生成的 Embedding 文档与一年前生成的区分开来。

这导致了一种特定的失效模式。源文档在不断演进——API 更新、政策变更、产品功能的发布与弃用、价格调整、合规要求的变化。但是,代表这些文档的 Embedding 并没有随之演进。向量数据库会继续提供具有高置信度得分的过时 Embedding,因为查询的语义内容仍然与旧文档的语义内容匹配。

这种失效对标准监控来说是不可见的,原因有三:

  • 相似度得分依然很高。 检索到的文档在语义上与查询相关——只是它们不再准确。关于“如何使用我们的 API 进行身份验证”的问题,可能会检索到相似度得分为 0.92 的旧版 OAuth 1.0 文档,即使系统在六个月前就已经迁移到了 OAuth 2.0。
  • 延迟和吞吐量不受影响。 过时的向量检索速度并不比新鲜的慢。每一项运行指标看起来都是正常的绿色。
  • 单次检索看起来都很合理。 没有任何一个查询会触发明显的错误。这种退化是分布式的——在数百个查询中,准确率从 90% 下降到 65%,但没有哪个单独的回复会让你觉得“完全错了”。

来自跟踪这一现象的团队的生产数据显示,随着时间的推移,检索召回率从 0.92 降至 0.74,之前排名第一的相关文档从第 2 位漂移到了第 8 位——而这一切都发生在没有任何代码更改或基础设施事件的情况下。

新鲜度问题的三个来源

新鲜度问题有三个截然不同的原因,每个原因都需要不同的解决方法。

源文档漂移 (Source Document Drift)

最明显的形式:现实世界的信息发生了变化,但向量索引没有更新。你的知识库引用了一个已被弃用的产品功能、一个已重组的价格层级或一个已修订的合规政策。Embedding 依然存在,依然匹配查询,并且依然被检索出来。

在 1 月份语料库上生成的 Embedding,在应用于 6 月份信息的查询时,检索准确度可能会下降 15–20%。这不是模型质量问题——而是数据工程问题。Embedding 忠实地表达了文档当时的内容,只是文档的内容不再真实了。

Embedding 模型漂移 (Embedding Model Drift)

当你升级 Embedding 模型时——例如,从 text-embedding-ada-002 升级到 text-embedding-3-large——向量空间的几何结构会发生彻底改变。不同模型生成的向量占据不同的语义空间,无法通过余弦相似度进行有意义的比较。工程师们将此描述为“表示剪切”(representation shearing),即旧向量与新向量失去了几何对齐。

这种情况的危险版本不是完整的模型更换(这种变动通常足够剧烈,团队往往能察觉到),而是部分重索引(partial re-embedding):你用新模型重新生成了部分文档的 Embedding,但仍保留了旧索引。其结果是一个混合代(mixed-generation)的向量存储,其中旧向量和新向量之间的余弦相似度毫无意义,但系统却无法区分它们。

块边界漂移 (Chunk Boundary Drift)

改变分块(chunking)或预处理逻辑会产生一种更隐蔽的新鲜度问题。即使源文档没有变化,修改分块窗口大小、重叠参数、HTML 去除行为或 Unicode 标准化也会改变输入到 Embedding 模型的 Token 序列。由于模型使用子词分词(sub-word tokenization),即使只是改变一个空格或标点符号,也可能改变整个 Token 序列,从而产生实质上不同的 Embedding。

如果你修改了分块策略却只对新文档进行重新 Embedding,你最终会得到两类向量,它们以几何上不同的位置编码了语义上等价的内容。

在用户察觉之前衡量陈旧度

如果你无法衡量新鲜度,你就无法管理它。以下是能在陈旧内容触达用户之前将其识别出来的具体指标。

随时间变化的嵌入向量余弦距离。 定期对一组稳定的参考文档进行采样,并使用当前的流水线重新进行嵌入(embedding)。将新的嵌入向量与索引版本进行对比:

余弦距离状态
< 0.001稳定 — 无需采取行动
0.001–0.02轻微漂移 — 密切监控
0.02–0.05显著变化 — 调查流水线变更
> 0.05严重 — 检索质量正在下降

近邻稳定性。 每周运行一组规范查询,并将检索到的前 k 个文档 ID 与基准进行比较。健康的系统在不同时间间隔内的 Top-10 结果应保持 85–95% 的重合度。当重合度降至 70% 以下时,说明即使相似度分数看起来没问题,你已经面临实际的质量损失。

按类别划分的新鲜度分布。 跟踪索引中每个文档的摄入时间戳和最后验证日期。当超过新鲜度阈值的检索分块(chunks)比例激增时发出警报。并非所有文档的衰减速度都相同:

  • API 参考文档:2 周有效期
  • 合规性和政策文档:6 个月有效期
  • 架构概览或愿景文档:1–2 年有效期

这些阈值应该是每个文档上的显式元数据,而不是深藏在团队认知中的隐式假设。

检索与生成的一致性。 针对一组轮换的监控查询,将 LLM 的回答与当前的真实来源进行比较。这比单纯的检索指标更昂贵,但它能捕捉到端到端的故障模式:即虽然检索到了正确但陈旧的内容,从而导致模型自信地给出了错误答案。

变更数据捕获:保持向量同步

保持新鲜度的暴力方法是定期的全量重新索引——按计划重新嵌入你的整个语料库。这种方法有效,但成本高昂。一个每周重新嵌入 1 TB 语料库的团队报告称,仅在嵌入 API 调用上每月就要花费 12,000 美元,这还不包括分块、预处理和索引重建的计算费用。

变更数据捕获(CDC)是另一种选择。与其定时重新索引所有内容,不如检测哪些源文档实际发生了变化,并仅对这些文档进行重新嵌入。

数据库源 是最简单的情况。像 Debezium 这样的工具可以从 PostgreSQL、MySQL 或 MongoDB 中以亚分钟级的延迟流式传输行级变更。每一行变更都会触发受影响文档的重新分块和重新嵌入。对于结构化数据,这意味着只需重新嵌入修改后的行,而不是重建整个表。

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates