T
traeai
登录
返回首页
InfoQ

为什么仅靠向量搜索不够:RAG中的混合检索

9.0Score
为什么仅靠向量搜索不够:RAG中的混合检索

TL;DR · AI 摘要

仅靠向量搜索无法满足生产级RAG需求,必须结合BM25与RRF实现混合检索以提升精确度。

核心要点

  • 向量搜索擅长语义相似但无法精确匹配版本号或错误码等实体。
  • BM25通过IDF、词频饱和和文档长度归一化实现高精度排序。
  • RRF无需复杂归一化即可融合向量与BM25结果,提升召回精度。

结构提纲

按章节快速跳转。

  1. 向量嵌入擅长语义相似查找,但无法区分具体实体如版本号或错误码。

  2. 真实查询常需同时满足语义理解与精确匹配,单一方法无法应对。

  3. §BM25如何弥补向量搜索缺陷

    BM25通过逆文档频率、词频饱和和文档长度归一化实现高精度排序。

  4. RRF仅依据排名位置融合结果,避免分数归一化带来的复杂性。

  5. 推荐BM25+向量搜索+RRF+可选交叉编码器重排序的分层架构。

思维导图

用一张图看清主题之间的关系。

查看大纲文本(无障碍 / 无 JS 友好)
  • 混合检索解决RAG精度问题
    • 向量搜索局限
      • 无法精确匹配实体
    • BM25补充机制
      • IDF + 词频饱和 + 长度归一化
    • RRF融合策略
      • 基于排名位置融合

金句 / Highlights

值得收藏与分享的关键句。

  • 向量嵌入是近似引擎,擅长查找语义相似内容,但系统性地弱于区分具体实体如版本号、错误码和功能标志名称。

    第1段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • BM25使用三种机制:逆文档频率(IDF)、词频饱和和文档长度归一化。

    第3段

    ⬇︎ 下载 PNG𝕏 分享到 X
  • Reciprocal Rank Fusion(RRF)无需分数归一化即可融合BM25与向量结果。

    第4段

    ⬇︎ 下载 PNG𝕏 分享到 X
#RAG#向量搜索#混合检索#BM25#RRF
打开原文

核心要点

  • 向量嵌入是近似引擎,擅长发现语义相似内容,但在区分特定实体(如版本号、错误码和功能开关名称)方面系统性较弱。
  • 生产环境中的查询很少能纯粹归类为语义或词法查询;大多数是混合查询,需要同时兼顾语义和精确匹配,而单一方法检索在此处失效。
  • BM25(Best Matching 25 的缩写)是一种排名函数,能够提供向量嵌入无法提供的精确度。它通过三种机制实现:逆文档频率(IDF),用于加权稀有且具区分性的词元;词频饱和机制;以及文档长度归一化。
  • 互惠排名融合(RRF)无需繁琐的分数归一化即可结合 BM25 和向量检索结果。它仅基于排名位置进行操作,奖励两个检索器都一致认可的文档。
  • 生产级检索栈是分层架构。BM25 加向量搜索通过 RRF 融合,并可选地再经过一个交叉编码器重排序阶段,在少量候选集中进一步提升最终相关性。

贵公司最近推出了一款内部全渠道搜索系统,该系统采用 检索增强生成(RAG) 技术构建,覆盖了公司所有历史问题、设计文档、发布文档、运行手册及错误修正方案(COEs)。工程师、产品经理和经理们可通过由大型语言模型驱动的聊天界面进行查询。团队还将此系统封装为 MCP 工具,以便其 AI 编程助手可以直接获取上下文。

随后,生产支持组的一名值班工程师输入:“运行手册以在生产环境中启用 payment_v2_enforce 功能开关”,聊天助手却告诉他们应禁用该功能。系统内部按嵌入相似度对文档进行排序。

对嵌入模型而言,这两份运行手册几乎完全相同:它们拥有相同的开关名称、相同的服务、相同的词汇量,以及相似的上下文环境。值班工程师不会直接看到这种排序结果,而是看到聊天助手根据检索器返回的前 K 条结果生成的回答(有时正确的运行手册甚至不在前 K 名内)。答案要么被稀释,要么干脆就是错误的。

#### 相关推荐

如果您曾使用 嵌入技术 构建过搜索系统,这种情况可能十分熟悉。系统在宏观层面表现良好,但忽略了真正重要的细粒度、具体细节。

上述查询要求两个条件:对“功能开关运行手册”的语义理解,以及对操作(启用 vs 禁用)的精确匹配。向量搜索 仅处理了前者。

这不是您嵌入模型的缺陷,而是向量相似性的工作方式决定的。嵌入模型寻找的是与查询语义相近的内容,而非精确匹配。由于检索结果会被送入 LLM 作为上下文,因此排序的重要性不亚于召回率。

即使正确答案出现在前 K 名内,如果错误答案排在其前面,也无济于事。解决方案并非替换嵌入模型,而是将其与实际文本上的经典关键词匹配相结合,使概念相关性和精确术语匹配共同贡献最终排序。

仅依赖向量的 RAG 流水线为何失效

要理解为何会出现这种情况,不妨拉远视角,审视整个流水线。如图 1 所示,RAG 流水线包含三个阶段。

图 1/filters:no_upscale()/articles/vector-search-hybrid-retrieval-rag/en/resources/218figure-1-1779972809482.jpg)

图 1. 典型 RAG 流水线包含三个阶段:切块、检索和生成。(来源:作者原创)

图 1 中各元素定义如下:

  • 切块:将源语料库拆分为可索引单元。
  • 检索:接收用户查询,在这些切块中搜索并返回最相关的前 K 条结果。
  • 生成:将这些切块作为上下文传递给大型语言模型,并要求其生成答案。

假设第一阶段和第三阶段工作正常。文档会在合理的边界处被分割,大语言模型(LLM)会基于提供的上下文给出答案,不会出现幻觉。失败点出现在检索阶段:检索器将查询嵌入为向量,与索引中的文档向量进行比较,并返回在嵌入空间中距离最近的文档。嵌入空间中的“接近”指的是语义相似性,而非字面相同。两个针对同一功能开关的运行手册——一个用于启用,一个用于禁用——在嵌入空间中彼此非常靠近。它们的措辞仅在一个词上不同,而嵌入模型对几乎相同的文本生成几乎相同的向量。检索器无法可靠地区分它们。因此,当用户询问启用该开关的运行手册时,有时禁用它的运行手册反而更接近查询,检索器以同等信心将其返回。这就是崩溃点:同一个向量空间和相同的评分机制导致错误的文档排在首位。

问题在于嵌入是近似引擎

BERT 这样的嵌入模型将文本转换为固定维度的数值向量,这些向量捕捉了文本的语义含义。语义相近的文本会产生相似的向量。“功能开关”、“紧急关闭”、“灰度发布闸门”和“配置切换”都会在向量空间中聚类在一起。这种聚类在用户搜索概念时非常有用;但当用户需要精确实体——比如特定的功能开关名称、特定错误码或特定部署版本时,它就变成了精度问题。

同样的近似行为也会出现在不同的故障模式中。当有人搜索 ERR_PAYMENT_GATEWAY_TIMEOUT 时,相关代码如 ERR_PAYMENT_GATEWAY_REJECTEDERR_PAYMENT_GATEWAY_UNAUTHORIZED 因共享前缀 ERR_PAYMENT_GATEWAY 并出现在类似的故障排除文档中,会被检索器视为接近查询的结果。区分它们的后缀词本身权重很低。嵌入模型的行为完全符合设计初衷:它旨在寻找相似而非完全相同的内容。当区分元素相对于周围文本而言很小的时候,嵌入就会抹平这种区别。

图2展示了一个嵌入空间,在其中语义相似的项目会聚在一起。在某个簇内,区分具体实体(例如启用与禁用功能开关的运行手册)变得困难。这就是混合搜索所解决的精度失败问题。

Image 2/filters:no_upscale()/articles/vector-search-hybrid-retrieval-rag/en/resources/164figure-2-1779972809482.jpg)

图2. 语义相似的项目会聚在一起。并非每个查询都有相同的问题。(来源:作者原创)

根据哪种检索方法最适合处理它们,搜索查询大致可分为三类:

#### 语义查询

用户询问“当某个区域离线时我们的协议是什么?”是在询问一个概念。标题为“灾难恢复架构”、“主动-主动复制策略”和“故障转移运行手册”的文档即使与查询无共同词汇,也应排名靠前。嵌入天然适合这种情况,因为它们捕捉的是语义而非字面匹配。

#### 精确匹配查询

这类查询在信息检索文献中也被称为词汇查询。用户将堆栈跟踪或日志中的错误码粘贴到搜索框中,例如 "ERR_PAYMENT_GATEWAY_TIMEOUT",他们已经知道想要查找的具体标识符。对于这类查询,语义相似性正是用户不希望看到的结果。向量嵌入可能会适得其反,通过返回语义相近但标识符不同的文档(如 ERR_PAYMENT_GATEWAY_REJECTED 而非 TIMEOUT)来干扰结果。关键词搜索能高效且正确地处理这类查询。

#### 混合查询

用户搜索“v3.2部署的回滚运行手册”需要语义理解(即部署回滚操作的运行手册),同时还需要精确匹配区分性词元:“v3.2”以选择正确的版本,“rollback”以区别于“rollout”。用户搜索“Outlook 2019同步错误0x80004005故障排除”则需要症状的语义匹配,加上版本号和错误码的精确匹配。这类查询要求两者兼备。在我实际使用生产级RAG系统的过程中,这类查询占多数。本文其余部分将介绍如何处理它们。

BM25 在嵌入近似的地方提供精度

向量搜索需要一个搭档,这个搭档就是BM25——经典信息检索的核心概率排序函数。它是Elasticsearch、OpenSearch及大多数词汇搜索引擎的默认评分器,也是过去三十年来主导关键词搜索算法的选择。它恰恰在向量搜索失败的地方表现出色。它依赖概率信息检索理论,内置三种机制直接应对精确匹配问题。

逆文档频率(IDF)衡量术语在整个语料库中的稀有程度。常见词如“服务”或“部署”权重较低,而稀有区分性词元如“v3.2”、"ERR_PAYMENT_GATEWAY_TIMEOUT" 或 "payment_v2_enforce" 则获得高权重。正是这种机制使得BM25在精确匹配查询上优于嵌入模型。那些区分一个文档与其他文档的关键稀有词元,正是BM25最重视的部分。

词频(TF)饱和度控制重复术语的影响。术语首次出现时对得分影响显著,但后续出现则收益递减。得分趋向于一个上限,而非线性增长。这种机制防止了关键词堆砌文档通过操纵排名来获取优势。

长度归一化解决了文本检索中的另一个偏差问题。较长的文档往往得分更高,仅仅是因为它们包含更多词汇,从而拥有更多匹配查询词的机会。长度归一化通过在计算相关性得分时考虑文档长度进行修正,不仅考虑术语出现次数,还考虑其相对于文档长度的频率。这一点在具有可变长度块的 RAG 系统中尤为重要,否则没有此调整,较大的块将始终优于较小的块。

使用逆秩融合的混合搜索

如图 3 所示,混合检索同时运行 BM25 和向量搜索,在并行执行后使用 RRF 将两个排序列表融合,可选地再用交叉编码器重新排序,最后将前 K 个块传递给大语言模型。

Image 3/filters:no_upscale()/articles/vector-search-hybrid-retrieval-rag/en/resources/139figure-3-1779972809482.jpg)

图 3. 混合检索。(来源:作者制作)

此时我们有两个互补能力的检索器:向量搜索和 BM25。向量搜索捕捉语义含义,而 BM25 匹配精确词元。每个检索器各自生成一份排序列表。为了处理混合查询,需要将这两份列表合并为一份。

合并本身是难点。向量余弦相似度介于 -1 到 1 之间,而 BM25 得分无界。将它们归一到同一尺度上很困难。正确的权重依赖于查询:对于某个查询,BM25 的合适权重可能是 0.7,而对于另一个查询则是 0.3。在生产规模下按查询校准这些权重不切实际。正是在这种情况下,逆秩融合(RRF) 提供了解决方案。

深入探讨 RRF 如何帮助合并得分

RRF 完全绕开了归一化问题,它不使用任一检索器的原始得分,仅依据排名位置进行操作:

RRF_Score(d) = Σ 1 / (k + rank_r(d))

常数 _k_ 通常取 60 (Cormack, Clarke, and Buettcher 2009),用于平滑每个排名位置的贡献。排名第一的文档贡献 1/61 ≈ 0.0164,排名第 10 的文档贡献 1/70 ≈ 0.0143。若某文档未出现在检索器的前 K 结果中,则该检索器对其贡献为 0。

机制非常简单。两个检索器均将其排在前列的文档获得最高融合得分,因为它们从每个检索器都获得非零贡献。仅有一个检索器发现的文档会被降级,即使该检索器将其排在首位。RRF 奖励共识。

以下三个表格分别演示三种查询类型的情况:语义查询、精确匹配查询和混合查询。它们共同展示了 RRF 明显胜出的情形、保持正确结果但边界较窄的情形,以及本文论点的实际落点。

在考虑排名列时,每个检索器都在数千篇文档的完整语料库上进行搜索。表中显示的 BM25 和向量排名是它们各自完整检索输出中的位置,而非每张表中展示的四篇文档内的排名。因此,BM25 排名 12 表示该文档在整个语料库中位列第十二。

下面演示的三个查询均可在本地 Elasticsearch 实例上端到端运行。示例应用代码和数据集可在 此 GitHub 示例 中找到。

语义查询

#### 查询:"我们的认证系统如何处理过期令牌?"

这是一个概念性问题。正确的文档是一份名为“认证服务中令牌刷新与过期处理指南”的运行手册。它共享多个查询词(“token”、“expiration”/“expired”、“handling”/“handle”、“auth”),因此 BM25 能够找到它,但一篇关于“system”和“token”词频更高的相关性较低的文档在 BM25 排序中反而更靠前。

文档BM25 排名向量排名RRF 得分 认证服务中令牌刷新与过期处理指南 3 1 1/63 + 1/61 = 0.0323 OAuth 流程设计文档 6 2 1/66 + 1/62 = 0.0313 系统令牌轮换运行手册 1 8 1/61 + 1/68 = 0.0311 认证服务架构概览 2 11 1/62 + 1/71 = 0.0302

BM25 找到了正确的文档,但不如它找到“系统令牌轮换运行手册”那样自信——后者在常见术语上有多次匹配,但内容不同。向量搜索因捕捉到查询与文档内容之间的概念一致性,将正确文档排在首位。RRF 因两个检索器均高度评价该文档而奖励它,使其在融合列表中位居榜首。接下来两个 RRF 结果(“OAuth 流程设计文档”和“系统令牌轮换运行手册”)都是 LLM 消费该前 K 文档时合理的补充上下文。

文档BM25 排名向量排名RRF 得分 运行手册:ERR_PAYMENT_GATEWAY_TIMEOUT (payment-svc)1 6 1/61 + 1/66 = 0.0316 运行手册:ERR_PAYMENT_GATEWAY_REJECTED (payment-svc)12 1 1/72 + 1/61 = 0.0303 运行手册:ERR_PAYMENT_GATEWAY_UNAUTHORIZED (payment-svc)15 2 1/75 + 1/62 = 0.0295 支付服务通用错误处理指南-3 0 + 1/63 = 0.0159

考虑到合理性,相邻错误代码的运行手册出现在 BM25 结果中,是因为相关的运行手册通常在其故障排除步骤中相互引用(“如果您看到 ERR_PAYMENT_GATEWAY_REJECTED,请参阅此运行手册”)。查询词匹配这些交叉引用。如果没有这些交叉引用,BM25 将仅返回 TIMEOUT 运行手册本身,而相邻运行手册行将完全缺失于 BM25 结果中。

RRF 将正确的运行手册置于首位,但其与被拒运行手册之间的优势微弱,且第二和第三名 RRF 结果是错误的错误代码运行手册。对于此类纯标识符查询,仅使用 BM25 能产生比混合结果更干净的前 K 名结果。BM25 的第 2 和第 3 名将是 LLM 可以忽略的相关文档,而 RRF 的第 2 和第 3 名则是外观相似的错误运行手册,可能误导 LLM 关于用户实际粘贴的是哪个错误代码。这是混合检索真正有效的场景——它在分布层面提升了效果,而非对每个查询都严格优于单一方法。

混合查询

#### 查询:“v3.2 部署的回滚运行手册”

文档BM25 排名向量排名RRF 得分 v3.2 部署回滚运行手册 1 3 1/61 + 1/63 = 0.0323 v3.2 部署发布运行手册 4 1 1/64 + 1/61 = 0.0320 v3.2 部署事后分析 6 2 1/66 + 1/62 = 0.0313 v3.1 部署回滚运行手册 2 7 1/62 + 1/67 = 0.0311

BM25 因 “rollback” + “v3.2” + “deployment” + “runbook” 全部匹配而将正确文档排在首位。向量搜索则将 v3.2 的发布运行手册排在首位,并非因为嵌入模型认为发布比回滚更适合回滚查询,而是因为查询与两个运行手册之间的余弦相似度相差约 0.01 至 0.02。哪一个排在第一更像是噪声而非信号。在不同的日期或针对不同的嵌入模型,顺序可能会颠倒。

这种对查询中最关键操作性区分——用户实际想要执行的操作——的噪声级不确定性,正是混合检索所解决的失败模式。BM25 对回滚运行手册的偏好打破了平局,支持了用户所请求的操作。RRF 则推广了两个检索器均排在前三名内的文档,即正确的 v3.2 回滚运行手册。

##### 三个查询共同体现的模式

在三种查询类型中,该模式保持一致。在语义查询中,向量搜索找到正确文档,RRF 保留其首位并加入 BM25 的共识信号。在精确匹配查询中,BM25 找到正确文档,RRF 保留其首位,尽管次优结果相比仅用 BM25 更嘈杂。在混合查询中,每个检索器单独都有不同的失败模式。BM25 的第一名正确,但其第二名是错误版本。向量搜索的第一名是错误操作。RRF 的组合产生了正确第一名和错误但相关第二名的结果,这是三者中最干净的结果。

根据我的经验,生产环境中的查询分布主要由第三种情况主导。大多数现实世界查询结合了语义意图与特定标识符、版本号、错误代码或其他需要精确匹配的令牌。混合检索正是对这种分布的工程化响应。

生产环境中的混合检索

生产级 RAG 系统已普遍采用混合检索。Perplexity 在 Vespa 上融合了基于词典和嵌入式的评分器,覆盖数百亿个 URL,并通过多阶段排序最终进行交叉编码重排序。Glean 在企业搜索中叠加了词典检索和稠密嵌入,构建于专有的知识图谱之上。两个不同领域,相同的架构模式。

Elasticsearch 的生产级实现

Elasticsearch 和 OpenSearch 均通过检索器 API 原生支持混合检索(Elasticsearch 8.13+,OpenSearch 随后跟进)。原生支持意味着融合过程在搜索引擎内部单次查询内完成,无需应用层合并逻辑。以下示例使用 Elasticsearch 语法;OpenSearch 语法几乎相同。

索引映射

您的索引需包含一个用于 BM25 的标准文本字段和一个用于语义检索的稠密向量字段:

code
PUT /rag_knowledge_base
{
  "mappings": {
    "properties": {
      "title":   { "type": "text" },
      "content": { "type": "text", "analyzer": "standard" },
      "content_vector": {
        "type": "dense_vector",
        "dims": 768,
        "index": true,
        "similarity": "cosine"
      }
    }
  }
}

图 4. Elasticsearch 索引映射定义了一个用于 BM25 的文本字段和一个 768 维的稠密向量字段用于语义检索。

使用 RRF 的混合查询

查询结构同时运行两个检索器并在单次请求中融合它们:

code
POST /rag_knowledge_base/_search
{
  "retriever": {
    "rrf": {
      "retrievers": [
        {
          "standard": {
            "query": { "match": { "content": "rollback runbook for v3.2 deployment" } }
          }
        },
        {
          "knn": {
            "field": "content_vector",
            "query_vector": [0.12, -0.45, ...],
            "k": 50,
            "num_candidates": 100
          }
        }
      ],
      "rank_constant": 60
    }
  }
}

图5. 使用 Elasticsearch 的 RRF 检索器进行混合检索查询,通过单次请求并行运行 BM25 和 kNN 检索,并融合其排序结果。

生产环境调优

上述默认配置是一个合理的起点,但生产系统几乎总是需要调优。三个参数尤其主导了你将遇到的相关性与延迟之间的权衡。

#### 排名常数 (k)

排名常数是 RRF 公式中的平滑参数,用于控制排名贡献的衰减速度;排名为 r 的文档对其融合得分的贡献为 1/(k + r)。默认值 60 适用于通用检索场景。将其降低至 20-30 可偏向于高排名结果的贡献,这在 BM25 检索结果高度精确时非常有用,例如错误码、版本字符串或功能标志名称。若将其提升至 80-100,则会平缓排名贡献曲线,更倾向于同时出现在两个列表中的文档,而非仅在一个列表中排名靠前的文档。合适的值取决于你是希望获得高精度(较低 k)还是高召回率(较高 k)。

#### kNN 候选数量

num_candidates 参数决定了 HNSW 图遍历在返回前 K 个结果之前探索多少个向量,从而控制近似最近邻搜索中的召回率与延迟权衡。设置 k=50 且 num_candidates=100 是一个强大的基准。如果你发现向量搜索召回率偏低(相关文档始终出现在前 50 名之外),通常将 num_candidates 提高到 200-300 可以适度提高召回率,而对延迟影响较小,因为额外计算发生在向量索引内部,而非增加额外的网络往返。

#### 使用交叉编码器重排序

使用 RRF 进行混合检索可获得一组强候选集,但加入交叉编码器重排序阶段可显著提升最终相关性。与独立生成查询和文档嵌入的双编码器不同,交叉编码器会将完整的查询-文档对联合输入 Transformer,使查询词与文档内容在 token 级别上实现交互。正是这种架构差异,使得交叉编码器在相关性任务上持续优于双编码器:它们能够建模独立嵌入无法捕捉的细微关系。

实践中,通常的做法是让 RRF 检索出二十到五十个候选文档,再将它们传入如 ms-marco-MiniLM-L-6-v2 这样的交叉编码器进行最终排序。交叉编码器不适合第一阶段检索(因其需对每个查询-文档对执行一次前向传播),但对于重排序少量候选集,延迟通常是可接受的——在 GPU 上处理五十个候选文档通常不到一百毫秒。交叉编码器在标准检索基准(如 BEIR)上始终优于双编码器,大型模型在跨领域查询上表现最佳,即使是轻量级模型在领域内也能提供显著增益。对于生产系统而言,每一分相关性的提升都至关重要,因此这一最终阶段值得投入。

结论

稠密嵌入解决了检索中的泛化问题,即使查询词与文档词不匹配,仍能找到概念上相关的文档。BM25 解决了精确匹配问题,它基于稀有、区分性的词元找到完全匹配的结果。但两者单独使用均不足以满足生产级 RAG 的需求。

嵌入本质上是近似引擎,这也是其优势所在,也是其局限性所在。使用 RRF 的混合搜索并非是对模型质量暂时不足的补救措施,而是必须同时处理概念相关性和精确匹配查询系统的架构正确方案。

如果你仅依赖嵌入运行 RAG 流水线,你正在放弃检索质量。请加入 BM25,用 RRF 融合二者,并考虑加入交叉编码器重排序阶段。

关于作者

Image 4

#### Aaditya Chauhan

Aaditya Chauhan 是亚马逊的一名高级软件开发工程师,他领导大规模搜索基础设施和 AI 驱动的检索系统的工作。拥有超过十二年的行业经验,他专注于分布式检索、RAG 流水线以及在生产规模下的向量搜索,包括混合稠密/稀疏检索,以及将这些系统从基准测试部署到生产环境的实际工程挑战。他在搜索架构、RAG 评估及生产级检索系统的工程权衡方面撰写文章并发表演讲。

AI 可能会生成不准确的信息,请核实重要内容