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

张开发
2026/5/6 21:10:55 15 分钟阅读
RAG 检索全攻略:从原理到落地,一篇搞懂混合检索
❝做 RAG 系统十个团队九个栽在检索上。本文把语义检索、关键词检索、混合检索、Rerank 重排序一次讲清楚。❞先说结论❝「生产级 RAG 必须用混合检索。单一检索方式无论是语义还是关键词都有致命盲区。」❞下面展开讲为什么。一、RAG 检索的关键在哪RAGRetrieval-Augmented Generation的核心流程是「先搜再答」。「搜得好」→ LLM 有好的参考资料 → 答得靠谱「搜得烂」→ LLM 只能编 → 答得一塌糊涂检索层的质量直接决定了 RAG 系统的上限。❝「Garbage In, Garbage Out.」❞目前主流有三种检索方式二、三种检索方式一图看懂假设用户问了一句话「Transformer 模型的注意力机制是什么」用户 Query │ ├── ① 语义检索理解你想问啥 │ Query → Embedding 向量 → 向量数据库搜索 │ 能找到自注意力机制通过 Q/K/V 实现序列内部关联 │ 可能漏掉包含 multi-head attention 但语义向量偏远的文档 │ ├── ② 关键词检索一字不差地匹配 │ Query → 分词 → 关键词匹配 │ 能找到包含 Transformer、注意力 等关键词的文档 │ 可能漏掉自注意力机制让模型学会了序列中各位置的关联 │ └── ③ 混合检索 ① ② 融合排序 同时跑两路合并结果 兼顾语义理解和精确匹配 ← 这才是正解看个更直观的对比场景纯语义纯关键词混合检索「同义词理解」提升代码质量 → 提高程序可维护性支持不支持支持「精确术语」BGE-M3 模型不支持支持支持「产品型号/错误码」Error 0x80070005不支持支持支持「长尾知识/罕见词」不支持支持支持「多语言混合」Golang 内存泄漏排查支持部分支持支持只有混合检索在所有场景下都能覆盖。三、语义检索让机器读懂你的意思核心原理文本 → Embedding 模型 → 稠密向量1024 维浮点数组 → 向量数据库 ANN 搜索简单说就是把文字变成一串数字然后用数字之间的距离来衡量语义相不相近。机器学习是人工智能的一个子领域 → [0.23, -0.45, 0.67, 0.12, ..., -0.01] 1024 维每维都有值 特点 • 维度固定768 / 1024 / 1536取决于模型 • 几乎每一维都非零 → 所以叫稠密 • 捕捉的是语义同义词、上下位关系都能 handle优势理解你想问什么——不同表述、不同说法都能匹配上跨语言能力——多语言模型让中英文互搜成为可能对拼写错误、口语化表达有不错的容错性局限专有名词、低频术语在训练数据中太少向量认不准产品型号、错误码这类「精确标识符」完全编不进去黑盒——你没法解释为什么返回了这条结果四、关键词检索两条截然不同的技术路线关键词检索不是只有一种做法它有「两条完全不同的路线」。很多人搞混了所以我重点展开讲。关键词检索 │ ┌────────────┴────────────┐ ▼ ▼ 路线 A稀疏向量 路线 B全文索引 分词 (BM25 in Milvus) (jieba Qdrant/ES) │ │ 把关键词检索 用经典的 向量化统一到 倒排索引 向量检索框架中 直接做关键词匹配两条路线「都能实现关键词检索」但实现机制、能力边界、适用场景完全不同。路线 A稀疏向量——把关键词伪装成向量核心思路把文本用 BM25 或学习型模型SPLADE、BGE-M3 Sparse编码成一个「超高维但绝大部分维度为 0」的向量然后存进向量数据库用内积搜索来匹配。文本 → 分词 → BM25 算法 → 稀疏向量大部分维度为 0 ↓ Milvus SparseFloatVector 字段 ↓ 查询时也转成稀疏向量用内积IP匹配稀疏向量长什么样词汇表 {猫: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 中怎么用// 定义 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」先把文本拿分词器拆成一个个词然后建词 → 文档列表的反向映射。查询时也拆词直接查映射表。文本 → jieba 分词 → 建倒排索引 ↓ 词项 → [文档ID 位置 词频] ↓ 查询时分词 → 查倒排索引 → BM25 打分倒排索引长什么样文档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 场景中是最关键的环节之一它直接决定了索引质量# 默认分词——可能出问题 jieba.cut(深度学习是人工智能的核心技术) → [深度, 学习, 是, 人工智能, 的, 核心, 技术] # ↑ 深度学习 被拆开了 # 加自定义词典——效果立竿见影 jieba.add_word(深度学习, freq10000) jieba.add_word(ChatGPT, freq10000) jieba.add_word(BGE-M3, freq10000) jieba.cut(深度学习是人工智能的核心技术) → [深度学习, 是, 人工智能, 的, 核心, 技术] # ↑ 完美保持整词同义词扩展{ 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 自定义词典 同义词「精确匹配」较弱很强「查询能力」只有相似度搜索布尔、短语、通配符、正则...「运维复杂度」低一个系统中~高分词能力核心区别这是两条路线「最本质的差异」。「稀疏向量」——黑盒分词无法控制 深度学习 是拆成两词还是保持整词 无法添加业务专有术语 无法配置同义词 → 适合通用场景「全文索引」——白盒分词自定义词典: jieba.add_word(深度学习) 专有名词: jieba.add_word(ChatGPT) 同义词扩展: LLM ↔ 大语言模型 停用词控制: 过滤 的、是、了 → 适合中文和专业领域❝「划重点如果你做的是中文 RAG自定义词典和同义词扩展几乎是刚需这时候全文索引方案有明显优势。」❞查询表达力另一个关键差异「稀疏向量」只能做相似度搜索给你一个排好序的结果列表没了。「全文索引」支持丰富的查询语法# 精确短语 机器学习 → 必须连续出现 # 布尔组合 (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 融合。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全文索引方案双通道两次独立调用 应用层融合。// 第一步向量检索 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倒数排名融合」简单又有效公式: 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 用更精确的模型做精排Embedding 模型Bi-Encoder: 分别编码 Query 和 Chunk → 独立向量 → 快但精度有限 RerankerCross-Encoder: 同时编码 Query Chunk → 联合理解 → 慢但精度高得多打个比方「召回是海选Rerank 是终面。」常用 Rerank 模型模型特点「BGE-Reranker-v2-m3」开源多语言中文友好「Cohere Rerank」商业 API效果好易集成「bce-reranker-base_v1」中英双语轻量级最佳实践混合检索取 Top 20~50 → Rerank 精排 → 输出 Top 5 关键参数: • 召回数量: 最终要 N 条先召回 4N 条 • 分数阈值: 过滤 Rerank 分数太低的结果 • 降级策略: Rerank 挂了就退回原始排序保证可用性完整代码示例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 }八、方案选型三种架构方案 决策树方案 AMilvus 单引擎稠密 稀疏向量┌─────────────────────────────────┐ │ Milvus │ │ ┌──────────┐ ┌──────────┐ │ │ │ Dense Vec│ │Sparse Vec│ │ │ │ (语义) │ │ (BM25) │ │ │ └──────────┘ └──────────┘ │ │ HybridSearch RRF │ └─────────────────────────────────┘优点架构最简一个库搞定混合检索一次调用不足分词不可控无复杂查询方案 B双引擎向量库 全文搜索┌───────────────┐ ┌───────────────┐ │ Milvus │ │Elasticsearch │ │ (语义检索) │ │ (关键词) │ └───────┬───────┘ └───────┬───────┘ └────────┬───────────┘ ▼ 应用层 RRF 融合 → Rerank优点各取所长分词可控复杂查询不足两套系统需要自己写融合方案 C全能引擎单引擎双模式┌─────────────────────────────────┐ │ Qdrant / ES v8 / PostgreSQL │ │ ┌──────────┐ ┌──────────┐ │ │ │ Vector │ │ 全文索引 │ │ │ │ (语义) │ │ (关键词) │ │ │ └──────────┘ └──────────┘ │ │ 单引擎覆盖两种检索模式 │ └─────────────────────────────────┘优点单引擎双模运维简单分词可控不足超大规模下不如专业向量库选型决策树你的 RAG 项目需要什么 │ ├── 快速上线 数据 1000万 通用领域 │ → 方案 AMilvus 单引擎 │ ├── 中文场景 需要自定义词典 精确匹配 │ │ │ ├── 数据 5000万性能要求高 │ │ → 方案 BMilvus 语义 ES 关键词 │ │ │ └── 数据量适中运维简单优先 │ → 方案 CQdrant / ES v8 单引擎 │ ├── 多租户 SaaS 不同客户不同需求 │ → 方案 C全能引擎 按需组合 │ └── 已有 PostgreSQL 不想引入新组件 → 方案 Cpgvector ParadeDB方案对比总结方案 AMilvus方案 B双引擎方案 C全能引擎「哲学」一切皆向量术业有专攻每个引擎都全能「运维」简单复杂中等「分词」不可控可控可控「精确匹配」弱强强「超大规模」最佳最佳中等「代表」Dify (Milvus)自研大系统Qdrant / ES v8附录核心术语速查术语解释「RAG」检索增强生成先搜再答「ANN」近似最近邻搜索用少量精度换速度「BM25」经典关键词检索算法「RRF」倒数排名融合多路结果合并算法「SPLADE」学习型稀疏编码模型「Bi-Encoder」分别编码 Query 和 DocEmbedding 模型「Cross-Encoder」同时编码 Query DocReranker「倒排索引」从词到文档列表的映射「jieba」中文分词库支持自定义词典「Dense Vector」稠密向量编码语义「Sparse Vector」稀疏向量编码关键词权重「Rerank」重排序对召回结果精排「pgvector」PostgreSQL 向量搜索扩展「ParadeDB」PostgreSQL 全文搜索扩展「HNSW」多层级近邻图索引写在最后RAG 的检索层看似简单但真正做好需要理解「语义检索」理解你想问什么但对精确术语无能为力「关键词检索」擅长精确匹配但对同义表述视而不见「混合检索」是唯一的正确答案关键在于选对技术路线「Rerank」是从 80 分到 95 分的最后一公里「选对架构」让你面对不同场景都能从容应对

更多文章