RAG 工程与检索系统设计

第13章 Hybrid Search:向量召回与关键词召回的融合

作者 杨艺韬 · 11,235 字

第13章 Hybrid Search:向量召回与关键词召回的融合

“Fusion is not a compromise. It’s a way to get properties neither parent had.” — 稀疏与稠密融合的核心观念

本章要点

  • Hybrid Search 不是”先 dense 后 BM25”的串联,是两路并行召回后的排序融合
  • 四类主流融合方法:RRF(简单且强)、权重线性(可调但要标定)、ColBERT 重打分(精度上限)、纯 rerank(让 cross-encoder 做最终仲裁)
  • RRF 是生产默认——无需标定、跨量纲稳健、代码 20 行
  • 选型看三个因素:候选规模 / 是否有 rerank / 延迟预算
  • 融合前各路独立好是前提——一路召回差到底、融合救不回来

13.1 为什么不是简单相加

已知 dense 和 BM25 各自产出 top-50 候选带分数。最朴素的融合:两个分数加一起取 top-k。问题:

  • 分数不同量纲:BM25 分数无上限(典型 5-30)、cosine 相似度 [-1, 1]——直接加 BM25 淹没 cosine
  • 分布偏移:同一个 query 下 BM25 分数的尺度和另一个 query 不同(term 稀有度差异)——跨 query 的绝对分数不可比
  • 分数不校准:BM25=10 的 doc 不等于”相关性 = 10”、cosine=0.85 也不等于”相关 85%“——都是相对排名意义上的信号

融合方案的核心就在绕开这些坑。

flowchart TB
    Q[query] --> D[Dense 召回 top-50]
    Q --> S[BM25 召回 top-50]

    D --> FUSE{融合策略}
    S --> FUSE

    FUSE --> M1[RRF]
    FUSE --> M2[权重线性]
    FUSE --> M3[Rerank 仲裁]
    FUSE --> M4[ColBERT 重打分]

    M1 & M2 & M3 & M4 --> OUT[统一 top-k]

13.2 RRF:最朴素也最强的方案

RRF(Reciprocal Rank Fusion,Cormack 2009):不看分数、只看排名。

RRF_score(doc) = Σ_{each ranker} 1 / (k + rank(doc))

k 是常数(典型 60),rank 是该 doc 在某路召回里的位置。

直觉

排第 1 贡献 1/61、排第 2 贡献 1/62……排名越靠前贡献越大、衰减快。两路都排前的 doc 合计分数最高。

例子

query 查询两路召回:

docdense rankBM25 rankRRF 分 (k=60)
A1101/61 + 1/70 = 0.0306
B511/65 + 1/61 = 0.0317
C331/63 + 1/63 = 0.0317
D1∞ (未命中)1/61 = 0.0164
E11/61 = 0.0164

两路都能命中的 C 和 B 分数最高——两个独立信号达成共识的 doc 更可信。这正是 hybrid 的本质。

RRF 的工程优势

  • 无标定:不用训练、不用业务数据调参,直接跑
  • 跨量纲稳健:BM25 / cosine / ColBERT score 都能融合
  • 代码极简:20 行 Python 搞定
  • 效果强:多次公开 benchmark 里 RRF 接近或超过精调的线性加权

Elasticsearch 8.9+、Qdrant、Milvus 都内置了 RRF API。默认 k=60(来自 Cormack 原论文)、实践中 k ∈ [30, 80] 都稳。

RRF 的局限

  • 丢失了信号强度:排第 1 和第 2 的 RRF 分数相差很小,但如果实际分数差巨大(0.95 vs 0.3),RRF 无法体现
  • 两路贡献固定等权——如果一路明显更可信(domain-specific),RRF 没法调

多数场景 RRF 够用。追求极致质量或有明确权重信号时用下节的线性加权。

13.3 权重线性融合

思路:把各路分数归一化到同一量纲后加权求和。

score(doc) = α × norm(dense_score) + (1-α) × norm(BM25_score)

归一化方法

  • Min-Max(x - min) / (max - min) 把分数压到 [0, 1]。简单但对异常值敏感
  • Z-score(x - mean) / std 转正态分布。稳健但需要统计量
  • Softmaxexp(x) / Σ exp(x) 转概率分布。适合概率解释场景

生产常用 min-max per query——每个 query 下各路的 top-k 分数独立归一。

α 的选择

α = dense 权重(1-α = BM25 权重)。典型 α=0.5-0.7——dense 略重但 BM25 不忽略。

标定方法:在 gold set 上扫 α ∈ {0.3, 0.4, …, 0.9},看哪个值 recall@10 最高。不同 domain 的最优 α 差别可以很大:

  • 通用问答:α=0.6-0.7(dense 主导)
  • 法律 / 医疗:α=0.4-0.5(BM25 更重)
  • 代码检索:α=0.5-0.6(均衡)

线性融合 vs RRF 的选择

  • 已有业务 gold set 能标定:线性融合精度可能略高 1-3 个点
  • 没有 gold set / 追求稳健:RRF

生产推荐:MVP 阶段用 RRF 快速起步;业务成熟后做 A/B 对比线性融合、取胜者

13.4 rerank 作为融合器:让 cross-encoder 做仲裁

更激进的方案:不显式融合,让 cross-encoder reranker(第 14 章)直接对两路 union 的候选重打分。

流程:

  1. Dense 召回 top-k1(如 50)
  2. BM25 召回 top-k2(如 50)
  3. Union 去重 → 候选集 top-100 左右
  4. Cross-encoder 对这 100 条 (query, chunk) 重新打分
  5. 取 top-k(如 5)

这种方式下 “融合” 其实是 rerank 阶段一次性完成 的——候选集只要足够宽就行、不用关心各路分数。

优点

  • 逻辑简单:没有权重、没有 k 常数、没有归一化
  • 精度上限最高:cross-encoder 看 (query, doc) 对全文、比任何静态融合更准
  • 容错强:某一路召回差、另一路能补——rerank 从 union 里挑即可

缺点

  • 延迟:rerank 100 个候选比 20 个多 5 倍耗时
  • 成本:cross-encoder 推理不便宜
  • rerank 必须有:没有 rerank 能力的系统走不通这条路

何时选这条路

  • 对质量要求最高、延迟可接受(500ms+)的场景
  • 已经有稳定 rerank 服务
  • 融合权重难调、不想维护

13.5 ColBERT 重打分:精度上限

ColBERT v2(第 9 章提过)可以作为融合器——把 dense + BM25 的 union 候选用 ColBERT 的多向量相似度重打分。

ColBERT 的 late-interaction 捕捉到 query 每个 token 和 chunk 每个 token 的匹配关系——比 single-vector cosine 精细得多、比 cross-encoder 轻量。

BEIR 上 ColBERT v2 重打分比 RRF + 普通 rerank 高 1-3 个点。但:

  • 需要在索引时存储 per-token 向量(存储膨胀 30-100×)
  • 部署需要 ColBERT 推理引擎
  • 生态不如 cross-encoder rerank 成熟

实操:超大规模 + 追极致质量的少数场景用;绝大多数团队用 RRF 或 rerank 仲裁就够了。

13.6 融合前的预处理:去重

两路召回会有同一个 doc 的重复出现——必须去重后再融合。去重维度:

  • chunk_id 完全相同:必然去重
  • 同 doc 的不同 chunk:保留各自、但 chunk 多的 doc 容易在融合里占便宜——生产通常限制”每个 doc 最多保留 3 chunk”
  • 内容高度相似:用 minhash / simhash 检测、相似度 > 0.9 合并

去重发生在融合之前还是融合之后有讲究:

  • 之前去重:融合只处理独立 chunk、排序干净
  • 之后去重:保留每路独立信号、再综合——容错好

生产常见:同 chunk_id 之前去重(精确)、同 doc 多 chunk 之后聚合

13.7 多路召回的一般化

Hybrid search 不必只有 dense + BM25 两路。生产 RAG 常见三路以上:

  • Dense embedding
  • BM25 / SPLADE
  • Metadata filter(结构化查询走 SQL-like)
  • Graph search(如果有实体图)
  • Keyword alert(特殊术语专门召回)

N 路召回用 RRF 融合天然支持(Σ of N rankers)。每加一路候选集扩大、融合分数越鲁棒。但:

  • 延迟增加(并行但有网络 overhead)
  • 成本增加(每路都要维护独立索引)

实操:默认 2 路(dense + BM25)。需求明确时按场景加特定路,不要”先加上看看”。

flowchart LR
    Q[query] --> R1[Dense]
    Q --> R2[BM25]
    Q --> R3[Metadata filter]
    Q --> R4[Graph]

    R1 & R2 & R3 & R4 --> RRF[RRF 融合]
    RRF --> RR[Rerank top-20]
    RR --> OUT[top-5]

13.8 实测:融合方案效果对比

某企业 FAQ RAG 在 gold set(500 条 QA)上的对比(典型数字、仅示意):

方案recall@10MRRP99 延迟
仅 Dense0.760.5425ms
仅 BM250.710.5115ms
RRF (50+50)0.850.6235ms
线性 (α=0.6)0.860.6335ms
Union + rerank0.890.68280ms
Union + ColBERT0.900.69120ms

观察:

  • Hybrid(任一方案)比单路强 10+ 个点——融合收益远大于选哪种融合
  • RRF 和线性差别不大、RRF 无需标定所以更推荐
  • rerank 和 ColBERT 再多 4-5 点,成本是延迟翻 4-8 倍

取舍原则:先上 hybrid(RRF 就行)、再判断加不加 rerank

13.9 RRF 的进阶:加权 RRF 和带 offset RRF

经典 RRF 每路等权,但生产里常需要 两路不等权——某路更可信时应该占主导。Weighted RRF(wRRF):

wRRF(doc) = Σ_i w_i / (k + rank_i(doc))

w_i 是第 i 路的权重。例子:dense=0.6、BM25=0.4 时、wRRF 保留 RRF 的跨量纲稳健性、同时反映各路的可信度。

带 offset 的 RRF1 / (k + rank + offset_i)——给特别可信的一路降低 k 让其分数更陡。这比 wRRF 更细——但调参复杂。多数场景用 wRRF 就够。

k 常数的调优

RRF 的 k=60 是 Cormack 原论文经验值。实际对 k 做扫描:

k 值特性适用
k=10前几名权重极大、排名末尾几乎不贡献top-k 小、追求精度
k=30前 20 名有影响、之后迅速衰减中间档、次常用
k=60标准选择、平滑衰减通用
k=100衰减很平缓、各排名都有贡献候选集大、权重分散

gold set 上扫 k ∈ {10, 30, 60, 100}、看 recall@k 峰值。典型最优 k 在 30-80 之间。

13.10 动态融合:按 query 类型切换

不同 query 适合不同融合权重。例子:

  • 查询 "订单 OD-12345":包含订单号——BM25 权重应加大
  • 查询 "怎么理解私有化部署":概念性问题——dense 权重应加大
  • 查询 "某商品价格":结构化——metadata filter 优先

实现 query classifier(基于规则 or 小模型)将 query 打标、按标签选融合权重或路由:

def route(query):
    if has_structured_id(query):
        return {"bm25": 0.8, "dense": 0.2}
    if is_conceptual(query):
        return {"bm25": 0.3, "dense": 0.7}
    return {"bm25": 0.4, "dense": 0.6}  # default

这种”自适应融合”比全局一个 α 强 2-5 个点。但增加一个组件(classifier)维护成本、需要业务稳定后做。

13.11 MMR:多样性重排

融合产出 top-k 后、还有一个常被忽视的问题——多样性。Rerank / 融合只管相关度、不管 top-k 内部是否过于相似。举例:问”企业版功能”、top-5 可能都是关于 SSO 的 chunk——其他功能(LDAP / 审计 / API 网关)根本没出现。

MMR(Maximal Marginal Relevance,Carbonell & Goldstein 1998)的解法:

MMR_score(d) = λ × rel(d, q) - (1-λ) × max_{d' ∈ selected} sim(d, d')

选下一个 chunk 时、平衡两件事:

  • rel(d, q):和 query 的相关度
  • max sim(d, d'):和已选 chunk 的最大相似度

相关但和已选 chunk 相似度高的——降权。λ 典型 0.5-0.8——偏向相关度但保留一定多样性。

MMR 适用场景

  • 广覆盖查询:“企业版有什么功能”——需要多个独立功能点、不是同一功能重复讲
  • 对比场景:“A 和 B 的区别”——要同时召回 A 和 B 的内容
  • 研究式问题:“这个领域的主流方案有哪些”——需要多流派各自代表

不适用场景:单一事实查询(“企业版 SSO 价格”)——重复的正确答案反而是信号增强、不是冗余。

何时接入 MMR

生产 RAG 里 MMR 有三个可能位置:

  • hybrid 融合之后 / rerank 之前:对融合 top-30 做 MMR、留 15 条送 rerank。不让 rerank 看到冗余候选
  • rerank 之后 / packing 之前:rerank top-10 做 MMR 留 top-5。最常见位置
  • 不用 MMR + rerank 精调:部分场景 rerank 训练时已考虑多样性、不需要额外 MMR

多数项目先上 rerank、看 badcase 里是否有”同质答案堆积”、再考虑加 MMR。不要过早优化。

13.12 融合里的常见坑

坑 1:两路 top-k 不等

dense 召回 50、BM25 召回 20——融合时两路排名量纲不同。对齐方法:要么两路都召回相同 k、要么 RRF 用完整排名(不是 top-k 裁剪后的)。

坑 2:分数归一化跨 query 污染

把所有 query 的分数一起 min-max——某 query 分数被另一 query 的极值”归一化”到误值。必须 per-query 归一化

坑 3:BM25 分数 NaN

空召回或单 term 命中时 BM25 分数可能极端值。融合前过滤 NaN 和 inf。

坑 4:融合后 chunk 数量失衡

某路多的 chunk 在融合里占上风、另一路的独特信号被稀释。限制每路最多贡献 X 条候选后再融合。

坑 5:融合分数当相关度 threshold

“融合分 < 0.5 过滤掉”——这是错的。融合分数不是相关度量纲——只能做排序、不能做阈值过滤。

13.13 工程实现:融合服务的位置

Hybrid search 的融合逻辑应该放在哪一层?三种架构:

架构 A:应用层融合

应用代码分别调 dense 库和 BM25 库、内存里做 RRF、返回融合结果。

  • 优点:逻辑在代码里、容易改
  • 缺点:两次网络往返、延迟差
  • 适合:中小规模、自定义逻辑多的项目

架构 B:向量库原生融合

现代向量库(Qdrant 1.10+、Milvus 2.4+、Weaviate)原生支持 hybrid search——一次 API 调用内部做两路召回 + 融合返回。

  • 优点:一次 RPC、低延迟、维护成本低
  • 缺点:融合算法受限于向量库提供的(通常只有 RRF)

架构 C:独立融合服务

给融合逻辑一个独立 microservice——调各个检索组件、做融合、对外统一接口。

  • 优点:融合逻辑集中、可独立迭代、可加复杂路由
  • 缺点:多一跳、多一个服务要维护

选型:MVP 用 A、中期用 B、大型系统选 C

13.14 融合的可观测性

生产 Hybrid 系统要监控的指标:

  • dense_only_recall / bm25_only_recall / hybrid_recall:分别跑、看融合收益
  • overlap_rate:两路 top-20 的重合率——过低说明两路看的是完全不同的东西(可能有一路异常)、过高说明两路冗余
  • bm25_contrib_rate:hybrid 最终 top-5 里来自 BM25 独有召回的比例——这个高说明 BM25 在补 dense 的短板
  • fusion_latency:融合本身的延迟(通常 <5ms、远高就是代码实现问题)

每天看一次、异常告警。融合逻辑默默退化是 hybrid 系统最隐蔽的故障。

13.15 融合参数的离线调参工作流

前面几节给出了 α、k、各路权重的常见取值,但具体项目里这些参数怎么调才靠谱?一次性拍脑袋标一个 α 就上线是反模式——业务数据分布不断变化,融合参数同样要持续回归。

调参 pipeline

flowchart LR
    classDef data fill:#fed,stroke:#c60
    classDef step fill:#def,stroke:#06c
    classDef guard fill:#efe,stroke:#080

    GOLD[Gold set<br/>train/dev/test 分片]:::data --> SWEEP[参数扫描<br/>在 train 跑]:::step
    SWEEP --> PICK[dev 选最佳点]:::step
    PICK --> TEST[test 锁定分数]:::guard
    TEST --> SHADOW[线上 shadow<br/>真实流量对比]:::step
    SHADOW --> AB[A/B 小流量]:::step
    AB --> FULL[全量发布]:::step
    FULL --> MON[持续监控<br/>gold 日跑]:::guard

六步不可跳。最常见的错误是从 “在 train 上扫出最好的 α” 直接上线——等于把 dev/test 污染吞了。

Gold set 的防过拟合

调参 gold set 必须分三份:

  • train(60%):参数扫描用
  • dev(20%):选最佳参数组合
  • test(20%):最终评估、不参与任何决策

Train 和 dev 选错的后果是参数”在你的 gold 上最好但上线差”。test 锁定的分数才有对外承诺的价值。

跨 domain 子集:如果业务跨多个 domain(技术文档 / 销售 FAQ / 法律条款),gold set 要保证每个 domain 都有代表样本、最终参数在每个 domain 都不退化。只看总体 recall 会掩盖某个小 domain 被牺牲的情况。

Query log replay 发现 badcase

Gold set 永远不完整——真实用户问的问题总会超出你的想象。生产 hybrid 调参必做的补充:

  • 每周抽线上 query log 样本 N 条(通常 200-500 条)
  • 人工或 LLM 辅助标:“top-5 里有正确答案吗”
  • 累加到 gold set 的 badcase 池、定期回归

Badcase 池比初始 gold set 更能反映当前业务——gold set 是守卫已知、badcase 池是发现未知

调参频率

不是调一次就永久生效的。触发重调的信号:

  • 业务数据分布变:新产品线上线、文档体量翻倍、用户群体变化
  • 嵌入模型升级:dense 分布变了、α 需重标
  • 监控指标退化:recall@10 在 gold 上下降 > 2%
  • badcase 池累积够:新增 badcase ≥ 50 条时重新扫参

典型节奏:MVP 阶段每月一次、稳定期每季度一次、模型升级后立即调。

Shadow 与 A/B 的分工

shadow 和 A/B 都是线上验证,作用不同:

  • shadow:复制真实流量到新参数、对比检索结果(召回集交并差)、不影响线上。发现”新参数和旧参数的召回差异”——定位是否存在结构性退化
  • A/B:小流量(1-5%)真实发到新参数、对比业务指标(点击率、答案满意度、session 时长)。确认”新参数带来真实价值”

只跑 shadow 不上 A/B——没有真实业务指标背书、不敢全量。只跑 A/B 不做 shadow——发现退化时已经影响用户、回滚成本高。

常见反模式

  • 一次性调参就停:参数和业务同步演化、静态参数会悄悄退化
  • 只看总体 recall:掩盖 domain 倾斜、要拆分看
  • 在 train 集过拟合 α:正确做法是 train 扫、dev 选、test 锁
  • badcase 只修不回归:修完某个 badcase 但没加进 gold——下次同类问题可能又错
  • 调参日志不落库:每次调参的参数、gold 分数、决策人、时间都要留档、回滚时才有依据

调参是工程纪律不是艺术直觉——每一次参数变动都要可追溯、可回滚、可归因

13.16 边界 query 下的 hybrid 失效与防线

前 15 节默认 query 是”正常长度 + 正常表达”的查询。生产里大量查询位于边界:一个字、十句话、纯代码符号、中英混杂。这些边界 query 让 dense 或 sparse 其中一路失效、hybrid 融合也救不回来——除非针对性地加防线。

四类边界 query 与各自失效模式

flowchart LR
    classDef q fill:#fed,stroke:#c60
    classDef fail fill:#fdd,stroke:#c00
    classDef fix fill:#dfd,stroke:#080

    Q1[超短 1-2 词]:::q --> F1[dense 语义不足<br/>BM25 独木难支]:::fail
    Q2[超长 > 50 词]:::q --> F2[embedding 噪声<br/>BM25 被低 IDF 稀释]:::fail
    Q3[多实体对比]:::q --> F3[单路召回只命中<br/>一个实体]:::fail
    Q4[代码符号]:::q --> F4[dense 完全跑偏<br/>tokenizer 切错]:::fail

    F1 --> X1[强制双路 + HyDE 扩展]:::fix
    F2 --> X2[query 摘要缩到 < 20 词]:::fix
    F3 --> X3[拆解成子 query<br/>分别召回 union]:::fix
    F4 --> X4[代码专用 tokenizer + 精确匹配加权]:::fix

失效 1:超短 query

典型例子:“SSO”、“退款”、“价格表”。一两个词、信息极少:

  • Dense 路:embedding 1-2 词产生的向量噪声大、召回漂移到同主题但不相关的 chunk
  • BM25 路:命中一堆高频词 chunk、精度不够
  • 融合:两路都差、RRF 融合是”差 + 差”

防线:短 query 强制走 query expansion(第 15 章同义词扩展 + HyDE),把 1-2 词扩到 5-10 词再送两路召回。判定阈值:query token 数 < 3 时自动扩展。

失效 2:超长 query

典型例子:用户粘贴一整段错误日志(“求帮忙分析一下下面这段报错,2026-04-20 14:23 ERROR…”)、或描述详细到几百字:

  • Dense 路:长输入 embedding 有饱和——信号被稀释、向量表达不如短 query 尖锐
  • BM25 路:stop word 多、低 IDF 词淹没关键词
  • 融合:两路都精度下降

防线query 摘要——用小 LLM 把长 query 压缩成 15-30 词关键摘要,原 query 和摘要都送检索、两次结果 RRF 融合。典型实现:Haiku 级模型做压缩、延迟 150-300ms、收益远大于成本。

失效 3:多实体对比 query

典型例子:“企业版和专业版 SSO 有什么区别”、“A 产品 vs B 产品的定价”:

  • Dense 路:embedding 把”对比”语义压成一个点、召回通常偏向其中一个实体(embedding 空间里出现更多的那个)
  • BM25 路:某实体的 chunk 数多时、BM25 被它主导
  • 融合:单路压倒多路

防线query 拆解(第 15 章 §15.3)—— LLM 把”A vs B”拆成两个子 query:

  1. “A 的 SSO 功能”
  2. “B 的 SSO 功能”

各自跑 hybrid、结果 union 去重、统一送 rerank。RRF 融合发生在拆解之后,不是之前。

判定标准:query 里含对比词(vs、区别、对比、差异)且识别出 ≥ 2 个已知实体。

失效 4:代码 / 技术符号 query

典型例子:“UserService.login()”、“getUserById”、“SELECT … WHERE user.id = ?”:

  • Dense 路:embedding 对代码符号的语义空间稀疏、getUsergetUsers 可能映射到同一点、或相反——完全跑偏
  • BM25 路:如果 tokenizer 按空格切、UserService.login 被切成 UserService + login——丢失整体符号
  • 融合:dense 路帮倒忙、BM25 路切错

防线:第 12 章讨论的 代码专用 tokenizer + 精确符号匹配路径

  • query classifier 识别代码 query(驼峰命名、含 .::、反引号包裹)
  • 走独立的符号索引(tree-sitter AST 切词)
  • 精确匹配的 chunk 加权放大(×2-3),hybrid 融合后仍保留精确命中的 top 位置

代码 query 是 hybrid 不能套用通用公式的典型——必须分路由。

判定和路由

四类边界 query 要在 query understanding 阶段识别、走不同 pipeline:

def route_hybrid(query):
    tokens = tokenize(query)
    if len(tokens) < 3:
        return "short"         # → 扩展 + hybrid
    if len(tokens) > 50:
        return "long"          # → 摘要 + hybrid
    if has_comparison_entities(query):
        return "multi_entity"  # → 拆解 + hybrid union
    if is_code_like(query):
        return "code"          # → 符号索引 + 精确加权
    return "normal"            # → 标准 hybrid

路由的代价是一次 classifier(小模型 + 规则 < 20ms)、收益是边界 query 的 recall 提升 10-20 点。

边界 query 的可观测性

各类 query 的占比要监控——不同业务分布不同:

  • 客服 FAQ 里短 query 占 40%+
  • 代码搜索里代码 query 占 80%+
  • 研究文献里长 query 占 30%

每类 query 分别跑 gold set 看 recall——总体 recall 好看、某类 query 悄悄崩掉的情况常见。分 segment 指标是防止这类”局部失败全局看不见”的关键。

为什么 hybrid 不是自动解

前面章节把 hybrid 描述成”把 dense 和 sparse 融合就完事”——这节是反驳:融合不自动抹平输入侧的问题。query 表达本身是检索质量的上游、hybrid 是下游。上游有结构性偏差,下游融合只能减少恶化、不能治本。

成熟 RAG 的检索链路是 query classification → 按类路由 → 每类用合适的召回 → 按类融合——远比”无脑跑 dense + BM25 再 RRF”复杂。这也是为什么第 15 章 Query Rewrite 和本章 Hybrid 要独立成章——它们解决不同层的问题、都是必要的。

13.17 学习排序融合:从静态权重到动态模型

§13.3 的线性加权和 §13.9 的 weighted RRF 都用静态权重——α=0.6、k=60 这类值一次标定长期使用。这种静态融合有上限:权重是全局最优、但单个 query 可能需要完全不同的权重。学习排序融合(Learning to Fuse, LTF)用小模型动态生成融合分数、精度再提 3-8 点。这是推荐系统标准路线、RAG 领域 2025 年后逐步普及。

静态权重的上限

看一个真实场景:

  • query A “订单号 OD-12345 状态”:BM25 权重应该 0.9(精确匹配主导)
  • query B “用户登录态保持机制”:dense 权重应该 0.8(概念检索)

用全局 α=0.6 ——A 被 dense 部分拖累(没精确命中的 chunk 占了 top 位)、B 被 BM25 部分干扰(字面不相关的 chunk 排到前面)。一种权重不可能同时最优化两种 query

flowchart LR
    classDef stat fill:#fed,stroke:#c60
    classDef learn fill:#efe,stroke:#080

    Q[query] --> S[静态融合<br/>全局 α=0.6]:::stat
    Q --> L[学习融合<br/>per-query 权重]:::learn

    S --> S1[全局最优<br/>单 query 次优]
    L --> L1[每 query 动态<br/>全局更优]

学习融合的核心思路

不直接用权重、而是用机器学习模型从多个信号里学出最终排序:

final_score = f(dense_score, bm25_score, rerank_score, 
                query_type, chunk_type, freshness, 
                has_exact_match, query_length, ...)

f 是 gradient-boosted tree(LightGBM / XGBoost)或小神经网络。输入是各路分数 + query/chunk 特征、输出是相关度。

模型 per-query 动态加权——query 含精确 ID 时模型学会”把 BM25 分抬高”、概念 query 时”压低 BM25 抬 dense”——不需要手工定规则。

特征工程:融合模型的核心

静态融合只用”各路分数”作输入、学习融合可以加多维特征

特征类示例作用
原始分数dense_score, bm25_score, rerank_score基础信号
Query 特征length, has_numeric, has_code_symbol, lang区分 query 类型
Chunk 特征freshness, doc_type, authority_score多目标 rerank(ch14)
交互特征exact_term_hit_count, query_chunk_overlap精确匹配信号
历史特征click_rate, CTR_per_chunk用户反馈沉淀
上下文特征is_first_turn, previous_rerank_score多轮会话

特征越丰富、模型越能学到细微的权衡。但特征工程成本高——真正 SOTA 的融合模型往往几十个精心设计的特征。

训练数据从哪来

学习融合模型需要 (query, chunk, relevance) 三元组作训练数据:

  • 人工标注:高质量、量少(千到万级)。gold set 扩展版
  • 用户点击 / 采纳:规模大(日百万级)、但有点击偏差(排前的 chunk 更容易被点、哪怕不更相关)
  • LLM-as-judge 标注:LLM 批量打分、规模中、成本中
  • 隐式反馈:用户后续是否追问、采纳答案——“好答案”的代理信号

生产典型组合:30% 人工 + 50% LLM-judge 扩展 + 20% 点击反馈。多来源让模型不至于只拟合某一类偏差。

模型选择

  • LightGBM:生产首选。训练快、推理快(< 1ms per rank)、特征解释清楚
  • XGBoost:类似 LightGBM、社区生态强
  • 小神经网络 MLP:可处理特征间非线性交互、但推理慢 10×
  • LambdaRank:专门为排序设计、比分类 / 回归 loss 更对齐排序目标

多数 RAG 用 LightGBM + LambdaMART——精度够好、工程可控。

训练和推理 pipeline

# 训练
features = extract_features(query_chunk_pairs)  # N × D 矩阵
labels = get_relevance(query_chunk_pairs)       # 0-4 五级标注
groups = group_by_query(query_chunk_pairs)      # LambdaRank 需要分组
model = lgb.LGBMRanker(...)
model.fit(features, labels, group=groups)

# 推理(每次 RAG 请求)
def fuse(query, candidates):
    feats = [extract_features(query, c) for c in candidates]
    scores = model.predict(feats)
    return sorted(zip(candidates, scores), key=lambda x: -x[1])

推理开销典型 1-3ms per query——可忽略。核心工作量在特征提取和训练。

何时上学习融合

不是所有项目都该上——判断依据:

  • 有足够训练数据(gold set 至少 5000 条 + 点击 10 万+)→ 值得
  • 静态融合已经到瓶颈(过去几轮调参收益 < 1 点)→ 值得
  • query 分布多样、单一权重明显次优 → 值得
  • 团队有 ML 工程师维护模型 → 必要条件
  • MVP 阶段 → 不要上、静态 RRF 先跑通

过早上学习融合是典型过度工程——训练数据不够、模型过拟合、生产效果还不如 RRF。

学习融合的冷启动

新上线项目没点击数据、学习融合怎么冷启动?

  • 阶段 1:静态 RRF、收集 3-6 个月点击和标注
  • 阶段 2:用 LLM-as-judge 扩展 gold set 到几万条、训初版模型
  • 阶段 3:模型上线 shadow + A/B、逐步放量
  • 阶段 4:稳态运营、月度重训

这套路径是推荐系统三十年的标准工序、RAG 直接复用就行。

可观测性

学习融合模型需要监控:

  • 特征分布漂移:某特征的均值 / 方差随时间变化——数据分布变了、模型可能退化
  • 预测分数分布:输出分数是否稳定、突增突降说明模型 ill-conditioned
  • feature importance 漂移:哪些特征被模型重视——变化说明业务在变
  • 模型版本 vs 静态基线:每次重训和上一版、以及静态 RRF 基线对比、保证没退化

反模式

  • 过早上学习融合:数据不够、过拟合、效果不如 RRF
  • 只用 dense/bm25 分数作特征:忽视其他信号、模型学不到 per-query 差异
  • 不做 shadow 验证:直接上线、badcase 暴涨
  • 模型版本混用:多个版本并行服务、结果不稳定

学习融合是 “RAG 足够成熟了再上” 的优化——它是高 ROI 但需要前置投入的技术、不适合所有项目。

13.18 Early fusion vs late fusion:融合的两种哲学

前面 17 节讲的所有融合方法(RRF、线性加权、rerank 仲裁、ColBERT、学习排序)本质都属于同一类——late fusion:多路各自独立检索、最后合并。IR 领域还有另一条路线:early fusion——在检索之前就把信号融合、走统一的索引和打分。两者是融合技术的两大哲学、各有适用场景。理解这层区别让融合选型从”选方法”升到”选哲学”。

两种哲学的定义

flowchart TB
    classDef late fill:#fed,stroke:#c60
    classDef early fill:#def,stroke:#06c

    subgraph LATE[Late fusion 后融合]
        direction LR
        Q1[query] --> D1[Dense 检索]:::late
        Q1 --> B1[BM25 检索]:::late
        D1 & B1 --> F1[合并排序]:::late
    end

    subgraph EARLY[Early fusion 前融合]
        direction LR
        Q2[query] --> M1[统一向量表示]:::early
        M1 --> I1[统一索引]:::early
        I1 --> F2[单路检索]:::early
    end
  • Late fusion:多路独立跑、结果层面合并。前 17 节全部属于这类
  • Early fusion:把不同信号(词项、语义、上下文)编码到同一个向量空间或索引、只跑一次检索

Early fusion 的典型例子

SPLADE(ch12 §12.6)是最有名的 early fusion——把 BM25 词项权重和 BERT 语义信号融合到同一稀疏向量里:

SPLADE 向量维度 = vocabulary_size(几万)
每维度 = 该词对文档的相关度(学出来的权重)
多数维度 = 0(稀疏)、保留词汇匹配能力
非零维度学到了同义词扩展、语义相关性

查询时只用 SPLADE 一路检索——既有 BM25 的词匹配能力、又有 dense 的语义扩展——不需要两路融合。

其他 early fusion 路线:

  • ColBERT 的统一模型:同时学词项和语义
  • 多向量 per chunk + 学习排序特征:把 dense + sparse 向量作同一模型特征
  • Unified retrieval models(UNT, Tevatron 等):学术界 2024-2025 研究方向

Late fusion 和 early fusion 的对比

维度Late fusionEarly fusion
实现复杂度低(现有组件拼)高(新模型 / 新索引)
训练成本高(需要大规模训练)
推理延迟两路并行、取最慢一路、理论上更快
信号灵活度高(随意加路)低(固定在训练里)
可解释性高(每路独立)低(黑盒)
精度上限高(但受融合方法限制)理论上更高(学到的融合)
生态成熟度高(2-3 年成熟)低(仍在发展)

两者不是”更好”的关系、是不同哲学:late fusion 是模块化工程、early fusion 是端到端学习。

实战中的选择

场景推荐
一般 RAG 项目Late fusion(RRF 为主)
大规模 + ML 团队支持Late + Early 混合(SPLADE + rerank)
追求极致精度的学术Early fusion SOTA
通用型 + 零训练预算Late fusion only
特定领域 + 有标注数据训练 early fusion 模型

多数生产项目选 late——成熟、灵活、低风险。Early fusion 是”研究前沿”、适合特定场景和有资源的团队。

SPLADE 作为 early fusion 的实战

§12.6 已讨论过 SPLADE。从 fusion 视角看:

  • 传统 late fusion:dense + BM25 两个索引 + RRF
  • SPLADE early fusion:一个 SPLADE 索引取代 dense + BM25——单路检索

SPLADE 的生产案例显示它在 BEIR 等 benchmark 上和 dense + BM25 + RRF 打平、延迟更低——但部署比 late fusion 复杂(需要 SPLADE 模型推理 + 稀疏倒排索引)。

这是 early fusion 的典型权衡:合一减少延迟和复杂度、但引入模型依赖

混合 early + late

实际最有意思的路线是混合

  • Early fusion 内部已经融合词项 + 语义(如 SPLADE)
  • 外部再和 dense embedding 做 late fusion
  • Rerank 作为最后的融合器
SPLADE 检索 → top-50  
+ Dense 检索 → top-50
→ RRF 融合 → top-30
→ Rerank → top-5

三层信号叠加——early fusion 里有词项 + 部分语义、late fusion 外部加全量 dense、rerank 最终仲裁。精度经常接近或超过纯 late fusion。

训练一个 early fusion 模型需要什么

如果想自己训 early fusion(如自定义的 SPLADE 变体):

  • 大规模 (query, relevant_doc) 对:几百万级、MS MARCO / 自家日志
  • 强 GPU:8 × A100 训 3-5 天
  • ML 工程师:懂 IR + transformers 的至少 1 人
  • 评估 gold set:10k+ 条、多领域覆盖

投入门槛高——所以商用 early fusion 少。开源 SPLADE 已经做过基础工作、多数团队直接用预训练的就好。

未来趋势:Unified Retrieval

2025-2026 年学术前沿的 unified retrieval models 想把更多信号都塞进一个模型:词项 + 语义 + graph + metadata + 时间 + 个性化——一个模型统一检索。理论最优、但仍在研究。

生产短期(1-2 年)主流仍是 late fusion——模块化工程的稳健性胜过理论最优。但长期(3-5 年)early fusion 和 unified retrieval 可能逐步普及——跟进这个方向、但不追最新。

这节的实践价值

多数读者不会训练 early fusion 模型——但理解这层区别能:

  • 看 SOTA 论文时知道它在哪个哲学体系
  • 判断新工具 / 新模型的价值(是 late 的新融合方法?还是 early 的新统一模型?)
  • 选型时不把 “统一 vs 模块化” 想成对错、而是 trade-off

知识层次上、这是融合技术的顶层分类——比个别方法更长寿、值得记住。

13.19 Hybrid 和 rerank 的协同优化:别让两者互相掣肘

§13.4 已经讨论过 rerank 可以作融合器的特殊方案。一般工程里、hybrid 融合和 rerank 是两个独立阶段:hybrid 产出 top-30、rerank 精排到 top-5。但两个阶段独立调优经常互相掣肘——融合的某个选择让 rerank 发挥不出、rerank 的某个特性又被融合前的过滤抹杀。这节把两者的协同讲清楚、让整条 pipeline 达到全局最优。

独立调优的问题

flowchart LR
    classDef bad fill:#fdd,stroke:#c00
    classDef ok fill:#dfd,stroke:#080

    subgraph IND[独立优化]
        direction LR
        H1[Hybrid<br/>团队 A 调]:::bad --> R1[Rerank<br/>团队 B 调]:::bad
    end

    subgraph JOINT[联合优化]
        direction LR
        H2[Hybrid]:::ok --> R2[Rerank]:::ok
        R2 -.反馈.-> H2
    end

独立调优典型问题:

  • Fusion top-30 给 rerank、但里面重复很多:rerank 浪费计算在重复 chunk 上
  • Rerank 的优势 query(如长 chunk)被 fusion 过滤了:fusion 阶段没保留、rerank 看不到
  • Fusion 权重偏向 dense、但 rerank 模型专精代码搜索:两者方向不一致、彼此抵消

三种协同模式

模式 A:保守独立

  • Hybrid 追求 recall@30 最高
  • Rerank 在 top-30 上精排
  • 简单、多数项目的默认

适合:团队分工明确、不需要极致精度

模式 B:融合 → rerank → 反馈调融合

  • Rerank 的输出作为 fusion 的训练信号
  • 定期用 rerank 结果反调 fusion 权重
  • 长期协同、慢但稳

适合:有 ML 能力、长期优化项目

模式 C:端到端联合

  • 用 LTR 模型同时学 fusion 权重 + rerank 分数
  • 训练时把两阶段放一起优化
  • 最 SOTA、复杂度最高

适合:大公司、研究性质项目

多数项目用模式 A 即可;有 ML 团队的 B;有研究预算的 C。

Fusion top-k 的正确选法

Fusion 给 rerank 多少 chunk?常见误区:

  • 太少(top-10):rerank 可能错过相关候选
  • 太多(top-100):rerank 成本高、延迟高、噪声稀释信号

正确选法:看 rerank 的”召回收益曲线”

def find_fusion_topk(gold_set):
    for k in [10, 20, 30, 50, 100]:
        hybrid_topk = fusion(queries, top_k=k)
        rerank_top5 = rerank(hybrid_topk, top_k=5)
        recall = recall_at_5(rerank_top5, gold_set)
        print(f"fusion_top{k} → rerank_recall@5 = {recall}")

典型结果:

fusion_top10  → rerank_recall@5 = 0.87
fusion_top20  → rerank_recall@5 = 0.93
fusion_top30  → rerank_recall@5 = 0.94  ← 拐点
fusion_top50  → rerank_recall@5 = 0.945
fusion_top100 → rerank_recall@5 = 0.946

拐点通常在 top-30 ~ top-50——再多 rerank 也榨不出更多召回。选拐点、不要盲目大 top-k。

Fusion 去重给 rerank 腾空间

Fusion 阶段的严格去重(§13.6)让 rerank 更有效:

  • Fusion top-30 有 5 个同文档相邻 chunk——rerank 的 5 个 slot 浪费了
  • Fusion 去重后 top-30 都是独立 chunk——rerank 能看到 30 个真不同候选

去重前后、rerank 后 recall@5 的差距可能 3-5 点。

Rerank 偏好和 fusion 策略的对齐

不同 rerank 模型有不同”口味”——fusion 策略要配合:

  • 通用 rerank(bge-reranker):对各类 chunk 差别不大、fusion 可以 RRF 简单融合
  • 代码专用 rerank:对代码 chunk 敏感、fusion 阶段应保留更多代码类 chunk
  • 长 context rerank:能处理 1000+ token chunk、fusion 不用过早截断

选 rerank 模型后、回头调 fusion 的 top-k 和过滤——两者对齐。

Rerank 结果反馈给 fusion

高级协同——用 rerank 分数训练 fusion 权重:

# 收集数据: (query, candidate, rerank_score)
training_data = []
for query in query_log.sample(10000):
    hybrid_top30 = fusion(query, top_k=30)
    rerank_scores = rerank(query, hybrid_top30)
    for cand, score in zip(hybrid_top30, rerank_scores):
        training_data.append({
            "query": query,
            "dense_score": cand.dense_score,
            "bm25_score": cand.bm25_score,
            "rerank_score": score,  # 作 ground truth
        })

# 训 LightGBM 学 fusion weights
model = LGBMRanker()
model.fit(
    X=[[d["dense_score"], d["bm25_score"]] for d in training_data],
    y=[d["rerank_score"] for d in training_data],
)

用 rerank 的”高分”作为 gold 学 fusion——让 fusion 的 top 更贴近 rerank 想要的。

延迟预算的联合分配

Hybrid 和 rerank 都占延迟、预算要联合分配

方案Fusion 延迟Rerank 延迟总延迟召回效果
A:大 top-k fusion + 小 rerank200ms200ms400ms
B:小 top-k fusion + 大 rerank80ms400ms480ms
C:中 top-k fusion + 中 rerank120ms300ms420ms最好

C 通常最优——两阶段各吃一半预算、不是一阶段吃光。

Hybrid 和 rerank 的共同评估

独立评估:

  • Hybrid:recall@k(k=30 或 50)
  • Rerank:NDCG@5、MRR

联合评估(推荐):

  • End-to-end recall@5:经过 fusion → rerank 后的最终 top-5 的 recall
  • End-to-end latency:两阶段总延迟
  • Cost per query:两阶段总成本

单看某一阶段指标容易局部最优——看端到端才是真效果。

协同优化的 A/B

联合 A/B 比独立 A/B 复杂:

  • 不只改 fusion 或只改 rerank、要组合测
  • 如:fusion_v1+rerank_v1 vs fusion_v2+rerank_v2
  • 2×2 矩阵:fusion 两版 × rerank 两版 = 4 种组合
  • 看哪个组合 end-to-end 最好、不只各自

这让 A/B 的流量需求翻倍——但避免”各自最优、组合次优”的陷阱。

共同调优的一个真实经验

某团队经验:

  • 独立调优、fusion recall@30 从 0.88 → 0.93(进步 5 点)
  • 但 end-to-end recall@5 只从 0.82 → 0.83(进步 1 点)

根因:fusion 改进引入的新 chunk 是”表面相似但 rerank 打低分”的——rerank 压掉了、整体没收益。

改为联合调优:

  • 每次 fusion 改动、看 rerank 后的 end-to-end
  • 只保留真正提升 end-to-end 的 fusion 改动
  • 最终 end-to-end recall@5 从 0.82 → 0.89

提升 7 点——联合视角找到真正有价值的改动。

协同的工程组织

团队组织要配合:

  • 不要”fusion 团队” vs “rerank 团队”:互相 finger-pointing
  • 一个 owner 负责 end-to-end 召回质量:fusion 和 rerank 都在 scope 里
  • 共同 OKR:两组共享 end-to-end recall 指标、而不是各自 owner 自己的指标

组织设计不到位、技术协同也跑不起来——这是 ch22 §22.10 组织学在 hybrid 上的具体应用。

何时不追协同

如果项目规模小、优化 ROI 低:

  • MVP 阶段:先各自能用、后期再协同
  • QPS 极低:优化省的钱不如团队精力成本
  • 业务简单:通用 fusion + 通用 rerank 够用

协同优化是高 ROI 的最后一公里——前提是前面九公里做好了。不要跳过前面就上协同。

13.20 Hybrid 的冷启动:没有数据时怎么调参

前面几节讲了 hybrid 的各种调参方法(§13.15 离线 gold set、§13.17 学习排序)——但项目刚上线时没有任何数据:没 gold set、没反馈、没 badcase 库。这时怎么调 hybrid 参数?这节讲 cold start 阶段的 hybrid 实践——从”用默认值起步”到”数据飞轮转起来”的完整路径、和 ch14 rerank cold start 呼应。

Cold start 的 hybrid 挑战

flowchart TB
    classDef cold fill:#fdd,stroke:#c00

    C[Cold start hybrid 的难题]
    C --> C1[没 gold set 标定 α / k]:::cold
    C --> C2[不知道 query 类型分布]:::cold
    C --> C3[没 badcase 库驱动改进]:::cold
    C --> C4[没用户反馈做 learning]:::cold

这时候 “理论最优” 没用——先跑起来、再慢慢调

冷启动的默认参数

没数据时、用行业经验值作起点:

default_hybrid_config = {
    "fusion_method": "RRF",    # 最稳健、零参数
    "rrf_k": 60,               # 经典值(Cormack 原论文)
    "dense_top_k": 30,         # 够给 rerank
    "bm25_top_k": 30,          # 同上
    "after_rrf_top_k": 30,     # 送 rerank
    "rerank_model": "bge-reranker-v2-m3",
    "final_top_k": 5,
}

这套参数是行业平均配置——多数业务跑起来没问题。别过早调——没数据调不准。

用 MVP 数据快速迭代

上线后 2-4 周、收集初步数据

  • 日常 query log
  • 用户点击引用 / 采纳
  • 管理员抽样 review

这些数据构成初版 gold set(几十到几百条)——粗糙但比没强。用它跑一次参数扫描、看哪些默认值偏离业务最优。

分阶段的 hybrid 迭代

flowchart LR
    classDef s fill:#def,stroke:#06c

    S1[T+0: 默认 RRF]:::s
    S1 --> S2[T+1 月: gold set 100 条]:::s
    S2 --> S3[T+3 月: gold set 500 条]:::s
    S3 --> S4[T+6 月: 动态融合]:::s
    S4 --> S5[T+12 月: 学习排序]:::s

    S1 --> S1a[无调优]
    S2 --> S2a[小规模标定 α / k]
    S3 --> S3a[按 query type 分桶]
    S4 --> S4a[自动切换策略]
    S5 --> S5a[端到端 LTR]

每阶段有明确的数据门槛——达到才升级、不然维持当前档。

Cold start 的可观测性

Cold start 阶段更需要 instrumentation:

  • 每路召回的贡献:dense vs BM25 各自召回的 chunk 比例
  • RRF 排序的离散度:top-5 是集中在某一路还是均衡
  • Badcase 的分布:哪类 query 表现最差

这些数据让团队快速理解自己的业务 query 分布——比看 gold set 快。

Cold start 的快速反馈方法

没正式 gold set、怎么快速评估改动?

方法 A:员工 dogfood

  • 团队自己用 RAG、遇到问题立即报
  • 报的问题进 “内部 badcase 库”
  • 改动后在这些 case 上验证

质量不如正式 gold set、但比没强、启动快。

方法 B:对比新旧

  • 改动前后、对同一批 query 各跑一遍
  • 人工比对两个版本的答案
  • 看改动是否”肉眼可见地更好”

比定量慢、但低门槛。

方法 C:LLM-as-judge 早期版

  • 用通用 LLM 作 judge、粗略比较新旧
  • 不等建 gold set、立刻用
  • 后续用正式 gold set 校准 LLM judge

这三种方法都是过渡——cold 期拿来用、warm 后切到正式评估。

别做的事

Cold start 阶段别做

  • 学习排序:没点击数据、训不了
  • 按 query type 切换:query type 分布还不知道
  • 跨查询的复杂 tuning:没数据支持

做了这些是过早优化——浪费精力、结果不稳。

和 Rerank / Query Rewrite 的 cold start 联动

Ch14 §14.18 讲过 rerank cold start、§15 的 rewrite 也有类似阶段——整个 RAG 系统的 cold start 要协调

  • Week 1-4:三者都用默认、先跑
  • Month 2-3:hybrid 调 RRF k、rewrite 试 HyDE、rerank 保留通用
  • Month 4-6:三者用累积数据分别优化
  • Month 6+:协同优化

别试图在 month 1 就优化三者——一个一个来、先建数据飞轮

Cold start 的指标期望

期望 cold start 阶段的指标:

阶段recall@10faithfulness用户满意度
T+0(默认)0.75-0.850.75-0.8260-70%
T+3 月(基础调优)0.85-0.900.82-0.8770-80%
T+6 月(数据驱动)0.88-0.930.85-0.9075-85%
T+12 月(成熟)0.91-0.960.88-0.9380-90%

注意:

  • T+0 就达标 80%+ 满意度——默认配置 RRF + 通用 rerank 足够
  • 追求极致(95%+)要 6-12 月 + 数据飞轮

团队对 cold start 的期望管理

对业务 / 老板解释:

  • “上线第一天完美不现实”——设定合理期望
  • “6 月内看到持续提升”——给长期承诺
  • “需要反馈和时间”——要业务配合

没这层沟通、cold start 阶段质量普通、团队被 KPI 压——气氛紧张。

Cold start 的 ROI

Cold start 阶段的投入产出:

  • 投入:搭 pipeline、默认配置——1-2 人月
  • 产出:一个跑得动的 hybrid RAG
  • 后续提升:靠 6-12 月的数据和迭代

别指望 cold start 一次就完美——是持续过程的起点。

一个 cold start 的真实节奏

某企业 RAG 的真实节奏:

  • Week 1-2:MVP 上线、用 RRF 默认、内部 dogfood
  • Week 3-4:建 100 条 gold set、发现 recall 偏低
  • Month 2:分析发现 BM25 权重不够、调 wRRF (α=0.5)、recall +3 点
  • Month 3:上线 rerank(通用 bge)、NDCG +5 点
  • Month 4:发现代码 query 特别差、加代码专用路由
  • Month 6:gold set 扩到 500 条、开始系统化 A/B
  • Month 12:学习排序上线、再 +3 点

这套 12 月节奏不是快捷路径——是耐心走的路径。想跳步的基本都走不到 month 12。

对新 RAG 项目的建议

从零建 RAG 的团队:

  • 第一周别调参:跑起来、观察基础指标
  • 第一月建最小 gold set:100 条 manually 标的
  • 前三月专注 hybrid + rerank:不上 graph / 多向量等高级技术
  • 六月后考虑个性化 / 学习:有数据才有价值

耐心是 cold start 的核心技能——急于一次到位几乎必失败。

13.21 跨书关联:融合是推荐系统的老问题

推荐系统里”多路召回 + 融合”的历史和 IR 并列——Netflix、YouTube、Amazon 三十年都在做这件事。RRF 本身也是来自推荐领域的经典。RAG 的 hybrid search 是在搜索 + 推荐两条脉络的交汇点——这也是为什么做过搜推的工程师上手 RAG 极快。

《LangGraph 设计与实现》讨论 multi-agent 融合多个子 agent 的结果时、核心也是融合——只是信号变成了 agent 的子回答。融合的通用哲学:多个独立不完美信号的共识比单个信号的最优更稳健

13.22 本章小结

  1. Hybrid = 两路召回并行 + 融合排序——不是串联
  2. 四类融合:RRF(默认首选)、权重线性(可调需标定)、rerank 仲裁(最准需 rerank)、ColBERT(精度上限)
  3. RRF 的优势:无标定、跨量纲、代码简单、效果强
  4. 融合效果通常比单路强 10+ 个点——先上 hybrid、再优化融合
  5. 动态融合(按 query 类型切权重)是下一步的优化
  6. 多路召回可以扩到 3+ 路——但每加一路都有成本

下一章讨论 Rerank——把 top-50 候选用 cross-encoder 精打细算成 top-5。