混合检索 + RRF + Rerank 实战评测:三条路线在 47 个真实问题上的取舍

混合检索 + RRF + Rerank 实战评测:三条路线在 47 个真实问题上的取舍

本文所有数字来自我自研的 RAG 平台 DocMind 的真实评测,评测脚本开源可复现(scripts/bench_report.py),不存在"编一个提升 30%"的修辞。TL;DR:纯向量在"规范提问"上已经满分,真正的分水岭在口语化/中英混杂的"困难问题"上;RRF 把困难集 Recall 从 94.1% 拉到 100%,Rerank 再把排序质量 MRR 从 0.873 提到 0.941------但代价是 Recall 掉了一条边。这个"掉"不是缺陷,而是阈值语义的必然结果,下文细说。

一、为什么要做路线对比:RAG 检索最怕"感觉还行"

大量 RAG 项目的检索模块停留在"调通了"的状态:接一个 embedding API,Chroma 里一查,返回 4 条,看起来像那么回事。问题在于没有任何度量------你不知道换一条路线会好多少,更不知道哪些 query 在被漏掉。

我做 DocMind 时的原则是:检索任何改动必须有评测集数字背书。于是先固定了一个 47 样本的评测集,分两组:

  • 基础集(30 条):规范问法,比如"什么是 RAG?""Temperature 参数有什么作用?"
  • 困难集(17 条):口语化、中英混杂、缩写、俚语,比如"知识库检索不准怎么办?"(期望命中概念文档,但问法与文档措辞差得很远)

评测口径两个:Recall@4 (前 4 条里有没有正确文档------决定"能不能答")和 MRR(正确文档排多靠前------决定"喂给模型的上下文质量")。

47 条样本量不大,但覆盖了我业务场景下的主要 query 模式;结论适用于同类 RAG 场景,不建议直接外推到通用搜索领域

二、三条路线

路线 检索流程
纯向量 query → embedding → 余弦相似度 top-k
混合 RRF BM25 关键词路 + 向量语义路并行召回 → RRF 按排名融合
混合 + Rerank 混合 RRF 出候选(≤10 条)→ gte-rerank 精排 → 阈值过滤

注:混合+Rerank 的 Recall 下降并非 Rerank 本身导致,而是后续阈值过滤砍掉了边缘相关候选(详见第 3 节)。

三路都走同一条评测脚本,全链路真实调用(embedding API + BM25 + rerank API),结果如下:

检索路线 基础集 Recall@4 基础集 MRR 困难集 Recall@4 困难集 MRR
纯向量 100.0% 0.978 94.1% 0.843
混合 RRF 100.0% 1.000 100.0% 0.873
混合 + Rerank 100.0% 1.000 94.1% 0.941

三、三个值得展开的发现

1. 基础集三条全满分------区分度全在困难集

规范问法下,现代 embedding 模型基本不会失手。这意味着用"正常问题"做的评测没有区分度,你永远看不出改动的收益。真正的分水岭在困难集:用户不会按你文档的措辞提问。

2. RRF 把困难集 Recall 从 94.1% 拉到 100%

困难集里大量是"钓鱼链接是什么意思"这类带黑话/缩写的问题。纯向量对这类词的语义泛化是弱项(embedding 空间里"钓鱼"和"phishing 链接"不一定近),而 BM25 的字面匹配正好补位。

关键在融合方式。BM25 分数无上界(跟文档长度、词频相关),余弦相似度是 0~1,直接加权求和等于让 BM25 的量纲统治一切。RRF(Reciprocal Rank Fusion)按排名融合,每条路的贡献只取决于"排第几"而不是"分多高":

python 复制代码
def rrf_merge(vec_hits, bm25_hits, k=60):
    """每路的贡献 = 1/(k + rank),天然免疫两路分数量纲差异"""
    rrf = {}
    for rank, hit in enumerate(vec_hits, 1):
        rrf[hit.id] = rrf.get(hit.id, 0.0) + 1.0 / (k + rank)
    for rank, (idx, _) in enumerate(bm25_hits, 1):
        rrf[idx] = rrf.get(idx, 0.0) + 1.0 / (k + rank)
    return sorted(rrf.items(), key=lambda kv: kv[1], reverse=True)

k=60 是 Cormack 等人验证过的稳健选择;在我的业务场景下未观察到需要调整的迹象,但不同召回深度的场景可能需要实验。。(上面是节选示意------仓库实现还含切片索引对齐与 ACL 过滤。)RRF 的代价是丢失分数的绝对语义(融合分只是排名的调和,说不出"这条到底多相关")------这个坑下面 Rerank 一节正好接住。

3. Rerank:MRR 0.873 → 0.941,但 Recall 掉了一条

Rerank 模型(cross-encoder)把 query 和文档拼在一起过一遍 transformer,相关性判断远比双塔的独立编码准。它把正确答案排得更靠前(MRR 0.873 → 0.941),这是预期收益。

但注意困难集 Recall 从 100% 掉回了 94.1%------一条边缘相关的候选被阈值过滤砍掉了。我的过滤规则是"绝对下限 + 相对头部":

python 复制代码
def filter_reranked(ranked, abs_floor=0.05, rel_ratio=0.15, min_top=0.08):
    """头部候选过低 → 整体无关(触发拒答链);
    其余候选跟头部比,而不是跟固定线比"""
    if not ranked or ranked[0].score < min_top:
        return []
    floor = max(abs_floor, ranked[0].score * rel_ratio)
    return [h for h in ranked if h.score >= floor]

为什么不用固定阈值?因为 relevance_score 的真实分布在 0.1~0.9 全区间都有,固定线会随语料漂移失准。(签名是示意------仓库实现走 config 常量。)为什么明知道会砍掉边缘命中还要砍?因为下游是证据拒答:如果检索放进来一条弱相关片段,模型会基于它编一个似是而非的答案------这比"承认库里没有"危害大得多。评测上掉的那条,和线上"宁可拒答不可胡说"是同一套哲学在两个层面的投影。

这个取舍是可以调的:把 rel_ratio 降到 0.10,那条就回来了,但弱相关片段也会混进来。我的选择是用评测集固定住这组参数,任何改动跑回归。

四、两个工程细节,比数字更值钱

多库统一精排 。多知识库场景下,如果每个库各自 rerank 再合并,N 个库就是 N 次 rerank API 调用。做法是:各库只做双路召回 → 合并去重 → 全局只 rerank 一次。省 API 钱,而且跨库分数在同一把尺子上可比。

降级链 。rerank 是外部 API,会挂。失败时自动降级返回 RRF 结果(保证可用性),连续失败 3 次进熔断冷却 60 秒,避免每个请求都吃 8 秒超时。增强类组件的故障永远不允许阻断主链路------这是我在设计 RAG 系统时的底线之一。

五、性能账

三条路线全链路(embedding + BM25 + RRF + rerank)单机压测,QPS 在 2.2 封顶,瓶颈不在服务端而在上游 LLM API(每请求 1 次 embedding + 1 次 rerank 远程调用)。优化路径按 ROI 排序是:语义缓存 → 查询向量缓存 → rerank 结果缓存。后两者我后来做了,同一套代码优化前后对比:热点查询吞吐从 QPS 3.1 提到 801(重复问题直接命中确定性缓存),冷查询零退化------这是另一篇的内容,这里只说明:检索路线的"效果"和"成本"要放在一起谈,rerank 每条 query 都多一次 API 调用,值不值要看评测收益(困难集 MRR +7.8%)和你的预算。

六、复现

bash 复制代码
python scripts/bench_report.py   # 47 样本三路线评测,输出全部表格数字

评测集、加噪逻辑、阈值参数全部在仓库里。最后说一句:这套对比做下来最大的收获不是"混合检索更好"这个结论------这个结论文献里早有------而是每个设计决策都有了可复现的数字锚点。后来每一次改切片参数、换 embedding 模型、动阈值,都是改完跑一遍,数字说话。

仓库与评测脚本github.com/Ice-Breakin...scripts/bench_report.py 可复现本文全部表格)

系列文章 :二、LoRA 查询改写器 A/B · 三、确定性分层缓存 · 四、模型路由的诚实成本账


评测环境:macOS (Apple M4),text-embedding-v3 + gte-rerank-v2(百炼),BM25 用 rank_bm25,中文分词 jieba。评测集 47 样本(基础 30 + 困难 17)。

相关推荐
一点一木2 小时前
憋了7周没动静,OpenClaw 2.0带着16000个PR杀回来了
人工智能·github
一点一木6 小时前
🚀 2026 年 8 月 GitHub 十大热门项目排行榜 🔥
人工智能·github
怕浪猫17 小时前
FDE 最大的浪费不是写出有 bug 的代码,而是漂亮地解决了一个错误的问题
面试·架构·github
黄敬峰20 小时前
Next.js 全栈实战:数据清洗、ORM 设计与 AI Prompt 工程最佳实践
面试·github
峰向AI1 天前
AI reads books:用 AI 逐页「啃」PDF 的知识提取器,一个脚本搞定一切
github
Experience-摆渡1 天前
GitHub 47万星免费公共API清单深度调研:收录规模、维护现状与实测
github
逛逛GitHub1 天前
GitHub 上 2.5 万星星的开源 Skill 让 AI 画出漂亮图表。
github
Cosolar1 天前
一文弄懂 Agent Harness 与 Agent Runtime 的区别
java·后端·github
YH55269841 天前
如何减少大模型的token消耗?日常使用,可以通过什么方式减少大模型 token 的消耗
gpt·chatgpt·github