RAG 检索结果怎么用 CrossEncoder 重新排序
来源:17golang原创
时间:2026-09-06 06:22:47 237浏览 收藏
RAG 的召回结果不理想时,通常不用立刻更换向量库。更实用的做法是保留第一阶段的高效召回,再把查询和每个候选片段一起交给 CrossEncoder 重新打分,按分数取最终 Top-K。它能看到查询与文档的交互关系,但需要为每一对文本做一次推理,所以只适合重排一个有限的候选池。
推荐的链路是“先召回 Top-N,再用 CrossEncoder 重排,最后把 Top-K 放进提示词”。N 要大于 K,但不能大到让重排延迟吞掉 RAG 的响应预算。
- 双编码器或 BM25 负责从大语料中快速找候选,CrossEncoder 负责精排,不负责建立可索引的单文本向量。
- 输入必须是
[query, document]成对数据;排序只比较同一次查询下的分数。 - MS MARCO 类模型可能输出原始 logit,排序时直接比较即可;只有展示概率或设阈值时才考虑 Sigmoid。
先把 RAG 的两阶段职责分开
在百万级语料中逐个计算查询与文档的 CrossEncoder 分数,成本会随候选数量线性增长。Sentence Transformers 的官方 Retrieve & Re-Rank 示例因此把流程拆成两段:双编码器或关键词检索先取出一小批候选,CrossEncoder 再给这些候选排序。第一段追求覆盖率,第二段追求相关性。
| 阶段 | 输入与输出 | 主要目标 | 常见调参点 |
|---|---|---|---|
| 召回 | 查询 + 大语料 → Top-N | 尽量不要漏掉答案 | 向量模型、BM25、N |
| 重排 | 查询 + 每个候选 → 分数 | 把真正相关片段提前 | 模型、batch_size、max_length |
| 组装 | Top-N → Top-K 上下文 | 控制提示词长度和噪声 | K、去重、来源约束 |
例如召回 50 段、最终放入 5 段,就只计算 50 个查询-文档对,而不是对整个知识库做配对。这里的 N 和 K 不是固定标准:N 太小会让重排器没有机会找回漏掉的答案,K 太大又会把相似但无关的片段塞回上下文。

用 CrossEncoder 给候选片段逐对打分
下面示例使用 Sentence Transformers 文档中的常见 MS MARCO CrossEncoder。模型名只是示例,生产环境应结合中文语料、领域和延迟预算选择经过验证的模型。候选文档保留 id、正文和来源,重排后只改变顺序,不丢掉这些元数据。
from sentence_transformers import CrossEncoder
# 用一个查询和多个候选片段组成成对输入。
query = "如何为 RAG 检索结果增加重排阶段"
candidates = [
{"id": "doc-1", "text": "先用向量检索得到候选,再用 CrossEncoder 重新排序。", "source": "rag-guide"},
{"id": "doc-2", "text": "BM25 适合按关键词匹配文档。", "source": "search-note"},
{"id": "doc-3", "text": "CrossEncoder 同时读取查询和候选文本,输出相关性分数。", "source": "model-note"},
]
# 加载可用于句对相关性打分的模型;首次运行会下载模型文件。
model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L6-v2")
pairs = [[query, item["text"]] for item in candidates]
scores = model.predict(pairs, batch_size=16, show_progress_bar=False)
# 分数只用于同一查询下排序,元数据随候选一起保留。
ranked = sorted(zip(candidates, scores), key=lambda pair: float(pair[1]), reverse=True)
top_k = [{**item, "rerank_score": float(score)} for item, score in ranked[:2]]
for item in top_k:
print(item["id"], item["rerank_score"], item["source"])
predict 接收的是句对列表,而不是一组孤立句子。示例里的 batch_size=16 只影响推理批次,不改变排序逻辑;显存不足时可以调小,吞吐允许时再逐步调大。scores 可能是 NumPy 数组或类似序列,转成 Python float 后再写入结果,便于序列化和日志记录。

把重排结果接回 RAG 上下文
重排结束后,不要只把一串文本直接拼进提示词。建议先按分数取 K,再做轻量去重和长度裁剪,最后保留来源标识。这样模型既能看到靠前的证据,也能在回答中指出证据来自哪一个片段。
# 只选重排后的前 K 个片段,并限制单片段长度。
def build_context(ranked_items, top_k=5, max_chars=1200):
blocks = []
seen = set()
for item in ranked_items[:top_k]:
# 用文档 ID 去重,避免同一片段的不同切片重复占用上下文。
if item["id"] in seen:
continue
seen.add(item["id"])
text = item["text"].strip()[:max_chars]
blocks.append(f"[{item['source']}] {text}")
return "\n\n".join(blocks)
context = build_context(top_k, top_k=2)
prompt = f"请只依据以下资料回答问题:\n{context}\n\n问题:{query}"
print(prompt)
最终的 K 应由提示词窗口、回答任务和离线评估共同决定。问答任务可以从较小的 K 开始;需要跨段落综合时,宁可扩大召回 N 并评估重排后的 Recall@K,也不要只凭一次示例把 K 写死。
用指标和边界控制重排成本
性能测试至少记录“每次查询的候选数 N、最终 K、重排耗时、端到端 P95 延迟、Recall@K 或 nDCG@K”。可以固定一组真实问题,分别比较不重排、N=20 和 N=50 的结果;如果 N 增大后 Recall@K 不再提升,却持续增加延迟,就没有必要继续扩大候选池。
| 现象 | 优先检查 | 调整方向 |
|---|---|---|
| 最终答案相关性没提升 | 召回池是否已经漏掉正确片段 | 先提升第一阶段 Recall,再谈重排 |
| 延迟突然变高 | N、文本长度和 batch_size | 限制候选与片段长度,按显存调整批次 |
| 分数看起来不像概率 | 模型的输出定义 | 排序直接比较;需要概率时再配置 Sigmoid |
| 中文领域结果不稳定 | 模型训练语料与领域差异 | 准备标注查询-片段对,评估或微调领域模型 |
CrossEncoder 的分数不是跨模型、跨查询都可直接比较的业务概率。它最可靠的用途是:在同一个查询的候选集合中排序。若业务要按阈值过滤,先用带标注的验证集校准阈值;若候选规模本身很小,也可以直接对全部候选打分,但不要把这种做法照搬到大语料全量扫描。
常见问题
CrossEncoder 能替代向量检索吗?
通常不能。它需要为查询和候选逐对计算,适合第二阶段重排;大规模语料仍应先用向量检索、BM25 或其他高效方法缩小候选集。
分数必须转换成 0 到 1 吗?
不必须。排序只需要比较相对大小。Sentence Transformers 文档说明部分 MS MARCO 模型输出 raw logits;若展示概率或按概率阈值决策,再使用 Sigmoid,并重新用验证集确认阈值。
N=100、K=5 是固定最佳值吗?
不是。N 影响召回覆盖和重排成本,K 影响上下文噪声与提示词长度。用真实查询集同时看 Recall@K、nDCG@K 和 P95 延迟,按业务预算选择。
落地时可以先从“召回 20 段、重排后取 5 段”做基线,再逐步增加候选数。只要把召回覆盖、重排排序和上下文组装分别记录,RAG 的问题就能从“感觉答得不准”变成可测量、可调参的检索链路。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
科技周边 · 人工智能 | 3小时前 | python · 人工智能 · transformers · 流式输出 · SSE Transformers TextIteratorStreamer 流式生成472 收藏
-
384 收藏
-
273 收藏
-
282 收藏
-
286 收藏
-
103 收藏
-
299 收藏
-
科技周边 · 人工智能 | 1天前 | oauth · 人工智能 · mcp · Agent 工程 · resource MCP OAuth 2.1 RFC 8707 token audience 远程 MCP123 收藏
-
119 收藏
-
293 收藏
-
科技周边 · 人工智能 | 2天前 | 异步任务 · mcp · 协议扩展 · MCP Tasks io.modelcontextprotocol/tasks tasks/get tasks/update260 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习