Reranker 重排序模型技术实践:为什么找到了正确答案,AI 却还是答错?
摘要: 在 RAG 知识库和语义搜索系统中,Embeddings 模型可以快速找出一批相关文档,但排在前面的未必是最有用的。Reranker(重排序模型)就是为解决这个问题而设计的。它通过联合理解用户问题和候选文档,对相关性重新评分。本文介绍 Reranker 的核心原理,并以 BGE-Reranker 为例,通过企业知识库检索、智能客服意图匹配两个案例,演示如何使用 Python 提升搜索结果的质量。
一、为什么已经有了 Embeddings,还需要 Reranker?
假设我们正在开发企业内部知识库。
员工问:
出差回来以后,机票和酒店费用应该怎么报销?
Embeddings 模型检索出以下内容:
| 原始排名 | 文档内容 |
|---|---|
| 1 | 员工出差前需要提交出差审批申请 |
| 2 | 公司统一预订机票和酒店的管理办法 |
| 3 | 出差结束后,员工应上传机票、住宿发票和审批单,提交费用报销 |
| 4 | 员工日常交通费及办公用品报销流程 |
可以发现,这四段内容都与“出差”“机票”“报销”有关。
但真正能回答用户问题的,显然是第三条。
Embeddings 擅长快速找到语义接近的内容,却不一定能准确判断哪段内容最能回答当前问题。
这就是 Reranker 发挥作用的地方。
它不是重新搜索整个知识库,而是对已经召回的候选文档重新评分,把真正有用的内容排到前面。
1.1 Reranker 的核心原理
最常见的 Reranker 使用 Cross-Encoder(交叉编码器)架构。
它与 Embeddings 模型的区别非常直观。
Embeddings:分别理解两个文本。
用户问题 ──> Embedding ──> 向量 A
│
▼
相似度
▲
│
候选文档 ──> Embedding ──> 向量 B
Reranker:把两个文本放在一起理解。
用户问题 + 候选文档
│
▼
Cross-Encoder
│
▼
相关性分数
在 Cross-Encoder 中,用户问题和候选文档进入同一个 Transformer,模型能够通过注意力机制直接分析二者之间的关系。
例如:
- 用户问的是报销流程,文档讲的是出差审批,虽然主题相关,却不能直接回答。
- 用户问的是退款到账时间,文档讲的是如何申请退款,两者相关,但并不完全匹配。
这样的细微区别,往往是 Reranker 的优势。
1.2 为什么不能直接用 Reranker 搜索?
原因很现实:计算成本。
Embeddings 可以提前计算并保存所有文档的向量。
但 Cross-Encoder 必须针对每一个“问题—文档”组合进行推理。
如果知识库有 100 万段文本,那么一次查询就需要处理 100 万个文本对,通常不划算。
因此,实际系统往往采用两阶段架构:
用户问题
│
▼
Embeddings / 关键词检索
│
▼
快速召回 Top-50
│
▼
Reranker 精细排序
│
▼
筛选 Top-5
│
▼
LLM 生成答案
简单理解就是:
Embeddings 负责海选,Reranker 负责复试。
需要注意:Reranker 只能重新排列已有候选。如果正确文档根本没有被第一阶段召回,它也无能为力。
二、有哪些值得研究的 Reranker 模型?
目前比较有代表性的模型包括:
| 模型 | 参数量 | 主要特点 |
|---|---|---|
| BGE-Reranker-v2-m3 | 568M | 多语言 Cross-Encoder,支持中文 |
| Qwen3-Reranker-0.6B | 0.6B | 基于语言模型,支持任务指令 |
| Qwen3-Reranker-4B | 4B | 更大容量,适合复杂相关性判断 |
| jina-reranker-v2-base-multilingual | 278M | 多语言,支持较长文本,商用需关注许可证 |
这里有一个值得注意的变化。
早期 Reranker 主要采用 BERT、RoBERTa 一类的编码器模型。
现在也出现了基于生成式大模型架构的 Reranker。
例如 Qwen3-Reranker,可以将相关性判断组织为一个受约束的 Yes/No 任务,再利用模型对答案的打分构造排序分数。
但这并不意味着参数越多效果就一定越好。对于短文本、固定业务领域和低延迟场景,小型 Cross-Encoder 依然非常有竞争力。
本文选用:
BAAI/bge-reranker-v2-m3
主要因为它支持中文、调用简单,也方便与上一篇介绍的 BGE Embeddings 模型组合。
开源地址:
三、实践案例一:为 RAG 知识库增加重排序
3.1 业务场景
继续使用前面的企业知识库。
我们希望构建一条完整的检索链路:
- Embeddings 召回相关文档。
- Reranker 对候选文档重新评分。
- 选取最相关的文档提供给 LLM。
这个方案正是 Sentence Transformers 官方 Retrieve & Re-Rank 实践所采用的基本思路。
3.2 环境安装
pip install -U sentence-transformers FlagEmbedding numpy
我们使用两个模型:
BAAI/bge-small-zh-v1.5:负责初步召回。BAAI/bge-reranker-v2-m3:负责重排序。
以下代码可以在 CPU 上运行,但推荐使用 GPU。首次加载需要下载模型文件。
3.3 Python 完整实现
import numpy as np
from sentence_transformers import SentenceTransformer
from FlagEmbedding import FlagReranker
# 1. 加载模型
embedder = SentenceTransformer(
"BAAI/bge-small-zh-v1.5"
)
reranker = FlagReranker(
"BAAI/bge-reranker-v2-m3",
use_fp16=False
)
# 2. 模拟企业知识库
documents = [
"员工出差前需要完成出差审批,"
"审批通过后才能预订机票。",
"公司为员工提供统一的机票和酒店"
"预订服务,预订标准按职级执行。",
"员工出差结束后,需上传机票、"
"酒店住宿发票及出差审批单,"
"通过财务系统提交报销申请。",
"员工购买办公用品后,可以凭"
"有效发票办理日常费用报销。",
"差旅费用原则上应在出差结束后"
"30天内完成报销。",
"员工可以通过人力资源系统"
"查询年度休假余额。"
]
query = "出差回来,机票和酒店费用怎么报销?"
# 3. Embeddings 召回
doc_vectors = embedder.encode(
documents,
normalize_embeddings=True
)
query_vector = embedder.encode(
"为这个句子生成表示以用于检索相关文章:" + query,
normalize_embeddings=True
)
similarities = doc_vectors @ query_vector
# 取前5个候选
indices = np.argsort(similarities)[::-1][:5]
candidates = [documents[i] for i in indices]
print("===== Embeddings 召回 =====")
for i in indices:
print(round(float(similarities[i]), 4), documents[i])
# 4. Reranker 精排
pairs = [
[query, doc]
for doc in candidates
]
scores = reranker.compute_score(pairs)
# 5. 按新分数排序
ranked = sorted(
zip(candidates, scores),
key=lambda x: float(x[1]),
reverse=True
)
print("\n===== Reranker 精排 =====")
for doc, score in ranked[:3]:
print(round(float(score), 4), doc)
3.4 如何理解结果?
第一阶段根据向量相似度选出五条候选文档。
第二阶段的 Reranker 会分别计算:
问题 + 文档1 → Score 1
问题 + 文档2 → Score 2
问题 + 文档3 → Score 3
问题 + 文档4 → Score 4
问题 + 文档5 → Score 5
然后根据分数重新排序。
我们希望关于“上传机票、住宿发票和审批单”的文档排在前面,因为它最直接地回答了用户问题。
但具体排序和分数必须以真实运行结果为准,并不是每次重排序都会比原始检索更好。
在生产环境中,最终选出的 Top-3 或 Top-5 文档,就可以作为 RAG 系统中的上下文,交给 LLM 生成回答。
这样可以减少不相关文档占用上下文窗口的情况。
四、实践案例二:智能客服的相似问题与意图匹配
4.1 业务背景
第二个案例参考了 Cohere 官方关于 FAQ Reranking 的实践思路,改为中文智能客服场景。
假设用户问:
快递显示已经签收,但我根本没收到东西,怎么办?
系统中有以下几个意图:
| 意图 | 描述 |
|---|---|
| 物流进度查询 | 用户想了解快递到哪里了 |
| 物流异常 | 用户遇到丢件、错误签收等异常情况 |
| 退款查询 | 用户关心退款什么时候到账 |
| 退货申请 | 用户想退回购买的商品 |
| 修改收货地址 | 用户希望修改订单配送地址 |
这些类别都可能出现在客服语义检索结果中。
但它们之间有明显区别:用户不是简单地问快递到哪里了,而是遇到了已签收却未收到的异常情况。
如果我们把每个意图的定义作为候选文本,就可以使用 Reranker 进行匹配。
4.2 Python 实现
继续使用之前加载的 reranker:
# 用户输入
query = "快递显示已经签收,但我根本没收到东西,怎么办?"
# 意图类别
intents = {
"物流进度查询":
"用户想了解包裹的运输状态、"
"快递位置、发货进度或预计送达时间。",
"物流异常":
"用户遇到包裹丢失、错误签收、"
"显示已签收但实际没有收到商品等问题。",
"退款查询":
"用户已经申请退款,想了解"
"退款审核状态或者到账时间。",
"退货申请":
"用户收到商品后希望退回商品,"
"申请退货或者退还货款。",
"修改收货地址":
"用户希望修改订单的收货地址、"
"收件人或者联系电话。"
}
# 构造问题与意图描述的文本对
names = list(intents.keys())
pairs = [
[query, description]
for description in intents.values()
]
# 计算相关性分数
scores = reranker.compute_score(pairs)
# 按分数排序
results = sorted(
zip(names, scores),
key=lambda x: float(x[1]),
reverse=True
)
for name, score in results:
print(f"{name}: {float(score):.4f}")
print("首选候选意图:", results[0][0])
在这个例子中,我们希望“物流异常”得到最高的相关性评分,而不是“物流进度查询”。
这里展示的是可执行代码,没有人为编造模型的实际分数。
4.3 能不能直接用 Reranker 做意图分类?
可以,但需要分清楚使用场景。
如果只有十几个意图,那么直接让 Reranker 对所有意图进行评分,是一种相当直观的零样本分类方法。
如果有几百个二级意图,则可以先用 Embeddings 召回一批候选,再进行精排。
用户问题
│
▼
Embeddings
│
▼
召回 Top-20 意图
│
▼
Reranker
│
▼
精排 Top-5 意图
│
▼
决策模型 / 分类器
│
▼
最终意图
不过,Reranker 的相关性评分并不等于意图分类概率。
尤其在多意图场景下,最高分只能说明它是当前候选中模型最偏好的一个,不能说明其他意图不存在。
例如:
我的快递一直没到,如果今天还不发货,我就申请退款。
这句话同时涉及物流和退款条件。
如果最终需要输出多个意图,建议把 Reranker 当作候选精排器,再使用多标签分类器或决策模型完成最终判断。
五、Reranker 的分数到底能不能当概率?
这是实际使用时很容易踩的坑。
BGE-Reranker 默认返回原始相关性分数,也称 Logit。
例如,官方模型示例中可能出现:
相关文本:5.26
不相关文本:-8.18
分数可以为负,也可以大于 1。
如果希望映射到 0~1,可以设置:
scores = reranker.compute_score(
pairs,
normalize=True
)
其底层相当于执行 Sigmoid 变换:
[
P=\frac{1}{1+e^{-s}}
]
但这里有个关键问题:
经过 Sigmoid 得到 0.95,不代表该文档有 95% 的实际概率能够回答用户问题。
它只是在不改变排序顺序的前提下,把原始分数压缩到了 0~1。
如果希望根据分数做自动路由、拒识或人工复核,仍需要使用业务标注数据对阈值进行验证,必要时还要做概率校准。
对于纯粹的 Top-K 排序任务,通常直接使用原始分数就足够了。
六、实际部署时,哪些问题值得注意?
1. 召回数量不是越多越好。
很多系统会先召回 Top-20~Top-100,再交给 Reranker。
但增加候选数量意味着更多文本对需要推理,延迟也可能随之增加。
应该结合 Recall@K 和 P95 延迟,选择合理的召回规模。
2. Reranker 不一定能处理完整的超长文档。
大多数 Cross-Encoder 有输入长度限制。
如果直接把一篇几万字的文章送进去,内容可能被截断,真正相关的段落反而没有进入模型。
更好的办法是先分块,再按文本块召回和重排序。
3. Reranker 可能成为新的性能瓶颈。
Embeddings 文档向量通常可以离线预计算,但 Reranker 要在每次查询时重新计算候选文本对。
因此,实际部署可以考虑 GPU 批量推理、FP16、模型量化,以及限制候选数量。
4. 要衡量它是不是真的改善了效果。
不能只看某个例子中相关文档是否被排到了第一名。
建议用一批真实标注数据比较:
| 指标 | 主要用途 |
|---|---|
| Recall@K | 第一阶段有没有召回正确文档 |
| MRR@K | 第一个正确结果排得是否靠前 |
| NDCG@K | 综合评价排序质量 |
| P95 Latency | 95% 请求的端到端处理耗时 |
| RAG 答案准确率 | 重排序是否改善了最终回答 |
只有确认 Reranker 带来的质量收益大于其延迟和成本,才有必要在生产系统中长期保留这一阶段。
七、总结
Reranker 的作用其实很好理解:它不是负责找到更多信息,而是负责从已经找到的信息中,挑出更值得关注的内容。
本文的两个案例分别展示了:
- RAG 重排序: Embeddings 负责快速召回,Reranker 负责提升结果排序质量。
- 客服意图匹配: 利用问题与意图描述之间的相关性,帮助区分语义相近的类别。
把它与前面介绍过的模型放在一起,可以形成一条比较完整的 AI 信息处理链路:
语音 / 图片 / PDF / 文本
│
▼
ASR / OCR
│
▼
文本
│
▼
Embeddings
│
▼
候选召回
│
▼
Reranker
│
▼
精细排序
│
▼
LLM / 决策模型
│
▼
业务输出
当然,并不是每套 AI 系统都需要增加 Reranker。如果候选数量很少、原始排序已经足够准确,那么多一次推理反而可能得不偿失。
但在知识库规模较大、文本语义相近、用户问题比较具体的场景中,它往往值得认真评估。
最后记住一个核心区别:
Embeddings 解决“哪些内容可能相关”,Reranker 进一步解决“哪些内容最符合当前问题”。
很多时候,RAG 系统回答不准确,不是因为最后使用的 LLM 不够强,而是因为它一开始拿到的资料就没有排对。
在考虑换一个更昂贵的大模型之前,不妨先检查一下检索和重排序环节。
- 点赞
- 收藏
- 关注作者
评论(0)