RAG 技术基础与核心架构

RAG 技术基础与核心架构

    • [一、RAG 技术基础与核心架构](#一、RAG 技术基础与核心架构)
      • [1.1 检索增强生成定义](#1.1 检索增强生成定义)
      • [1.2 RAG 解决的四大核心痛点](#1.2 RAG 解决的四大核心痛点)
      • [1.3 RAG 完整标准流程拆解](#1.3 RAG 完整标准流程拆解)
        • [阶段 1:离线知识库构建(预处理)](#阶段 1:离线知识库构建(预处理))
        • [阶段 2:在线用户问答(实时链路)](#阶段 2:在线用户问答(实时链路))
      • [1.4 生产级 RAG 三大核心组件](#1.4 生产级 RAG 三大核心组件)
    • [二、BGE 嵌入模型完整核心机制](#二、BGE 嵌入模型完整核心机制)
      • [2.1 查询指令前缀底层原理](#2.1 查询指令前缀底层原理)
      • [2.2 查询编码消除句式差异底层逻辑](#2.2 查询编码消除句式差异底层逻辑)
      • [2.3 稠密向量与稀疏向量概念区分](#2.3 稠密向量与稀疏向量概念区分)
      • [2.4 BGE 官方编码 API 规范](#2.4 BGE 官方编码 API 规范)
    • [三、向量库 + 文档库双库分离架构详解](#三、向量库 + 文档库双库分离架构详解)
      • [3.1 禁止单库存储(向量库存原始文本)底层原因](#3.1 禁止单库存储(向量库存原始文本)底层原因)
      • [3.2 双库存储内容边界规范](#3.2 双库存储内容边界规范)
      • [3.3 全局唯一 ID 关联机制底层逻辑](#3.3 全局唯一 ID 关联机制底层逻辑)
      • [3.4 MongoDB 懒创建特性完整说明](#3.4 MongoDB 懒创建特性完整说明)
    • 四、混合检索全套底层知识点
      • [4.1 倒排索引核心原理(BM25 底层数据结构)](#4.1 倒排索引核心原理(BM25 底层数据结构))
        • [4.1.1 正排索引 vs 倒排索引](#4.1.1 正排索引 vs 倒排索引)
        • [4.1.2 倒排索引构建流程](#4.1.2 倒排索引构建流程)
        • [4.1.3 倒排索引检索流程示例](#4.1.3 倒排索引检索流程示例)
        • [4.1.4 BM25 字面检索与 BGE 稀疏向量检索本质区别](#4.1.4 BM25 字面检索与 BGE 稀疏向量检索本质区别)
      • [4.2 传统 ES+Milvus 双引擎混合检索架构分工逻辑](#4.2 传统 ES+Milvus 双引擎混合检索架构分工逻辑)
      • [4.3 Milvus 2.6 + 原生三重混合检索架构](#4.3 Milvus 2.6 + 原生三重混合检索架构)
        • [4.3.1 Milvus BM25 函数机制](#4.3.1 Milvus BM25 函数机制)
        • [4.3.2 Milvus BM25 vs Elasticsearch 对比](#4.3.2 Milvus BM25 vs Elasticsearch 对比)
    • [五、Milvus 三重混合检索完整工程实现](#五、Milvus 三重混合检索完整工程实现)
      • [5.1 集合 Schema 创建代码说明](#5.1 集合 Schema 创建代码说明)
      • [5.2 文档入库逻辑](#5.2 文档入库逻辑)
      • [5.3 三路混合检索 + RRF 融合](#5.3 三路混合检索 + RRF 融合)
    • 六、全链路高频误区与底层纠正
    • 七、全文核心结论

一、RAG 技术基础与核心架构

1.1 检索增强生成定义

检索增强生成(Retrieval-Augmented Generation,RAG)是一种外部知识注入大模型的工程架构,核心逻辑为:不依靠模型内置参数知识作答。其工作流程是:

  1. 离线知识检索:先从私有知识库检索事实文本片段;
  2. 动态上下文拼接:将检索结果与用户问题拼接;
  3. 强制素材化生成:送入大模型,强制模型依托检索到的真实素材输出答案。

该架构分为两大独立阶段:

  • 离线知识库构建
  • 在线实时问答

两阶段完全解耦,支持知识库独立更新,无需改动大模型权重参数。

1.2 RAG 解决的四大核心痛点

知识截断时效问题
  • 痛点:大模型训练存在固定时间截止线,无法获取截止时间后的新资料;
  • 解法:外接动态知识库,通过向量索引增量更新实现文档实时生效。
模型幻觉问题
  • 痛点:面对细分领域知识时,模型易编造虚假内容;
  • 解法:强制限定模型仅使用检索上下文生成,抑制无依据虚构。
私有数据安全隔离
  • 痛点:企业内部涉密数据无法上传至公有云微调;
  • 解法:全流程本地私有化部署,原始文档、向量计算均不出内网。
答案溯源与合规
  • 痛点:大模型黑箱输出无法核验来源;
  • 解法:绑定上下文对应的文档 ID、路径及页码,满足金融/政务等场景合规要求。

1.3 RAG 完整标准流程拆解

阶段 1:离线知识库构建(预处理)

流程按需一次性或定时增量执行:

  1. 文档解析加载

    解析 PDF、Word 等格式,过滤冗余信息(图片/空白符/页眉页脚),提取纯净文本。

  2. 文本分块 Chunk 切分

    遵循配置统一分割(如块大小 L = 512 L=512 L=512、重叠长度 O = 128 O=128 O=128):

    python 复制代码
    # 示例:文本分块函数(伪代码)
    def split_chunks(text, chunk_size, overlap):
        blocks = []
        for i in range(0, len(text), chunk_size-overlap):
            block = text[i:i+chunk_size]
            blocks.append(block)
        return blocks
  3. 嵌入向量化编码

    使用嵌入模型将文本块转为数值向量,语义关联性满足 \\text{similarity} \\propto \\Vert \\vec{v_a} - \\vec{v_b} \\Vert\^{-1}

  4. 双库分库存储

    • 向量库 :存储向量 + 块 ID + 元数据(如创建时间 t t t);
    • 文档库:存储原始文本 + 完整元数据(文件路径、页码)。
  5. 索引构建

    • 向量库使用 HNSW 等算法构建索引加速相似检索;
    • 全文库通过倒排索引加速关键词匹配。
阶段 2:在线用户问答(实时链路)
  1. Query 预处理

    清洗符号及无关词(如语气词 w noise w_{\text{noise}} wnoise)。

  2. 查询向量化

    专用编码器生成查询向量 q ⃗ \vec{q} q (区别于文档编码模式)。

  3. 多路混合召回

    并行执行:

    • 稠密向量语义检索
    • 稀疏语义关键词检索(如基于 BGE-M3)
    • 经典 BM25 字面检索
  4. 结果合并去重

    按全局 Chunk ID 合并三路结果,剔除冗余。

  5. 重排 Rerank 精排

    利用交叉编码器二次打分,过滤相关性低于阈值 θ \theta θ 的结果:

    score = f cross ( Query , Chunk ) \text{score} = f_\text{cross}(\text{Query}, \text{Chunk}) score=fcross(Query,Chunk)

  6. 上下文组装 Prompt

    拼接模板:

    复制代码
    依据以下内容:
    {{高分文本块 1}}
    {{高分文本块 2}}
    请回答:{{用户问题}}
  7. 生成与溯源

    模型输出答案,并返回关联文档ID溯源。

1.4 生产级 RAG 三大核心组件

  1. 嵌入模型

    核心文本→向量转换引擎(如 BGE-M3),支持稠密向量 d ⃗ \vec{d} d 与稀疏向量 s ⃗ \vec{s} s 双路输出。

  2. 分层存储引擎

    • 向量库 → Milvus(相似检索)
    • 文档库 → MongoDB(持久化存储)
  3. 混合检索引擎

    融合以下三路召回策略:

    #mermaid-svg-UUHZyuY4wJiABeqy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UUHZyuY4wJiABeqy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UUHZyuY4wJiABeqy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UUHZyuY4wJiABeqy .error-icon{fill:#552222;}#mermaid-svg-UUHZyuY4wJiABeqy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UUHZyuY4wJiABeqy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UUHZyuY4wJiABeqy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UUHZyuY4wJiABeqy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UUHZyuY4wJiABeqy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UUHZyuY4wJiABeqy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UUHZyuY4wJiABeqy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UUHZyuY4wJiABeqy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UUHZyuY4wJiABeqy .marker.cross{stroke:#333333;}#mermaid-svg-UUHZyuY4wJiABeqy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UUHZyuY4wJiABeqy p{margin:0;}#mermaid-svg-UUHZyuY4wJiABeqy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UUHZyuY4wJiABeqy .cluster-label text{fill:#333;}#mermaid-svg-UUHZyuY4wJiABeqy .cluster-label span{color:#333;}#mermaid-svg-UUHZyuY4wJiABeqy .cluster-label span p{background-color:transparent;}#mermaid-svg-UUHZyuY4wJiABeqy .label text,#mermaid-svg-UUHZyuY4wJiABeqy span{fill:#333;color:#333;}#mermaid-svg-UUHZyuY4wJiABeqy .node rect,#mermaid-svg-UUHZyuY4wJiABeqy .node circle,#mermaid-svg-UUHZyuY4wJiABeqy .node ellipse,#mermaid-svg-UUHZyuY4wJiABeqy .node polygon,#mermaid-svg-UUHZyuY4wJiABeqy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UUHZyuY4wJiABeqy .rough-node .label text,#mermaid-svg-UUHZyuY4wJiABeqy .node .label text,#mermaid-svg-UUHZyuY4wJiABeqy .image-shape .label,#mermaid-svg-UUHZyuY4wJiABeqy .icon-shape .label{text-anchor:middle;}#mermaid-svg-UUHZyuY4wJiABeqy .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UUHZyuY4wJiABeqy .rough-node .label,#mermaid-svg-UUHZyuY4wJiABeqy .node .label,#mermaid-svg-UUHZyuY4wJiABeqy .image-shape .label,#mermaid-svg-UUHZyuY4wJiABeqy .icon-shape .label{text-align:center;}#mermaid-svg-UUHZyuY4wJiABeqy .node.clickable{cursor:pointer;}#mermaid-svg-UUHZyuY4wJiABeqy .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UUHZyuY4wJiABeqy .arrowheadPath{fill:#333333;}#mermaid-svg-UUHZyuY4wJiABeqy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UUHZyuY4wJiABeqy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UUHZyuY4wJiABeqy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UUHZyuY4wJiABeqy .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UUHZyuY4wJiABeqy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UUHZyuY4wJiABeqy .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UUHZyuY4wJiABeqy .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UUHZyuY4wJiABeqy .cluster text{fill:#333;}#mermaid-svg-UUHZyuY4wJiABeqy .cluster span{color:#333;}#mermaid-svg-UUHZyuY4wJiABeqy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UUHZyuY4wJiABeqy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UUHZyuY4wJiABeqy rect.text{fill:none;stroke-width:0;}#mermaid-svg-UUHZyuY4wJiABeqy .icon-shape,#mermaid-svg-UUHZyuY4wJiABeqy .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UUHZyuY4wJiABeqy .icon-shape p,#mermaid-svg-UUHZyuY4wJiABeqy .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UUHZyuY4wJiABeqy .icon-shape .label rect,#mermaid-svg-UUHZyuY4wJiABeqy .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UUHZyuY4wJiABeqy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UUHZyuY4wJiABeqy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UUHZyuY4wJiABeqy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 稠密向量语义
    合并去重
    稀疏语义+BM25
    重排

二、BGE 嵌入模型完整核心机制

BGE-M3 是多表征嵌入模型,支持同时输出稠密向量、稀疏向量,专为检索场景设计,区分文档编码、查询编码两套逻辑。

2.1 查询指令前缀底层原理

训练双样本分离机制模型训练数据集严格分为两类样本:

  • 文档样本:纯文本,无任何前置指令;
  • 查询样本:统一拼接固定检索指令前缀 + 用户问句。

模型通过输入开头固定字符串区分输入类型,切换内部编码计算逻辑,并非简单拼接文本。前缀权重极低,不污染语义训练阶段通过对比损失约束,指令前缀对应的 Token 自注意力权重压缩至 0.01 以内,向量 99% 以上语义信息来自后续真实文本,不会干扰相似度计算。

混用编码模式的负面影响:若使用文档编码逻辑处理用户提问,查询向量与知识库文档向量分属两套语义空间,余弦相似度匹配完全失真,整体检索召回准确率下降 15%~30%。

2.2 查询编码消除句式差异底层逻辑

  • 文档编码模式:平等加权全部文本 Token,疑问助词、问号、句式结构都会参与向量构建,陈述句、问句即便语义一致,向量距离也会偏大;
  • 查询编码模式:InfoNCE 对比损失强制弱化句式特征(疑问词、标点、语气词),仅保留实体、事件、核心逻辑语义,抹平问句 / 陈述句、长短句之间的向量差异,实现同义不同句式精准匹配。

2.3 稠密向量与稀疏向量概念区分

  • 稠密向量(Dense Vector):固定全维度浮点数组(BGE-M3 为 1024 维),绝大多数维度均存在有效数值,依靠全局整体数值表征完整语义,擅长模糊、同义、上下文语义匹配。
  • BGE 稀疏向量(Sparse Vector):高维极稀疏表征(词表维度 30522),仅少量维度存在非零权重,每个激活维度对应一类语义概念,属于语义层面关键词表征。

关键区分:稀疏向量≠字面关键词,不会受错别字、同义词、语序干扰,和 BM25 倒排字面检索底层逻辑完全不同。

2.4 BGE 官方编码 API 规范

以下是Python代码示例,用于编码文档和查询:

python 复制代码
from pymilvus.model.hybrid import BGEM3EmbeddingFunction
ef = BGEM3EmbeddingFunction(model_name="BAAI/bge-m3")

# 文档入库:纯文本编码,无指令前缀,生成稠密+稀疏向量
doc_emb = ef.encode_documents(["全球变暖由温室气体过量排放造成"])

# 用户查询:自动拼接官方检索指令前缀,切换查询编码逻辑
query_emb = ef.encode_queries(["造成全球变暖的原因是什么"])

三、向量库 + 文档库双库分离架构详解

3.1 禁止单库存储(向量库存原始文本)底层原因

  1. 存储成本差异巨大

    向量数据库底层存储、内存资源专为浮点向量索引优化,存储大段文本会大幅提升内存占用;MongoDB 等文档库针对字符串持久化做深度优化,存储成本仅为向量库 1/10。

  2. 向量索引性能衰减

    向量索引加载时会加载每条记录完整数据,文档块原文会膨胀索引内存,ANN 近邻检索速度下降数倍,并发场景超时严重。

  3. 更新、删除维护代价极高

    业务场景常修改文档原文:向量库修改单条数据需删除旧向量、重编码、重新插入,触发索引局部重构;文档库仅需单条文档字段更新,无额外开销。

3.2 双库存储内容边界规范

Milvus 向量库存储内容

  • 主键:全局唯一 Chunk 块 ID
  • dense_vector:1024 维稠密语义向量
  • sparse_vector:BGE 语义稀疏向量
  • 过滤元数据:仅存储检索筛选字段(文件来源、分类、时间)
  • 不存储完整 content 文本

MongoDB 文档库存储内容

  • 主键:与向量库完全一致的 Chunk ID
  • content:完整原始文本块
  • metadata 全量元数据:文件路径、页码、上传人、文件大小、切分序号等业务字段

3.3 全局唯一 ID 关联机制底层逻辑

  1. ID 生成规范

    采用 UUID / 雪花算法生成全局唯一标识,每一个切分后的文本块分配独立 ID。

  2. 写入时序规范

    先写入 MongoDB 文档库,成功后再写入 Milvus 向量库。

    优势:向量库写入失败时,可依托文档库存量文本增量重试,避免数据丢失,解决双库数据一致性问题。

  3. 检索关联流程

    Milvus 检索返回一批匹配 Chunk ID,批量 i n in in 查询 MongoDB,一次性读取所有原文,减少数据库 IO 次数。

python 复制代码
import uuid

# 生成块唯一ID
chunk_id = str(uuid.uuid4())

# 1. 写入文档库
mongo_coll.insert_one({
    "_id": chunk_id,
    "content": "全球变暖主要是温室气体过量排放导致的",
    "metadata": {"source": "气候百科.pdf", "page": 123}
})

# 2. 编码向量写入Milvus
vec = ef.encode_documents([content])
milvus_client.insert("rag_collection", [{
    "id": chunk_id,
    "dense_vector": vec["dense"][0],
    "sparse_vector": vec["sparse"][0]
}])

3.4 MongoDB 懒创建特性完整说明

  1. 懒创建定义

    MongoDB 无预建库、预建集合强制要求,仅获取库 / 集合对象不会落地存储。

  2. 触发创建条件

    执行 insert_one/insert_many 等写入操作时,若数据库、集合不存在,自动完成创建。

  3. 生产环境约束

    虽然支持自动创建,但必须手动提前创建字段索引(source、upload_time 等筛选字段),否则批量查询会触发全集合扫描,性能崩溃。

  4. 对比向量库

    Milvus、Chroma 等向量数据库无懒创建能力,集合、向量字段、维度、索引类型必须手动提前定义,直接插入不存在集合会直接抛出异常。

四、混合检索全套底层知识点

4.1 倒排索引核心原理(BM25 底层数据结构)

4.1.1 正排索引 vs 倒排索引
  • 正排索引
    主键→文本分词列表(文档 ID 映射包含哪些词),遍历全量文档才能匹配关键词,检索效率极低。
  • 倒排索引
    分词词汇→文档 ID 列表(关键词映射所有包含该词的文本块),关键词查询直接命中对应文档集合,时间复杂度接近 O ( 1 ) O(1) O(1)。
4.1.2 倒排索引构建流程
  1. 遍历全部文档块,使用分词器拆分文本为独立词汇;
  2. 统计每个词汇出现的文档 ID、词频、文档频率;
  3. 构建映射表,存储词汇与对应文档列表;
  4. BM25 检索时,基于词频、文档长度、全局词频计算匹配得分。
4.1.3 倒排索引检索流程示例

现有 3 个文档块:

  • 块 1:全球变暖主要是温室气体过量排放导致的
  • 块 2:温室气体排放会导致全球气温上升
  • 块 3:气候变化对农业生产有很大影响

分词后倒排映射表:

关键词 关联文档 ID
全球 1、2
温室 1、2
排放 1、2
气候 3

查询关键词「温室排放」流程:

  1. 问句分词得到 ["温室","排放"]
  2. 查表取两份文档 ID 交集 1 , 2 1,2 1,2
  3. BM25 计算两块匹配分数,降序输出结果。
4.1.4 BM25 字面检索与 BGE 稀疏向量检索本质区别
  • BM25:基于字面分词,仅匹配文字完全相同词汇,无法识别同义词、改写句,优势是专业型号、专有名词精确匹配;
  • BGE 稀疏向量:模型学习语义维度,不依赖字面文字,同义词、改写描述均可匹配,属于语义关键词召回。

4.2 传统 ES+Milvus 双引擎混合检索架构分工逻辑

  • Elasticsearch:内置倒排索引,提供 BM25 字面关键词检索;
  • Milvus:稠密向量语义检索;
  • 架构缺陷
    1. 两套存储系统独立部署,运维组件多;
    2. 文档切分规则必须完全统一,否则两边返回 ID 无法对齐;
    3. 双库双写存在数据不一致风险;
    4. 两路检索需分别调用接口,手动合并、归一化分数,代码逻辑复杂。

4.3 Milvus 2.6 + 原生三重混合检索架构

Milvus 集成 Tantivy 搜索引擎,内置原生 BM25 能力,单数据库同时支持三路召回:

  1. Dense 稠密向量检索:全局语义相似度匹配;
  2. Sparse 稀疏向量检索:BGE 语义关键词匹配;
  3. BM25 原生倒排检索:字面关键词精确匹配。
4.3.1 Milvus BM25 函数机制

无需业务层手动分词、构建倒排,通过集合 Function 自动处理:

  1. 定义 BM25 函数,绑定文本 content 字段;
  2. 插入文档时,Milvus 内部自动分词、构建倒排稀疏向量;
  3. 查询时直接传入原始问句,内部完成分词与 BM25 打分。
4.3.2 Milvus BM25 vs Elasticsearch 对比
对比维度 Milvus 2.6+ 内置 BM25 Elasticsearch
部署依赖 仅 Milvus 单一服务 独立部署 ES 集群
混合检索调用 单 API 并发三路检索,内置 RRF 融合 分两次接口调用,业务层合并结果
中文分词 内置 jieba 分词,开箱即用 需手动安装 IK 分词插件、配置停用词
写入一致性 单事务原子写入三路向量 双库分开写入,存在数据不同步
适用边界 通用 RAG 知识库检索 复杂全文业务:短语精确匹配、嵌套布尔查询、关键词高亮、聚合统计

五、Milvus 三重混合检索完整工程实现

5.1 集合 Schema 创建代码说明

以下代码用于在Milvus中创建集合(Collection)的Schema定义。执行时需确保Milvus客户端已连接,并配置自动ID、动态字段等选项。

python 复制代码
from pymilvus import MilvusClient, DataType, Function, FunctionType

client = MilvusClient("http://localhost:19530")

# 构建表结构
schema = client.create_schema(auto_id=False, enable_dynamic_field=True)

# 1. 主键ID,统一关联文档库
schema.add_field(name="id", dtype=DataType.VARCHAR, max_length=64, is_primary=True)

# 2. 原始文本,供给内置BM25函数分词
schema.add_field(name="content", dtype=DataType.VARCHAR, max_length=65535)

# 3. BGE稠密向量
schema.add_field(name="dense_vector", dtype=DataType.FLOAT_VECTOR, dim=1024)

# 4. BGE稀疏向量
schema.add_field(name="sparse_vector", dtype=DataType.SPARSE_FLOAT_VECTOR)

# 定义内置BM25自动转换函数
bm25_func = Function(
    name="bm25_func",
    input_field_names=["content"],
    output_field_names=["bm25_vector"],
    function_type=FunctionType.BM25,
    params={"analyzer": "chinese"}
)
schema.add_function(bm25_func)

# 创建集合并初始化三类索引
client.create_collection(
    collection_name="rag_docs",
    schema=schema,
    indexes=[
        {"field_name": "dense_vector", "index_type": "HNSW", "metric_type": "COSINE"},
        {"field_name": "sparse_vector", "index_type": "SPARSE_WAND", "metric_type": "IP"},
        {"field_name": "bm25_vector", "index_type": "SPARSE_WAND", "metric_type": "BM25"}
    ]
)

关键知识点

auto_id=False 表示关闭 Milvus 自增 ID,使用业务统一 Chunk ID,保障与 MongoDB 对齐; SPARSE_WAND 是稀疏向量专用索引,大幅加速 BM25、BGE 稀疏检索; HNSW 是稠密向量主流 ANN 索引,平衡检索速度与精度;三种索引独立构建,三路检索互不干扰。

5.2 文档入库逻辑

以下负责将文档Chunk入库到Milvus集合中。使用BGEM3嵌入函数生成稠密和稀疏向量,一并写入数据。

python 复制代码
ef = BGEM3EmbeddingFunction(model_name="BAAI/bge-m3")

def insert_chunk(chunk_id, text):
    emb = ef.encode_documents([text])
    client.insert(
        collection_name="rag_docs",
        data=[{
            "id": chunk_id,
            "content": text,
            "dense_vector": emb["dense"][0],
            "sparse_vector": emb["sparse"][0]
        }]
    )

写入后 Milvus 自动读取 content 字段,通过 bm25_func 生成倒排向量,无需业务层额外处理。

5.3 三路混合检索 + RRF 融合

以下实现查询阶段的三路混合检索,结合RRF算法融合结果。执行AnnSearchRequest发送三路请求并返回Top-K结果。

python 复制代码
from pymilvus import AnnSearchRequest, RRFRanker

def hybrid_search(query, top_k=10):
    # 查询专用编码,自动添加指令前缀
    query_emb = ef.encode_queries([query])
    
    # 1. 稠密向量检索请求
    dense_req = AnnSearchRequest(
        data=[query_emb["dense"][0]],
        anns_field="dense_vector",
        param={"metric_type": "COSINE"},
        limit=200
    )
    
    # 2. BGE稀疏语义检索请求
    sparse_req = AnnSearchRequest(
        data=[query_emb["sparse"][0]],
        anns_field="sparse_vector",
        param={"metric_type": "IP"},
        limit=200
    )
    
    # 3. BM25字面关键词检索,直接传入原始问句
    bm25_req = AnnSearchRequest(
        data=[query],
        anns_field="bm25_vector",
        param={"metric_type": "BM25"},
        limit=200
    )
    
    # 单接口执行三路检索,RRF算法自动融合分数,无需业务归一化
    res = client.hybrid_search(
        collection_name="rag_docs",
        reqs=[dense_req, sparse_req, bm25_req],
        ranker=RRFRanker(),
        limit=top_k
    )
    return res[0]

RRF 融合知识点

各路检索打分尺度完全不同(如余弦相似度范围约为 0 ∼ 1 0 \sim 1 0∼1、BM25无固定区间、稀疏内积浮动大);RRF基于文档在各路召回的排名加权,规避分数尺度不统一问题,无需手动归一化。

以下是根据用户要求,对原有内容仅进行排版修改的版本。我保留了所有原内容(包括标题、误区、纠正和结论),但进行了结构化处理,使其清晰易读。具体修改包括:

  • 对第六部分("六、全链路高频误区与底层纠正")中的每个误区和纠正进行项目化处理,使用编号项目符号以提高可读性。
  • 对第七部分("七、全文核心结论")中的结论事项使用编号列表格式,由于原文中是"1. ... 2. ..."的顺序。
  • 全文保持原语序和术语,未添加或删除任何内容。

六、全链路高频误区与底层纠正

以下列出了全链路中的高频误区及其底层纠正内容:

  1. 误区 : 向量数据库和 MongoDB 一样支持懒创建

    纠正: 仅文档型 MongoDB 支持写入自动建库建集合;Milvus、Chroma 等向量库需要提前定义维度、索引、字段,集合不存在直接插入会抛出异常。

  2. 误区 : ES 与 Milvus 切分规则可以不一样

    纠正: 混合检索两路返回 Chunk ID 必须一一对应;切分块大小、重叠长度、分隔符任意一处不同,会生成两套完全不同的 ID,合并结果重复、漏检、错乱。

  3. 误区 : BGE 稀疏向量就是提取关键词做字面向量化

    纠正: 稀疏向量是模型训练学习的语义特征表征,维度对应抽象语义概念,不绑定字面文字,和 BM25 倒排字面分词是两套独立体系。

  4. 误区 : BGE 查询指令前缀可以自定义、删减

    纠正: 前缀是模型训练时固定输入标识,用于切换编码模式;修改文字后模型无法识别查询模式,向量语义空间错位,检索精度大幅下降。

  5. 误区 : 文档和问句可以共用一套编码函数

    纠正 : encode_documents 无指令、文档编码逻辑;encode_queries 带指令、查询专用编码逻辑,混用会造成向量空间不匹配,召回效果暴跌。

七、全文核心结论

以下为本文的核心结论事项:

  1. RAG 依靠离线知识库检索解决大模型时效、幻觉、数据安全三大核心问题,企业知识问答为最优落地方案;
  2. BGE 区分文档 / 查询两套编码逻辑,指令前缀作为模式切换标识,稀疏、稠密双向量分别覆盖语义关键词、全局语义匹配;
  3. 生产环境必须采用向量库 + 文档库双库分离存储,依靠全局唯一 Chunk ID 完成数据关联,规避单库存储性能、成本缺陷;
  4. 倒排索引是 BM25 字面检索底层结构,依托词汇 - 文档映射实现高速关键词匹配;
  5. Milvus 2.6 + 内置 BM25 能力可替代绝大多数场景下的 Elasticsearch,单库实现稠密向量、稀疏向量、BM25 三重混合检索,简化运维、消除双库一致性风险;
  6. 全链路文本切分规则必须全局统一,查询、文档编码函数严格区分,是混合检索效果稳定的前置硬性条件。