嵌入偏移:正在杀死你长期运行的 RAG 系统的沉默退化
你的 RAG 系统运行正常。延迟处于常规水平。错误率为零。但一位询问“加州雇佣法”的用户却不断得到关于房地产的搜索结果 —— 而你的日志显示一切正常。
这就是嵌入漂移(embedding drift)在作祟:这是一种不会抛出异常、不会导致错误率飙升,也不会出现在标准可观测性仪表盘上的检索失效模式。当你的向量数据库积累了在不同条件下生成的嵌入时 —— 比如不同的模型版本、不同的分块规则、不同的预处理流水线 —— 向量开始指向不兼容的方向,这种情况就会发生。系统仍在处理请求,但语义坐标已不再对齐,检索质量在数周或数月内悄然恶化。
长期运行的 RAG 系统极易受到这种问题的困扰。你十八个月前部署的系统可能使用了某种嵌入模型对初始语料库进行索引,随后通过稍有不同的预处理流水线添加了新文档,并将查询编码迁移到了更新的模型 —— 而这一切都发生在没有明确决定混合嵌入空间的情况下。当时的每一次单独更改看起来都是合理的。但综合起来,它们产生了一个再也无法可靠地将相关内 容排在无关内容之上的向量库。
为什么混合嵌入空间会破坏检索
嵌入模型将文本转换为高维空间中的一个点。语义相似的两个文本应该产生相近的点。无关的两个文本应该产生相距较远的点。余弦相似度(几乎所有向量搜索背后的相似度函数)的整个前提,都取决于这一属性在索引中的所有向量上保持一致。
问题在于,“相近”和“较远”只在单一模型的坐标系中才有意义。不同的嵌入模型以不同的方式划分向量空间。由 text-embedding-ada-002 生成的向量与由 text-embedding-3-large 生成的向量存在于根本不兼容的空间中。用余弦相似度比较它们,就像比较来自两种不同投影方式的 GPS 坐标 —— 数字看起来相似,但它们指向的并不是同一个地方。
当你明确切换了嵌入模型却忘记重新对语料库进行嵌入时,这一点显而易见。但在生产环境中真正伤害团队的情况往往不那么明显:
- 你更新了一个预处理步骤,更激进地去除了 HTML 伪影。旧文档嵌入时带有噪声,新文档则没有。语义密度发生了偏移。
- 你为了提高精度将分块大小从 512 令牌改为 256 令牌。旧的分块有更宽的上下文窗口,新的则较窄。边界条件不同了。
- 你的数据流水线存在不一致性:开发环境去除了末尾空格和 Unicode 变体,而生产环境没有。在开发和生产环境中嵌入的文档最终落在略有不同的位置。
- 当新模型发布时,你运行了部分重新嵌 入,覆盖了过去六个月添加的文档。较旧的文档仍在使用原始模型。
任何这些变化都会产生一个混合向量库。这种不匹配会逐渐降低召回率:结果不会在一夜之间消失,但出现在排名靠前的文档越来越倾向于那些碰巧是最近嵌入的,或是当前流水线下处理的文档。
检索退化究竟是什么样子的
嵌入漂移会产生一种特定的失败模式,如果你不刻意寻找,很容易漏掉。查询与高度相关文档之间的健康余弦相似度应超过 0.85。在混合向量库中,对于同样的语义关系,跨模型比较通常会产生 0.65 左右的相似度。这并不是灾难性的故障 —— 系统不会崩溃 —— 但这足以扰乱排名。
受影响系统的检索召回率通常会从 0.92 下降到 0.74 左右,且没有任何相应的警报。如果你不定期针对基准查询集运行检索基准测试,你就无法察觉。标准的监控 —— CPU、内存、延迟、错误率 —— 都显示一切正常。
这种失效模式会随着时间的推移而加剧。随着查询编码器的偏离,使用原始模型嵌入的语料库变得越来越难以被发现。即便旧文档更相关,在当前流水线下嵌入的新文档也会占据结果的主导地位。用户学会了在查询中添加更具体的限定词,这在一定程度上起到了补偿作用 —— 直到他们再也无法通过这种方式解决问题。
