犀牛鸟 2026 研究笔记 estelledc.github.io

Ch04: 检索三路军——BM25、向量、知识图谱的协奏

Part 2: RAG 核心技术 前置章节:Ch03: RAG 技术演进——从朴素管线到 Agent 自主检索 后续章节:Ch05: 文档解析与分块——输入质量决定一切


一、开场:为什么一条路不够

结论先行:没有任何单一检索信号能覆盖所有查询类型。纯向量检索对精确关键词表现差,纯关键词检索对语义理解表现差,两者合并后仍无法做多跳推理。只有多路协作、融合排序,才能逼近”理想检索”。

想象你在一个万人演唱会的散场人群中找一个朋友。你有三种策略:

第一种,喊名字(BM25)。你站在出口大喊”张伟!”——如果你的朋友恰好叫这个名字,他会回头。但如果人群里有三个张伟,你就需要进一步确认;如果你只记得”那个高个子戴眼镜的”,喊名字完全没用。

第二种,凭外貌找(向量检索)。你在脑中浮现朋友的模样——身高一米八、戴黑框眼镜、穿蓝色外套——然后在人群中扫视,找到外貌最接近的人。这对”长得像的人”很有效,但如果你朋友换了衣服剪了头发,你可能认不出他。更重要的是,如果你只知道”那个叫张伟的”,凭外貌根本找不到。

第三种,通过社交关系找(知识图谱)。你知道朋友的女朋友在C区入口等他,你先找到女朋友,再通过她找到朋友。即使你既不知道朋友今天的样子,也忘了他的全名,通过关系链仍然能找到。

这三种策略各有擅长的场景,也各有盲区。现实世界的检索需求千变万化——有时用户输入的是精确的错误码”ERR_OOM_KILL_001”(喊名字最快),有时是模糊的描述”怎么让系统跑得更快”(凭外貌找),有时是间接的推理”这个 bug 的作者还写过哪些模块”(通过关系找)。单一检索路径只能覆盖其中一类需求。

这就是 RAG 系统演进到今天,从单路检索走向双路、三路混合的根本原因。本章将逐一拆解 BM25 关键词检索、向量语义检索、知识图谱推理检索三条路径的原理与边界,然后讨论如何将三路结果融合为一个统一排序,最后对比各种 Rerank 精排策略。


二、第一路:BM25 关键词检索

2.1 BM25 是什么

BM25(Best Matching 25)是经典的关键词检索算法,诞生于 1994 年,由 Stephen Robertson 等人在概率检索框架的第 25 次迭代中提出。它是 TF-IDF 的工程改良版——在词频和文档频率的基础上,加入了词频饱和效应和文档长度归一化两个关键改进。

日常类比:BM25 就像图书馆的卡片目录。你知道一本书的关键词(书名、作者名、ISBN),在目录中精确查找——能直达目标,速度极快。但如果你只记得”那本讲人工智能历史的书”,目录系统帮不了你,因为它不理解”语义”。

2.2 BM25 公式详解

BM25 的完整公式如下:

\[BM25(q, d) = \sum_{i=1}^{n} IDF(q_i) \cdot \frac{tf(q_i,d)\,(k_1+1)}{tf(q_i,d)+k_1\left(1-b+b\frac{|d|}{\mathrm{avgdl}}\right)}\]

各组件含义:

IDF(逆文档频率):衡量一个词的”区分度”。公式为:

\[IDF(q_i) = \log\left(\frac{N - n(q_i) + 0.5}{n(q_i) + 0.5} + 1\right)\]

其中 $N$ 为语料库中总文档数,$n(q_i)$ 为包含词项 $q_i$ 的文档数。

直觉理解:如果一个词在几乎所有文档中都出现(如”的”“是”“一个”),它的 IDF 接近零——对定位目标文档毫无帮助。如果一个词只在少数文档中出现(如”ERR_OOM_KILL_001”),它的 IDF 很高——这个词本身就是一把精确的钥匙。就像找人时,”穿衣服的人”毫无区分度,但”左手腕有红色纹身的人”立刻把范围缩小到极少数人。

tf(词频):词项在文档中出现的次数。出现越多,文档与该词越相关。但 BM25 的精妙设计是词频存在饱和效应:出现 3 次比出现 1 次提升很大,但出现 30 次比出现 10 次的提升可以忽略。参数 $k_1$(通常取 1.2 到 2.0)控制饱和速度——$k_1$ 越大,饱和越慢,高词频文档越受益。

b(长度归一化参数):通常取 0.75。长文档天然包含更多词项——一篇万字长文中出现 5 次”知识图谱”和一篇百字短文中出现 5 次”知识图谱”,后者显然更聚焦于此话题。$b=0.75$ 意味着文档长度会被适度惩罚,让短而聚焦的文档在评分中不吃亏。$b=0$ 则完全不做长度归一化,$b=1$ 则完全按比例归一化。

2.3 BM25 擅长什么,不擅长什么

BM25 闪光的场景:

BM25 失败的场景:

2.4 Python 伪代码实现

import math
from collections import Counter

def bm25_score(query_terms, doc_terms, corpus_stats, k1=1.2, b=0.75):
    """计算单个文档对查询的 BM25 分数"""
    score = 0.0
    doc_len = len(doc_terms)
    avgdl = corpus_stats['avg_doc_len']
    N = corpus_stats['total_docs']
    tf_doc = Counter(doc_terms)

    for term in query_terms:
        tf = tf_doc.get(term, 0)
        df = corpus_stats['doc_freq'].get(term, 0)
        # IDF 计算
        idf = math.log((N - df + 0.5) / (df + 0.5) + 1.0)
        # TF 饱和 + 长度归一化
        numerator = tf * (k1 + 1)
        denominator = tf + k1 * (1 - b + b * doc_len / avgdl)
        score += idf * numerator / denominator
    return score

2.5 中文分词的挑战

英文天然以空格分词,BM25 开箱可用。中文没有天然分词边界——”知识图谱检索”是一个词还是”知识/图谱/检索”三个词?分词粒度直接影响 BM25 质量。如果分词器把”知识图谱”切成”知识”和”图谱”,搜索”知识图谱”时,任何包含”知识”或”图谱”的文档都会被召回,精度大幅下降。WeKnora 使用支持自定义词典的中文分词器,允许用户把领域术语注册为不可拆分的词项。


三、第二路:向量语义检索

3.1 核心思想

向量语义检索的核心思想可以用一句话概括:把文本变成高维空间中的一个点,语义越相似的文本,点与点之间的距离越近。

日常类比:想象你给每一句话分配一个 GPS 坐标——不是地理坐标,而是”意义坐标”。”如何提速”和”性能优化方案”虽然用词完全不同,但它们在”意义地图”上的坐标几乎重合。而”如何提速”和”今天天气不错”在意义地图上相距甚远。向量检索就是在这张意义地图上,找到与查询坐标最近的几个点。

3.2 Embedding 过程

文本变成向量的过程叫 embedding(嵌入)。流程如下:

输入文本: "BM25 是经典的关键词检索算法"
     |
     v
Embedding 模型(如 text-embedding-3-small、bge-large-zh-v1.5)
     |
     v
输出: [0.023, -0.156, 0.891, ..., 0.042]  (768 维浮点数向量)

为什么是 768 维?这是 BERT-base 架构的隐藏层维度,后续许多 embedding 模型沿用了这个设定。更大的模型可能用 1024 维或 1536 维(如 OpenAI 的 text-embedding-3-large)。维度越高,向量能编码的语义信息越丰富,但存储和计算开销也越大。

两个向量之间的相似度通常用余弦相似度衡量:

cosine_similarity(a, b) = (a . b) / (|a| * |b|)

取值范围: [-1, 1]
  1.0 = 完全相同方向(语义一致)
  0.0 = 正交(无关)
 -1.0 = 完全相反方向(语义对立,实际很少见)

3.3 ANN 近似最近邻搜索

当向量库中有百万甚至千万条向量时,逐一计算余弦相似度(暴力搜索)不可接受——一次查询可能需要数秒。ANN(Approximate Nearest Neighbor,近似最近邻)算法通过牺牲少量精度换取数量级的速度提升。

HNSW(Hierarchical Navigable Small World):构建一个多层图结构。底层包含所有向量节点并建立密集连接,高层只保留少数”关键节点”和稀疏连接。搜索时从最高层开始,在稀疏图中快速跳到目标的大致区域,然后逐层下降,在越来越密集的图中精化搜索。

类比:在城市中导航。先上高速公路(高层图)快速接近目标城区,然后下到主干道(中层图)找到街道,最后转入小巷(底层图)定位具体门牌号。HNSW 的查询复杂度为 O(log N),远低于暴力搜索的 O(N)。

IVF(Inverted File Index):先用 K-Means 将所有向量聚成若干簇(如 1024 个),每个簇有一个质心。搜索时,先计算查询向量与所有质心的距离,选出最近的 nprobe 个簇(如 10 个),只在这些簇内做精确搜索。

类比:图书馆按类别分区。找科技书只需去科技区扫描书架,不必遍历全馆。IVF 的代价是可能错过属于其他簇但实际相关的向量——nprobe 越大越精确但越慢。

3.4 向量检索的优势与局限

向量检索闪光的场景:

向量检索失败的场景:

3.5 Python 伪代码实现

import numpy as np

def embed_text(text, model):
    """将文本转为向量(调用 embedding 模型)"""
    return model.encode(text)  # 返回 768 维向量

def cosine_search(query_vec, doc_vectors, top_k=10):
    """余弦相似度搜索"""
    # 归一化
    query_norm = query_vec / np.linalg.norm(query_vec)
    doc_norms = doc_vectors / np.linalg.norm(doc_vectors, axis=1, keepdims=True)
    # 计算余弦相似度(归一化后内积 = 余弦)
    similarities = doc_norms @ query_norm
    # 取 top-k
    top_indices = np.argsort(similarities)[::-1][:top_k]
    return [(idx, similarities[idx]) for idx in top_indices]

3.6 向量数据库选型概览

向量数据库 部署方式 典型规模 核心优势 适合团队
Milvus 分布式/单机 十亿级 极高扩展性,生产级 大规模企业
Qdrant 单机/集群 亿级 Rust 编写,性能优异 中大规模
FAISS 纯内存库 千万级 Meta 出品,速度极快 研究/原型
Chroma 嵌入式 百万级 轻量开箱即用 小型项目/原型
pgvector PG 扩展 千万级 复用已有 PostgreSQL 已有 PG 的团队
Weaviate 云/自建 亿级 原生混合搜索 需要 BM25+向量一体化

选择依据通常是:已有什么基础设施、数据规模多大、是否需要分布式。不存在”最好的向量数据库”,只有最适合场景的。


四、第三路:知识图谱推理检索

4.1 核心思想

知识图谱以”实体-关系-实体”三元组的形式存储知识,通过图遍历实现推理检索——找到与查询实体存在间接关联的内容,即使这些内容在文本层面与查询没有直接匹配。

日常类比:社交网络。你想找某位专家的论文,但不知道他的名字。你知道他是你导师的同事——通过”导师→同事→专家→论文”这条关系链,你找到了目标。在文本检索中,你搜”导师”找不到那篇论文(因为论文里根本没提你导师),但通过知识图谱的关系链,你能跳转到目标。

4.2 PMI 图谱构建方法

传统知识图谱构建依赖命名实体识别(NER)和关系抽取模型——需要大量标注数据和训练成本。WeKnora 使用了一种更简洁的统计方法:PMI(Pointwise Mutual Information,点互信息)。

PMI 公式:

\[PMI(x, y) = \log_2\left(\frac{P(x,y)}{P(x) \cdot P(y)}\right)\]

其中 $P(x)$ 为实体 $x$ 在语料中出现的概率(出现 $x$ 的文档数 / 总文档数),$P(y)$ 为实体 $y$ 在语料中出现的概率,$P(x,y)$ 为实体 $x$ 和 $y$ 在同一文档/段落中共同出现的概率。

PMI 的直觉理解:如果两个实体的共现频率远超”随机碰巧在一起”的概率,它们之间很可能存在有意义的关联。具体来说:

4.3 图谱构建步骤

Step 1: 实体抽取
  从文档中提取候选实体(名词短语、专业术语)
  方法: 中文分词 + 词性标注过滤(保留名词/专有名词)
  可选: LLM 辅助抽取高质量实体

Step 2: 共现统计
  扫描所有文档,统计每对实体在同一段落/chunk 内的共现频率
  窗口大小: 通常为段落级别(一个 chunk 内)

Step 3: PMI 计算
  对每对共现实体计算 PMI 值
  过滤: 共现次数 < N 的低频对不可靠,直接丢弃

Step 4: 图谱构建
  PMI > 阈值的实体对 -> 在图中建立边
  边的权重 = PMI 值
  形成加权无向图

Step 5: 图谱索引
  对图做预处理(BFS/DFS 可达性索引)
  支持快速 1-2 跳图遍历查询

4.4 知识图谱检索的工作流程

当用户查询到来时,知识图谱检索分三步:

第一步,实体识别。从查询中提取关键实体。例如查询”WeKnora 的知识图谱用了什么算法”,提取出”WeKnora”和”知识图谱”两个实体。

第二步,图遍历。从这两个实体出发,在知识图谱中做 1-2 跳遍历,找到关联实体。例如”知识图谱” –(1跳)–> “PMI”,”PMI” –(2跳)–> “共现统计”。

第三步,文档召回。找到关联实体后,检索包含这些实体的文档 chunk。包含”PMI 共现统计”的那段文档——它可能通过 BM25 搜不到(因为不包含”算法”这个词),通过向量搜索也可能排名不高(因为语义上离”知识图谱算法”有距离),但通过图谱的关系推理,我们找到了它。

4.5 知识图谱的优势与局限

知识图谱闪光的场景:

知识图谱失败的场景:

4.6 三路检索的覆盖率分析

查询类型 举例 BM25 向量 图谱 需要几路
精确关键词 “ERR_OOM_KILL_001” 单路够
语义查询 “怎么提高性能” 单路够
同义表达 “密码重置” vs “忘记密码” 单路够
精确+语义混合 “v3.2 的性能优化” 双路互补
多跳推理 “写了X模块的人还负责什么” 三路必需
模糊间接 “那个让内存不崩的参数” 三路互补

双路混合覆盖了 80-90% 的查询场景。三路混合主要解决多跳推理和模糊间接查询的”最后 10-15%”。但在企业场景中,高管和跨团队协作者问的往往正是这类跨领域问题——这”最后 10%”可能是最关键的。


五、融合策略对比

三路检索各自返回一个排名列表。接下来的核心问题是:如何把这些列表合并成一个统一排序?

5.1 RRF(Reciprocal Rank Fusion)——WeKnora 的选择

RRF 是 2009 年 Cormack 等人提出的排名融合算法。它的核心优雅之处在于:完全忽略分数的绝对值,只看排名。

公式:

\[\text{RRF}_{\text{score}}(d) = \sum_{i=1}^{m} \frac{w_i}{k + rank_i(d)}\]

其中 $d$ 为待评分文档,$m$ 为检索路数,$w_i$ 为第 $i$ 路的权重,$rank_i(d)$ 为文档 $d$ 在第 $i$ 路结果中的排名(从 1 开始),$k$ 为平滑常数(WeKnora 使用 $k=60$)。

WeKnora 权重分配:$w_{\text{向量}}=0.7$,$w_{\text{BM25}}=0.3$,$w_{\text{图谱}}$ 动态调整(根据图谱密度和查询中实体的匹配程度)。

为什么用排名而不用分数? 因为 BM25 分数和向量余弦相似度不在同一个量纲上。BM25 分数范围可能是 [0, 30],余弦相似度范围通常在 [0.5, 0.99]。直接加权 0.7 * cosine + 0.3 * bm25 没有数学意义——BM25 的绝对值会主导结果。传统解决方案是归一化(min-max、z-score),但归一化方式本身就是超参数,不同方式在不同数据上表现差异大。RRF 完全绕开了这个问题——排名天然是归一化的,第 1 名就是第 1 名,不管分数是 0.99 还是 25.7。

k=60 的数学直觉

k 控制排名差异的”敏感度”。用具体数字感受:

当 k=1 时:
  第 1 名: 1/(1+1) = 0.500
  第 2 名: 1/(1+2) = 0.333
  第 10 名: 1/(1+10) = 0.091
  第1名比第10名高 5.5 倍——排名差一位就有巨大差距

当 k=60 时:
  第 1 名: 1/(60+1) = 0.01639
  第 2 名: 1/(60+2) = 0.01613
  第 10 名: 1/(60+10) = 0.01429
  第1名比第10名只高 1.15 倍——前10名几乎同一水平

k=60 的效果是:单路中排名极高但其他路中排名低的文档不会”一骑绝尘”。一个文档必须在多路检索中都表现不错才能获得高分——这就是 RRF “共识优先”的设计哲学。k=60 是 RRF 原始论文在 TREC 实验上的经验最优值。

RRF 计算示例

文档 D: BM25 排名 #3, 向量排名 #1, 图谱排名 #8
RRF(D) = 0.3/(60+3) + 0.7/(60+1) + w_g/(60+8)
       = 0.00476 + 0.01148 + w_g * 0.01471

文档 E: BM25 排名 #1, 向量排名 #15, 图谱未召回
RRF(E) = 0.3/(60+1) + 0.7/(60+15) + 0
       = 0.00492 + 0.00933
       = 0.01425

文档 D 在多路中都有不错排名,总分高于只在 BM25 中突出的文档 E。这体现了”多路共识比单路极端更可靠”的设计理念。

5.2 Python 实现

def rrf_fusion(ranked_lists, weights, k=60):
    """
    RRF 排名融合
    ranked_lists: list of lists, 每个子列表是该路按排名排序的文档 ID
    weights: 每路的权重
    k: 平滑常数
    """
    scores = {}
    for i, ranked_list in enumerate(ranked_lists):
        w = weights[i]
        for rank, doc_id in enumerate(ranked_list, start=1):
            if doc_id not in scores:
                scores[doc_id] = 0.0
            scores[doc_id] += w / (k + rank)
    # 按 RRF 分数降序排列
    sorted_docs = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    return sorted_docs

5.3 归一化加权求和——Dify 的方案

Dify 对两路分数做 min-max 归一化后加权求和:

final_score = alpha * normalize(vector_score) + (1 - alpha) * normalize(bm25_score)

normalize(x) = (x - min) / (max - min)

默认 alpha = 0.7(向量权重 70%,BM25 权重 30%)

优点是直觉简单、实现容易。缺点在于 min-max 归一化对异常值敏感:如果某次检索中有一个余弦相似度极高的异常结果(如 0.99),其他正常分数的区分度被严重压缩。此外,归一化依赖当前批次的分数分布——同一篇文档在不同查询中的归一化分数可能完全不同,缺乏跨查询的一致性。

5.4 权重融合——RAGFlow 的方案

final_score = w * vector_score + (1 - w) * bm25_score
w 可配置(默认 0.7)

与 Dify 的本质区别不大,核心差异在于 RAGFlow 提供了更灵活的 UI 让用户调节权重值。根本问题仍然相同——分数量纲不一致,需要依赖归一化的质量。

5.5 单路检索——AnythingLLM、FastGPT

AnythingLLM 和 FastGPT 的基础模式采用纯向量单路检索,无需融合。FastGPT 虽然支持 MongoDB 全文检索作为第二路,但融合方式是简单的线性插值:

final_score = rerankWeight * vector_score + (1 - rerankWeight) * fulltext_score

量纲不一致问题严重——余弦相似度范围 [0.5, 0.99],MongoDB textScore 范围 [0.1, 5.0]。除非 rerankWeight 设得极高(>0.9),全文分数的贡献会被向量分数淹没。

5.6 融合策略对比表

对比维度 RRF (WeKnora) 归一化加权 (Dify) 权重融合 (RAGFlow) 线性插值 (FastGPT) 无融合 (AnythingLLM)
输入要求 只需排名 需要分数 需要分数 需要分数 N/A
需要归一化 是(隐式) 否(但有量纲问题) N/A
异常值鲁棒性 极低 N/A
多路扩展性 天然支持 每加一路需重新归一化 同左 不支持 N/A
参数量 k + 权重 alpha + 归一化方式 w rerankWeight 0
实现复杂度
共识优先 N/A
代表项目 WeKnora Dify RAGFlow FastGPT AnythingLLM

核心结论:RRF 的优势在于对量纲差异天然免疫,且”共识优先”的特性让多路互补的价值最大化。代价是它丢弃了分数的绝对值信息——如果某路的分数确实包含重要的置信度信号,RRF 会忽略它。


六、Rerank 精排三种方案

融合(Fusion)解决的是”把多路结果合成一个列表”的问题,Rerank 解决的是”对这个列表做精细排序”的问题。类比:融合像海选把所有选手汇聚到一起,Rerank 像面试官逐一深入评估每个选手。

6.1 方案一:Cross-Encoder 单模型 Rerank(Dify、RAGFlow、FastGPT)

工作方式:把查询 q 和每个候选文档 d 拼成 [CLS] q [SEP] d [SEP],送入 Cross-Encoder 模型(如 bge-reranker-v2-m3、Cohere Rerank),输出一个 0-1 之间的相关性分数。

Bi-Encoder(粗排阶段):
  Embed(query) -> q_vec     # query 和 doc 分别编码
  Embed(doc)   -> d_vec     # 无交互,速度快
  score = cosine(q_vec, d_vec)

Cross-Encoder(Rerank 阶段):
  score = CrossEncoder([query; doc])  # query-doc 拼接后一起编码
                                      # 注意力机制建模两者的交互
                                      # 精度高但每对需要一次前向传播

优点:精度高,Cross-Encoder 的注意力机制让 query 和 doc 之间有深度交互——能理解”查询中的代词指代文档中的哪个实体”。实现简单,一个模型调用即可。

缺点:速度慢(N 个候选文档需要 N 次前向传播);领域适应性差(通用模型在垂直领域专业术语上可能判断失误);无法感知来源质量(官方文档和论坛帖子只要文本相似就打同样分数)。

6.2 方案二:复合三信号 Rerank(WeKnora)

WeKnora 的复合 Rerank 在 Cross-Encoder 基础上叠加了两个额外信号:

final_score(d) = 0.6 * rerank_model_score(q, d)
               + 0.3 * base_relevance_score(d)
               + 0.1 * source_credibility(d)

三个信号的分工:

rerank_model_score(权重 0.6):Cross-Encoder 对 (query, doc) 对的深度语义评估。这是”最聪明”的信号,能理解复杂的语义关系。

base_relevance_score(权重 0.3):RRF 融合后的基础排名分,代表多路检索的”共识”。作用是兜底——即使 Rerank 模型在某个专业术语上判断失误(比如医疗领域的”阳性”在不同检查中含义不同),多路检索的共识排名仍然提供稳定的参考。

source_credibility(权重 0.1):文档来源的可信度评分。不是从文本内容推断的,而是在文档摄入时根据元数据(来源 URL、作者、发布渠道)预先标注的。评分标准示例:

来源类型           可信度分数
官方文档            1.0
产品文档            0.9
技术博客(官方)      0.8
社区文档            0.6
论坛帖子            0.4
用户笔记            0.3

为什么三信号比一信号鲁棒?考虑一个场景:医疗知识库中,用户问”阿司匹林的禁忌症”。文档 A 是官方药品说明书(措辞严谨),文档 B 是论坛帖子(表达通俗但不够严谨)。单模型 Rerank 可能因为 B 的表达与查询更”对话式匹配”而给 B 打高分。但复合 Rerank 中,source_credibility 信号让 A(官方文档 = 1.0)显著优于 B(论坛 = 0.4),base_score 也可能因为 A 在多路中都被召回而更高。三信号合力,确保最终答案基于权威来源。

6.3 方案三:无 Rerank(AnythingLLM 的刻意选择)

AnythingLLM 完全不做 Rerank。这不是疏忽,而是设计选择——它的核心定位是”本地优先、隐私第一、部署极简”。Rerank 需要额外的模型(Cross-Encoder)运行在本地,增加 GPU/内存占用和部署复杂度。对于个人用户在笔记本上运行的场景,向量检索的 top-K 结果已经”够用”——知识库规模小时,召回质量的边际提升不值得额外的计算开销。

6.4 Rerank 方案对比表

对比维度 Cross-Encoder 单模型 复合三信号 (WeKnora) 无 Rerank
精度 最高 低(依赖粗排质量)
鲁棒性 中(领域偏差无兜底) 高(多信号互补兜底)
来源感知 有(0.1 权重)
延迟开销 ~500ms (N=20) ~600ms (N=20) 0
部署复杂度 中(需要 Rerank 模型) 中高(模型 + 元数据)
适合场景 通用知识库 企业级严肃场景 个人/小规模
代表项目 Dify, RAGFlow, FastGPT WeKnora AnythingLLM

6.5 为什么复合 Rerank 更鲁棒

复合 Rerank 的核心价值不是”更高的平均精度”(Cross-Encoder 本身精度已经很高),而是”更低的最差情况”。在以下边缘场景中,三信号的互补价值凸显:

场景一:领域术语偏差。通用 Rerank 模型对”buffer overflow”和”缓冲区溢出”的关联度评估可能不如领域模型准确。但 base_score 信号中,两者在 BM25 和向量检索中都会被召回并排名靠前,提供了稳定的兜底。

场景二:模型服务不稳定。如果 Rerank 模型的 API 响应异常(超时、错误分数),base_score 和 source_credibility 仍然能提供合理的排序——系统不会因为一个组件的失败而彻底崩溃。

场景三:对抗性内容。如果有人故意在论坛中发布与官方文档高度相似但内容错误的帖子,单模型 Rerank 无法区分。source_credibility 信号让官方文档始终优先。


七、本章小结

本章的核心观点:BM25、向量检索、知识图谱是互补而非竞争的三条路径。BM25 擅长精确关键词匹配,向量擅长语义理解,知识图谱擅长关系推理——三者各有不可替代的覆盖区域。

融合策略的选择直接决定多路检索能否”1+1+1 > 3”。RRF 通过”只看排名、忽略分数”的设计天然免疫量纲差异,且”共识优先”的特性鼓励多路结果的交叉验证。归一化加权方案更简单但对异常值敏感。无融合方案适合规模小、场景单一的个人用途。

Rerank 精排是检索管线的最后一道质量关卡。Cross-Encoder 单模型方案实现简单但鲁棒性有限,WeKnora 的复合三信号方案通过 base_score 和 source_credibility 兜底模型偏差和来源质量问题,在企业级严肃场景中价值显著。

下一章将进入检索管线的”上游”——文档解析与分块。正如一句老话:垃圾进、垃圾出(GIGO)。再精妙的检索算法,如果输入的 chunk 质量不过关——表格被切碎、语义边界被打断、OCR 文字错乱——召回的就是残缺信息。Ch05 将详细拆解五个项目在文档解析和分块策略上的差异化设计。


导读目录:README.md 上一章:Ch03: RAG 技术演进——从朴素管线到 Agent 自主检索 下一章:Ch05: 文档解析与分块——输入质量决定一切