在这里插入图片描述

基于 Milvus 向量数据库实现 RAG 检索系统

随着大语言模型(LLM)在智能问答、企业知识库、智能客服、文档检索等场景大规模落地,其知识固化、无法实时更新、无私有数据认知的核心缺陷愈发明显。大模型仅能依托训练数据输出内容,无法识别企业内部文档、最新规章制度、私有业务资料等外部数据,极易出现幻觉、答非所问、知识滞后等问题。
为解决大模型的知识局限性,RAG(Retrieval-Augmented Generation,检索增强生成)技术成为工业界主流解决方案。其核心逻辑简单高效:先从私有知识库检索匹配的上下文内容,再将检索结果送入大模型,辅助大模型生成精准、可靠、无幻觉的答案。


文章目录

第一章 为什么需要向量数据库

随着大语言模型(LLM)的快速发展,越来越多的企业开始将 AI 应用于知识问答、智能客服、文档检索等业务场景。然而,大模型本身存在一个天然缺陷——它只能回答训练时学到的知识

例如:

用户提问:“公司最新员工手册中的请假制度是什么?”

由于这份员工手册属于企业内部资料,大模型并没有参与训练,因此无法直接回答。为了解决这一问题,RAG(Retrieval-Augmented Generation,检索增强生成)应运而生。

RAG 的核心思想非常简单:

先从知识库检索相关内容,再将检索结果交给大模型生成最终答案。

整个流程如下:

用户问题

Embedding

向量数据库

检索相关Chunk

LLM

最终答案

可以发现,在整个 RAG 系统中,大模型负责"生成",而真正负责"查资料"的是向量数据库


1.1 为什么不能使用 MySQL?

很多初学者都会有一个疑问:

既然数据最终还是保存下来,为什么不用 MySQL?

MySQL 擅长的是精确查询

例如:

SELECT *
FROM user
WHERE id = 1001;

或者:

SELECT *
FROM employee
WHERE name='张三';

这类查询要求数据必须完全一致。

而 RAG 的检索并不是寻找完全相同的数据,而是寻找语义最相近的数据

例如:

文档中写的是:

如何安装 Docker?

用户输入:

Docker 怎么部署?

虽然两个句子没有任何相同的关键词,但表达的是同一个意思。

MySQL 无法理解语义,因此很难完成这种查询。


1.2 什么是向量检索?

Embedding 模型会把一句话转换成一个高维向量。

例如:

安装 Docker

↓

[-0.31,
0.56,
0.92,
......]

另一句话:

Docker 如何部署

↓

[-0.28,
0.58,
0.90,
......]

虽然文本不同,但是它们的向量位置非常接近。

因此只需要计算两个向量之间的距离,就能够判断它们是否表达了相同的语义。

整个过程如下:

文本A

Embedding

文本B

Embedding

向量空间

计算距离

返回最相近文档

向量数据库就是专门解决这个问题而设计的。


1.3 向量数据库解决了什么问题?

如果没有向量数据库,那么每次查询都需要遍历所有向量。

例如:

100 万条数据

↓

第一条

↓

第二条

↓

第三条

↓

......

↓

第一百万条

这种方式称为 Brute Force(暴力搜索)

时间复杂度:

O(n)

数据越多,速度越慢。

Milvus 会提前建立索引,例如:

  • HNSW
  • IVF
  • DiskANN

因此查询复杂度可以降低到接近:

O(logN)

即使数据达到数亿条,也能在毫秒级完成检索。


第二章 Milvus 在 RAG 中的作用

Milvus 是目前最流行的开源向量数据库之一,也是企业级 RAG 项目中最常见的选择。

在整个 RAG 系统中,它承担两个核心职责:

  1. 保存向量
  2. 检索向量

完整流程如下:

原始文档

文档解析

Chunk切分

Embedding

Milvus

用户提问

Embedding

TopK Chunk

LLM

最终答案

整个过程中:

  • 文档解析、切分、Embedding 属于数据准备阶段;
  • Milvus 负责存储和检索;
  • 大模型负责组织语言并生成最终答案。

因此,Milvus 更像是 RAG 系统中的"知识仓库"。


2.1 为什么选择 Milvus?

目前主流向量数据库有很多,例如:

数据库 特点
FAISS Facebook 开源,适合本地开发
Chroma 轻量级,适合快速验证
PgVector PostgreSQL 插件
Elasticsearch 同时支持全文检索和向量检索
Milvus 企业级、高性能、支持混合检索

Milvus 的优势主要体现在以下几个方面:

  • 支持十亿级向量存储;
  • 支持多种 ANN 索引算法;
  • 支持稠密向量稀疏向量
  • 原生支持 Hybrid Search;
  • 支持 Reranker重排
  • 支持分布式部署。

对于生产环境而言,Milvus 是目前非常成熟的解决方案。


第三章 Milvus 两阶段执行流程(重点)

Milvus 的整个工作流程可以分为两个阶段:

  1. 索引阶段(离线)
  2. 检索阶段(在线)

二者共同构成了整个向量检索系统。

整体流程如下:

文档

切分Chunk

Embedding

Milvus

建立索引

用户问题

Embedding

Milvus Search

Reranker

LLM

Answer

下面分别介绍两个阶段。


3.1 第一阶段:索引阶段(Indexing)

索引阶段主要完成数据入库。

流程如下:

加载文档

文档切分

Embedding

生成Dense Vector

生成Sparse Vector

Insert

Growing Segment

Flush

Sealed Segment

后台建立索引

完成入库

整个阶段可以拆分为六个步骤。


(1)加载文档

系统首先读取知识库中的各种文件,例如:

  • PDF
  • Word
  • Markdown
  • TXT
  • Excel

统一转换为 Document 对象。


(2) 文档切分

由于大模型上下文有限,一篇几十页的 PDF 不可能整体存入数据库。

因此需要按照一定规则切分。

例如:

原文

↓

Chunk1

↓

Chunk2

↓

Chunk3

↓

Chunk4

每个 Chunk 后续都会生成一个向量。

合理的 Chunk 大小通常控制在 300~600 Token 左右,同时保留一定的重叠区域,避免上下文信息丢失。


(3)Embedding

每个 Chunk 会调用 Embedding 模型生成向量。

例如:

Chunk

↓

Embedding

↓

Dense Vector

+

Sparse Vector

其中:

Dense Vector 用于语义检索。

Sparse Vector 用于关键词检索。

如果使用 BGE-M3模型,一次编码即可同时生成两种向量。


(4)Insert

生成向量之后,客户端调用 Milvus 的 Insert 接口,将数据写入数据库。

每条数据通常包括:

ID

Text

Dense Vector

Sparse Vector

Metadata

这些数据首先不会立即建立索引,而是进入 Growing Segment。


(5)Growing Segment → Flush

Growing Segment 可以理解为"内存中的临时数据段"。

此时:

  • 数据可以查询;
  • 数据尚未建立索引;
  • 查询采用暴力扫描。

当满足以下任一条件时,会触发 Flush:

  • 数据量达到阈值;
  • 到达定时刷新时间;
  • 手动调用 Flush。

Flush 后,数据写入对象存储,并转换为 Sealed Segment


(6)后台建立索引

数据完成落盘后,Data Node 会在后台自动构建索引。

例如:

Dense Vector(稠密向量):

HNSW(分层导航小世界)是当下最常用的基于图的索引算法,具有出色的搜索精度和低延迟,但需要
较高内存开销。它构建多层图(类似不同缩放级别的地图),底层包含所有数据点,上层由采样子集组
成:

Sparse Vector(稀疏向量):

SPARSE_INVERTED_INDEX:利用倒排索引的原理,为稀疏数据创建高效的搜索结构。查询时先通过倒排索引找到包含query中token的文档,然后计算相似度分数:

索引建立完成后,后续查询便不再需要遍历所有数据,而是直接通过索引快速定位候选向量。

至此,整个索引阶段结束。


3.2 第二阶段:检索阶段(Searching)

当用户发起查询时,Milvus 会进入在线检索阶段。

整体流程如下:

用户输入问题

Embedding

Dense Search

Sparse Search

Hybrid Merge

Reranker

TopK

LLM生成答案

整个过程主要包括五个步骤。

(1)Query Embedding

首先将用户输入转换成查询向量。

如果使用 BGE-M3,则会同时生成:

  • Dense Query Vector
  • Sparse Query Vector

(2)双路召回

Milvus 同时执行两次搜索:

  • Dense Search(语义召回)
  • Sparse Search(关键词召回)

Dense Search 更擅长理解语义关系,例如:

Docker 怎么部署

≈

如何安装 Docker

Sparse Search 更擅长关键词匹配,例如:

《民法典》第236条

两种方式各有优势,因此通常会同时使用。


(3)Hybrid Search

Milvus 将 Dense Search 和 Sparse Search 的结果进行融合。

得到更大的候选集。


(4)Reranker

融合后的结果仍然存在排序不准确的问题,因此需要进行二次排序(Rerank)。

例如:

Dense:

A

B

C

Sparse:

B

C

D

↓

Rerank

↓

B

C

A

D

最终得到更加准确的 TopK 结果。


(5)LLM 生成答案

Milvus 返回最相关的若干 Chunk。

随后将这些内容拼接到 Prompt 中发送给大模型。

最终生成符合上下文的回答。

至此,整个在线检索阶段结束。


第四章 Milvus 核心概念与索引机制

在上一章节中,我们介绍了 Milvus 在 RAG 系统中的两阶段执行流程。了解整体流程后,还需要理解 Milvus 中几个核心概念,包括 Collection、Schema、向量字段、索引以及相似度度量方式。这些内容是后续进行向量检索和混合搜索的基础。


4.1 Collection(集合)

Collection 是 Milvus 中最重要的数据组织单位,可以理解为关系型数据库中的一张数据表。

例如,一个企业知识库中的所有文档片段都可以存放到一个 Collection 中。

Knowledge Collection
├── id
├── text
├── vector
├── sparse_vector
└── metadata

其中:

字段 作用
id 主键,唯一标识一条数据
text 保存原始文本
vector 保存稠密向量
sparse_vector 保存稀疏向量
metadata 保存来源、页码等元数据

通常情况下,一个知识库对应一个 Collection,不同业务可以建立不同的 Collection,实现数据隔离。


4.2 Schema(数据结构)

Schema 用于定义 Collection 的字段信息,相当于 MySQL 中的表结构。

例如:

from pymilvus import MilvusClient, DataType

schema = (
    MilvusClient.create_schema(auto_id=True)
    .add_field(
        field_name="id",
        datatype=DataType.INT64,
        is_primary=True
    )
    .add_field(
        field_name="vector",
        datatype=DataType.FLOAT_VECTOR,
        dim=1024
    )
    .add_field(
        field_name="sparse_vector",
        datatype=DataType.SPARSE_FLOAT_VECTOR
    )
    .add_field(
        field_name="text",
        datatype=DataType.VARCHAR,
        max_length=1500
    )
    .add_field(
        field_name="metadata",
        datatype=DataType.JSON
    )
)

整个 Schema 定义了 Collection 中允许存储的数据类型。

其中最重要的是两个向量字段:

  • Dense Vector(稠密向量)
  • Sparse Vector(稀疏向量)

4.3 Dense Vector(稠密向量)

稠密向量主要用于语义检索。

Embedding 模型会将一句文本转换为一个固定长度的浮点数组,例如:

如何安装 Docker

↓

[0.13,
-0.57,
0.42,
......
]

每一个数字代表文本在高维空间中的一个坐标。

语义越接近,两段文本之间的向量距离越近。

例如:

如何安装 Docker

↓

Embedding

↓

[0.21,0.37,...]
Docker 怎么部署

↓

Embedding

↓

[0.19,0.35,...]

虽然两句话完全不同,但是向量距离非常接近,因此能够检索成功。


4.4 Sparse Vector(稀疏向量)

稀疏向量主要用于关键词检索。

与 Dense Vector 不同,它不会保存每一个维度,而是只保存非零元素。

例如:

民法典 第236条

生成后的稀疏向量可能类似:

{
236:0.95,
民法典:0.88
}

因此检索速度非常快。

尤其适用于:

  • 法律文档
  • 产品型号
  • API名称
  • 编号
  • 专有名词

例如:

SpringBoot3

RedisTemplate

Docker

MySQL8

这些关键词采用 Sparse Search 的效果通常优于 Dense Search。


4.5 Metadata(元数据)

Metadata 用于保存文档的附加信息。

例如:

{
    "source":"Docker教程.pdf",
    "page":12,
    "author":"admin"
}

这些信息不会参与向量计算,但是可以作为过滤条件。

例如:

filter='metadata["source"]=="Docker教程.pdf"'

也可以结合向量检索一起使用。

例如:

先搜索向量

↓

再过滤来源

↓

最终结果

这种方式可以有效减少无关文档,提高检索准确率。


4.6 Index(索引)

如果没有索引,每次查询都需要遍历所有向量。

例如:

100万条数据

↓

第1条

↓

第2条

↓

......

↓

第1000000条

这种方式称为 FLAT(暴力搜索)。

优点:

  • 100%召回

缺点:

  • 数据越多越慢

因此实际项目中都会建立索引。

Milvus 支持多种索引:

  • FLAT
  • IVF_FLAT
  • IVF_PQ
  • HNSW
  • DiskANN

其中 RAG 项目最常用的是 HNSW。


4.7 HNSW 索引原理

HNSW(Hierarchical Navigable Small World)是一种基于图结构的近似最近邻(ANN)索引,也是目前 Milvus 默认推荐的稠密向量索引。

可以把它理解为一张多层导航地图。

Layer2 全国地图

Layer1 城市地图

Layer0 街道地图

目标向量

整个搜索过程不是遍历所有节点,而是逐层缩小搜索范围。

例如:

第一层:

全国地图

↓

上海附近

第二层:

上海

↓

浦东新区

第三层:

浦东新区

↓

目标餐厅

这种搜索方式可以快速定位目标,大幅减少计算量。

相比暴力搜索:

O(n)

HNSW 的查询复杂度约为:

O(logN)

因此即使面对千万甚至上亿条数据,依然能够保持毫秒级检索速度。


4.8 SPARSE_INVERTED_INDEX

稀疏向量使用的是倒排索引(SPARSE_INVERTED_INDEX)。

它的思想与搜索引擎类似。

例如数据库中有三篇文档:

Doc1

Docker 安装
Doc2

Docker Compose
Doc3

SpringBoot

系统会建立如下倒排索引:

Docker

↓

Doc1

Doc2
SpringBoot

↓

Doc3

查询:

Docker

无需遍历全部数据,直接定位:

Doc1

Doc2

因此关键词检索速度非常快。


4.9 相似度度量(Metric Type)

建立索引之后,还需要指定向量之间如何计算相似度。

Milvus 支持三种常见度量方式。

(1)IP(Inner Product)

计算向量内积。

特点:

  • 计算速度最快
  • 适用于已经归一化的文本向量

目前多数文本 Embedding 模型推荐使用 IP。


(2)COSINE

计算余弦相似度。

特点:

  • 消除向量长度影响
  • 更关注方向是否一致

文本语义检索中应用最广泛。

例如:

安装 Docker

↓

Docker 怎么部署

方向接近,因此余弦相似度较高。


(3)L2

计算欧氏距离。

特点:

  • 衡量空间距离
  • 常用于图像向量

距离越小,相似度越高。


4.10 如何选择 Metric?

实际项目中的推荐方案如下:

场景 推荐
BGE、M3 等文本模型 IP
不确定是否归一化 COSINE
图像检索 L2
稀疏向量 IP

对于目前主流 RAG 项目,大多数都会采用:

  • Dense Vector:HNSW + IP(或 COSINE)
  • Sparse Vector:SPARSE_INVERTED_INDEX + IP

这样既保证了检索效率,又兼顾了语义理解能力。


第五章 代码实战:Milvus 稠密向量检索

本章基于 Python + pymilvus + BGE-M3 实现完整的稠密向量语义检索流程,包括文档加载、文本切分、Embedding 向量生成、数据入库、索引构建以及相似度检索,帮助读者快速掌握 Milvus 稠密向量检索的完整开发流程。


5.1 初始化 Milvus 客户端

首先连接本地部署的 Milvus 服务。

from pymilvus import MilvusClient

# 连接本地 Milvus 服务
client = MilvusClient(
    uri="http://localhost:19530",
    token=""
)

5.2 定义 Collection Schema

创建 Collection 时,需要提前定义数据结构(Schema)。

本案例包含以下字段:

字段 类型 说明
id INT64 主键
vector FLOAT_VECTOR 稠密向量(1024维)
text VARCHAR 原始文本
metadata JSON 文档元数据
from pymilvus import MilvusClient, DataType

def build_schema():
    return (
        MilvusClient.create_schema(auto_id=True)
        .add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
        .add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
        .add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000)
        .add_field(field_name="metadata", datatype=DataType.JSON)
    )

5.3 构建 HNSW 向量索引

为了提高向量检索效率,需要提前构建索引。

本案例采用:

  • 索引类型:HNSW
  • 相似度计算:COSINE(余弦相似度)
def build_index():
    index_params = MilvusClient.prepare_index_params()

    # 稠密向量配置 HNSW + 余弦相似度
    index_params.add_index(
        field_name="vector",
        index_type="HNSW",
        metric_type="COSINE"
    )

    return index_params

5.4 创建 Collection

创建 Collection 之前,如果已存在同名 Collection,则先删除,避免重复创建导致的数据冲突。

COLLECTION_NAME = "demo_collection"

# 存在则删除
if client.has_collection(COLLECTION_NAME):
    client.drop_collection(COLLECTION_NAME)

client.create_collection(
    collection_name=COLLECTION_NAME,
    schema=build_schema(),
    index_params=build_index()
)

5.5 加载文档并进行文本切分

使用 LangChain 加载 Word 文档,并采用递归切分器将长文本划分为多个 Chunk。

from langchain_community.document_loaders import UnstructuredWordDocumentLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 加载 Word 文档
docs = UnstructuredWordDocumentLoader(
    "assets/sample.docx",
    mode="single"
).load()

# 文本切分
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。"]
)

chunks = splitter.split_documents(docs)

参数说明:

参数 说明
chunk_size 每个文本块长度
chunk_overlap 相邻 Chunk 重叠字符数
separators 优先切分符

5.6 加载 Embedding 模型并生成稠密向量

这里使用 BGE-M3 作为文本向量化模型,仅生成稠密向量。

from FlagEmbedding import BGEM3FlagModel

# 加载 BGE-M3
model = BGEM3FlagModel("BAAI/bge-m3")

# 生成稠密向量
vectors = model.encode(
    [doc.page_content for doc in chunks],
    return_dense=True,
    return_sparse=False
)

dense_vectors = vectors["dense_vecs"]

5.7 向量数据入库

将文本、向量以及元数据一起写入 Milvus。

insert_data = []

for doc, vector in zip(chunks, dense_vectors):
    insert_data.append({
        "vector": vector,
        "text": doc.page_content,
        "metadata": doc.metadata
    })

client.insert(
    collection_name=COLLECTION_NAME,
    data=insert_data
)

5.8 稠密向量检索查询

完成数据入库后,即可根据用户问题进行语义检索。

# 用户查询
query = "如何安装 Docker"

# 查询向量化
query_vector = model.encode(
    [query],
    return_dense=True,
    return_sparse=False
)["dense_vecs"][0]

# 执行向量检索
results = client.search(
    collection_name=COLLECTION_NAME,
    data=[query_vector],
    anns_field="vector",
    limit=5,
    search_params={
        "metric_type": "COSINE"
    },
    output_fields=["text", "metadata"]
)

# 输出检索结果
for hit in results[0]:
    print(f"相似度分数:{hit['distance']}")
    print(f"匹配文本:{hit['entity']['text']}\n")

5.9 稠密检索优缺点总结

优点

  • 能够理解文本语义
  • 支持同义词匹配
  • 支持自然语言提问
  • 对问答系统效果优秀
  • 非常适合通用 RAG 场景

缺点

  • 精准关键词匹配能力较弱
  • 对编号、版本号、法条等文本容易漏检
  • 专业术语检索效果不如关键词搜索

第六章 代码实战:Milvus 稀疏向量检索

稀疏向量检索主要解决关键词匹配问题,可有效弥补稠密向量在法条、编号、产品型号、接口名称等精准匹配场景中的不足,也是构建 Hybrid Search(混合检索)的重要组成部分。


6.1 支持双向量的 Schema 定义

在原有 Schema 的基础上新增 稀疏向量字段

def build_schema():
    return (
        MilvusClient.create_schema(auto_id=True)
        .add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
        .add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=1024)
        .add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
        .add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000)
        .add_field(field_name="metadata", datatype=DataType.JSON)
    )

6.2 构建双索引(稠密 + 稀疏)

一个 Collection 可以同时拥有多个索引。

def build_index():
    index_params = MilvusClient.prepare_index_params()

    # 稠密向量索引
    index_params.add_index(
        field_name="vector",
        index_type="HNSW",
        metric_type="COSINE"
    )

    # 稀疏向量倒排索引
    index_params.add_index(
        field_name="sparse_vector",
        index_type="SPARSE_INVERTED_INDEX",
        metric_type="IP"
    )

    return index_params

6.3 同时生成稠密向量与稀疏向量

BGE-M3 支持一次编码,同时生成两种向量。

vectors = model.encode(
    [doc.page_content for doc in chunks],
    return_dense=True,
    return_sparse=True
)

dense_vectors = vectors["dense_vecs"]

sparse_vectors = vectors["lexical_weights"]

6.4 双向量数据入库

每条数据同时保存稠密向量和稀疏向量。

insert_data = []

for doc, dense, sparse in zip(
    chunks,
    dense_vectors,
    sparse_vectors
):
    insert_data.append({
        "vector": dense,
        "sparse_vector": sparse,
        "text": doc.page_content,
        "metadata": doc.metadata
    })

client.insert(
    collection_name=COLLECTION_NAME,
    data=insert_data
)

6.5 稀疏向量检索实战

下面演示使用稀疏向量进行精准关键词检索。

# 查询内容
query = "民法典 第236条"

# 生成稀疏查询向量
query_sparse = model.encode(
    [query],
    return_dense=False,
    return_sparse=True
)["lexical_weights"][0]

# 执行稀疏向量检索
results = client.search(
    collection_name=COLLECTION_NAME,
    data=[query_sparse],
    anns_field="sparse_vector",
    limit=5,
    search_params={
        "metric_type": "IP"
    },
    output_fields=["text"]
)

# 输出结果
for hit in results[0]:
    print(f"匹配分数:{hit['distance']}")
    print(f"文本内容:{hit['entity']['text']}\n")

6.6 稀疏检索适用场景

相比稠密检索,稀疏检索更加擅长精准匹配关键词。

典型应用场景包括:

  • 法律法规检索
  • 产品型号查询
  • API 接口名称匹配
  • 版本号搜索
  • 医学术语检索
  • 编号、工单号、订单号查询
  • 企业知识库精准搜索

由于稀疏检索侧重关键词匹配,而稠密检索侧重语义理解,因此两者结合使用能够兼顾语义召回精准匹配,是当前高质量 RAG 系统普遍采用的检索方案。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐