快速回顾
| 项目 | 内容 |
|---|---|
| 核心思路 | 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 在这里使用 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 会把问题和候选子块作为一对输入,让模型联合读取,进一步判断它们的相关性。
比如两段内容都讨论补贴,语义上都接近,但用户问的是申请材料,Reranker 可以更细地判断哪一段真正列出了材料要求。因为这种联合评分成本较高,我们只对融合后的有限候选使用。地区、权限、时间和版本仍然由硬条件过滤处理,相关性分数再高也不能让不适用的资料进入回答。
实现细节
双塔向量召回分别编码查询和子块,重排则联合输入二者。两者的计算方式与成本不同。RRF 改变候选池,Reranker 在候选池内排序,候选阶段漏掉的依据无法靠精排补回。
| 环节 | 依据 | 作用与边界 |
|---|---|---|
| RRF | 候选在各通道中的排名 | 合并不同通道,不读取正文判断具体条件 |
| BGE Reranker | 问题与候选文本联合输入 | 精细判断相关性,成本较高 |
| 硬条件过滤 | 权限、地区、时间和版本 | 确定证据是否适用,不能由相关性分数替代 |
实现与调参
面试回答
收到问题后,先确定用户可访问的知识库、地区和时间范围,把能够提前确定的过滤条件下推到两路检索。BM25 和 BGE-M3 并行召回子块,按子块 ID 对齐后做 RRF 融合,再交给 BGE Reranker 精排。
候选返回后,还会回查当前文档状态、版本和权限,防止索引更新滞后。确认可用后,按父块聚合、补充条款上下文,并按 token 预算装配证据,最终生成带 Evidence ID、文档和页码的回答。时间过滤根据用户问题确定,历史查询保留当时适用的版本,不能一律只取最新文件。
实现细节
检索前过滤减少无效候选占位,检索后回查处理状态变化和索引滞后。发布、生效与失效时间分别记录;当前查询与历史查询采用各自的适用时间,具体链路见步骤。
- 接收用户问题,确定知识库、权限和时间条件。
- BM25 与 BGE-M3 向量检索并行召回子块。
- 按子块 ID 对齐结果,进行 RRF 融合。
- BGE Reranker 对候选子块精排。
- 回查文档状态、版本和权限。
- 按父块聚合,补充上下文并控制 token 预算。
- 生成带 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 个父块 |
面试回答
我主要调平滑常数 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 的输入包含问题、文档标题、章节路径和子块正文。我用对应模型的 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。
指标口径与证据:待核实。