AI Prompt Engineering · 课时

对检索到的文本块重新排序

使用交叉编码器重新排序。

第 2 / 4 课13 个步骤

对检索到的文本块重新排序 是 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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 超越朴素 RAG
  2. 对检索到的文本块重新排序
  3. 上下文压缩
  4. 查询改写与 HyDE
← 返回 AI Prompt Engineering