Rerank 与上下文工程:重排、压缩与引用溯源的收益边界

Rerank 与上下文工程:重排、压缩与引用溯源的收益边界

一句话结论:Rerank 解决的是"排序",不是"召回"。它的收益取决于初召回质量与候选集大小;上下文工程的核心不是"塞更多",而是"放对位置、去掉噪声、可溯源"。

适用版本 :LangChain4j 1.23.0 / JDK 21


0. 先说结论

先区分三个问题:

  1. 没召回:正确文档不在候选集里 → Rerank 救不了,要修召回;
  2. 召回了但排得靠后:正确文档在 Top-20,但不在 Top-5 → Rerank 有用;
  3. 召回了、也排对了,但模型没用:上下文太长/位置不好/噪声多 → 上下文工程。

很多团队把这三个问题都归因于"模型不行",然后换模型、加 Rerank,最后效果仍然不稳定。

本文按"召回 → 重排 → 压缩 → 注入 → 引用"拆开讲。


1. Rerank 解决的是排序,不是召回

RAG 的召回阶段通常追求"别漏",所以会取 Top-20/Top-50;但最终喂给模型的只有 5--10 条。Rerank 的作用是从候选集中挑出最相关的几条,并排好序。

问题 Rerank 是否有用 应该先做什么
正确文档不在候选集 ❌ 修分块/混合检索/Query 改写
正确文档在候选集但排名低 ✅ 加 Rerank
候选集噪声太多 ✅ Rerank + 压缩
上下文太长导致忽略 ⚠️ 压缩/重排位置
答案需要多文档整合 ⚠️ 提高召回 + 结构化上下文

先确认"正确文档在不在候选集"。 不在,Rerank 只是给错误候选排序。


1.1 一个"加 Rerank 没效果"的案例

背景:团队给 RAG 加了 Rerank,nDCG 几乎没变。

排查:

  • 正确文档在初召回中排第 35;
  • Rerank 只拿到 Top-20 候选;
  • 正确文档根本没进候选集。

修复:

  1. 改分块(表格整表成块);
  2. 加 BM25 混合召回;
  3. 正确文档进入 Top-8;
  4. 再加 Rerank,排到 Top-3。

结论:Rerank 只能"在候选集里挑",不能"把没召回的文档变出来"。先修召回,再谈重排。


2. Bi-Encoder 与 Cross-Encoder

类型 工作方式 优点 缺点 适用
Bi-Encoder query 与 doc 分别编码,算向量相似度 快、可预计算 交互少、精度有限 初召回
Cross-Encoder query + doc 一起输入模型打分 交互充分、通常更准 慢、不能预计算 重排
LLM Rerank 让 LLM 对候选打分/排序 灵活、可解释 贵、慢、不稳定 小候选集

Rerank 的成本与候选数成正比:20 条候选要跑 20 次 cross-encoder,50 条就是 50 次。候选 K 不是越大越好。


2.1 先判断:你到底需不需要 Rerank

text 复制代码
1. 统计正确文档在初召回 Top-K 中的比例
2. 如果比例低 → 先修召回(分块/混合/Query 改写)
3. 如果比例高但 nDCG 低 → Rerank 有空间
4. 估算 Rerank 延迟与成本
5. 小流量灰度,成对比较
观察 结论
Recall@20 高、nDCG@5 低 Rerank 值得试
Recall@20 低 先修召回,Rerank 无用
Recall 高、nDCG 也高 不需要 Rerank
答案错但证据在 Top-3 上下文/位置/压缩问题

先做这个判断,能省掉一半"上了 Rerank 没效果"的无效工作。


2.2 Cross-Encoder 的输入怎么构造

项 建议 原因
输入 query + [SEP] + chunk 保留交互
长度 截断到模型上限 超长会截掉关键证据
截断位置 保留开头+结尾 中间易丢
批大小 按延迟/显存调 批太小慢,批太大爆
元数据 不放进打分文本 避免噪声

截断策略很重要:如果块很长,直接截断可能把答案截掉。优先保留包含 query 词的句子窗口。


3. 一条完整的重排与上下文管线

#mermaid-svg-PPwYESvt6iyUAHY8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PPwYESvt6iyUAHY8 .error-icon{fill:#552222;}#mermaid-svg-PPwYESvt6iyUAHY8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PPwYESvt6iyUAHY8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PPwYESvt6iyUAHY8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PPwYESvt6iyUAHY8 .marker.cross{stroke:#333333;}#mermaid-svg-PPwYESvt6iyUAHY8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PPwYESvt6iyUAHY8 p{margin:0;}#mermaid-svg-PPwYESvt6iyUAHY8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PPwYESvt6iyUAHY8 .cluster-label text{fill:#333;}#mermaid-svg-PPwYESvt6iyUAHY8 .cluster-label span{color:#333;}#mermaid-svg-PPwYESvt6iyUAHY8 .cluster-label span p{background-color:transparent;}#mermaid-svg-PPwYESvt6iyUAHY8 .label text,#mermaid-svg-PPwYESvt6iyUAHY8 span{fill:#333;color:#333;}#mermaid-svg-PPwYESvt6iyUAHY8 .node rect,#mermaid-svg-PPwYESvt6iyUAHY8 .node circle,#mermaid-svg-PPwYESvt6iyUAHY8 .node ellipse,#mermaid-svg-PPwYESvt6iyUAHY8 .node polygon,#mermaid-svg-PPwYESvt6iyUAHY8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PPwYESvt6iyUAHY8 .rough-node .label text,#mermaid-svg-PPwYESvt6iyUAHY8 .node .label text,#mermaid-svg-PPwYESvt6iyUAHY8 .image-shape .label,#mermaid-svg-PPwYESvt6iyUAHY8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-PPwYESvt6iyUAHY8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PPwYESvt6iyUAHY8 .rough-node .label,#mermaid-svg-PPwYESvt6iyUAHY8 .node .label,#mermaid-svg-PPwYESvt6iyUAHY8 .image-shape .label,#mermaid-svg-PPwYESvt6iyUAHY8 .icon-shape .label{text-align:center;}#mermaid-svg-PPwYESvt6iyUAHY8 .node.clickable{cursor:pointer;}#mermaid-svg-PPwYESvt6iyUAHY8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PPwYESvt6iyUAHY8 .arrowheadPath{fill:#333333;}#mermaid-svg-PPwYESvt6iyUAHY8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PPwYESvt6iyUAHY8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PPwYESvt6iyUAHY8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PPwYESvt6iyUAHY8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PPwYESvt6iyUAHY8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PPwYESvt6iyUAHY8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PPwYESvt6iyUAHY8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PPwYESvt6iyUAHY8 .cluster text{fill:#333;}#mermaid-svg-PPwYESvt6iyUAHY8 .cluster span{color:#333;}#mermaid-svg-PPwYESvt6iyUAHY8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PPwYESvt6iyUAHY8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PPwYESvt6iyUAHY8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-PPwYESvt6iyUAHY8 .icon-shape,#mermaid-svg-PPwYESvt6iyUAHY8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PPwYESvt6iyUAHY8 .icon-shape p,#mermaid-svg-PPwYESvt6iyUAHY8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PPwYESvt6iyUAHY8 .icon-shape rect,#mermaid-svg-PPwYESvt6iyUAHY8 .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PPwYESvt6iyUAHY8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PPwYESvt6iyUAHY8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PPwYESvt6iyUAHY8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-PPwYESvt6iyUAHY8 .client>*{fill:#0E2A33!important;stroke:#22D3EE!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .client span{fill:#0E2A33!important;stroke:#22D3EE!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .client tspan{fill:#E8EEF9!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .recall>*{fill:#0F2A1D!important;stroke:#22C55E!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .recall span{fill:#0F2A1D!important;stroke:#22C55E!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .recall tspan{fill:#E8EEF9!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .candidate>*{fill:#111A2E!important;stroke:#4F8CFF!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .candidate span{fill:#111A2E!important;stroke:#4F8CFF!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .candidate tspan{fill:#E8EEF9!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .rerank>*{fill:#2A1F3D!important;stroke:#A78BFA!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .rerank span{fill:#2A1F3D!important;stroke:#A78BFA!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .rerank tspan{fill:#E8EEF9!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .compress>*{fill:#2A1F0B!important;stroke:#F5A524!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .compress span{fill:#2A1F0B!important;stroke:#F5A524!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .compress tspan{fill:#E8EEF9!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .context>*{fill:#1E293B!important;stroke:#94A3B8!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .context span{fill:#1E293B!important;stroke:#94A3B8!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .context tspan{fill:#E8EEF9!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .answer>*{fill:#0F2A1D!important;stroke:#22C55E!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .answer span{fill:#0F2A1D!important;stroke:#22C55E!important;color:#E8EEF9!important;stroke-width:2px!important;}#mermaid-svg-PPwYESvt6iyUAHY8 .answer tspan{fill:#E8EEF9!important;} Query
初召回

向量 + BM25
候选集

Top-20/50
Rerank

Cross-Encoder
压缩/去重

保留证据
上下文注入

位置/顺序
回答 + 引用

图 1:召回、重排、压缩、注入、引用。每一步都有独立的收益与成本。


3.1 Rerank 的三种实现方式

方式 代表 优点 缺点 适用
托管 Rerank API Cohere/Jina 等 接入快、效果好 外部依赖、按次计费 快速上线
本地 Cross-Encoder ONNX/BGE 等 数据不出域、可控 需要模型/机器 合规/成本敏感
LLM 打分 通用大模型 灵活、可解释 贵、慢、波动 小候选集

LangChain4j 侧通常通过 ScoringModel 与 ReRankingContentAggregator 接入(类名与签名以 1.23.0 为准)。如果使用托管 API,注意:

  • 超时与重试;
  • 批量打分,减少网络往返;
  • 结果缓存(同一 query + 候选可复用);
  • 失败时回退到原排序。

3.2 Rerank 与 RRF 的关系

两者不冲突,顺序是:

text 复制代码
多路召回 → RRF 融合 → 候选集 → Rerank → 最终 Top-K
  • RRF 解决"多路结果怎么合并";
  • Rerank 解决"合并后怎么精排"。

不要对每一路分别 Rerank 再合并------成本高,且候选重复。先去重/融合,再统一重排。


4. 候选 K 的收益边界

K 召回上限 Rerank 成本 风险
5 低 低 正确文档可能进不来
10 中 中 常见起点
20 较高 较高 延迟上升
50 高 高 成本/延迟显著

怎么定 K:

  1. 先看正确文档在初召回中的排名分布;
  2. 如果 90% 的正确文档在 Top-20,K=20 足够;
  3. 如果大量正确文档在 30--50,先修召回,不要盲目加 K;
  4. 用 nDCG@K / Recall@K 评估,不要只看"感觉"。

没有"最佳 K"。它由你的数据、召回质量、延迟预算共同决定。


4.1 候选集构建:先去重,再截断

多路召回后,候选集通常有重复:

  • 同一块被向量与 BM25 同时召回;
  • 同一文档的相邻块内容重叠;
  • 父子块同时命中。

处理顺序:

  1. 按 chunkId 去重;
  2. 按文档限制每篇最多 2--3 块;
  3. 文本相似度去重(可选);
  4. 按分数截断到 K;
  5. 保留多样性(MMR 等)。
规则 作用 风险
chunkId 去重 去重复 无
每文档上限 防单文档霸屏 可能漏多段证据
近重复去重 去模板/页眉 可能误删
MMR 增多样性 增加计算

候选集越干净,Rerank 越省、越准。


4.2 候选 K 的实验方法

固定其他变量,只改 K:

K nDCG@5 答案正确率 P95 成本
10 待测 待测 待测 低
20 待测 待测 待测 中
50 待测 待测 待测 高

选择"拐点":再增加 K,指标提升很小,但延迟/成本明显上升。不要因为"候选多更安心"就取 100。


5. 上下文压缩:少即是多

方法 做法 优点 缺点
去重 按 chunkId/文本相似度 降 Token、降噪声 可能误删
抽取式压缩 只保留相关句子 保真、可引用 需要句子级打分
生成式压缩 让模型总结候选 短、连贯 可能改写/丢事实
元数据过滤 按版本/权限/时间 精准 依赖元数据质量
结构化注入 表格/要点/字段 模型易读 需要预处理

压缩的目标不是"越短越好",而是保留回答所需的最小证据集。

生成式压缩要特别小心:它可能把"7 天"改成"一周",把"不支持"改成"支持"。涉及数字、否定、条件的场景,优先抽取式。


5.1 抽取式压缩的实现

java 复制代码
// 伪代码:按句子打分,保留证据句及上下文
List<String> sentences = splitSentences(chunk.text());
List<ScoredSentence> scored = sentences.stream()
        .map(s -> new ScoredSentence(s, score(query, s)))
        .sorted(byScoreDesc())
        .limit(maxSentences)
        .toList();

String compressed = joinWithContext(scored, 1); // 每句带前后各 1 句

打分可以用:

  • query 词重叠;
  • 句子 embedding 与 query 相似度;
  • Rerank 模型对句子的分数。

压缩时必须保留:

  • 数字、日期、金额;
  • 否定词(不、禁止、除外);
  • 条件(如果、仅限、除非);
  • 引用所需的来源标识。

如果压缩后句子读不通,宁可不压缩。可读性也是上下文质量的一部分。


6. Lost in the Middle:位置会影响使用

长上下文里,模型对开头和结尾的信息更敏感,中间内容容易被忽略。这不是"模型不认真",而是注意力分布的现实。

三个应对:

  1. 按相关性排序:最相关的放前面;
  2. 关键证据放首尾:不要埋在中间;
  3. 减少无关上下文:压缩比"加更多"更有效。
位置 模型使用概率 建议
开头 高 放核心结论/最强证据
中间 低 放补充材料
结尾 高 放需要强调的约束/引用

6.1 上下文注入顺序的设计

推荐的结构:

text 复制代码
[系统约束]
[证据 1 | source=... | page=...]
[证据 2 | source=... | page=...]
...
[用户问题]
[输出格式:结论 + 引用 chunkId]

三个细节:

  1. 最强证据放最前,次强放最后,补充材料放中间;
  2. 每条证据带来源标识,方便模型引用;
  3. 把"只依据证据回答"的约束放在系统消息里,不要只放在用户消息。

如果证据之间冲突,在证据前加"以下资料可能存在版本差异,请以最新版本为准",并保留版本元数据。


6.2 多文档冲突怎么处理

冲突类型 例子 策略
版本冲突 新旧制度同时存在 按 effectiveDate 取最新,引用版本
权威冲突 官方文档 vs 社区帖子 权威排序,优先官方
范围冲突 通用政策 vs 特殊条款 特殊优先,并引用两条
事实冲突 两个文档数字不同 不强行回答,提示冲突并给来源

处理原则:

  1. 先按元数据(版本/权威/范围)排序;
  2. 冲突无法消解时,明确告诉用户"资料存在冲突",不要编一个折中答案;
  3. 引用必须覆盖冲突双方,让用户可判断。

7. 引用溯源:让答案可验证

引用不是"附几个链接",而是"每个结论能定位到证据"。

java 复制代码
public record Citation(
        String chunkId,
        String source,
        int page,
        String quote) {}

三个层次:

层次 做法 价值
文档级 回答后列出文档 弱,无法定位
块级 每个结论标注 chunkId 中,可定位
句子级 引用具体句子 + 页码 强,可验证

实现要点:

  • 块元数据保留 docId、page、section、version;
  • 让模型输出结构化引用(chunkId + quote),服务端校验 quote 是否真的在块里;
  • 引用不存在的 chunkId 直接判失败/重试;
  • 前端支持点击跳转到原文位置。

引用做得好,用户会更信任;引用造假,比不引用更糟。


7.1 引用格式与校验

让模型输出结构化引用:

json 复制代码
{
  "answer": "签收后 7 天内可无理由退货。",
  "citations": [
    {
      "chunkId": "policy-2026-11#3",
      "quote": "签收后 7 天内可无理由退货"
    }
  ]
}

服务端必须校验:

  1. chunkId 是否存在于本次上下文;
  2. quote 是否是块文本的子串;
  3. 页码/来源是否与元数据一致;
  4. 没有引用的结论是否允许(如"需要人工确认")。
java 复制代码
boolean valid(Citation c, Map<String, TextSegment> context) {
    TextSegment seg = context.get(c.chunkId());
    return seg != null && seg.text().contains(c.quote());
}

校验失败时,优先重试一次;仍失败则返回"无法确认来源",不要编造引用。


8. 什么时候不该上 Rerank

  • 初召回 Top-5 已经足够准;
  • 延迟预算极紧,Rerank 成为瓶颈;
  • 没有可用的 Rerank 模型/服务;
  • 语料很小,排序差异不大;
  • 真正的问题是分块/召回,Rerank 只是掩盖。

Rerank 是"精排补丁",不是"RAG 必备件"。


8.1 上下文预算怎么分

部分 建议占比 说明
System/约束 10% 人设、边界、输出格式
证据 60% 检索/重排后的块
用户问题 10% 当前问题/历史摘要
输出预留 20% 模型回答空间

如果证据超预算:

  1. 先去重/压缩;
  2. 再降 Top-K;
  3. 最后才截断单块(保留首尾与引用句)。

不要为了塞更多证据而牺牲输出空间;回答被截断比少一条证据更糟。


9. 评估:看排序,也看最终答案

指标 衡量 说明
nDCG@K 排序质量 关注相关性等级
Recall@K 候选集是否包含正确文档 Rerank 不改变召回
Answer Correctness 最终答案 人工/LLM Judge
Citation Accuracy 引用是否对得上 抽样校验
P95 延迟 端到端 Rerank 增量
Token 成本 上下文大小 压缩收益

评估要成对看:Rerank 提升了 nDCG,但 Token 成本翻倍,是否值得?答案取决于业务。

本文不提供未实测的"提升百分比"。用你的黄金集跑前后对比。


9.1 评估实验设计

成对比较,不要跨数据集:

项 基线 实验
候选集 同一批 Top-20 同一批 Top-20
Rerank 无 有
模型 同一模型/参数 同一模型/参数
数据集 同一黄金集 同一黄金集
指标 nDCG/Recall/答案/延迟 同上

至少记录:

  • 正确文档在候选集中的比例;
  • Rerank 前后的 nDCG@5;
  • 答案正确率(人工或 Judge);
  • P95 增量;
  • 失败案例类型。

如果提升只出现在"候选集本来就有正确文档"的子集,说明 Rerank 在做它该做的事;如果整体没提升,先看候选集质量。


9.2 评估结果的解读

  • 小样本上的小幅提升可能只是噪声;
  • 分场景看,避免平均值掩盖退化;
  • 用配对比较(同一 query 前后对比),不要跨数据集;
  • 报告置信区间/样本量,不报单一数字;
  • 业务指标(答案正确率)优先于排序指标。

Rerank 的评估不是"分数涨了就上线",而是"在目标场景、可接受延迟下,答案真的更好"。


10. 常见失败模式

现象 根因 修复
Rerank 后更差 候选集本身缺正确文档 先修召回
延迟翻倍 K 太大/串行 降 K、并行、批处理
引用对不上 块元数据缺失 补 docId/page
答案忽略证据 证据在中间/被压缩掉 重排位置、保留证据
压缩后事实错 生成式压缩改写 改用抽取式
成本上升 上下文没压缩 去重 + 抽取

10.1 Rerank 的降级策略

故障 行为 兜底
超时 放弃重排 返回原排序
限流 降级 只对 Top-5 重排
服务不可用 熔断 关闭 Rerank 开关
分数异常 丢弃 用原排序
成本超预算 降 K 或按 query 路由

三个工程要求:

  1. Rerank 必须有超时,且小于端到端预算;
  2. 结果可缓存(query + 候选哈希);
  3. 监控"重排后 Top-1 变化率",异常时自动降级。

Rerank 是增强项,不能成为单点故障。


11. FAQ

Rerank 能提升召回率吗?

不能。它只重排已有候选;正确文档不在候选集时,Rerank 无能为力。

候选 K 取多少?

看正确文档在初召回中的排名分布。常见起点是 20,结合 nDCG@K 与 P95 调。

Cross-Encoder 一定比 Bi-Encoder 准吗?

通常更准,但取决于模型与领域;成本也更高。用黄金集验证,不要假设。

生成式压缩安全吗?

涉及数字、否定、条件时不安全。优先抽取式;生成式只做辅助,并保留原文引用。

引用一定要句子级吗?

至少块级。强合规/高信任场景建议句子级,并做 quote 校验。


11.1 Rerank 需要和谁一起评估

至少三层一起看:

  1. 召回层:正确文档是否在候选集;
  2. 重排层:nDCG/Recall 是否改善;
  3. 生成层:最终答案与引用是否正确。

只看重排分数,可能忽略"模型根本没用证据"的问题。端到端指标才是上线依据。如果三层无法同时满足,优先保召回与答案正确率,再优化排序与延迟;更不要为了漂亮的重排指标牺牲召回,请务必记住这一点。


12. 上线检查清单

  • 正确文档在候选集中的比例已统计;
  • K 有评估依据,不是拍脑袋;
  • Rerank 有超时与降级;
  • 压缩保留数字/否定/条件;
  • 关键证据不在上下文中部;
  • 引用可定位到块/句子;
  • 引用做了存在性校验;
  • 有 nDCG/Recall/延迟的回归集。

12.1 一个最小重排接口

java 复制代码
public interface Reranker {
    List<Content> rerank(String query, List<Content> candidates, int topK);
}

实现可以是托管 API、本地模型或 LLM。无论哪种,都要满足:

  • 输入候选数有上限;
  • 超时可配置;
  • 失败时返回原排序;
  • 结果可缓存;
  • 有单元测试(固定输入、固定输出)。

接口小,替换成本才低。


13. 系列导航

  • 上一篇:A09《混合检索实战:BM25 + 向量 + RRF 的工程实现与消融实验》
  • 下一篇:A11《RAG 评估体系:黄金集、检索指标与 LLM-as-Judge 落地》
  • 全系列:《LangChain4j 生产级实战》20 篇路线图(专栏内查看)

相关推荐
Flynn Liang6 小时前
RAG 评估体系:黄金集、检索指标与 LLM-as-Judge 落地
java·langchain·rag·评估·langchain4j·检索指标
nice_lcj52019 小时前
【从零理解 Spring AI · 14】RAG 的四个环节:加载、切分、向量化、检索
java·spring boot·embedding·向量数据库·rag·spring ai·ai应用开发
典学长编程1 天前
RAG 进阶学习:索引构建阶段与检索增强阶段
向量数据库·rag·索引构建·文本向量化·mmr·索引增强·最大边际相关性
啊吧怪不啊吧1 天前
LangChain之绑定工具
langchain
nice_lcj5201 天前
【从零理解 Spring AI · 15】让检索更准:先宽召回,再精排序
java·后端·rag·spring ai·ai应用开发·检索优化·重排序
Flynn Liang2 天前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
努力的小郑2 天前
数据查到了,回答去哪儿了?一次百炼 + LangChain 排查实录
后端·langchain·agent
Java后端的Ai之路3 天前
RAG 还是 Agent 自主检索?Claude Code 删掉向量库之后,我把选型讲透了
开发语言·人工智能·prompt·agent·rag