笔记本退货事件如何击溃RAG管道

3 阅读13分钟

\n\n这篇分析揭示了RAG管道中纯向量搜索的局限性,因其无法处理时间、权限等结构化数据,导致检索准确性问题。文章提出了混合搜索作为解决方案,通过结合向量相似度与SQL谓词来确保检索的正确性、安全性和效率,并建议将向量和结构化数据存储在同一数据库中。

译自:The laptop return that broke a RAG pipeline

作者:Ed Huang

以及如何通过混合搜索解决它

几个月前,我们的一位用户提交了一个让我印象深刻的错误报告。他们在一个RAG(检索增强生成)管道之上构建了一个客户支持智能体。他们遇到了以下场景:用户询问是否可以退回三周前购买的笔记本电脑。智能体检索了一个退货政策文件,引用了30天期限,并告诉客户将其寄回。回答自信满满。却完全错误。

该文件是真实的——它只是恰好来自2023年,而目前的政策已更改为电子产品14天期限。向量相似度没有近时性或范围的概念,因为查询嵌入和2023年政策之间的余弦距离非常出色。为什么不会呢?措辞几乎相同。

当我深入研究时,我意识到这并非真正的错误。这是一个架构问题。这次笔记本电脑退货请求改变了我对数据库在AI时代需要做什么的看法。

没人谈论的鸿沟

随着团队将RAG从原型推向生产,我们更频繁地看到这类问题。在过去的两年里,我们整个行业都专注于幻觉,即模型虚构信息。RAG是解决方案:将模型基于真实文档。而且它确实有效。但在此过程中,我们开始将检索视为一个已解决的问题。事实并非如此。

我一直回到这个问题:语义相似性和事实正确性并非一回事。向量搜索找到与你的查询意义相近的文档。这很有用,但“意义相近”并不意味着“在此上下文中正确”。一个已废弃的政策与当前政策语义相似。一份限定于企业客户的文档与免费用户发出的查询语义相似。租户A命名空间中的机密文档与租户B发出的查询语义相似。

“向量搜索找到与你的查询意义相近的文档。这很有用,但‘意义相近’并不意味着‘在此上下文中正确。’”

我称之为“检索准确性鸿沟”。(这是RAG的另一种类型。)它是向量相似度认为相关的内容与你的应用程序实际要求正确的内容之间的距离。你无法通过更好的嵌入来弥合它。缺失的信息——时间戳、范围、权限——是结构化数据。它存在于列中,而不是向量空间中。

这是一个数据库问题。

我所说的混合搜索

当我谈论混合搜索时,我指的是一个具体的事情:一个将向量相似度与结构化SQL谓词结合起来的单一数据库查询。而不是一个两阶段的管道,你进行向量搜索,得到100个候选结果,然后在应用程序代码中进行过滤。它是由数据库引擎整体优化过的单一查询。

这种差异比听起来更重要。当过滤发生在应用程序代码中时,你正在进行昂贵的工作——扫描完整的向量索引——然后才应用廉价的约束。这是本末倒置。一个既理解向量操作又理解关系操作的数据库可以利用选择性估计来决定是先过滤还是先扫描。这与我们关系数据库几十年来一直使用的查询规划逻辑相同。它只需要扩展到向量索引。

让我具体地展示一下这是怎样的。假设有这样的模式:


CREATE TABLE documents (
  id         BIGINT PRIMARY KEY AUTO_INCREMENT,
  content    TEXT,
  embedding  VECTOR(1536),
  team_id    BIGINT NOT NULL,
  doc_type   VARCHAR(50),
  updated_at DATETIME NOT NULL,
  status     ENUM('active','deprecated','draft'),
  INDEX idx_embedding USING HNSW (embedding),
  INDEX idx_team_status (team_id, status)
);

没什么特别的。带有向量列的标准关系模式。以下是解决我描述的故障模式的三种查询模式。

模式1:近时性过滤

当你添加时间约束时,过时文档问题就会消失:


SELECT id, content,
       VEC_COSINE_DISTANCE(embedding, @query_vec) AS distance
FROM documents
WHERE status = 'active'
  AND updated_at >= NOW() - INTERVAL 90 DAY
ORDER BY distance
LIMIT 5;

WHERE子句在向量扫描之前修剪了候选集。在一个千万行语料库中,这通常会消除60-80%的行。数据库同时变得更快更准确。这就是我喜欢的那种权衡。

模式2:通过JOIN实现租户隔离

这是我最担心的一种模式,因为它一旦失败,就是一次安全事件,而不仅仅是一个错误的答案:


SELECT d.id, d.content,
       VEC_COSINE_DISTANCE(d.embedding, @query_vec) AS distance
FROM documents d
JOIN user_permissions p
  ON p.team_id = d.team_id
WHERE p.user_id = @current_user
  AND d.status = 'active'
ORDER BY distance
LIMIT 5;

针对权限表的关系型连接(JOIN)。无论文档与查询在语义上多么相似,用户都永远不会看到超出其范围的内容。此约束由数据库引擎强制执行,而不是由某个可能忘记更新的应用程序代码强制执行。

尝试使用独立的向量数据库来做这件事。你将不得不将整个ACL复制到元数据标签中,每次权限更改时都重新索引,并希望你的基于标签的过滤能够处理权限组的组合爆炸。我见过团队尝试这样做。结果都不好。

模式3:带聚合的类别排名

有时正确的答案不是单个文档,而是许多文档中的一种模式。此查询按类型对匹配项进行分组,并找到信号最密集的地方:


SELECT d.doc_type,
       COUNT(*)                                  AS match_count,
       MIN(VEC_COSINE_DISTANCE(d.embedding, @query_vec)) AS best_dist,
       GROUP_CONCAT(d.id ORDER BY
         VEC_COSINE_DISTANCE(d.embedding, @query_vec)) AS doc_ids
FROM documents d
WHERE d.status = 'active'
  AND VEC_COSINE_DISTANCE(d.embedding, @query_vec) < 0.3
GROUP BY d.doc_type
ORDER BY match_count DESC, best_dist ASC
LIMIT 3;

这告诉LLM:“答案最可能出现在FAQ文档(7个匹配)中,而不是博客文章(2个匹配)中。” 然后,你从获胜类别中检索顶部文档。这是一个GROUP BY。向量数据库无法执行GROUP BY。这是关系代数,当你的语料库包含重叠的文档类型时,它会显著改变检索质量。

数据看起来怎么样

我们根据针对一个千万行企业知识库的生产工作负载对这两种方法进行了建模。这包括18个月的内容、各种文档类型以及500个带有手动标记真实值的查询。以下是我们的发现:

指标纯向量(前5名)混合(前5名)
Recall@572%94%
Precision@558%87%
前5名中的过期文档23%< 1%
跨租户泄露率8%0%
p50延迟45 毫秒62 毫秒
p99延迟120 毫秒155 毫秒

延迟成本是15-30毫秒。对用户来说是不可见的。零跨租户泄露率并非统计学上的改进,而是由关系型连接强制执行的保证。这是你可以带到安全审查中的一种特性。

我发现有趣的是,在许多实际场景中,混合搜索实际上更快,因为结构化过滤器极大地减少了向量搜索空间。当你的语料库中有70%在向量扫描开始之前就被修剪掉时,实际运行时间就会下降。请记住,结果会因语料库分布和过滤选择性而异。

“向量伴随器”反模式

我想谈谈我经常看到的一种架构模式,因为我认为它是生产中大多数RAG质量问题的根本原因。

这种模式是这样的:你有一个主数据库——通常是MySQL或PostgreSQL——其中存储着你的应用程序数据。然后你搭建了一个单独的向量数据库用于嵌入。现在你需要一个同步管道来保持它们的一致性。每个文档的插入、更新和删除都必须传播到这两个系统。你维护着两个模式、两个连接池、两个监控仪表板,以及一个脆弱的ETL作业在两者之间。

我称之为向量伴随器,它会产生三个随着时间推移而加剧的问题:

  • 一致性窗口。 两个系统之间总会存在一个不一致的间隙。一个文档在你的主数据库中可能被标记为已废弃,但直到同步赶上之前,它仍然会被向量存储返回。在退货政策示例中,这正是发生的情况,政策在主数据库中更新了,但向量索引仍然是过时的。
  • 无跨系统连接。 你无法在单个查询中将ACL表与向量索引连接起来。因此,你最终将权限数据复制为元数据标签,这意味着每次权限更改都需要重新索引。在大规模场景下,这会变得昂贵且容易出错。
  • 双倍的运营面。 两个数据库意味着两套值班轮换、两个容量规划模型和两种故障模式。我构建分布式系统已经十多年了,提高可靠性最有效的方法就是减少活动部件的数量。

“我构建分布式系统已经十多年了,提高可靠性最有效的方法就是减少活动部件的数量。”

替代方案很简单:将向量和结构化数据放在同一个数据库中。一个连接字符串。一个事务边界。一个一致性模型。数据库处理查询规划,根据选择性估计决定是先扫描向量索引还是先过滤。

这就是我们将向量支持直接内置到 TiDB 中的原因之一。当我们开始看到用户遇到这些确切的问题——一致性错误、跨租户泄露、操作复杂性——解决方案并不是一个更好的 同步管道。而是完全消除了对同步管道的需求。

为什么SQL兼容性在这里很重要

这其中有一个我低估了的实用维度。几年前,当我们决定在TiDB中实现MySQL线协议时,是为了减少采用摩擦。但在AI时代,它被证明具有更深层次的好处。

SQL是应用程序开发的通用语言。每个ORM都使用它。每个连接池都支持它。你团队中的每个工程师都编写过SQL查询。当你的AI数据库使用相同的协议时,我上面描述的混合搜索模式就不再是陌生的,因为它们只是SQL查询。你的团队不需要学习新的查询语言、新的客户端库或新的操作模型。他们编写他们一直以来编写的SQL,只是增加了向量距离函数。

我从观察数千个TiDB部署中了解到,采用障碍比功能列表更重要。最好的架构是你的团队能够真正交付的架构。

何时不需混合搜索

我想诚实地说明纯向量搜索在哪些情况下完全没问题,因为我认为任何技术建议的可信度都取决于承认其局限性。

  • 单租户、单文档类型的语料库。 如果你正在为一个团队的某个产品文档构建知识库搜索,一个 结合良好嵌入模型的纯向量搜索 将能很好地为你服务。我描述的故障模式源于异构性,例如多租户、文档类型或时间范围。
  • 探索性或创意性用例。 如果用户正在头脑风暴——“给我找一些与这个想法相关的东西”——那么近似检索实际上是你想要的。严格的正确性不是目标。
  • 人工参与的工作流程。 如果人类在每个结果被执行之前都进行审查,那么偶尔出现一个过时文档的成本是可控的。当 智能体自主行动时,风险随之改变

如果正确性是可选的,那么向量就足够了;如果正确性是必需的,那么它们就不够。但当你拥有多个租户、文档会过期,或任何不正确的检索导致没有人工审查的不正确行动的场景时,你就需要混合搜索。对于大多数生产RAG系统来说,这从第一天就开始了。

中间层

我一直在思考AI堆栈中真正的杠杆作用在哪里。行业在两个层面上投入了巨大的精力:嵌入模型(我们如何很好地编码意义?)和生成模型(我们如何很好地综合答案?)。两者都很重要。但在这两者之间存在第三层,它一直被视为商品:实际检索上下文的数据库查询。

这一中间层是正确性所在。嵌入模型将问题转化为向量。生成模型将文档转化为答案。但检索查询决定了模型看到哪些文档。如果这一步出错,那么下游的一切也都会出错,无论你的嵌入或LLM有多好。

混合搜索——在单个查询中、在单个数据库中结合向量相似度与关系过滤器——是你弥合检索准确性鸿沟的方式。这不是一种复杂的技术。它是带有距离函数的标准SQL。唯一的先决条件是数据库不强迫你在向量和关系之间做出选择。

十多年前,当我们开始构建TiDB时,我们的论点是数据库应该适应应用程序,而不是反过来。这意味着MySQL兼容性,这样你就不必重写你的应用程序。它意味着水平可伸缩性,这样你就不必在增长拐点处重新架构。现在,它意味着原生向量支持,这样你就不必为了构建AI功能而附加一个单独的系统。

这个论点没有改变。应用程序变了。全 工智能