跳到主要内容

源头受污:RAG 语料库衰减与向量存储的数据治理

· 阅读需 12 分钟
Tian Pan
Software Engineer

你的 RAG 系统在上线时运行良好。三个月后,它在三分之一的用户查询中自信地给出了错误答案——而你的追踪日志显示一切正常。检索器在抓取文档,模型在生成回复,整个流水线看起来健康运转。问题是不可见的:向量存储中的每个向量依然有相似度分数,但其中一半已经指向了不再存在的事实。

这就是语料库衰减。它不会抛出异常,不会触发告警,而是在后台悄无声息地积累。等你通过用户投诉或质量下滑察觉到时,你的向量存储已经变成了一个负担。

向量数据库是基础设施。构建生产 RAG 系统的工程师最终都会以惨痛的方式认识到这一点。你不能把检索语料库当作一次性加载就可以忘记的静态制品。它会老化,会产生矛盾,会被污染。而且与应用数据库不同——数据库里的过期行会引发明显、可追踪的故障——过期的嵌入会产生自信、听起来合理的幻觉,能骗过你所有现有的监控指标。

为什么过时性在结构上是不可见的

RAG 语料库衰减的核心问题在于,向量相似度衡量的是主题相关性,而非时间相关性。一篇 18 个月前讨论同一概念的文档与最新文档得分相当——甚至更高,因为它可能写得更详尽。

当你索引一篇文档时,你把它转换成高维空间中的一个位置。这个位置编码的是语义,而不是时间。没有任何机制会把过期文档从查询中心点拉开。六个月前被取代的政策文档与其替代文档占据完全相同的区域。当用户询问该政策时,两份文档都会浮现出来。模型综合它们,给出的答案介于当前和过时之间——如果你的提示中没有来源元数据,模型和用户都无从判断哪些事实是最新的。

第二个不可见因素是置信度。LLM 经过训练,会产生流畅、连贯的回应。它们表达的不确定性与检索到的上下文有多旧并不成正比。三年前产品规格的检索命中与昨天更新的命中语气完全相同。你的监控仪表板衡量检索相关性分数和答案流畅度——这两个信号在语料库老化时都不会退化。

这造成了业内人士所说的"静默失败循环":系统持续运转,指标保持稳定,质量却在用户无法察觉的方式下持续侵蚀——直到通过用户行为(更长的编辑率、更多重试、最终放弃)或高风险失败才变得可见。

语料库污染的三大类别

把语料库健康视为单一问题会导致单线程解决方案。在实践中,RAG 语料库通过三种不同机制退化,需要不同的缓解措施。

时间衰减是最常见的。摄取时准确的内容因外部变化而变得错误:产品功能更新、法规改变、人员换岗、API 被废弃。向量存储对此一无所知。文档会无限期保持索引,除非你明确删除它们。律师事务所对此深有体会——早上发布的判决案例会影响下午的法律建议,但静态语料库迫使律师使用上次索引的快照。这不是边缘案例,而是所有不明确处理新鲜度的 RAG 系统的默认行为。

内部冲突更为微妙。随着语料库增长,你会积累多份对相同事实做出矛盾声明的文档。产品文档的 1.2 版说一件事,1.4 版说相反的事,两者都已索引。模型检索到两者,检测到矛盾,要么含糊其辞("这可能已经改变了"),要么随机选择其中一个。两种结果都不好。当你从多个更新周期不一致的来源摄取内容时——内部 Wiki、外部文档、导出的 CRM 数据、Slack 导出——冲突会尤其迅速积累,这些来源之间没有任何同步机制。

对抗性注入是讨论最少但日益真实的威胁。由于检索器通过向量相似度排名,能够将内容插入你的语料库的攻击者可以精心制作文档,使其嵌入被优化为拦截特定查询类型。这些"向量磁铁"——人类读起来像合法内容但在嵌入空间中被定位为捕获安全或金融相关查询的文本——将用户重定向到攻击者控制的内容。这种攻击不需要访问你的模型权重或提示,只需要能让一篇文档被摄取。对于接受用户贡献内容、第三方集成或网络爬取的语料库,这个攻击面相当可观。

TTL 设计:将新鲜度作为一等模式属性

时间衰减的基础修复是在摄取时为每个文档块分配明确的过期元数据。这意味着将你的向量存储视为缓存,而非数据库。

索引中的每个数据块至少应携带:

  • created_at:摄取时间戳
  • expires_at:基于内容类型的预期过期阈值
  • source_version:来自上游文档的哈希或版本标识符
  • last_verified_at:内容最后一次被确认仍然有效的时间

过期策略应根据内容波动性进行校准。产品文档在需要重新验证前可能有 90 天 TTL。新闻文章可能设为 7 天。参考数据(术语表、稳定概念)可能设为一年。关键是没有文档应该有无限 TTL——每个数据块都应该有明确的"何时过期"答案。

在检索过程中,你的流水线应该在语义相似度之外主动过滤新鲜度。结合两个维度权重的混合排名方法可以防止高分但过时的文档主导结果:

final_score = (semantic_score × 0.7) + (freshness_boost × 0.3)

新鲜度提升是文档年龄相对于其 TTL 的衰减函数。超过 TTL 阈值的文档应该从检索中完全排除,或者在提示上下文中用明确的不确定性标记。

过期文档不应该悄无声息地消失——你需要审计追踪。单独的归档索引或墓碑记录让你能追踪删除了什么以及何时删除,这对调试意外的质量退化至关重要。

摄取时冲突检测与去重

等到检索时再处理重复项和冲突为时已晚。等你在服务查询时,矛盾内容已经在索引中,并且会以高概率在相关查询上浮现。

冲突检测必须在摄取时发生。一个健壮的摄取流水线在多个层面应用检查:

在文档层面,在任何处理之前计算内容哈希(原始文本的 SHA-256)。如果索引中已存在相同哈希,跳过重新插入。这消除了精确重复——最常见的检索噪音来源。

在数据块层面,在插入每个新嵌入之前运行近似最近邻搜索。如果一个新数据块与来自不同源文档的现有数据块余弦相似度超过阈值(通常 0.95+),提交审查而不是静默插入。这可以捕获语义重复:不同文档做出相同声明,这会成倍增加该声明的有效检索权重,并使模型偏向该声明,无论其是否准确。

对于冲突检测——真正的矛盾,而非仅仅是重复——你需要实体解析。当一篇新文档对命名实体(产品版本、政策、特定配置值)做出声明时,你的流水线应该检查现有索引文档是否对同一实体做出了矛盾声明。这更复杂,需要一些结构化元数据提取,但它能捕获最危险的语料库污染类别。

Kafka Streams 或 Apache Flink 等流处理框架在这里效果很好,因为它们可以在摄取流中维护状态,实现跨文档冲突检测,而无需在每次摄取作业时将整个语料库加载到内存中。

检索层的访问控制

在多租户部署中,或任何不同用户应该看到语料库不同子集的系统中,访问控制必须在检索时强制执行——而不仅仅是在应用层。

失败模式很常见:团队在 API 层构建 RBAC("用户 X 可以调用此端点"),却没有意识到检索流水线本身不强制执行这些边界。命中向量存储的查询会从整个索引中拉取文档,除非你明确按访问元数据过滤。如果你的语料库包含来自多个客户、业务单元或敏感级别的文档,配置错误的过滤器可能会将敏感内容泄露到任何用户的检索上下文中。

正确的模式是在摄取时将访问元数据嵌入文档块——租户 ID、安全分类、允许的角色——并在每次检索调用时将该元数据作为强制预过滤应用。向量相似度搜索只考虑用户访问范围内的文档。

技术权衡在于预过滤和后过滤之间。预过滤在 ANN 查找之前缩小搜索空间,对于访问碎片化程度高的语料库(许多用户,每个用户只看到一小部分)更快。后过滤运行完整 ANN 搜索然后删除未授权结果,当大多数用户可以看到大多数文档时表现更好。对于混合语料库,混合方法——命名空间级预过滤结合元数据后过滤——通常提供最佳平衡。

关于动态 RBAC 分区(HONEYBEE)的最新研究表明,智能分区策略与朴素行级安全相比可以实现高达 13 倍的更低查询延迟,这在企业级部署的检索规模下非常重要。

语料库监控:真正该测量什么

传统 RAG 评估框架关注检索相关性、忠实度和答案质量。当你的语料库腐烂时,这些指标都会退化——但它们不会告诉你为什么质量退化,也不会在失败到达用户之前给你提前警示。

语料库健康监控需要自己的指标层:

过时分布:活跃检索索引中文档年龄的直方图。你应该了解索引内容的中位数、第 95 百分位和最大年龄。这告诉你 TTL 策略是否真正在执行,以及文档是否按计划刷新。

冲突密度:新摄取文档与现有索引内容发生语义冲突的比率。上升的冲突密度表明语料库污染,或者某个来源发布更正的速度快于你的流水线处理速度。

检索来源覆盖率:检索到的数据块中携带完整 source_versionlast_verified_at 元数据的百分比。任何缺少此元数据的数据块对基于新鲜度的过滤实际上是不可见的。

嵌入漂移:当你升级嵌入模型或在新数据上重新训练时,向量空间中位置的几何含义会改变。用旧模型嵌入的文档相对于用新模型编码的查询不再正确映射。追踪同一文档在不同模型版本间嵌入的余弦距离——稳定系统的漂移小于 0.005;大于 0.05 的值表明需要重新索引。

Arize AI、Langfuse 和 Evidently AI 等平台提供 RAG 特定的可观测性,可以将这些指标与传统检索质量信号一起呈现。

将 RAG 视为数据工程问题

认为 RAG 主要是 AI 问题的思维框架——使用哪个模型、调优哪种检索算法、编写哪种提示模板——导致了上述衰减模式。检索层从根本上是一个数据系统。它与任何生产数据库面临相同的生命周期挑战:模式演化、数据质量、访问控制、新鲜度保证、冲突解决。

构建持久 RAG 系统的团队已经汇聚到一套任何数据工程师都会熟悉的规范:

语料库的数据目录:溯源追踪,记录每个文档的来源、摄取时间戳、版本和转换历史。当质量退化时,你需要追溯哪些文档导致了错误答案。

摄取与检索之间的数据契约:明确规定每个数据块在可以被索引之前必须携带哪些元数据的模式,以及拒绝摄取缺少必需字段数据块的验证。

像迁移一样处理语料库更新的刷新流水线——增量式、有版本控制、可回滚。而不是有人记得触发时才运行的一次性脚本。

在任何文档进入索引之前运行冲突检测、去重和新鲜度验证的摄取质量门控。无论模型多么强大,垃圾进依然是垃圾出。

40% 的生产 RAG 失败追溯到数据质量问题,这不是模型问题,也不是基础设施问题。这是把语料库视为次要关切的后果。修复治理层,模型才有可信赖的内容可以利用。


实践要点:今天就审计你的向量存储。提取活跃检索索引中文档年龄的分布。找出你的活跃检索索引中最旧的数据块。如果你没有元数据来回答这个问题,那就是首先要修复的——而不是你的检索器、提示模板或模型。

References:Let's stay in touch and Follow me for more thoughts and updates