对检索到的文本块重新排序
使用交叉编码器重新排序。
对检索到的文本块重新排序 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。
为什么需要重排序
第一阶段检索(稠密或稀疏)在大规模场景下优化的是召回率:确保标准分块出现在前 50 个结果中的某个位置。它速度快,但较为粗略。随后,第二阶段的重排序器会按照精确率重新排列这个候选短列表,将真正相关的分块呈现到顶部。
这种先广泛检索、再精确重排序的模式是高级 RAG 的骨干。
def two_stage(query, k_retrieve=50, k_final=5):
candidates = first_stage_retrieve(query, k_retrieve) # high recall
reranked = rerank(query, candidates) # high precision
return reranked[:k_final]双编码器与交叉编码器
双编码器将查询和文档分别编码为向量,再通过余弦相似度进行比较;它速度快且适合建立索引,但会丢失查询与文档之间的交互信息。交叉编码器则将查询和候选内容一起输入模型,并输出相关性分数,从而捕捉细粒度的交互。
交叉编码器的准确率高得多,但无法预先计算,因此只能在候选短列表上运行。
# Bi-encoder: score = cos(enc(q), enc(d)) -> precomputable
# Cross-encoder: score = model(q, d) -> 0..1 -> per-pair, no index
def cross_encode(query, doc):
return cross_encoder.predict([(query, doc)])[0] # joint attention一次交叉编码器重排序
重排序器会计算每个候选内容与查询的匹配分数,然后按降序排列。由于交叉编码器会联合关注查询和文档,它能够解决双编码器遗漏的细微相关性问题:否定表达、精确匹配需求,以及答案与主题之间的区别。
与其他单项 RAG 改进相比,这一阶段通常能带来更大的答案准确率提升。
def rerank(query, candidates):
pairs = [(query, c.text) for c in candidates]
scores = cross_encoder.predict(pairs) # batched
for c, s in zip(candidates, scores):
c.rerank_score = s
return sorted(candidates, key=lambda c: c.rerank_score, reverse=True)以 LLM 充当重排序器
如果没有适合您所在领域的训练性交叉编码器,可以使用 LLM 进行重排序。列表式提示词要求模型在一次调用中按照相关性排列一组段落;逐项式方法则独立地为每个段落评分。
列表式方法能够捕捉相对比较关系,并且令牌效率较高,但请注意位置偏差,并确保输出解析能够稳健处理模型遗漏或重复标识符的情况。
def llm_listwise(query, candidates):
passages = '\n'.join(
'[' + str(i) + '] ' + c.text for i, c in enumerate(candidates)
)
prompt = (
'Rank the passages by relevance to the query. '
'Return only IDs, most relevant first.\nQuery: ' + query +
'\n' + passages
)
order = parse_ids(llm(prompt, temperature=0))
return [candidates[i] for i in order]延迟与候选短列表大小
重排序成本会随着候选列表大小增加而上升。在 50 个候选项上运行交叉编码器,成本远低于在 500 个候选项上运行。请将第一阶段的 k 设得足够大,以便包含目标文本块(验证 recall@k),同时又要足够小,从而能在您的延迟预算内完成重排序。
请将成对打分批量处理,并在加速硬件上运行;交叉编码器非常适合对多个候选对进行并行计算。
def tune_shortlist(eval_set, ks=(20, 50, 100, 200)):
# find smallest k where recall@k saturates -> rerank fewer pairs
return {k: (recall_at_k(eval_set, k), rerank_latency(k)) for k in ks}混合第一阶段与重排序
最强的召回效果来自混合第一阶段(融合稠密检索与 BM25),再将一个去重后的候选短名单交给重排序器。稠密检索能够找回释义改写,稀疏检索能够找回精确标识符;随后,交叉编码器按照真实相关性对两者的并集进行排序。
这种组合适用于各种查询类型,从自然语言问题到字面查找都很稳健。
def hybrid_then_rerank(query, k_final=6):
dense = dense_retrieve(query, 50)
sparse = bm25_retrieve(query, 50)
fused = dedup(rrf(dense, sparse)) # reciprocal rank fusion
return rerank(query, fused)[:k_final]分数阈值与截止值
重排序器的分数可以进行校准。不要总是只取前n个结果,而应应用相关性阈值:保留分数高于该阈值的片段;如果没有片段达标,就如实返回无答案。这样可以避免用相关性很弱的填充内容塞满提示词。
请在验证集上调节阈值,在可回答问题的覆盖率与干扰内容的纳入之间取得平衡。
def threshold_select(reranked, tau=0.3, max_n=8):
kept = [c for c in reranked if c.rerank_score >= tau][:max_n]
if not kept:
return None # signal: no sufficiently relevant context
return kept重排序后的多样性
单纯按相关性排序可能会从同一文档返回多个近乎重复的片段,浪费上下文预算。请在重排序后应用MMR或设置每个文档的数量上限,确保最终结果涵盖不同的方面和来源。
对于答案横跨多个文档的多跳问题,这一点十分重要。
def diversify(reranked, max_per_doc=2, k=6):
out, per_doc = [], {}
for c in reranked:
d = c.meta['doc_id']
if per_doc.get(d, 0) < max_per_doc:
out.append(c)
per_doc[d] = per_doc.get(d, 0) + 1
if len(out) == k:
break
return out生成器的排序
选出排名靠前的片段后,请放置它们以充分利用注意力机制。考虑到中间遗忘现象,请将分数最高的单个片段放在上下文的开头或结尾,不要埋在其他片段之间。
有些流程会按相关性升序排列片段,使最佳片段最靠近问题,这与少样本学习中的近因策略相似。
def order_for_llm(chunks):
chunks = sorted(chunks, key=lambda c: c.rerank_score) # ascending
return chunks # most relevant chunk ends up last,
# nearest the trailing question评估重排序器
请使用排序指标评估重排序器,主要在带标注的查询-片段相关性上采用NDCG和 MRR,然后评估下游的answer 准确率。如果重排序器提高了 NDCG,却没有提高答案质量,可能只是重新排列了生成器本来就能处理的片段。
请始终围绕最终任务质量完成闭环,而不仅仅关注排序指标。
import math
def ndcg_at_k(relevances, k):
dcg = sum(r / math.log2(i + 2) for i, r in enumerate(relevances[:k]))
ideal = sorted(relevances, reverse=True)
idcg = sum(r / math.log2(i + 2) for i, r in enumerate(ideal[:k]))
return dcg / idcg if idcg else 0.0生产级重排序 pipeline
完整流程如下:通过混合检索获取 50 个候选项,去重,使用交叉编码器重排序,应用分数阈值,按文档实现多样化,根据注意力机制排序,然后生成并附带引用。根据阈值决定是否返回如实的无答案结果。
如果相同的流量会重复出现,请按(查询、片段)缓存嵌入和重排序器分数,以降低成本。
def pipeline(query):
shortlist = hybrid_then_rerank(query, k_final=20)
kept = threshold_select(shortlist, tau=0.3, max_n=8)
if kept is None:
return 'No relevant information found.'
ctx = order_for_llm(diversify(kept))
return generate_with_citations(query, ctx)快速检查
请选择正确的重排序架构。
回顾
关键要点:
- 先广泛检索以提高召回率,再对候选短名单进行重排序以提高精确率。
- 交叉编码器会联合评估查询-文档对(准确但无法建立索引);双编码器速度快,但结果较粗略。
- 基于 LLM 的列表式/逐点评分式重排序是后备方案;请注意位置偏差和解析问题。
- 调节候选短名单的规模,在延迟预算内使召回率趋于饱和;并结合混合第一阶段检索。
- 应用分数阈值,按文档实现多样化,根据注意力机制排列片段,并使用 NDCG 与下游 answer 准确率进行评估。
用 AI 导师学习 AI Prompt Engineering — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 53
- 课程
- 199
常见问题解答
「对检索到的文本块重新排序」课时是免费的吗?
是的 — 「对检索到的文本块重新排序」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。
「对检索到的文本块重新排序」这节课中我会学到什么?
使用交叉编码器重新排序。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Prompt Engineering 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「对检索到的文本块重新排序」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Prompt Engineering 课中编写并运行代码吗?
能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 超越朴素 RAG
- 对检索到的文本块重新排序
- 上下文压缩
- 查询改写与 HyDE