第 7 课:重排与上下文组装
RAG 课程 · 07 |目标:把“召回候选”变成“模型能有效使用的证据”|用时:25 分钟
初始召回追求覆盖率,结果往往包含重复、相似但不关键、缺少上下文或顺序混乱的 chunk。重排和上下文组装负责把候选集合变成高质量 prompt。
召回和重排不是一回事
text
召回:快速从大库找出可能相关的 20~100 个候选
重排:用更精细的模型重新判断候选与 query 的相关性常见实现是双编码器负责第一阶段,交叉编码器负责第二阶段:
- **Bi-encoder:**query 和 chunk 分别编码,向量可以提前建立索引,速度快。
- **Cross-encoder:**把 query 和 chunk 一起输入模型,判断更细,但需要逐候选计算,速度慢。
因此 reranker 通常只处理初始召回的几十个候选,而不是扫描整个向量库。
重排应该看什么
相关性不只是“主题相似”,还包括:
- 是否直接回答 query 的问题。
- 是否包含完整条件、时间、例外和限制。
- 是否来自当前租户和有权限的文档。
- 是否是最新的有效版本。
- 是否与已经选中的 chunk 重复。
一个重排结果至少应保留:
json
{
"chunk_id": "leave-policy-annual-001",
"retrieval_score": 0.82,
"rerank_score": 0.94,
"rank": 1,
"source": "员工请假制度 / 年假"
}不要覆盖初始分数。保留两阶段分数,才能判断问题发生在初始召回还是重排。
避免上下文重复
向量检索很容易返回相邻窗口或同一段落的重复版本。可以使用:
- 按
chunk_id去重。 - 按文本 hash 去重。
- 对同一
document_id + section限制数量。 - 使用 MMR,在相关性和多样性之间做平衡。
MMR 的直觉是:候选既要和 query 相关,也要和已经选择的内容不同:
text
MMR(d) = λ × relevance(d, query)
- (1 - λ) × similarity(d, selected)λ 越大越重视相关性,越小越重视多样性。具体参数必须通过评测确定。
上下文组装的六个步骤
推荐把上下文组装做成显式函数,而不是简单的 "\n".join():
- **过滤:**执行权限、租户、版本和状态过滤。
- **重排:**按 reranker 分数排序。
- **去重:**删除完全重复或高度重叠的 chunk。
- **扩展:**必要时根据
parent_id取回标题、邻居或父章节。 - **排序:**按相关性或原文档顺序组织,保留来源边界。
- **预算:**在 token 上限内选择最终上下文。
最终上下文最好显式标识来源:
text
[证据 1]
来源:员工请假制度 > 年假,页码:2
年假申请应至少提前 3 个工作日提交。
[证据 2]
来源:员工请假制度 > 病假,页码:3
病假超过 2 天时,需要上传医院证明。这样既帮助模型区分证据,也方便用户检查引用。
不要把所有候选塞进 prompt
上下文越多不一定越好:
- 无关内容会稀释关键信息。
- 重复内容会增加模型误判概率。
- 长上下文会增加延迟和成本。
- 超过模型有效注意力范围后,答案可能反而变差。
一个可用的初始方案是:初始召回 20 个,重排后选择 3~5 个,再根据父章节扩展和 token 预算调整。这个数字只是起点,不是通用答案。
失败时如何判断是哪一层
| 现象 | 优先检查 |
|---|---|
| 正确 chunk 不在候选中 | embedding、召回策略、metadata filter |
| 正确 chunk 在候选中但排名低 | reranker、query 改写、候选数量 |
| 前几名都相关但信息重复 | 去重、MMR、parent/neighbor 扩展 |
| 上下文相关但回答漏条件 | chunk 完整性、上下文顺序、prompt 约束 |
| 上下文过长且回答变差 | context_k、token 预算和文档扩展策略 |
本课练习
给定以下 5 个候选:
text
A:年假申请流程,提前 3 个工作日。
B:年假申请流程,提前三个工作日。(A 的重复版本)
C:病假超过 2 天需要医院证明。
D:员工考勤打卡异常处理。
E:年假余额查询方法。query 是“请假需要提前几天申请”。请写出:
- 你会保留哪些候选?
- 哪些候选需要去重?
- 最终 prompt 中证据的顺序是什么?
- 如何保留来源和引用?
主读材料
Passage Re-ranking with BERT。重点理解为什么“快速初筛”和“精细重排”需要不同模型和不同计算预算。
下一步
有任何不清楚的地方,直接向老师提问。