RAG面试篇8

13. 什么是多路召回?具体怎么做?

多路召回就是同时用多种不同的检索方式去捞候选内容,然后合并排序,而不是只靠单一的向量检索。我理解核心出发点是向量检索和关键词检索各有盲区,向量检索擅长语义相似,但对精确词语比如产品型号、缩写、数字效果比较差;BM25 关键词检索正好相反,精确匹配强,但不理解语义。我在项目里常用的组合是向量检索加 BM25 混合检索,再加上多 Query 扩展,也就是把用户问题改写成多个版本分别检索。多路的结果用 RRF 算法融合,最后送进 Rerank 精排。

什么是多路召回?

多路召回,顾名思义,就是同时用多种不同的检索方式去捞候选内容,最后再把各路结果合并、排序,交给 LLM 生成答案。

和它对应的是「单路召回」,也就是只用一种检索方式,最典型的就是只用向量检索:用户问题转成向量,去向量库里找相似度最高的 Top-K 个 chunk,然后直接喂给 LLM。这套流程在很多场景下能跑起来,但有明显的天花板。

你可能会问,既然向量检索有语义理解能力,为什么还不够用?原因很简单,没有任何一种检索方式是全能的,向量检索有它的盲区,BM25 也有它的盲区,把它们组合起来互相补盲区,总召回质量才会比单路高。

常见的组合是三路:第一路是向量检索 ,负责语义层面的覆盖,处理同义词、近义词、不同表达方式,「退货」和「申请售后」在向量空间里是邻居,能互相命中。第二路是BM25 关键词检索 ,负责精确词匹配,专门处理产品型号、专有名词、数字这类向量检索搞不定的场景。第三路是多 Query 扩展,把用户问题改写成多个不同角度的版本,覆盖更多表述差异,相当于撒了一张更宽的网。

三路各自召回一批候选,再用 RRF 算法把排名融合成一个统一的结果列表,最后送进 Rerank 精排,才把最终上下文交给 LLM。

为什么单路召回不够用?

理解了多路召回是什么之后,自然会追问:单路到底差在哪?为什么非要搞这么复杂?

单纯依赖向量检索是大多数 RAG 系统早期的做法,但它有一个明显的短板:对精确词语的召回效果差

比如用户问「M4 Pro 芯片的性能参数」,这里的「M4 Pro」是一个专有名词,向量模型可能把它和「苹果最新处理器」的向量拉得很近,但如果知识库里这个词本来就是「M4 Pro」,向量检索不如直接关键词匹配来得准。很多人以为换个更好的 Embedding 模型就能解决,其实不行------这是向量检索「只看语义、不看字面」的固有局限,不管用哪个模型都存在这个问题。

反过来,关键词检索(BM25)对同义词和不同表达方式无能为力。用户问「怎么退货」,文档里写的是「申请售后」,词没重叠,BM25 完全召回不到。

两种检索方式的盲区恰好互补,这就是「多路召回」的出发点。理解了这个互补关系,后面每一路的作用就非常清晰了。

第一路:向量检索(Dense Retrieval)

向量检索是多路召回的基础一路。核心做法是:把文档和用户问题都用 Embedding 模型转成向量,然后用余弦相似度在向量库里找最近的 top-K 个 chunk。

它擅长语义匹配,同义词、不同表达方式都能覆盖,但对精确词语(产品型号、缩写)效果不佳------而这恰好是第二路 BM25 的强项。

第二路:BM25 关键词检索(Sparse Retrieval)

理解了向量检索搞不定精确词语这个问题,BM25 的加入就顺理成章了。

BM25 是 TF-IDF 的改进版本,根据词频和文档频率给每个词打分,找出包含查询词最多、且这些词在整个语料里不太常见(也就是区分度高)的文档。它的核心逻辑是:一个词在这篇文档里出现多(词频高),但在整个知识库里出现少(区分度高),说明这个词对这篇文档很具代表性,权重就高。本质上是在问:「这个词有没有『代表』这篇文档?」

中文场景下用 BM25 需要先做分词(比如用 jieba 切词),再对分词后的词列表建索引和检索。实现上就是对每个 chunk 先切词建索引,查询时同样切词后计算 BM25 分数排序。纯文本匹配,速度极快,对精确词语、数字、专有名词的召回效果非常好。

前两路解决了「语义匹配」和「精确词匹配」的问题,但还有一个场景它们都覆盖不到------用户提问的角度和文档表述的角度压根不一样,不是同义词的问题,而是整个视角的差异。这就需要第三路出场了。

第三路:多 Query 扩展召回

为什么还需要第三路?

因为用户提问的角度和知识库里文档的表述角度不一致,这不是同义词能解决的。比如用户问「产品多久能送到」,文档里写的是「配送时效说明」,两句话角度完全不同,向量相似度可能不高,BM25 也匹配不到关键词。

多 Query 扩展的做法是:用 LLM 把用户的原始问题改写成 3~5 个不同角度的版本,分别去检索,然后把所有结果合并去重。这样只要有一个改写版本和文档的表述对上了,就能把正确的内容召回来,就像拦截网越宽,捕到鱼的概率越高。

实现上,先调 LLM 生成多个问题变体,每个变体分别跑向量检索,最后把所有结果汇总并去重(同一个 chunk 被多个变体召回时只保留一份)。代价是多了几次 LLM 调用,但在用户提问风格多变的场景下,召回覆盖率能提升 10%~20%。

结果融合:RRF 算法

三路召回各自拿回了一批候选,接下来的问题就是:怎么把三路结果合成一份?每一路都有自己的排序结果,分数单位不一样(向量检索是余弦相似度,BM25 是 TF-IDF 分数),没法直接加权平均。你可能会想,能不能归一化之后再加权?理论上可以,但实际上各路分数的分布差异很大,归一化效果不稳定,工程上也更复杂。

RRF(Reciprocal Rank Fusion,倒数排名融合)是目前最常用的融合方法,它只用排名而不用原始分数,巧妙地绕开了分数不可比的问题:

其中 k 是平滑参数(通常取 60),rank 是文档在某一路结果里的排名。

RRF 的直觉很好理解:不管各路分数怎么算(因为向量相似度和 BM25 分数本来就没有可比性),只看排名。一个文档在多路检索里都排名靠前,它的 RRF 综合分就高,就像多位评委都给高分的选手,最后的综合排名就高。这个方法不需要训练、计算量极小,工程落地成本很低。

值得注意的是,RRF 本质上还是粗排,适合在 Rerank 之前做候选集合并。如果对最终召回精度要求很高,还是要在 RRF 融合之后接一个 Cross-Encoder 结构的精排模型(比如 bge-reranker-v2-m3)做深度打分,把真正相关的内容筛到最前。

实战建议

不是每个场景都需要三路全上,按业务特点来选:

知识库里有大量专有名词、产品型号、数字的场景(比如电商、IT 文档),向量 + BM25 双路是标配,收益很明显。

用户提问方式多变、和文档表述差异大的场景(比如客服问答),加上多 Query 扩展,召回覆盖率能提升 10%~20%。

对召回质量要求极高的场景,三路全上,最后接 Rerank,把融合结果精排一遍再给 LLM,把质量做到天花板。

14. RAG 检索优化策略有哪些?

我理解 RAG 的检索优化可以从四个层次来看:索引层决定知识怎么存,查询层决定问题怎么转换,召回层决定从哪些路径去找,重排序层决定最终哪些内容进入 prompt。

每一层都有对应的优化手段,我的经验是单独优化一个层次往往效果有限,线上系统我会组合来用,先靠索引优化和多路召回来保证覆盖率,再用 Rerank 保证精度,如果用户提问质量比较差,再额外加上查询优化。

检索是 RAG 的命脉

要理解为什么检索优化这么重要,先想清楚一件事:LLM 只能根据送进去的 context 来回答,检索召回的内容就是整个系统的天花板。

生成层做得再好,如果检索没把相关内容找回来,LLM 也是巧妇难为无米之炊。反过来,只要检索能稳定地召回准确、相关的 chunk,生成质量自然差不到哪里去。

你可能会问,那优化生成层没用吗?当然有用,但投入产出比完全不同。生成层的优化(比如调 prompt、换模型)是锦上添花,而检索层的优化是从根本上提升系统的能力上限。所以在 RAG 系统里,检索优化是投入产出比最高的环节,没有之一。

四层优化全貌

RAG 检索优化可以从四个层次来理解,每一层解决的问题不同,优化手段也不同:

  • 索引层决定知识怎么「存」,也就是文档切割的粒度和方式,直接影响向量的语义质量;
  • 查询层决定问题怎么「转」,在检索之前对用户的 query 做加工,让它更容易命中知识库;
  • 召回层决定知识从哪里「找」,用多条不同的检索路径并行捞取候选,互补各自的盲区;
  • 重排序层决定候选里谁「最相关」,对粗召的候选集做精排,保证进入 prompt 的都是真正有用的内容。

这四层是递进关系,可以用一个比喻来理解:索引层决定「仓库里放了什么」,查询层决定「用什么钥匙开门」,召回层决定「从哪几扇门进去找」,重排序层决定「把找到的东西里最好的几件带出来」。下面依次展开说。

第一层:索引优化

先从最底层的索引说起,因为索引是所有后续优化的基础,如果知识存的方式就有问题,后面再怎么优化查询和检索都是白搭。

索引优化是 Chunking 策略的延伸,但它聚焦于一个更核心的矛盾:检索用的粒度和 LLM 读的粒度天然是矛盾的

理解这个矛盾,要从 chunk 承担的两个角色说起。一个 chunk 需要同时完成两个任务。

第一个任务是「检索时被找到」,这要求向量语义尽量聚焦。把一篇文章压成一个向量,这个向量里混合了太多不相关的语义,用户问细节问题时,这个笼统的向量很可能和问题向量距离较远,就检索不到了,所以检索需要小粒度的 chunk,每个 chunk 语义聚焦。

第二个任务是「被 LLM 读懂」,这要求有完整的上下文。断章取义的几句话 LLM 往往答不好,如果文档里前一段定义了术语、后一段才是真正的解释,只给 LLM 后一段它可能看不明白,所以 LLM 需要大粒度的 chunk,上下文完整。

很多人以为直接把 chunk 切小就好了,检索会变准,但切小之后 LLM 拿到的是碎片化的信息,回答质量反而下降。这就是两难困境:小 chunk 检索准但内容太碎,大 chunk 内容完整但检索时语义稀释。

解决这个矛盾的核心思路叫 Small-to-Big,也就是小块检索、大块使用 。具体有三种实现方式

反过来,关键词检索(BM25)也有自己的盲区:它只会数词频,不理解语义。用户问「怎么退货」,文档里写「申请售后」,词不重叠,BM25 完全召不到,但向量检索能处理这种同义表达。

两种检索方式的盲区恰好互补,这就是多路召回的出发点。不只走一条路,同时打开多扇门,把各路结果汇总起来,覆盖更多的可能性。

典型的三路并行是这样的:

第三层:召回优化

查询优化是从「问题」这边想办法,召回优化则是从「检索路径」这边想办法。

即使 query 已经改写得很好了,如果只走一条检索路径,还是会漏掉一些内容。

单一的向量检索有一个根本局限:它只擅长语义相似,对精确词语匹配效果差。比如用户问「M4 Pro 芯片的性能跑分」,这里的「M4 Pro」是一个精确的产品型号,如果知识库里就是这么写的,向量检索反而不如直接关键词匹配来得准,因为向量模型可能把「M4 Pro」和「苹果最新处理器」的向量拉近,但就是找不到包含字符「M4 Pro」的那条记录。

  • Parent-Child Chunking 是最直接的方案。把文档切成两个版本:一份是细粒度的子 chunk(比如 150 token 一个),一份是粗粒度的父 chunk(比如 500 token 一个),每个子 chunk 通过 parent_id 关联到对应的父 chunk。入库时只给子 chunk 建向量索引;检索时用子 chunk 的向量来匹配,精度高;命中之后,根据 parent_id 取出对应的父 chunk,把父 chunk 塞给 LLM 阅读,上下文完整。这样就做到了「检索用小的,阅读用大的」,两全其美。

  • 摘要索引(Summary Index)的思路稍有不同,它不是切割文档,而是让 LLM 为每一段内容生成一段摘要,用摘要来建向量索引。为什么这样做?因为文档原文有时候表述很散,而摘要是对核心意思的提炼,语义更聚焦,在向量空间里和用户的问题会更接近,命中率更高。检索时用摘要的向量匹配,命中后把原始段落塞给 LLM 阅读。

  • 多粒度分层索引则更激进,同时建章节级、段落级、句子级三层索引。不同类型的问题适合不同粒度:「什么是 RAG」这种宽泛的概念性问题,用章节级就够了;「退款申请需要几个工作日」这种细节性问题,用句子级更精准。系统根据问题类型自动选择合适的粒度去检索,能覆盖更多类型的用户需求。

    第二层:查询优化

    索引优化解决的是「知识怎么存」的问题,但即使索引建得再好,用户的提问方式和知识库里的表述方式之间还是会存在鸿沟,这不是存储端的问题,而是查询端的问题。

    来看一个具体的例子:用户问「苹果手机咋截图」,知识库里写的是「iPhone 截图操作方法」。两句话意思一样,但向量相似度可能不高,前者是口语中文,后者是正式书面语,表达风格的差异会拉开向量距离,检索就容易漏。

    你可能会想,不是已经用向量检索了吗,语义理解应该能处理这种差异吧?实际上向量检索虽然比关键词检索好很多,但口语和书面语的 Embedding 距离依然比人们直觉上以为的要远,尤其是在短文本场景下,信息量本来就少,表达差异对相似度的影响被放大了。

    查询优化就是在检索之前,先对用户的 query 做加工,让它在向量空间里离正确文档更近。主要有四种方法。

  • Query 改写是最基础的方法,用 LLM 把口语化、有歧义的 query 转化成更正式、更精准的书面表达。比如「它为什么这么贵」,这个「它」指代不明,LLM 结合对话历史把它改写成「iPhone 15 Pro Max 定价偏高的原因是什么」,改写之后的 query 就更容易命中文档里的相关内容了。

  • 多 Query 扩展(Multi-Query)解决的是另一个问题:用户的提问角度和文档的描述角度对不上。比如用户问「怎么退货」,文档里写的是「售后申请流程」,两种说法角度不同,向量相似度可能偏低。Multi-Query 的做法是用 LLM 把一个问题扩展成 3~5 个不同角度的问法,每种问法单独去检索,最后把结果合并去重。只要有一种问法和文档对上了,就能把正确内容召回来。可以用「撒网捕鱼」来理解:一个问题扩展成多个问法,就像多撒几条鱼线,只要有一条钓上来了就算成功。有一点需要注意:原始问题本身一定要保留在检索列表里,不能只用改写版本,因为改写过程中可能会丢失一些原始细节,原始问题反而最精准。

  • HyDE(Hypothetical Document Embeddings,假设文档嵌入)是一种更有创意的方法。正常情况下,我们用问题的向量去匹配文档的向量,但问题和文档本来就是两种文体,天然有距离。HyDE 的做法是:先让 LLM 根据问题生成一段「假设的答案」,然后用这段假设答案的向量去检索,而不是用原始问题的向量。假设答案和文档都是陈述性文字,风格更接近,向量距离也更近,命中率更高。需要注意的是,如果 LLM 生成的假设答案方向错了,反而会把检索带偏,所以一般在知识库领域比较明确的场景下效果更稳定。

  • Step-back Prompting(后退提问)解决的是「问题太具体,但知识库里只有通用背景」的情况。比如用户问「为什么 transformer attention 要除以 sqrt(d_k)」,知识库里可能没有这道题的直接答案,但有「attention 机制的数学原理」方面的内容。Step-back 就是先把具体问题往上抽象一层,生成一个更通用的背景问题去检索,把背景知识检索回来,再结合背景知识回答具体问题,两步走反而比直接查更准。

三路各自捞出一批候选,但它们的分数没法直接比较,向量相似度是 0~1 的余弦值,BM25 是 TF-IDF 分数,量纲完全不同。你可能会想,那归一化之后加权不就行了?实际上各路分数的分布差异很大,归一化效果不稳定,工程上也更复杂。

这时候需要一个统一的融合算法,RRF(Reciprocal Rank Fusion,倒数排名融合)是目前最常用的方案。

RRF 的思路很简单:不看原始分数,只看排名。

对每一路结果,排名第 1 的 chunk 贡献的分数最高,排名越靠后贡献越低。把同一个 chunk 在所有路径里的得分加起来,就是它的综合分。

这样,在多路检索里都排名靠前的 chunk,最终综合分就高。各路检索的分数没法直接比,就像百米赛跑和游泳比赛的成绩单位不同、没有可比性,但排名是通用的语言,综合各路排名来打分才公平。

公式是 score(chunk) = 求和(1 / (k + rank)),其中 k 是平滑参数,通常取 60。

这个 k 的作用是加一个「保底分」,不让排名靠后的候选因为偶尔失误就完全被淘汰,数值选 60 是实践中发现效果最稳定的经验值。

RRF 最大的优点是实现简单、不需要训练、计算量极小,工程落地成本几乎为零,但融合效果在大多数场景下都很好,是多路召回的标配融合方案。

第四层:重排序

经过前面三层:索引优化、查询优化、多路召回。

检索质量已经比朴素 RAG 好了很多,但还差最后一步。

多路召回之后,候选 chunk 可能有 20~30 个,这些 chunk 里难免混入一些不太相关的内容,直接全塞给 LLM 会出问题:一是 token 消耗暴涨,成本直线上升;二是上下文太长,LLM 在处理长文本时容易出现「Lost in the Middle」的现象,也就是只关注开头和结尾,中间的内容容易被忽略。

所以需要一个精排步骤,从 20~30 个候选里挑出最相关的 3~5 个,这就是 Rerank 的作用。

你可能会问,向量检索不是已经排过序了吗,为什么还需要再排一次?要理解 Rerank 为什么比向量检索更准,需要先理解两种不同的模型结构。

向量检索用的是 Bi-encoder 结构:query 和 chunk 各自独立编码成向量,再算余弦相似度。这个结构的优点是速度极快,因为 chunk 的向量可以提前计算好存库,检索时只要算一次 query 的向量然后做距离比较就行;缺点是 query 和 chunk 是分开编码的,模型没办法看到两段文字之间的具体词语关联,相关性判断不够精准。

Rerank 用的是 Cross-encoder 结构,把「query + chunk」拼成一对输入,让模型整体看这一对的相关性。Cross-encoder 能看到 query 中每个词对 chunk 的影响、chunk 里哪些词最能回答 query,相关性判断精度远高于 Bi-encoder。代价是每一个候选 chunk 都要单独跑一次 Cross-encoder,速度慢,所以只适合对小规模候选集做精排,不适合大规模召回阶段。

打个比方来理解两者的区别:Bi-encoder 像是只看了两个人的简历就判断他们合不合适合作,而 Cross-encoder 是把两个人放在一个房间里,观察他们怎么交流、怎么配合,判断当然更准确,但代价是需要花更多时间观察每一个人。

Rerank 的流程很清晰:多路召回得到 20~30 个候选 chunk,Cross-encoder Rerank 模型逐一给每个「(query, chunk)」对打相关度分,按分数降序排列后,取 top-3 到 top-5 拼入 prompt。

常用的开源 Rerank 模型有 BGE-Reranker-v2(BAAI 出品,中英双语效果都很好)、BCE-Reranker;不想自己部署的话,也可以用 Cohere Rerank 或 Jina Reranker 的 API。在实际效果上,加了 Rerank 之后最终答案质量通常有明显提升,是成本效益最高的优化手段之一。

四层优化怎么组合

一个典型的生产级搭配:Parent-Child 索引 + 向量 BM25 多路召回 + Rerank 精排。这三层组合基本能覆盖大多数场景的检索质量问题。如果用户提问质量比较差(口语化、指代不清),再额外加上 Query 改写。

从另一个角度来记这四层:索引层保证「存进去的知识可以被找到」,查询层保证「搜索的姿势是对的」,召回层保证「不漏掉该找到的内容」,Rerank 层保证「送进 LLM 的是真正有用的内容」 。每一层各司其职,组合起来才能把检索质量做到高水准。

相关推荐
Mr数据杨2 小时前
机载多光谱目标检测提升空中态势感知
人工智能·目标检测·机器学习·计算机视觉·数据分析·kaggle
郝学胜-神的一滴2 小时前
干货版《算法导论》 01:从问题定义到正确性证明
数据结构·人工智能·深度学习·神经网络·算法·机器学习
2301_796588502 小时前
Go语言如何压缩文件_Go语言gzip压缩教程【基础】
jvm·数据库·python
m0_617881422 小时前
c++如何通过重定向rdbuf来捕获第三方库的日志输出到文件【详解】
jvm·数据库·python
lizz6662 小时前
Hermes-Agent:钉钉dingtalk配置定时任务收集
人工智能·钉钉
MY_TEUCK2 小时前
【AI开发】从0到1写一个uni-app Vue3 小程序开发的Skill:用法、流程与踩坑复盘
人工智能·uni-app
ziuno2 小时前
01-语言模型+维特比
人工智能·语言模型·自然语言处理
Greyson12 小时前
mysql查询执行过程中如何追踪耗时_使用PROFILE分析指令周期
jvm·数据库·python
河阿里2 小时前
Java八股:面试高频50
java·面试