ARTICLE / ai

RAG 高级优化:混合检索、重排序、查询改写与评测闭环

一、Naive RAG 的局限性

多数团队在搭建 RAG 系统时,第一版往往采用"向量检索 + LLM 生成"的最简架构。这种 Naive RAG 在 Demo 阶段效果尚可,一旦部署到生产环境,便会暴露出一系列系统性缺陷。

1.1 四种典型失败模式

失败模式现象根因
检索不相关检索到的文档与用户查询语义不匹配Embedding 模型对领域术语理解不足;纯向量检索无法捕捉精确关键词匹配
上下文污染检索结果中混入噪声文档,LLM 被误导检索结果未经过滤,Top-K 中包含低质量或不相关的 chunk
Lost in the Middle真正相关的文档被夹在中间位置,LLM 忽略LLM 对上下文中间位置信息的注意力显著下降(Liu et al., 2023)
部分上下文幻觉LLM 基于部分检索结果生成看似合理但实际错误的回答上下文不足以回答问题,LLM 用自身参数知识填充

1.2 从 Naive RAG 到 Advanced RAG

Naive RAG                          Advanced RAG
┌──────────────┐                   ┌──────────────────────────────┐
│ 用户查询      │                   │ 用户查询                      │
└──────┬───────┘                   └──────────┬───────────────────┘
       ▼                                      ▼
┌──────────────┐                   ┌──────────────────────────────┐
│  Embedding   │                   │  查询改写 (Query Rewrite)     │
└──────┬───────┘                   │  HyDE / Multi-Query / Step   │
       ▼                           └──────────┬───────────────────┘
┌──────────────┐                              ▼
│  向量检索     │                   ┌──────────────────────────────┐
│  Top-K       │                   │  混合检索 (Hybrid Search)     │
└──────┬───────┘                   │  向量 + BM25 + RRF 融合       │
       ▼                           └──────────┬───────────────────┘
┌──────────────┐                              ▼
│  拼接 Prompt  │                   ┌──────────────────────────────┐
│  LLM 生成    │                   │  重排序 (Reranking)           │
└──────────────┘                   │  Cross-Encoder 精排            │
                                   └──────────┬───────────────────┘
                                              ▼
                                   ┌──────────────────────────────┐
                                   │  上下文优化                    │
                                   │  压缩 / 去重 / 相关性过滤       │
                                   └──────────┬───────────────────┘
                                              ▼
                                   ┌──────────────────────────────┐
                                   │  生成优化 + 引用溯源           │
                                   └──────────┬───────────────────┘
                                              ▼
                                   ┌──────────────────────────────┐
                                   │  评测闭环 (RAGAS / TruLens)   │
                                   └──────────────────────────────┘

接下来我们逐一拆解每个优化环节的原理与实现。


纯向量检索依赖 Embedding 模型将文本编码为稠密向量,通过余弦相似度匹配。它的优势是语义理解能力强,但存在明显短板:

  • 无法精确匹配关键词:用户搜索"HTTP 429 错误"时,向量检索可能返回"HTTP 错误处理"的泛化内容,而非包含精确错误码的文档
  • 对专业术语不敏感:领域特有名词在 Embedding 空间中的表示可能不够精确
  • 数值和日期匹配差:向量检索对"2024年第三季度营收"这类查询效果不佳

BM25 关键词检索基于词频(TF)和逆文档频率(IDF)计算文本相关性,擅长精确匹配,但缺乏语义理解能力。两者的互补性使得混合检索成为生产环境的标配。

2.1 Reciprocal Rank Fusion (RRF)

RRF 是合并不同检索结果的主流算法。其核心思想是根据每个文档在不同检索列表中的排名来计算融合分数:

$$ \text{RRF}(d) = \sum_{i=1}^{n} \frac{1}{k + \text{rank}_i(d)} $$

其中 $k$ 通常取 60(原始论文推荐值),$rank_i(d)$ 是文档 $d$ 在第 $i$ 个检索列表中的排名。

2.2 实现代码

import numpy as np
from dataclasses import dataclass, field


@dataclass
class SearchResult:
    doc_id: str
    content: str
    score: float
    rank: int = 0


def reciprocal_rank_fusion(
    result_lists: list[list[SearchResult]],
    k: int = 60,
    top_n: int = 10
) -> list[SearchResult]:
    doc_scores: dict[str, float] = {}
    doc_map: dict[str, SearchResult] = {}

    for results in result_lists:
        for result in results:
            rrf_score = 1.0 / (k + result.rank)
            doc_scores[result.doc_id] = doc_scores.get(result.doc_id, 0.0) + rrf_score
            if result.doc_id not in doc_map:
                doc_map[result.doc_id] = result

    sorted_docs = sorted(doc_scores.items(), key=lambda x: x[1], reverse=True)[:top_n]

    fused = []
    for rank, (doc_id, score) in enumerate(sorted_docs, start=1):
        result = doc_map[doc_id]
        result.score = score
        result.rank = rank
        fused.append(result)
    return fused

k=60 是 RRF 论文的推荐值,物理含义是"对排名靠后的结果给予衰减但不归零的权重"。k 越小,头部结果优势越明显;k 越大,融合越接近等权投票。实践中 k 在 20-100 之间对最终指标影响有限,真正重要的是参与融合的每路结果本身要有质量。

RRF 的隐藏优势是它只需要排名、不需要分数。这让它天然适合融合"分数不可比"的检索路径——BM25 的 TF-IDF 分数、向量的余弦相似度、图检索的跳数得分。这也是它成为默认融合方案的原因。

2.3 加权融合与分数归一化

若一定要用分数加权(例如业务上更信任向量检索),必须先归一化再融合:min-max 或 z-score,然后 score = α·norm(vec) + (1-α)·norm(bm25)。α 不能拍脑袋——在评测集上扫 α∈[0,1] 取 nDCG@10 最优值,多数中文技术语料落在 0.5-0.7。

生产上还有一类"动态权重"做法:查询含型号、错误码、专有名词等强字面特征时提高 BM25 权重,纯语义描述型查询提高向量权重。用一个规则或轻量分类器判断查询类型即可,成本很低但收益明显。


三、重排序(Reranking)

3.1 为什么检索之后还要再排一次

Bi-Encoder 把 query 和 doc 独立编码,算力上可行但无法建模词级交互;Cross-Encoder 把 (query, doc) 拼在一起过一遍 Transformer,能精确判断相关性,代价是不能用于全库扫描。于是形成标准分工:召回阶段粗取 Top-50~100,精排阶段只在这几十条上做 Cross-Encoder。

方案类型中文表现延迟(Top-50→Top-5)
bge-reranker-v2-m3开源 Cross-Encoder,可自托管强80-200ms(GPU)
Qwen3-Reranker开源,多语强视尺寸而定
Cohere RerankAPI中上150-400ms
Voyage / Azure RerankAPI中150-400ms
LLM-as-Reranker大模型逐条打分上限最高也最贵秒级

3.2 精排的三个实战要点

输入截断:Cross-Encoder 有最大长度限制(常见 512 / 1024 Token),长 chunk 要按句切段取最佳分段得分,或先做句子级压缩。直接砍掉尾部会丢失结论句——这是很多系统"加了精排反而更差"的真实原因。

分数阈值过滤:精排的真正价值不只是排序,而是给出"没有一条相关"的判断。务必设阈值(sigmoid 归一化后 <0.3 全部丢弃)并触发兜底策略,而不是永远返回 Top-5。

位置重排:精排完成后按"最相关放头尾、次相关放中间"拼装上下文,专门治 Lost in the Middle。


四、查询改写(Query Transformation)

用户提问与文档写作语言之间存在系统性鸿沟:口语 vs 术语、省略指代、多意图混杂。四类手段按成本递增:

手段做法适用风险
指代消解结合对话历史把"那它的缺点呢"改写为完整独立查询多轮对话必做历史窗口不足会改错
查询扩展一次生成 3-5 个语义变体并行检索后融合术语覆盖不足、同义词多成本 ×N,结果可能发散
Step-back先抽象到更上位的问题(“这类报错的通用成因”)再检索具体案例缺少通用文档抽象过度会召回无关内容
HyDE让 LLM 生成假设文档,用其向量检索零样本领域、问答对少假设文档自带幻觉

多轮对话还有一个常被漏掉的环节:不是每轮都该检索。追问"再具体一点"时应复用上一轮上下文,而不是重新检索——为此需要一个轻量路由判断"检索 / 复用 / 澄清"三选一。

子问题分解针对复合查询:“对比 A 与 B 的性能和成本"拆成四个可独立检索的子查询,各自召回后聚合。注意子查询要能独立成句、可独立验证,数量控制在 2-5 个,否则语义关联会断掉。


五、评测闭环

5.1 四个核心指标与它们的分工

RAGAS 一类框架给出的两组指标各自回答不同问题:

  • 检索侧(Context Precision / Context Recall):该找到的文档找到了吗?找到的内容有多少是噪声?
  • 生成侧(Faithfulness / Answer Relevancy):答案是否只基于给定上下文?是否真在回答这个问题?

指标必须配对使用——Context Recall 高但 Faithfulness 低,说明上下文里混进了互相矛盾的片段,模型开始"择优选信”;Faithfulness 高但 Answer Relevancy 低,说明检索到了相关但不对题的内容。单看任何一个都会误判。

5.2 自建评测集的六步法

  1. 从线上真实查询抽样 100-300 条,按意图分层(事实、对比、流程、故障排查)
  2. 人工标注 gold answer 与应命中的 chunk ID(一次性投入,长期复利)
  3. 定义可复现的指标口径(Recall@5、Recall@20、MRR、nDCG@10)
  4. 每次改动跑全量评测,保留 baseline 对照与 diff 报告
  5. 用 LLM-as-Judge 做大规模回归,抽样 10% 人工复核裁判一致性(Kappa ≥0.7 才可信)
  6. 线上反馈闭环:点踩样本、人工兜底样本自动回流评测集

最关键的一条纪律:任何"看起来更好"的改动——换 Embedding、调 chunk 大小、加 reranker、改 Prompt——都必须走同一个评测集。没有它,优化会退化成凭感觉调参,而这条链路上凭感觉的调整常常是负收益。

5.3 归因顺序

Badcase 分层定位的固定顺序是解析 → 分块 → 召回 → 精排 → 生成。先全文搜索目标原文确认它在库里;再确认它在检索/精排阶段是否进入 Top-K;只有它排名靠前而答案仍错,才该动生成层。跳过前两步直接改 Prompt 是最常见的无效努力。


六、优化优先顺序:一份务实清单

按投入产出比排序,多数团队应当照此顺序推进:

  1. 文档解析与表格处理——垃圾进垃圾出,这层不修好后面全白做
  2. 分块策略与上下文前缀(chunk 长度扫描 + 标题路径拼接)
  3. 混合检索 + RRF(治专有名词、错误码召回失败)
  4. Cross-Encoder 重排 + 阈值兜底(同时提升精度与拒答能力)
  5. 指代消解与多轮路由(对话产品的必需项)
  6. 评测集与回归流程(让以上每一步的收益可验证)
  7. 子问题分解、HyDE、知识图谱、Agentic 检索——留给前面全做完仍解决不了的那 10%

RAG 高级优化的本质不是堆算法,而是建立一条能被度量的链路:每个环节都有指标、每次改动都有对照、每个 badcase 都能归因。有了这套闭环,优化才是可累积的工程;没有它,所有"调优"都只是把系统换到另一种不确定状态。

(数据来源说明:本文为公开资料与技术方法整理,涉及的性能、比例与厂商能力描述以各官方文档与公开报告为准,具体数值会随版本变化;未在自有环境逐项复测的数据均以’约/通常/实践区间’表述。)

本文面向授权安全研究与防御建设,未经授权对任何系统实施文中技术属违法行为。