30 Rerank 重排序:从候选召回到高质量上下文
Rerank 重排序:从候选召回到高质量上下文
Rerank 的作用不是替代向量检索,而是在已经召回一批候选 chunk 之后,再判断哪些 chunk 最能回答当前问题。它解决的是“候选看起来都相关,但上下文只能放少量证据”的排序问题。
[TOC]
1. 本节解决什么问题
前面已经完成了 RAG 的主链路:
文档上传
-> 解析
-> 清洗
-> 切片
-> Embedding -> 向量入库
-> 向量检索
-> 基础问答
-> 混合检索
到这里,系统已经能回答问题。
但“能回答”和“答得稳定”不是一回事。
常见问题是:
向量检索返回了很多看起来相关的 chunk真正能支撑答案的 chunk 排名不够靠前
Prompt 上下文空间有限,不能把所有候选都塞进去
无关 chunk 混进上下文后,模型容易被带偏
Rerank 要解决的难题是:
如何从一批候选 chunk 中,挑出最值得交给大模型的少数几个
它关注的是排序质量,不是召回数量。
2. 先看一个场景
用户问:
退款后优惠券会不会退回?
向量检索可能召回这些候选:
| 原始排名 | chunk 内容方向 | 问题 |
|---|---|---|
| 1 | 退款申请流程 | 相关,但不一定回答优惠券 |
| 2 | 订单取消规则 | 有关系,但证据不直接 |
| 3 | 优惠券退回规则 | 很可能是核心证据 |
| 4 | 会员积分退回规则 | 语义相近,但主题偏了 |
| 5 | 退款到账时间 | 相关,但不能回答优惠券 |
如果直接取向量 Top3,可能会漏掉第 3 条以外的必要证据,或者把不相关的制度塞进 Prompt。
这时要先判断问题出在哪里:
正确 chunk 有没有被召回?
-> 没有:这是召回问题,Rerank 解决不了
-> 有,但排名靠后:这是排序问题,Rerank 可以处理
所以 Rerank 的前提是:
候选池里已经包含正确证据
如果候选池里根本没有正确 chunk,要先回头检查:
解析
切片
Embedding 模型
向量检索 topK混合检索
权限过滤
查询改写
3. 为什么不直接提高 TopK
一个直接想法是:
既然怕漏,就把 topK 从 5 调到 30
这能提高召回,但会带来新的问题。
Prompt token 增加
模型调用费用增加
响应延迟增加
无关证据变多
模型更难抓住真正依据
来源列表更难解释
提高 TopK 解决的是“候选数量不够”。
Rerank 解决的是“候选顺序不准”。
两者通常配合使用:
先宽召回:取 20 到 50 个候选
再精排序:挑 5 到 8 个进入 Prompt
4. 为什么不只靠 Prompt
另一个想法是:
把更多 chunk 塞给模型,然后让模型自己判断哪些有用
这样会把排序压力转移给生成模型。
问题是:
生成模型输入越长,成本越高
无关信息越多,回答越容易发散
模型可能引用弱相关来源
上下文里如果有互相冲突的片段,答案更难稳定
Rerank 的价值是把“挑证据”放在生成之前。
这样进入 Prompt 的内容更少、更集中、更可解释。
5. Rerank 在链路里的位置
graph TD;
A["用户问题"] --> B["生成问题向量"];
B --> C["向量检索 TopN"]; A --> D["关键词或全文检索 TopN"]; C --> E["合并候选"];
D --> E; E --> F["去重和权限校验"];
F --> G["Rerank 重排序"];
G --> H["保留 finalTopK"]; H --> I["构造带来源的上下文"];
I --> J["调用大模型生成回答"];
J --> K["返回答案和来源"];
这里有几个边界要分清:
向量检索:负责语义召回
关键词检索:负责精确字符串召回
RRF 融合:负责把多路候选合成一个候选池
Rerank:负责对候选池做更细的相关性排序
LLM:负责基于最终上下文生成答案
Day29 的混合检索解决的是:
正确 chunk 能不能进候选池
Day30 的 Rerank 解决的是:
正确 chunk 能不能排到上下文前面
6. 方案怎么选
面对“候选排序不准”,可以有几种方案。
| 方案 | 做法 | 优点 | 问题 |
|---|---|---|---|
| 只用向量距离 | 按 pgvector distance 排序 | 简单、快 | 相关不等于能回答 |
| 调大 topK | 多取候选,直接进 Prompt | 不容易漏 | 成本高,噪声大 |
| RRF 融合 | 向量和关键词按排名融合 | 不依赖分数量纲 | 仍是召回层排序 |
| Rerank 模型 | 用 question + chunk 重新判断 | 更贴近当前问题 | 增加延迟和成本 |
| 让 LLM 先筛选 | 把候选交给 LLM 选证据 | 灵活 | 成本更高,稳定性较差 |
学习阶段推荐顺序:
1. 先建立无 Rerank 基线
2. 再做混合检索,保证候选池覆盖
3. 最后引入 Rerank,观察排序是否提升
这样做的原因是:
没有基线,看不出 Rerank 是否真的有效
候选池没有正确证据,Rerank 无法凭空生成
只看最终答案,无法判断改进来自召回还是排序
7. 数据结构
Rerank 前后的数据要保留清楚。
候选结构可以这样设计:
public record RetrievedChunk(
String chunkId, String documentId, String content, String titlePath, Integer pageStart, Integer pageEnd, Double vectorScore, Double keywordScore, Integer vectorRank, Integer keywordRank, String hitType, Map<String, Object> metadata) {
}
字段含义:
| 字段 | 作用 |
|---|---|
chunkId |
稳定定位 chunk,用于去重和来源回查 |
documentId |
定位来源文档 |
content |
候选正文 |
titlePath |
标题路径,帮助模型理解上下文位置 |
pageStart/pageEnd |
PDF 等文档的页码范围 |
vectorScore |
向量检索原始分数 |
keywordScore |
关键词检索原始分数 |
vectorRank/keywordRank |
两路检索原始排名 |
hitType |
VECTOR、KEYWORD、BOTH |
metadata |
parser、section、source 等扩展信息 |
Rerank 输出结构:
public record RerankedChunk(
String chunkId, String documentId, String content, double rerankScore, int rerankRank, RetrievedChunk original) {
}
为什么要保留 original?
可以回看 Rerank 前的分数和排名
可以判断某个 chunk 是向量召回还是关键词召回
可以排查 Rerank 是否把正确候选降下去了
可以生成调试报告和评测明细
不要只保存最终文本。
只保存文本会丢掉排查线索。
8. 输入候选怎么准备
Rerank 不是直接对全库排序。
它通常只处理召回后的候选。
示例配置:
vectorTopK = 30
keywordTopK = 20
rerankCandidateLimit = 40
finalTopK = 5
含义:
vectorTopK:向量检索先召回30条
keywordTopK:关键词检索先召回20条
rerankCandidateLimit:合并去重后最多送40条给Rerank
finalTopK:Rerank后只保留5条进Prompt
为什么要限制 rerankCandidateLimit?
Rerank 成本通常和候选数量成正比
候选太多会增加延迟
候选太少又可能错过正确证据
第一版可以从 20 到 40 个候选开始。
后面用评测集调整。
9. Rerank 输入是什么
Rerank 判断的是:
这个 chunk 对当前 question 有没有帮助
所以输入不是单独的 chunk,也不是单独的问题,而是成对输入:
question + chunk
示例:
{
"query": "退款后优惠券会不会退回?",
"documents": [ { "chunkId": "chunk_001", "text": "退款申请提交后,平台会在3个工作日内审核。"
}, { "chunkId": "chunk_002", "text": "订单全额退款成功后,未过期优惠券会退回用户账户。"
} ]}
Rerank 期望输出:
[
{ "chunkId": "chunk_002", "score": 0.94 }, { "chunkId": "chunk_001", "score": 0.42 }]
这个分数不是向量相似度。
它表示候选片段对当前问题的相关性。
10. Rerank 怎么接入
伪代码:
List<RetrievedChunk> vectorCandidates = vectorRetriever.search(question, vectorTopK);List<RetrievedChunk> keywordCandidates = keywordRetriever.search(question, keywordTopK);
List<RetrievedChunk> mergedCandidates = candidateMerger.mergeAndDeduplicate( vectorCandidates,
keywordCandidates
);
List<RetrievedChunk> limitedCandidates = mergedCandidates.stream()
.limit(rerankCandidateLimit) .toList();
List<RerankedChunk> rerankedChunks = rerankService.rerank( question, limitedCandidates
);
List<RerankedChunk> finalChunks = rerankedChunks.stream()
.limit(finalTopK) .toList();```
每一步都应该有日志。
至少记录:
```text
vectorCandidateCount
keywordCandidateCount
mergedCandidateCount
rerankInputCount
finalTopK
rerankLatencyMs
rerankModel
这样才能知道慢在哪里、丢在哪里、排序是否合理。
11. 最终上下文怎么组织
Rerank 后进入 Prompt 的不是散乱文本。
建议带来源编号:
[来源1]
文档:售后规则.md
位置:退款规则 > 优惠券退回
内容:订单全额退款成功后,未过期优惠券会退回用户账户。
[来源2]
文档:优惠券规则.md
位置:使用限制
内容:已过期优惠券不支持退回。
Prompt 里再要求模型:
请只根据给定来源回答。
如果来源中没有答案,请说明资料不足。
回答中标注使用了哪些来源编号。
这样做解决两个问题:
模型能看到更清晰的证据结构
前端可以把答案里的来源编号映射回文档和页码
12. 和混合检索的关系
混合检索和 Rerank 是上下游关系。
混合检索:扩大候选覆盖
Rerank:提高候选排序质量
一个常见链路是:
向量检索 Top30关键词检索 Top20RRF 合并排序
取前40个候选
Rerank
取前5个进入 Prompt
如果没有混合检索,也可以只对向量候选做 Rerank。
但如果问题里有错误码、接口路径、编号,正确 chunk 可能根本进不了向量候选池。
这时应该先提高召回覆盖,再谈重排序。
13. 失败和降级
Rerank 是增强步骤,不应该让整个问答链路过于脆弱。
可以设计降级策略:
Rerank 成功:使用 rerank 后的 TopKRerank 超时:使用混合检索融合后的 TopKRerank 服务异常:使用原始候选 TopK,并记录 degraded=true候选数量很少:可以跳过 Rerank
响应或日志中记录:
{
"rerankEnabled": true, "rerankDegraded": true, "degradeReason": "RERANK_TIMEOUT", "fallback": "FUSION_TOPK"}
不能悄悄降级。
否则效果变差时,很难知道是检索问题、Rerank 问题,还是模型问题。
14. 什么时候启用 Rerank
适合启用:
制度问答
合同条款问答
售后政策问答
接口文档问答
需要返回来源的严肃问答
候选经常相关但不够精准的场景
可以暂时不启用:
闲聊
格式转换
低价值高并发入口
候选本身已经非常少的问题
召回质量还很差的早期阶段
判断顺序:
1. 正确 chunk 是否能被召回
2. 正确 chunk 是否经常排不进 finalTopK3. Rerank 是否提高引用支持率
4. 延迟和成本是否可接受
5. 是否需要按场景开关
15. 怎么评测
不要只看“最终答案好不好”。
Rerank 主要影响的是排序,所以要单独看排序指标。
建议准备固定问题集:
制度类问题
接口类问题
错误码问题
同义表达问题
容易混淆的问题
资料不足的问题
对比三组:
仅向量检索
混合检索
混合检索 + Rerank
记录指标:
| 指标 | 说明 |
|---|---|
| Hit@5 | 前5个候选里是否包含正确 chunk |
| MRR | 正确 chunk 排名越靠前越好 |
| 引用支持率 | 最终答案引用是否能支撑结论 |
| P95 延迟 | 95% 请求耗时 |
| 单次成本 | Rerank 和 LLM 的调用成本 |
| 降级率 | Rerank 超时或失败比例 |
如果 Rerank 后最终答案变好,但延迟明显不可接受,就要考虑:
只对高价值知识库启用
只对低置信度问题启用
减少 rerankCandidateLimit使用更快的 rerank 模型
缓存高频问题结果
16. 排查路径
当 Rerank 后答案仍然不对,可以按这个顺序看。
1. 原始问题是否清楚
2. 多轮追问是否已改写成独立问题
3. 候选池里是否包含正确 chunk4. 正确 chunk 在 Rerank 前排名是多少
5. 正确 chunk 在 Rerank 后排名是多少
6. finalTopK 是否把正确 chunk 保留下来
7. Prompt 是否清楚展示来源
8. 模型是否按来源回答
9. 来源是否真的支撑答案
根据结果判断责任层:
正确 chunk 没进候选池:召回问题
正确 chunk 进了但被排低:Rerank 问题
正确 chunk 进入 Prompt 但答案错:Prompt 或生成问题
答案对但来源错:引用映射问题
17. 常见问题
17.1 Rerank 能替代向量检索吗
不能。
Rerank 处理的是候选集合,不负责全库召回。
它需要向量检索、关键词检索或混合检索先把候选找出来。
17.2 Rerank 能解决召回漏掉的问题吗
不能。
如果正确 chunk 没有进入候选池,Rerank 看不到它。
这种情况要优化解析、切片、Embedding、topK、混合检索或查询改写。
17.3 finalTopK 是不是越大越好
不是。
finalTopK 越大,上下文越长,成本和噪声越高。
第一版可以从 5 开始,用评测集调整。
17.4 Rerank 一定提升效果吗
不一定。
如果候选池本身质量很差,或者 Rerank 模型不适合中文、业务术语、长文本,它可能把正确 chunk 排低。
所以必须保留 Rerank 前后排名,用评测验证。
18. 练习清单
完成这些练习:
准备20个固定问题
记录无Rerank时的Top5
把召回TopK提高到30
实现Rerank输入结构
保留chunkId、documentId、原始排名和rerank分数
Rerank后取finalTopK
对比Hit@5、MRR、引用支持率和P95
找3个失败样本分析原因
写清楚是否值得默认启用Rerank
19. 小结
Rerank 的本质是:
先让召回阶段尽量别漏
再让排序阶段把真正有证据价值的 chunk 放前面
最后只把少量高质量上下文交给大模型
它解决的是排序问题,不是召回问题,也不是生成问题。
判断是否要上 Rerank,要看四件事:
正确 chunk 是否已被召回
正确 chunk 是否经常排不进 finalTopKRerank 是否提升引用支持率
延迟和成本是否能接受