RAG 重排序 Rerank 实战:用 bge-reranker 把 Top-K 命中率拉回来

前言

做 RAG 时最常见的体感是:向量召回「看起来有了」,最终回答却经常答非所问。根因多半不在大模型,而在召回排序:embedding 相似度擅长「语义近」,不擅长「对这个问题有没有用」。

本文用一套可复现的最小闭环,把 召回(Retrieve)→ 重排(Rerank)→ 再交给 LLM 跑通,并给出阈值阈值与落地注意点。适合已有向量库、想用交叉编码器把 Top-K 精度拉回来的同学。

关键词:RAG、Rerank、bge-reranker、交叉编码器、向量检索、Top-K

为什么只靠向量相似度不够

双塔 / embedding 检索把 query 和 doc 分别编码,再算余弦或内积。优点是快、可预计算;缺点是:

  1. 缺少交互信息:query 与 doc 没有在同一模型里做 token 级交互,细节对齐弱。
  2. 分数难校准:相似度高 ≠ 可回答该问题(常见于同主题但切题偏了的段落)。
  3. 长尾 query:术语、否定、约束条件(「不要用 GPU」「只要 Python」)容易被语义近邻淹没。

交叉编码器(cross-encoder)把 (query, doc) 拼在一起打相关性分,慢但准,适合对召回的前几十条做重排。

最小架构

text 复制代码
用户问题
  → Embedding 召回 Top-N(如 N=50)
  → Reranker 打分,取 Top-K(如 K=5)
  → 拼进 Prompt,交给 LLM 生成

经验值(可按延迟预算调):

阶段 建议规模 说明
向量召回 Top-30 ~ Top-100 宁多勿少,交给 rerank 筛
重排输出 Top-3 ~ Top-8 再多会稀释上下文、涨延迟
单段长度 300~800 字 / 512~1024 token 过长先切片再检索

环境与模型

以开源 BAAI/bge-reranker-base(或 bge-reranker-v2-m3)为例,本地或内网推理均可。

bash 复制代码
pip install torch transformers sentence-transformers
# 若用 FlagEmbedding:
# pip install FlagEmbedding

下面用 sentence-transformers 的 CrossEncoder 接口示意(也可换成 FlagEmbedding / vLLM / 自建 HTTP 服务)。

代码:召回后重排

假设你已经有一批候选文档(来自 FAISS / Milvus / ES 等):

python 复制代码
from sentence_transformers import CrossEncoder

# 首次会下载权重;生产环境建议预热并固定版本
reranker = CrossEncoder("BAAI/bge-reranker-base", max_length=512)

query = "无人机正射影像接缝处色差怎么用羽化减弱?"
candidates = [
    "正射 DOM 局部修补:图章修复与亮度对比度调整步骤......",
    "大模型 Function Calling 参数校验与重试策略......",  # 噪声
    "镶嵌线 shp + 羽化接缝:色差过渡的实操思路......",
    "OSGB 白膜重建中的三角面分离......",
]

pairs = [[query, doc] for doc in candidates]
scores = reranker.predict(pairs)  # 越高越相关

ranked = sorted(
    zip(candidates, scores),
    key=lambda x: float(x[1]),
    reverse=True,
)
top_k = ranked[:3]
for i, (doc, score) in enumerate(top_k, 1):
    print(f"{i}. score={score:.4f} | {doc[:40]}...")

把 top_k 的原文拼进 prompt 即可。注意:

  • 截断策略 :max_length=512 时,过长 doc 会被截断;切片时尽量保留「问句能命中的关键句」。
  • 批量推理:生产上对 50 条做 batch,比逐条 for 循环稳得多。
  • 分数阈值 :不同模型分数区间不同,用相对排序即可,不要写死「>0.8 才采用」这类绝对阈值。

和 LLM 的衔接(推荐写法)

python 复制代码
def build_prompt(query: str, passages: list[str]) -> str:
    blocks = []
    for i, p in enumerate(passages, 1):
        blocks.append(f"[资料{i}]\n{p}")
    ctx = "\n\n".join(blocks)
    return (
        "你只能依据下列资料回答;资料不足时明确说「资料不足」,不要编造。\n\n"
        f"{ctx}\n\n"
        f"用户问题:{query}\n"
        "请给出可操作的步骤,并标注引用了哪条资料编号。"
    )

重排的价值在这里体现:同一 token 预算下,更切题的段落进上下文,幻觉和跑题会明显下降。

评估:别只看「感觉好了」

至少做一小组离线集(20~50 条真实问题):

  1. Hit@K:标准答案段落是否进入重排后的 Top-K。
  2. MRR / nDCG:排序质量。
  3. 端到端:人工或 LLM-as-judge 看最终回答是否可用。

对比实验建议固定召回集合,只开关 rerank,避免把「换了向量模型」和「加了重排」混在一起。

常见坑

  1. N 太小:召回只有 5 条再 rerank,重排几乎没空间。
  2. 重复近重复段落:切片重叠过大,Top-K 全是同一段的变体------重排前做简单去重(标题/hash/simhash)。
  3. 延迟爆炸:每次请求实时加载模型;应常驻进程或独立 rerank 服务。
  4. 中英混排语料 :优先选多语 reranker(如 bge-reranker-v2-m3),并在评测集里覆盖真实语言比例。
  5. 把 rerank 分数写进业务硬阈值:模型一换阈值全废;用排序位次 + 业务规则更稳。

小结

  • 向量召回解决「候选从哪来」,交叉编码器重排解决「谁更该进上下文」。
  • 落地时固定:Top-N 召回 → Rerank → Top-K → LLM,并用 Hit@K 验证,而不是凭感觉调参。
  • 下一篇可以继续写:多路召回(向量 + 关键词)融合后再 rerank,以及如何把重排服务化。

互动提问:你现在的 RAG 流水线里,重排是用交叉编码器、LLM 打分,还是根本没做?如果做过,Top-N / Top-K 大概取多少、延迟能否接受?欢迎在评论区贴一下你的配置,我可以帮你看有没有明显浪费算力的点。

相关推荐
专注仿真2 小时前
卷积神经网络(CNN)算法详解
人工智能·python·算法·代码·大模型训练
isfox3 小时前
Python3 协程:异步编程的核心机制与实战
python
用户298698530143 小时前
Python 将 Markdown 批量转为 HTML:单文件与目录遍历两种实现
python·html·markdown
miofly3 小时前
GitHub 今日推荐|auto-ontology:给 Agent 装上企业数据的语义索引
python·开源·github
青少儿编程课堂3 小时前
蓝桥杯青少组图形化 + 全国青少年信息素养大赛 + 电子学会Scratch考级:校园成语接龙大挑战解析
c++·python·算法·bfs·信息学竞赛
用户0332126663673 小时前
使用 Python 扁平化 PDF 文档 【附代码示例】
python
Java后端的Ai之路3 小时前
Java并发编程07-等待通知机制优化循环等待
java·数据库·python
小小张说故事3 小时前
Python 的 async def 为什么能同时跑几千个任务?拆开协程对象、await 与事件循环那次让出
后端·python
ZZHow10244 小时前
神经网络与深度学习笔记_Day1
人工智能·pytorch·笔记·python·深度学习·神经网络·机器学习