BM25 与 BGE-M3 混合检索、RRF 融合及重排

快速回顾

项目 内容
核心思路 BM25 精确匹配 + BGE-M3 语义召回 → RRF 融合 → Reranker 精排
初始召回配置 BM25 Top 50 + 向量 Top 50 → RRF 取 50 → 重排取 10 个子块
RRF 初始参数 平滑常数 k=60,BM25 与向量权重 1:1
RRF 调参范围 k 比较 20、60、100;权重比较 1:1、1.5:1、1:1.5
输入预算参考 Reranker 1024 tokens,受所选模型长度上限约束
测评集与结果 200 余道题;Recall@10:0.71 → 0.89;MRR@10:0.62 → 0.78

面试主回答

我们采用混合检索,是因为养老政策查询同时存在精确术语和口语化表达。BM25 负责政策名称、条款编号等关键词匹配,BGE-M3 负责补充语义召回,两路并行检索子块。两路分数不在同一尺度,所以用 RRF 按排名融合,避免直接相加造成某一路占优势。融合后再用 BGE Reranker 对问题与候选正文联合评分,把直接相关的内容排到前面。

最后检查文档权限、有效期和版本,再回溯父块补全条款上下文。我们用 200 余道题的离线测评集评估整套改动,Recall@10 从 0.71 提升到 0.89,MRR@10 从 0.62 提升到 0.78,分别观察证据覆盖和首条正确证据的排序情况。

原理与选型

为什么要混合检索?

面试回答

养老资料查询有两类表达需要同时照顾。一类包含明确的政策名称、条款编号或专业术语,BM25 的关键词匹配比较合适;另一类是用户用日常语言描述需求,和文档原文用词差异很大,需要 BGE-M3 的语义检索补充。

比如用户问“老人自己吃饭穿衣都困难,能申请什么帮助”,文档可能写的是生活自理能力、失能等级和照护补贴,纯关键词检索容易漏掉。反过来,纯向量检索也可能找到主题相近但条件不符的内容。所以我让两路并行召回,再用 RRF 融合和 Reranker 筛选,扩大证据覆盖后进一步判断相关性。

实现细节

“养老机构等级评定的申请材料”这类含明确术语的查询适合检查 BM25;口语化需求适合检查语义召回。两路候选都需要经过地区、时间、权限等适用性检查,主题相关不等于能够回答。

检索方式 擅长处理 容易出现的问题
BM25 政策名称、条款编号、机构名称、明确术语 用户与原文用词差异大时可能漏召回
BGE-M3 向量检索 同义表达、自然语言描述、语义相近问题 可能召回主题相近、具体条件却不匹配的内容

BM25 和 BGE-M3 分别怎么工作?

面试回答

BM25 是基于倒排索引的关键词相关性计算,主要看查询词出现的频率、词的区分度和文档长度。少见且准确命中的词通常更有价值,同一个词重复出现带来的加分会逐渐饱和,避免长文档单纯靠重复词占优势。

BGE-M3 在这里使用 dense 向量能力。入库时把子块编码成向量,查询时把用户问题用兼容的模型和编码方式生成向量,再交给 Milvus 查找相近结果。BM25 更直接利用词面匹配,BGE-M3 补充不同表达之间的语义关联,两路返回后按同一子块 ID 进行融合。

实现细节

BGE-M3 支持多种检索形式,本项目这一路使用 dense,关键词召回仍由 BM25 完成。查询与文档必须保持模型版本和编码配置兼容,否则向量距离无法按原有口径比较。

面试回答

BM25 分数和向量相似度的含义、数值范围都不同,直接相加会让数值范围更大的通道主导结果。我使用 RRF,把各路结果转换成排名贡献,再按稳定的子块 ID 合并,减少对原始分数尺度的依赖。

一个子块如果在两路里都排名靠前,就会得到两路加分;只被一路召回的候选也能保留。它的局限是看排名时会丢掉原始分数差距,无法区分第一名比第二名强很多还是只强一点。因此 RRF 用于融合候选,后面继续用 Reranker 联合读取问题和正文,做更细的相关性判断。

实现细节

计算使用 RRF(d) = Σᵢ wᵢ / (k + rankᵢ(d))。排名从 1 开始,wᵢ 是通道权重,未出现在某一路中的候选不获得该路分数。k 是平滑常数,与召回 Top-K 数量无关。同一子块分别排第 2、第 4 时,融合分数为两路贡献之和。

已经做了 RRF,为什么还要 BGE Reranker?

面试回答

RRF 依据候选在两路中的排名来融合,不能直接判断正文是否完整回答了问题。BGE Reranker 会把问题和候选子块作为一对输入,让模型联合读取,进一步判断它们的相关性。

比如两段内容都讨论补贴,语义上都接近,但用户问的是申请材料,Reranker 可以更细地判断哪一段真正列出了材料要求。因为这种联合评分成本较高,我们只对融合后的有限候选使用。地区、权限、时间和版本仍然由硬条件过滤处理,相关性分数再高也不能让不适用的资料进入回答。

实现细节

双塔向量召回分别编码查询和子块,重排则联合输入二者。两者的计算方式与成本不同。RRF 改变候选池,Reranker 在候选池内排序,候选阶段漏掉的依据无法靠精排补回。

环节 依据 作用与边界
RRF 候选在各通道中的排名 合并不同通道,不读取正文判断具体条件
BGE Reranker 问题与候选文本联合输入 精细判断相关性,成本较高
硬条件过滤 权限、地区、时间和版本 确定证据是否适用,不能由相关性分数替代

实现与调参

面试回答

收到问题后,先确定用户可访问的知识库、地区和时间范围,把能够提前确定的过滤条件下推到两路检索。BM25 和 BGE-M3 并行召回子块,按子块 ID 对齐后做 RRF 融合,再交给 BGE Reranker 精排。

候选返回后,还会回查当前文档状态、版本和权限,防止索引更新滞后。确认可用后,按父块聚合、补充条款上下文,并按 token 预算装配证据,最终生成带 Evidence ID、文档和页码的回答。时间过滤根据用户问题确定,历史查询保留当时适用的版本,不能一律只取最新文件。

实现细节

检索前过滤减少无效候选占位,检索后回查处理状态变化和索引滞后。发布、生效与失效时间分别记录;当前查询与历史查询采用各自的适用时间,具体链路见步骤。

  1. 接收用户问题,确定知识库、权限和时间条件。
  2. BM25 与 BGE-M3 向量检索并行召回子块。
  3. 按子块 ID 对齐结果,进行 RRF 融合。
  4. BGE Reranker 对候选子块精排。
  5. 回查文档状态、版本和权限。
  6. 按父块聚合,补充上下文并控制 token 预算。
  7. 生成带 Evidence ID、文档和页码的回答。

两路各召回多少条?

面试回答

我们的初始配置是 BM25 和向量检索各取 Top 50,按子块 ID 去重后最多得到 100 个候选,RRF 融合后取前 50 个交给 Reranker,再保留前 10 个子块。之后回溯父块、去重,并根据上下文预算决定真正装入多少证据。

前面保留较多候选,是给后续融合和精排留筛选空间。Top 20 可能过早漏掉正确证据,继续扩大又会增加重排耗时,所以比较 20、50、100 等配置下的候选覆盖率、最终 Recall@10、MRR@10 和延迟。最终保留 10 个子块也是效果与成本的配置选择,父块去重后数量可能更少,并不等于给模型塞 10 个父块。

实现细节

表中记录初始配置,不把某个 Top-K 当作所有数据集通用的最优值。两路召回名额可根据有效候选分布分别调整。子块排序指标在父块扩展前统计,扩展后的条件覆盖另行检查。

阶段 初始配置数量 输入与输出
BM25 Top 50 查询 → 关键词候选子块
BGE-M3 / Milvus Top 50 查询向量 → 语义候选子块
ID 对齐与 RRF 去重后最多 100,取前 50 两路候选 → 重排候选
BGE Reranker 保留前 10 个子块 问题与候选正文 → 精排结果
父块回溯 由去重和 token 预算决定 10 个子块不等于 10 个父块

RRF 参数怎么调?

面试回答

我主要调平滑常数 k 和两路权重,初始采用 k 等于 60、BM25 与向量权重 1 比 1。先固定召回和重排配置,比较 k 为 20、60、100 时的效果;k 小时更强调头部名次,k 大时头部与后部排名贡献的差距会缩小。

然后根据错误分析调整权重。精确名称、编号类问题不足时,比较偏向 BM25 的配置;口语化问题漏召回较多时,比较偏向向量检索的配置。每次少量改变参数,同时看重排前的证据覆盖和重排后的 Recall@10、MRR@10,避免只看最终排序,忽略候选池已经发生的变化。

实现细节

权重比较范围包括 1:1、1.5:1、1:1.5。在调参集上选择配置,最终结果使用未参与调参的题目。RRF 的平滑常数不控制召回数量,不能将 k=60 理解为召回 60 条。

参数 初始配置 调参比较 主要观察
平滑常数 k 60 20、60、100 头部与后部排名贡献的差距
BM25 : 向量权重 1:1 1:1、1.5:1、1:1.5 精确术语与口语表达的覆盖
候选与最终结果 配置分别记录 分开调参,固定其他条件 重排前覆盖率、最终 Recall@10 与 MRR@10

Reranker 输入如何截断?

面试回答

Reranker 的输入包含问题、文档标题、章节路径和子块正文。我用对应模型的 tokenizer 计算总长度,先给问题和标题预留空间,再分配正文预算。因为重排对象是子块,大部分输入本身较短,通常不需要直接处理整个父块。

遇到超长表格或片段时,按段落、句子或表格结构拆成多个窗口,每个窗口保留必要标题、表头,分别评分,再用最高分代表该候选的相关性。这样避免统一截取开头,把后面的关键条款删掉。原文和页码映射始终保留,后续生成时仍按需要读取原始证据。

实现细节

1024 tokens 可以作为工程输入预算,但必须以所选 Reranker 的实际支持长度为前提,不能视为所有 BGE Reranker 的统一上限。正文预算扣除问题、标题和模型输入所需空间后计算,不能按字符数直接截断。

某一路故障怎么降级?

面试回答

两路检索设置独立超时,某一路失败时尽量保留另一路的有效结果。BM25 不可用就使用向量候选,向量编码或 Milvus 不可用就使用 BM25 候选;如果 Reranker 失败,就用 RRF 排序结果兜底,并记录实际走了哪条链路。

降级后仍要检查证据是否足够,必要时补查、澄清或说明缺少依据。正常检索无命中和通道故障分开处理,前者可以改写查询,后者做有限重试和降级。权限、版本校验属于证据放行条件,这些检查不可用时不能跳过,否则可用性恢复了,访问范围和答案适用性却失去保证。

实现细节

记录通道错误、耗时和实际使用链路,便于判断质量变化来自模型还是降级。重试受总超时预算约束,不能反复等待单路服务拖垮整个请求。

情况 可用路径 限制
BM25 故障 使用向量候选继续处理 记录通道降级
向量编码或 Milvus 故障 使用 BM25 候选继续处理 记录通道降级
Reranker 故障 使用 RRF 排序兜底 检查证据是否充分
正常返回但无命中 改写查询或澄清条件 区别于通道故障
权限或版本校验不可用 不放行未经校验的证据 不能为可用性跳过访问检查

评测与结果

评测集怎么构建?

面试回答

我们围绕养老政策、机构制度和服务资料构建了 200 余道题,覆盖精确条款查询、口语化表达、多条件查询、时间版本和政策对比,也加入无答案、条件不足的题目。每道题记录问题、参考答案、文档版本、原文证据位置,以及回答必须覆盖的条件。

标注时区分“主题相关”和“足以回答”,不能把所有提到补贴的片段都算正确证据。检索评测检查原文依据有没有被召回,回答评测再看条件和引用是否完整。调参题和最终评测题分开,近似改写放在同一组,避免泄漏;无答案题单独检查证据不足与澄清行为,不混进普通 Recall 的分母。

实现细节

标准证据标到原文条款或范围,方便不同分块方案映射。题型分别承担定位词匹配、语义匹配、条件完整性和版本适用性问题的作用,不能只报告总分而不检查错误类型。

测评基线具体是什么?

面试回答

我把整体效果比较和组件对照分开。整体比较对应原始系统与最终系统,简历里的 Recall@10 从 0.71 到 0.89、MRR@10 从 0.62 到 0.78,反映的是整套改进的收益。

为了判断各环节的作用,另外固定文档、父子分块、过滤规则和测评集,比较 BGE-M3 单路、BM25 单路、两路加 RRF,以及在混合检索上增加 Reranker。这样能分别观察单路特点、混合召回和精排的贡献。分块方式再单独做实验,每次保存模型版本、召回数量、RRF 参数和重排配置,避免同时改动多个环节后无法解释效果来源。

实现细节

原始系统的完整检索、分块与重排配置需要和结果一起保存在实验记录中,不能仅凭两项旧指标反推它等于某个单路组。组件对照使用固定分块,父子分块收益使用另一组控制变量实验。

组别 检索配置 比较目的
A BGE-M3 单路向量检索 观察语义单路表现
B BM25 单路检索 观察关键词单路表现
C BM25 + 向量 + RRF 观察两路互补与融合收益
D C + Reranker 观察精排收益
原始系统 → 最终系统 固定评测口径,另存完整配置 Recall@10:0.71 → 0.89;MRR@10:0.62 → 0.78,为整体收益

易混点与回答检查

易混点:RRF 是排名融合,Reranker 是模型精排;RRF 的 k 与召回 Top-K 含义不同。

回答检查

补充记录

简历表述:融合 BM25 与向量检索,经 RRF 合并候选、BGE Reranker 重排,并按知识库范围和用户权限过滤;Recall@10 从 0.71 提升至 0.89,MRR@10 从 0.62 提升至 0.78。

指标口径与证据:待核实。

复盘手记

搜索笔记

输入关键词,搜索所有主题与问答。