Skip to content

02.5 - 检索与重排序

定位: RAG Pipeline 的核心执行环节——从向量数据库中找出最相关文档,然后精排 面试高频度: ⭐⭐⭐⭐⭐ 考查方式: 检索策略、相似度计算、混合检索、重排序原理

一、这是什么?为什么需要它?

是什么

检索与重排序是 RAG 系统中从"查询"到"可用的上下文"之间的关键步骤:

查询 -> [第一步:粗检索] -> Top-20/50 候选 -> [第二步:重排序] -> Top-3/5 最终结果 -> 生成

检索(Retrieval):从向量数据库中快速找到候选文档块 重排序(Reranking):用更强的模型对候选块精确排序

为什么需要两步?

为什么不能一步到位?

向量检索(ANN):速度快但精度有限
  Embedding 模型将整个句子压缩成一个向量 -> 可能丢失细节
  适合初筛:从百万中找到几十个候选(秒级)

重排序(Reranker):精度高但速度慢
  Cross-Encoder 同时处理查询+文档 -> 理解更精确
  适合精排:从几十个中找到最合适的几个(毫秒级/每个)

组合:兼顾效率与精度
  ANN 粗筛(召回率高) -> Reranker 精排(精确率高)

二、原理拆解

2.1 相似度计算方法

方法公式范围特点
余弦相似度cos(A,B) = A*B/(A*
点积A * B(-inf, +inf)未归一化时受向量长度影响
L2 距离A-B
python
import numpy as np

def cosine_similarity(a, b):
    """余弦相似度:最常用的文本相似度计算"""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

def dot_product(a, b):
    """点积:如果向量已归一化,等价于余弦"""
    return np.dot(a, b)

def l2_distance(a, b):
    """L2欧氏距离:越小越相似"""
    return np.linalg.norm(a - b)

经验:使用余弦相似度或点积(如果向量已归一化)。FAISS 默认用 L2,但对归一化向量效果等同。

向量检索的局限性:

纯向量检索的盲区:

查询:"iPhone 15 价格"
  向量检索 -> 理解"iPhone 15"是手机,"价格"是cost -> 可能有不错结果
  但:
  查询:"ABC-123-XYZ"(产品编号)
  向量检索 -> 无法理解这个编号,语义为空 -> 结果很差

解决方案:混合检索(向量 + 关键词)
  向量检索:负责语义理解和同义词匹配
  关键词检索(BM25):负责精确匹配专有名词和编号
  融合:加权合并两个结果集
混合检索流程:
                    +--------------+
                    |   用户查询    |
                    +------+-------+
                           |
                +----------+----------+
                |                     |
                v                     v
        +-------------+      +-------------+
        | 向量检索     |      | BM25 关键词  |
        | (语义相似)   |      | (精确匹配)   |
        +------+------+      +------+------+
               |                     |
               v                     v
        +-------------+      +-------------+
        | 候选集 A     |      | 候选集 B     |
        +------+------+      +------+------+
               |                     |
                +-------+-----------+
                        |
                        v
                +------------------+
                | 融合排序 (RRF)    |
                +--------+---------+
                         |
                         v
                +------------------+
                | 最终 Top-K 候选   |
                +------------------+

融合算法 RRF(Reciprocal Rank Fusion)

python
def rrf_fusion(vector_results, keyword_results, k=60):
    """RRF 融合:排名倒数加权"""
    scores = {}
    for rank, doc in enumerate(vector_results):
        doc_id = doc.id
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    for rank, doc in enumerate(keyword_results):
        doc_id = doc.id
        scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
    return sorted(scores.items(), key=lambda x: x[1], reverse=True)

2.3 重排序(Reranking)

为什么需要重排序:

向量检索用 Bi-Encoder:
  查询和文档分别编码 -> 两个独立向量 -> 余弦相似度
  优点:文档向量可预计算(快!)
  缺点:查询和文档的交互信息丢失

重排序用 Cross-Encoder:
  查询和文档拼接在一起编码 -> 直接输出相似度分数
  优点:查询和文档充分交互(精确!)
  缺点:不能预计算(慢),需要逐对计算

主流 Reranker 模型

模型语言特点
BAAI/bge-reranker-v2-m3多语言中文最优,推荐
BAAI/bge-reranker-large英文/中文性能均衡
Cohere rerank多语言 (API)全托管,简单
Jina Reranker多语言支持长文本

2.4 完整的检索流水线

+----------------------------------------------------------------+
|                    RAG 检索流水线                                 |
|                                                                 |
|  查询: "公司年假有多少天"                                         |
|                                                                 |
|  1. 查询预处理                                                  |
|     - 查询重写(HyDE / Multi-Query)                              |
|     - 生成多个查询角度                                           |
|                                                                 |
|  2. 多路检索                                                    |
|     - 向量检索: Top-30(语义相似)                                 |
|     - BM25: Top-30(关键词精确匹配)                               |
|     - 合并: 60个候选                                              |
|                                                                 |
|  3. 去重与过滤                                                  |
|     - 去除内容重复的文档块                                        |
|     - 按 metadata 过滤(权限/分类/时间)                           |
|                                                                 |
|  4. 重排序                                                      |
|     - Cross-Encoder 逐对计算 (查询, 文档) 分数                     |
|     - 按分数降序排列                                              |
|     - 取 Top-3 作为 LLM 上下文                                    |
|                                                                 |
|  5. 上下文组装                                                  |
|     [块3: "第3章 年假规定..."] [块1: "年假申请流程..."]             |
|                                                                 |
|  LLM 生成最终回答                                                |
+----------------------------------------------------------------+

三、图解全景

检索策略选择决策树

你的检索需求是什么?
  |
  +-- 需要语义理解(同义词匹配)?
  |     -> 必须用向量检索
  |
  +-- 需要精确匹配(专有名词/编号)?
  |     -> 必须用 BM25
  |
  +-- 两者都需要?
  |     -> 混合检索(Hybrid Search)
  |
  +-- 需要最高质量?
  |     -> 混合检索 + 重排序
  |
  +-- 延迟敏感?
  |     -> 仅向量检索(最快)
  |
  +-- 召回优先?
        -> 多查询 + 混合检索 + 重排序

检索质量 - 延迟权衡

检索策略                         质量    延迟
仅关键词 (BM25)                  **     1ms
仅向量检索                       ***    10ms
混合检索 (向量+关键词)           ****   20ms
混合检索 + 重排序                *****  80-200ms
多查询 + 混合检索 + 重排序       ****** 300ms-1s

四、实战验证

4.1 混合检索 + 重排序完整代码

python
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever

# 1. 创建向量检索器
vector_retriever = vectorstore.as_retriever(
    search_kwargs={"k": 30}
)

# 2. 创建 BM25 检索器
keyword_retriever = BM25Retriever.from_documents(
    documents=chunks
)
keyword_retriever.k = 30

# 3. 融合检索器(混合检索)
ensemble_retriever = EnsembleRetriever(
    retrievers=[vector_retriever, keyword_retriever],
    weights=[0.5, 0.5]
)

# 4. 添加重排序
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

reranker = CrossEncoderReranker(
    model=HuggingFaceCrossEncoder(
        model_name="BAAI/bge-reranker-v2-m3"
    ),
    top_n=3
)

final_retriever = ContextualCompressionRetriever(
    base_compressor=reranker,
    base_retriever=ensemble_retriever
)

# 5. 执行检索
results = final_retriever.invoke("公司年假政策是什么")
for doc in results:
    print(f"内容: {doc.page_content[:100]}...")

4.2 检索质量实验对比

策略Recall@3Precision延迟
仅向量检索0.720.6110ms
仅BM250.580.551ms
混合检索 (向量+BM25)0.810.6820ms
混合检索 + 重排序0.890.82100ms
多查询 + 混合检索 + 重排序0.930.85500ms

五、面试视角

问题答案要点
为什么 RAG 需要重排序?向量检索不够吗?向量检索用 Bi-Encoder,查询和文档独立编码,缺少交互。重排序用 Cross-Encoder,查询和文档一起编码,交互充分。组合使用兼顾效率与精度
混合检索解决了什么问题?纯向量检索对专有名词/编号/缩写效果差(无法理解"ABC-123"的语义)。混合检索加入 BM25 做精确匹配,互补向量检索的盲区
Bi-Encoder 和 Cross-Encoder 的本质区别?Bi-Encoder 独立编码查询和文档(可预计算,快但精度有限);Cross-Encoder 联合编码(计算量大但精度高)
RRF 融合算法的原理?Reciprocal Rank Fusion:对每个文档在不同检索方式中的排名取倒数再加权求和。排名越高贡献越大。简单有效,不需要调权重
如何评估检索质量?Recall@K(相关文档是否在 Top-K 中)、MRR(第一个相关文档的排名倒数)、NDCG@K(考虑排序位置的加权精度)

📚 相关链接

  • **Embedding模型与向量化** — 检索的向量基础
  • **向量数据库选型与对比** — 向量存储方案
  • **HyDE与查询转换** — 检索前的查询优化
  • **评估指标详解** — 检索质量评估指标
  • <- 返回 **检索系统索引**

Knowledge4J — Java 知识库