首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >RAG 检索全攻略:从原理到落地,一篇搞懂混合检索

RAG 检索全攻略:从原理到落地,一篇搞懂混合检索

作者头像
tunsuy
发布2026-04-16 15:50:02
发布2026-04-16 15:50:02
3260
举报

❝做 RAG 系统,十个团队九个栽在检索上。本文把语义检索、关键词检索、混合检索、Rerank 重排序一次讲清楚。 ❞

先说结论

「生产级 RAG 必须用混合检索。单一检索方式,无论是语义还是关键词,都有致命盲区。」

下面展开讲为什么。


一、RAG 检索的关键在哪?

RAG(Retrieval-Augmented Generation)的核心流程是:「先搜,再答」

  • 「搜得好」 → LLM 有好的"参考资料" → 答得靠谱
  • 「搜得烂」 → LLM 只能"编" → 答得一塌糊涂

检索层的质量,直接决定了 RAG 系统的上限。

「Garbage In, Garbage Out.」

目前主流有三种检索方式:


二、三种检索方式一图看懂

假设用户问了一句话:「"Transformer 模型的注意力机制是什么"」

代码语言:javascript
复制
用户 Query
  │
  ├── ① 语义检索(理解你想问啥)
  │     Query → Embedding 向量 → 向量数据库搜索
  │     能找到:"自注意力机制通过 Q/K/V 实现序列内部关联"
  │     可能漏掉:包含 "multi-head attention" 但语义向量偏远的文档
  │
  ├── ② 关键词检索(一字不差地匹配)
  │     Query → 分词 → 关键词匹配
  │     能找到:包含 "Transformer"、"注意力" 等关键词的文档
  │     可能漏掉:"自注意力机制让模型学会了序列中各位置的关联"
  │
  └── ③ 混合检索 = ① + ② + 融合排序
        同时跑两路,合并结果
        兼顾语义理解和精确匹配 ← 这才是正解

看个更直观的对比:

场景

纯语义

纯关键词

混合检索

「同义词理解」"提升代码质量" → "提高程序可维护性"

支持

不支持

支持

「精确术语」"BGE-M3 模型"

不支持

支持

支持

「产品型号/错误码」"Error 0x80070005"

不支持

支持

支持

「长尾知识/罕见词」

不支持

支持

支持

「多语言混合」"Golang 内存泄漏排查"

支持

部分支持

支持

只有混合检索在所有场景下都能覆盖。


三、语义检索:让机器"读懂"你的意思

核心原理

代码语言:javascript
复制
文本 → Embedding 模型 → 稠密向量(1024 维浮点数组) → 向量数据库 ANN 搜索

简单说就是:把文字变成一串数字,然后用"数字之间的距离"来衡量语义相不相近。

代码语言:javascript
复制
"机器学习是人工智能的一个子领域"
→ [0.23, -0.45, 0.67, 0.12, ..., -0.01]   (1024 维,每维都有值)

特点:
• 维度固定(768 / 1024 / 1536),取决于模型
• 几乎每一维都非零 → 所以叫"稠密"
• 捕捉的是语义:同义词、上下位关系都能 handle

优势

  • 理解"你想问什么"——不同表述、不同说法都能匹配上
  • 跨语言能力——多语言模型让中英文互搜成为可能
  • 对拼写错误、口语化表达有不错的容错性

局限

  • 专有名词、低频术语在训练数据中太少,向量"认不准"
  • 产品型号、错误码这类「精确标识符」完全编不进去
  • 黑盒——你没法解释"为什么返回了这条结果"

四、关键词检索:两条截然不同的技术路线

关键词检索不是只有一种做法,它有「两条完全不同的路线」。很多人搞混了,所以我重点展开讲。

代码语言:javascript
复制
                关键词检索
                    │
       ┌────────────┴────────────┐
       ▼                         ▼
 路线 A:稀疏向量               路线 B:全文索引 + 分词
 (BM25 in Milvus)             (jieba + Qdrant/ES)
       │                         │
 把关键词检索                   用经典的
 "向量化",统一到               倒排索引
 向量检索框架中                 直接做关键词匹配

两条路线「都能实现关键词检索」,但实现机制、能力边界、适用场景完全不同。


路线 A:稀疏向量——把关键词"伪装"成向量

核心思路

把文本用 BM25 或学习型模型(SPLADE、BGE-M3 Sparse)编码成一个「超高维但绝大部分维度为 0」 的向量,然后存进向量数据库,用内积搜索来匹配。

代码语言:javascript
复制
文本 → 分词 → BM25 算法 → 稀疏向量(大部分维度为 0)
                          ↓
              Milvus SparseFloatVector 字段
                          ↓
             查询时也转成稀疏向量,用内积(IP)匹配
稀疏向量长什么样?
代码语言:javascript
复制
词汇表 = {猫:0, 狗:1, 吃:2, 鱼:3, 睡觉:4, ...}  (假设 30000 个词)

"猫吃鱼" → {0: 1.2, 2: 0.8, 3: 1.5}  // 30000 维,只有 3 个维度非零

每个非零维度的值 = 该词的 BM25 权重(综合了词频、逆文档频率、文档长度等因素)。

生成稀疏向量的三种方式

方法

说明

特点

「BM25 统计」

基于语料统计算词权重

简单、可解释

「SPLADE」

学习型稀疏编码模型

效果更好,需要推理服务

「BGE-M3 Sparse」

BGE-M3 的稀疏输出

一个模型同时出稠密 + 稀疏

Milvus 中怎么用?
代码语言:javascript
复制
// 定义 Collection Schema
schema := &entity.Schema{
    CollectionName: "chunks",
    Fields: []*entity.Field{
        {Name: "id", DataType: entity.FieldTypeVarChar, PrimaryKey: true},
        {Name: "dense_vector", DataType: entity.FieldTypeFloatVector, Dim: 1024},
        {Name: "sparse_vector", DataType: entity.FieldTypeSparseVector}, // 稀疏向量
        {Name: "content", DataType: entity.FieldTypeVarChar},
    },
}

一个 Collection 里同时放稠密和稀疏两种向量,混合检索一次 API 搞定。


路线 B:全文索引 + 分词——经典信息检索的正统玩法

核心思路

用经典的「倒排索引(Inverted Index)」:先把文本拿分词器拆成一个个词,然后建"词 → 文档列表"的反向映射。查询时也拆词,直接查映射表。

代码语言:javascript
复制
文本 → jieba 分词 → 建倒排索引
                     ↓
     词项 → [文档ID + 位置 + 词频]
                     ↓
     查询时分词 → 查倒排索引 → BM25 打分
倒排索引长什么样?
代码语言:javascript
复制
文档1: "猫吃鱼"   → ["猫", "吃", "鱼"]
文档3: "狗吃骨头" → ["狗", "吃", "骨头"]
文档5: "猫睡觉"   → ["猫", "睡觉"]

倒排索引:
"猫"   → [{doc1, pos=[0]}, {doc5, pos=[0]}]
"吃"   → [{doc1, pos=[1]}, {doc3, pos=[1]}]
"鱼"   → [{doc1, pos=[2]}]
"狗"   → [{doc3, pos=[0]}]

查询 "猫吃鱼" → 分词 ["猫","吃","鱼"] → doc1 命中 3 个词 → 最相关
分词是灵魂

「jieba 分词」在中文 RAG 场景中是最关键的环节之一,它直接决定了索引质量:

代码语言:javascript
复制
# 默认分词——可能出问题
jieba.cut("深度学习是人工智能的核心技术")
→ ["深度", "学习", "是", "人工智能", "的", "核心", "技术"]
#  ↑ "深度学习" 被拆开了!

# 加自定义词典——效果立竿见影
jieba.add_word("深度学习", freq=10000)
jieba.add_word("ChatGPT", freq=10000)
jieba.add_word("BGE-M3", freq=10000)

jieba.cut("深度学习是人工智能的核心技术")
→ ["深度学习", "是", "人工智能", "的", "核心", "技术"]
#  ↑ 完美保持整词
同义词扩展
代码语言:javascript
复制
{
  "synonym_filter": {
    "type": "synonym",
    "synonyms": [
      "LLM, 大语言模型, 大模型",
      "RAG, 检索增强生成",
      "Embedding, 嵌入, 向量化"
    ]
  }
}

搜 "大模型" 时自动匹配 "LLM" 和 "大语言模型",召回率显著提升。

各引擎的实现方式

引擎

全文检索实现

分词支持

「Elasticsearch」

原生全文搜索,最成熟

内置 IK 分词、jieba 插件

「Qdrant」

Multilingual 全文索引

内置多语言分词

「PostgreSQL」

ParadeDB @@@ 全文匹配

pg_jieba 插件


五、稀疏向量 vs 全文索引,到底选哪个?

这是大家最关心的问题,做了一张详细对比表:

维度

稀疏向量(Milvus)

全文索引(jieba + ES/Qdrant)

「存储格式」

高维稀疏浮点向量

倒排索引

「和语义检索的关系」

和稠密向量在「同一系统」

可能在「不同系统」

「打分算法」

向量内积,近似 BM25

原生 BM25 / TF-IDF

「混合检索」

一次调用搞定

两次调用 + 结果合并

「分词控制」

黑盒,不可自定义

jieba 自定义词典 + 同义词

「精确匹配」

较弱

很强

「查询能力」

只有相似度搜索

布尔、短语、通配符、正则...

「运维复杂度」

低(一个系统)

中~高

分词能力:核心区别

这是两条路线「最本质的差异」

「稀疏向量」——黑盒分词:

代码语言:javascript
复制
无法控制 "深度学习" 是拆成两词还是保持整词
无法添加业务专有术语
无法配置同义词
→ 适合通用场景

「全文索引」——白盒分词:

代码语言:javascript
复制
自定义词典: jieba.add_word("深度学习")
专有名词:   jieba.add_word("ChatGPT")
同义词扩展: "LLM" ↔ "大语言模型"
停用词控制: 过滤 "的"、"是"、"了"
→ 适合中文和专业领域

「划重点:如果你做的是中文 RAG,自定义词典和同义词扩展几乎是刚需,这时候全文索引方案有明显优势。」

查询表达力:另一个关键差异

「稀疏向量」只能做相似度搜索,给你一个排好序的结果列表,没了。

「全文索引」支持丰富的查询语法:

代码语言:javascript
复制
# 精确短语
"机器学习"                    → 必须连续出现

# 布尔组合
(Transformer OR BERT) AND 预训练 NOT GPT-2

# 通配符
deep*                        → deeplearning, deepfake, ...

# 模糊匹配
machne~1                     → machine(允许 1 个编辑距离)

# 高亮
搜索 "注意力机制" → 返回 <em>注意力机制</em>

什么时候选哪个?

「选稀疏向量」

  • 只想维护一个向量库(Milvus/Zilliz)
  • 数据量几百万到几千万
  • 快速 MVP,不需要复杂检索
  • 通用领域,不需要自定义分词

「选全文索引」

  • 中文场景,需要 jieba 自定义词典
  • 需要精确匹配:产品型号、法律条款、医学术语
  • 亿级数据,ES 分布式更成熟
  • 需要布尔查询、短语匹配等高级功能

六、混合检索怎么实现?两种方案实操对比

方案 A:稀疏向量方案(Milvus 原生)

一次 API 调用,数据库内部同时搜两种向量,自动 RRF 融合。

代码语言:javascript
复制
searchRequests := []*milvus.ANNSearchRequest{
    // 稠密向量搜索(语义)
    milvus.NewANNSearchRequest("dense_vector", "COSINE", denseQuery, topK),
    // 稀疏向量搜索(关键词)
    milvus.NewANNSearchRequest("sparse_vector", "IP", sparseQuery, topK),
}

// Milvus 内部完成 RRF 融合
results, _ := client.HybridSearch(ctx, collectionName, searchRequests,
    milvus.NewRRFRanker(60), topK)

「一句话评价」:简单省事,但分词不可控。

方案 B:全文索引方案(双通道)

两次独立调用 + 应用层融合。

代码语言:javascript
复制
// 第一步:向量检索
vectorResults := qdrantClient.Search(ctx, &qdrant.SearchPoints{
    CollectionName: collection,
    Vector:         queryEmbedding,
    Limit:          uint64(topK),
})

// 第二步:全文检索
textResults := qdrantClient.Query(ctx, &qdrant.QueryPoints{
    CollectionName: collection,
    Query:          qdrant.NewQueryText("搜索关键词"),
})

// 第三步:合并去重 + Rerank
mergedResults := mergeAndDedup(vectorResults, textResults)
finalResults := reranker.Rerank(query, mergedResults)

「一句话评价」:灵活强大,但需要多走一步。

融合排序用什么算法?

最常用的是 「RRF(倒数排名融合)」,简单又有效:

代码语言:javascript
复制
公式: RRF_score(d) = Σ 1/(k + rank_i(d))     k = 60

举个例子:
  文档 X: 语义排第 1, 关键词排第 5
  → RRF = 1/61 + 1/65 = 0.03177

  文档 Y: 语义排第 3, 关键词排第 2
  → RRF = 1/63 + 1/62 = 0.03200

  → Y 排前面(两边都靠前 > 一边极前一边靠后)

RRF 的妙处在于:「只看排名,不看分数」。所以不用操心两路检索分数量纲不同的问题。


七、Rerank 重排序:从"差不多"到"真的准"

为什么还需要 Rerank?

混合检索的第一阶段(召回)追求的是「快」「全」,精度是有限的。Rerank 用更精确的模型做"精排":

代码语言:javascript
复制
Embedding 模型(Bi-Encoder):
  分别编码 Query 和 Chunk → 独立向量 → 快,但精度有限

Reranker(Cross-Encoder):
  同时编码 Query + Chunk → 联合理解 → 慢,但精度高得多

打个比方:「召回是海选,Rerank 是终面。」

常用 Rerank 模型

模型

特点

「BGE-Reranker-v2-m3」

开源,多语言,中文友好

「Cohere Rerank」

商业 API,效果好,易集成

「bce-reranker-base_v1」

中英双语,轻量级

最佳实践

代码语言:javascript
复制
混合检索取 Top 20~50 → Rerank 精排 → 输出 Top 5

关键参数:
• 召回数量: 最终要 N 条,先召回 4N 条
• 分数阈值: 过滤 Rerank 分数太低的结果
• 降级策略: Rerank 挂了就退回原始排序,保证可用性

完整代码示例:

代码语言:javascript
复制
func (s *SearchService) HybridSearchWithRerank(
    ctx context.Context,
    knowledgeBaseID string,
    query string,
    topK int,
) ([]*SearchResult, error) {
    denseVec, err := s.embedder.EmbedDense(ctx, query)
    if err != nil {
        returnnil, fmt.Errorf("embed dense: %w", err)
    }
    sparseVec, err := s.embedder.EmbedSparse(ctx, query)
    if err != nil {
        returnnil, fmt.Errorf("embed sparse: %w", err)
    }

    // 4 倍候选量,留给 Rerank 筛选
    candidates, err := s.vectorRepo.HybridSearch(
        ctx, knowledgeBaseID, denseVec, sparseVec, topK*4,
    )
    if err != nil {
        returnnil, fmt.Errorf("hybrid search: %w", err)
    }

    reranked, err := s.reranker.Rerank(ctx, query, candidates, topK)
    if err != nil {
        return candidates[:topK], nil// 降级:Rerank 挂了就用原始结果
    }
    return reranked, nil
}

八、方案选型:三种架构方案 + 决策树

方案 A:Milvus 单引擎(稠密 + 稀疏向量)

代码语言:javascript
复制
┌─────────────────────────────────┐
│            Milvus               │
│  ┌──────────┐  ┌──────────┐    │
│  │ Dense Vec│  │Sparse Vec│    │
│  │ (语义)   │  │ (BM25)   │    │
│  └──────────┘  └──────────┘    │
│        HybridSearch + RRF      │
└─────────────────────────────────┘

优点:架构最简,一个库搞定;混合检索一次调用 不足:分词不可控;无复杂查询

方案 B:双引擎(向量库 + 全文搜索)

代码语言:javascript
复制
┌───────────────┐    ┌───────────────┐
│   Milvus      │    │Elasticsearch  │
│  (语义检索)   │    │  (关键词)     │
└───────┬───────┘    └───────┬───────┘
        └────────┬───────────┘
                 ▼
         应用层 RRF 融合 → Rerank

优点:各取所长;分词可控;复杂查询 不足:两套系统;需要自己写融合

方案 C:全能引擎(单引擎双模式)

代码语言:javascript
复制
┌─────────────────────────────────┐
│  Qdrant / ES v8 / PostgreSQL   │
│  ┌──────────┐  ┌──────────┐    │
│  │ Vector   │  │ 全文索引  │    │
│  │ (语义)   │  │ (关键词)  │    │
│  └──────────┘  └──────────┘    │
│     单引擎覆盖两种检索模式       │
└─────────────────────────────────┘

优点:单引擎双模;运维简单;分词可控 不足:超大规模下不如专业向量库

选型决策树

代码语言:javascript
复制
你的 RAG 项目需要什么?
│
├── 快速上线 + 数据 < 1000万 + 通用领域
│   → 方案 A:Milvus 单引擎
│
├── 中文场景 + 需要自定义词典 + 精确匹配
│   │
│   ├── 数据 > 5000万,性能要求高
│   │   → 方案 B:Milvus 语义 + ES 关键词
│   │
│   └── 数据量适中,运维简单优先
│       → 方案 C:Qdrant / ES v8 单引擎
│
├── 多租户 SaaS + 不同客户不同需求
│   → 方案 C:全能引擎 + 按需组合
│
└── 已有 PostgreSQL + 不想引入新组件
    → 方案 C:pgvector + ParadeDB

方案对比总结

方案 AMilvus

方案 B双引擎

方案 C全能引擎

「哲学」

一切皆向量

术业有专攻

每个引擎都全能

「运维」

简单

复杂

中等

「分词」

不可控

可控

可控

「精确匹配」

「超大规模」

最佳

最佳

中等

「代表」

Dify (Milvus)

自研大系统

Qdrant / ES v8


附录:核心术语速查

术语

解释

「RAG」

检索增强生成,先搜再答

「ANN」

近似最近邻搜索,用少量精度换速度

「BM25」

经典关键词检索算法

「RRF」

倒数排名融合,多路结果合并算法

「SPLADE」

学习型稀疏编码模型

「Bi-Encoder」

分别编码 Query 和 Doc(Embedding 模型)

「Cross-Encoder」

同时编码 Query + Doc(Reranker)

「倒排索引」

从词到文档列表的映射

「jieba」

中文分词库,支持自定义词典

「Dense Vector」

稠密向量,编码语义

「Sparse Vector」

稀疏向量,编码关键词权重

「Rerank」

重排序,对召回结果精排

「pgvector」

PostgreSQL 向量搜索扩展

「ParadeDB」

PostgreSQL 全文搜索扩展

「HNSW」

多层级近邻图索引


写在最后

RAG 的检索层看似简单,但真正做好需要理解:

  1. 「语义检索」理解"你想问什么",但对精确术语无能为力
  2. 「关键词检索」擅长精确匹配,但对同义表述视而不见
  3. 「混合检索」是唯一的正确答案,关键在于选对技术路线
  4. 「Rerank」 是从 80 分到 95 分的最后一公里
  5. 「选对架构」让你面对不同场景都能从容应对
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 有文化的技术人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 先说结论
  • 一、RAG 检索的关键在哪?
  • 二、三种检索方式一图看懂
  • 三、语义检索:让机器"读懂"你的意思
    • 核心原理
    • 优势
    • 局限
  • 四、关键词检索:两条截然不同的技术路线
    • 路线 A:稀疏向量——把关键词"伪装"成向量
      • 核心思路
      • 稀疏向量长什么样?
      • 生成稀疏向量的三种方式
      • Milvus 中怎么用?
    • 路线 B:全文索引 + 分词——经典信息检索的正统玩法
      • 核心思路
      • 倒排索引长什么样?
      • 分词是灵魂
      • 同义词扩展
      • 各引擎的实现方式
  • 五、稀疏向量 vs 全文索引,到底选哪个?
    • 分词能力:核心区别
    • 查询表达力:另一个关键差异
    • 什么时候选哪个?
  • 六、混合检索怎么实现?两种方案实操对比
    • 方案 A:稀疏向量方案(Milvus 原生)
    • 方案 B:全文索引方案(双通道)
    • 融合排序用什么算法?
  • 七、Rerank 重排序:从"差不多"到"真的准"
    • 为什么还需要 Rerank?
    • 常用 Rerank 模型
    • 最佳实践
  • 八、方案选型:三种架构方案 + 决策树
    • 方案 A:Milvus 单引擎(稠密 + 稀疏向量)
    • 方案 B:双引擎(向量库 + 全文搜索)
    • 方案 C:全能引擎(单引擎双模式)
    • 选型决策树
    • 方案对比总结
  • 附录:核心术语速查
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档