检索增强生成(RAG)已成为现代 LLM 应用的核心,但有个鲜为人知的秘密:检索阶段往往决定了整个系统的成败。如果你的检索器无法获取相关文档,生成模型会带着确信度产生幻觉,无论怎么优化 prompt 都无济于事。
作为拥有生产经验的后端工程师,你可能已经遇到过这个问题。你可能尝试过向量搜索来理解语义,关键词搜索来匹配精确词项,甚至两者结合使用。但将这些检索方法合并成统一、相关的结果集比看起来要难得多。
本文将深入探讨 Reciprocal Rank Fusion (RRF)——这是 Azure AI Search、Elasticsearch 和 OpenSearch 中驱动混合搜索的算法。我们将探讨它的工作原理、如何实现它,以及在何时使用更高级的重排序策略如 cross-encoder 和 ColBERT。
混合检索的挑战
两个世界:密集检索 vs 稀疏检索
在 RAG 系统中,你通常有两种检索范式:
密集检索(向量搜索):
- 使用 OpenAI 的
text-embedding-3-small或 sentence-transformers 等模型将文档和查询转换为嵌入 - 计算余弦相似度、点积或欧几里得距离
- 擅长理解语义含义和上下文
- 分数范围:余弦相似度为 0.333-1.00,其他指标为 0-1
- 弱点:错过精确关键词匹配和罕见词项
稀疏检索(关键词搜索/BM25):
- 使用传统倒排索引和 TF-IDF 评分
- BM25 算法:基于词频和文档频率评分
- 擅长精确关键词匹配和罕见词处理
- 分数范围:无上界(没有上限)
- 弱点:无法理解语义相似度
归一化问题
问题变得复杂的地方在于:当你运行包含向量搜索和 BM25 的混合查询时,你会得到两个结果集,它们的评分尺度完全不同:
1 | 向量搜索结果: |
如何合并这些?你不能简单相加——尺度完全不同。你可以将分数归一化到 [0,1],但归一化很脆弱:它假设分数遵循可预测的分布,但往往并非如此。
这是 RRF 解决的核心问题:不试图归一化原始分数,而是使用排序——排序结果列表中的位置。
Reciprocal Rank Fusion (RRF) - 核心算法
公式
RRF 出奇地简单:
1 | RRF(d) = Σ(r ∈ R) 1 / (k + r(d)) |
其中:
d是一个文档R是排序器的集合(检索器)k是一个常数(通常为 60)r(d)是文档d在排序器r中的排名
工作原理
让我们通过两个检索器的示例来演示:
1 | 检索器 1(向量搜索): |
当 k=60 时,RRF 如何计算文档 B 的分数:
- 在检索器 1 中的排名:2 → 1/(60+2) = 0.0161
- 在检索器 2 中的排名:1 → 1/(60+1) = 0.0164
- RRF 分数:0.0161 + 0.0164 = 0.0325
对于文档 C:
- 在检索器 1 中的排名:3 → 1/(60+3) = 0.0159
- 在检索器 2 中的排名:4 → 1/(60+4) = 0.0156
- RRF 分数:0.0159 + 0.0156 = 0.0315
文档 B 获胜,因为它在两个列表中的排名都更高(一个第 1,一个第 2),而文档 C 在两个列表中都更低(第 3 和第 4)。
为什么有效
倒数排序:较高的排名(较小的数字)贡献更多。排名第 1 比排名第 2 更有价值,排名 1 和 2 之间的差异大于排名 100 和 101 之间的差异。
收益递减:贡献非线性递减。这捕捉了排名 1 和 2 之间的相关性差距可能大于排名 100 和 101 之间的直观理解。
排序聚合:通过对所有检索器的倒数排名求和,RRF 结合了来自多个来源的证据,使得最终排序更加稳健。
k = 60 的奥秘
为什么大家都使用 k=60?这并非任意选择:
- 经验性能:研究表明 k=60 在各种数据集和检索任务中表现良好
- 平衡影响:它为高排名和低排名项目之间提供了良好的平衡:
- 排名 1:1/(1+60) ≈ 0.0164
- 排名 10:1/(10+60) ≈ 0.0143
- 排名 100:1/(100+60) ≈ 0.00625
- 有效打破平局:有助于打破平局,尤其是对于排名较低的项,其中小的排名差异可能没有意义
- 鲁棒性:在不同检索系统和数据分布上证明是鲁棒的
虽然 k=60 很常见,但有些系统会针对其特定用例调整此参数。
Python 实现
这是一个干净、生产就绪的实现:
1 | from typing import List, Dict, Tuple |
加权 RRF - 精细调优检索影响
等权重的问题
标准 RRF 对所有检索器一视同仁。但在生产环境中,你经常需要优先考虑某些检索信号:
考虑餐厅搜索:
- 查询:”pizza near me” → 位置应该占主导地位
- 查询:”serve cacio e pepe 的意大利餐厅” → 菜系和菜单匹配应该占主导地位
- 查询:”高评分的意大利餐厅” → 评分应该占主导地位
加权 RRF 公式
加权 RRF 扩展了标准公式:
1 | Weighted_RRF(d) = Σ(r ∈ R) weight_r × 1 / (k + r(d)) |
每个检索器现在都有一个权重参数,可以影响其贡献。
实际示例
1 | def calculate_weighted_rrf( |
何时使用加权 RRF
- 当你对不同查询类型有明确的信号优先级时
- 当你需要精细调优检索而不使用自定义评分函数时
- 当你构建特定领域搜索时(例如法律、医疗、电子商务)
高级重排序策略
RRF 功能强大,但不是唯一的选择。对于生产系统,你通常会结合 RRF 和更复杂的重排序策略。
Cross-Encoder 重排序
Cross-encoder 接受查询-文档对并输出相关性分数。它们本质上是基于相关性判断训练的 transformer 模型:
优点:
- 极高的精确度
- 可以对复杂的查询-文档交互建模
- 在许多任务中达到最先进的准确性
缺点:
- 计算昂贵:n 个候选需要 O(n) 次前向传播
- 不适合大规模检索(用于重排序,而非初始检索)
典型流程:
- 使用 BM25 或向量搜索检索前 100 个候选
- 使用 cross-encoder 对前 20 个候选进行重排序
- 返回最终的 top 10
ColBERT(Late Interaction)
ColBERT 是速度和准确性之间的折衷:
- 分别编码查询和文档(像向量搜索一样)
- 高效计算 token 级别的相似度分数
- 使用”文档 token 的最大值”来找到最佳局部匹配
优点:
- 比 cross-encoder 更高效(没有完整的交叉注意力)
- 比标准向量搜索具有更高的准确性
- 良好的生产平衡
缺点:
- 仍然比简单的向量搜索更昂贵
- 需要专门的基础设施
Learning to Rank(LTR)
基于相关性判断训练的机器学习模型:
1 | # 伪代码:使用 XGBoost 的 Learning to Rank |
优点:
- 可以结合任意特征
- 高度可定制以适应你的用例
- 在大规模生产中已验证
缺点:
- 需要训练数据(相关性判断)
- 维护更复杂
决策矩阵
| 场景 | 推荐策略 |
|---|---|
| 生产系统,低延迟 | 标准 RRF |
| 需要精细控制 | 加权 RRF |
| 精确度是关键 | RRF + Cross-encoder 重排序 |
| 准确性和效率的平衡 | RRF + ColBERT |
| 有相关性训练数据 | RRF + Learning to Rank |
生产环境考虑
性能特征
| 策略 | 延迟 | 准确性 | 复杂度 |
|---|---|---|---|
| 仅 BM25 | ~10ms | 中等 | 低 |
| 仅向量 | ~50ms | 高 | 中等 |
| RRF(BM25 + 向量) | ~60ms | 高 | 中等 |
| RRF + Cross-encoder | ~200ms+ | 非常高 | 高 |
| RRF + ColBERT | ~150ms | 非常高 | 高 |
缓存策略
重排序缓存:为频繁的查询-文档对缓存 cross-encoder 结果:1
2
3
4
5from functools import lru_cache
def cached_cross_encoder_rerank(query: str, doc: str) -> float:
return cross_encoder_model.predict(query, doc)
嵌入缓存:缓存文档嵌入以避免重新计算。
监控指标
在生产环境中跟踪这些指标:
- 延迟:检索 + 重排序的 p50、p95、p99
- 准确性:NDCG@10、MRR、Precision@k
- 覆盖率:重排序改进结果的查询百分比
- 缓存命中率:对于嵌入和重排序缓存
常见陷阱
忽略查询类型:不要对所有查询应用相同的排序策略。位置查询的处理方式与内容查询不同。
过度调优 k:k=60 的 RRF 在许多用例中是鲁棒的。不要过度拟合到狭窄的数据集。
忘记冷启动:没有嵌入或 BM25 统计信息的新文档可能会被遗漏。构建适当的索引管道。
忽视评估:使用离线指标(NDCG)和在线指标(点击率、停留时间)。
实现最佳实践
选择正确的策略
从 RRF 开始。只有在你测量到问题时才增加复杂性:
1 | 1. 基线:仅 BM25 |
A/B 测试框架
1 | # 伪代码:A/B 测试排序策略 |
渐进式发布
- 影子模式:与现有策略一起运行新策略,不服务结果
- 10% 流量:逐渐转移流量,监控指标
- 50% 流量:如果指标稳定,增加流量
- 100% 流量:完整发布并监控
衡量改进
NDCG(归一化折损累积增益):
- 考虑相关性等级和位置
- 越高越好(0-1)
MRR(平均倒数排名):
- 关注第一个相关结果的位置
- 第一个相关结果的排名的倒数
Precision@k:
- top-k 结果中相关的分数
- 简单且可解释
结论
Reciprocal Rank Fusion 是一个看似简单但解决了一个难题的算法:将具有不同评分尺度的多种检索方法合并成统一、相关的结果集。
关键要点:
- RRF 使用排序,而非分数:这完全绕过了归一化问题
- 从简单开始:k=60 的 RRF 在许多用例中是鲁棒的
- 仅在需要时增加复杂性:加权 RRF、cross-encoder 和 ColBERT 各有其所用
- 衡量一切:使用离线指标(NDCG、MRR)和在线指标(点击、停留时间)
- 迭代渐进:A/B 测试新策略并渐进式发布
对于生产 RAG 系统,RRF 应该是你的默认选择。为特定领域需求添加加权 RRF,并在精确度关键时考虑 cross-encoder 重排序。
随着检索技术的不断发展,我们看到了越来越复杂 late-interaction 模型和神经重排序器。但 RRF 的基本见解——通过排序聚合结合来自多个来源的证据——仍然与以往一样相关。