混合检索 + 重排序

生产实践 · 约 9 分钟阅读 ·

纯向量检索常漏掉精确关键词匹配;纯 BM25 又漏掉改写表述。混合检索同时跑两路、合并候选列表,再由 reranker 在 LLM 看到内容前修正最终顺序。

为何用混合?

bi-encoder 向量检索擅长语义相似 ——「vehicle」能匹配「car」。BM25 擅长词面重叠 —— 产品 SKU、法律条款编号、错误码。企业 RAG 里,正确 chunk 往往需两种信号。混合检索加宽召回;重排序在合并后的短名单上提供精度。

混合检索找到更多对的干草;重排序在里头找到针。

检索—合并—重排序范式

1. BM25 top 50        ─┐
                       ├─▶ dedupe + merge ─▶ ~80 unique candidates
2. Vector top 50      ─┘
3. Cross-encoder rerank on merged list ─▶ top 5–10 for LLM

单路检索时宁可召回更宽 —— 每路可返回 40–60 条。去重后仍希望有 50–100 个唯一 chunk 进入 reranker。top-k 默认值见 为 RAG 加重排序

融合策略

方法原理适用场景
倒数排名融合(RRF)分数 = 各列表 Σ 1/(k + rank)默认首选 —— 无需分数归一化
线性组合α·向量分 + (1-α)·BM25 分两路分数已在你数据上标定
并集 + 仅 rerank拼接列表、去重、跳过融合reranker 够强且延迟预算允许

最小 Python 示例

from rank_bm25 import BM25Okapi
from sentence_transformers import CrossEncoder

reranker = CrossEncoder("BAAI/bge-reranker-base")

def hybrid_rerank(query, corpus_chunks, vector_hits, bm25_hits, top_n=5):
    # Merge by chunk id, preserve text
    seen, merged = set(), []
    for chunk_id, text in vector_hits + bm25_hits:
        if chunk_id in seen: continue
        seen.add(chunk_id)
        merged.append(corpus_chunks[chunk_id])
    pairs = [(query, c) for c in merged]
    scores = reranker.predict(pairs)
    ranked = sorted(zip(scores, merged), reverse=True)
    return [c for _, c in ranked[:top_n]]

Elasticsearch 混合查询、Weaviate 混合搜索、LlamaIndex QueryFusionRetriever 等框架封装同一思路 —— 多后端召回,再可选 rerank。

调优建议

看重排序如何收拾乱序短名单

Demo 含 bi-encoder 代理列 —— 粘贴干扰项,看 cross-encoder 如何纠正。

打开 Demo →

继续阅读