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

基于 Milvus 向量数据库实现 RAG 检索系统
随着大语言模型(LLM)在智能问答、企业知识库、智能客服、文档检索等场景大规模落地,其知识固化、无法实时更新、无私有数据认知的核心缺陷愈发明显。大模型仅能依托训练数据输出内容,无法识别企业内部文档、最新规章制度、私有业务资料等外部数据,极易出现幻觉、答非所问、知识滞后等问题。
为解决大模型的知识局限性,RAG(Retrieval-Augmented Generation,检索增强生成)技术成为工业界主流解决方案。其核心逻辑简单高效:先从私有知识库检索匹配的上下文内容,再将检索结果送入大模型,辅助大模型生成精准、可靠、无幻觉的答案。
文章目录
第一章 为什么需要向量数据库
随着大语言模型(LLM)的快速发展,越来越多的企业开始将 AI 应用于知识问答、智能客服、文档检索等业务场景。然而,大模型本身存在一个天然缺陷——它只能回答训练时学到的知识。
例如:
用户提问:“公司最新员工手册中的请假制度是什么?”
由于这份员工手册属于企业内部资料,大模型并没有参与训练,因此无法直接回答。为了解决这一问题,RAG(Retrieval-Augmented Generation,检索增强生成)应运而生。
RAG 的核心思想非常简单:
先从知识库检索相关内容,再将检索结果交给大模型生成最终答案。
整个流程如下:
可以发现,在整个 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,
......]
虽然文本不同,但是它们的向量位置非常接近。
因此只需要计算两个向量之间的距离,就能够判断它们是否表达了相同的语义。
整个过程如下:
向量数据库就是专门解决这个问题而设计的。
1.3 向量数据库解决了什么问题?
如果没有向量数据库,那么每次查询都需要遍历所有向量。
例如:
100 万条数据
↓
第一条
↓
第二条
↓
第三条
↓
......
↓
第一百万条
这种方式称为 Brute Force(暴力搜索)。
时间复杂度:
O(n)
数据越多,速度越慢。
Milvus 会提前建立索引,例如:
- HNSW
- IVF
- DiskANN
因此查询复杂度可以降低到接近:
O(logN)
即使数据达到数亿条,也能在毫秒级完成检索。
第二章 Milvus 在 RAG 中的作用
Milvus 是目前最流行的开源向量数据库之一,也是企业级 RAG 项目中最常见的选择。
在整个 RAG 系统中,它承担两个核心职责:
- 保存向量
- 检索向量
完整流程如下:
整个过程中:
- 文档解析、切分、Embedding 属于数据准备阶段;
- Milvus 负责存储和检索;
- 大模型负责组织语言并生成最终答案。
因此,Milvus 更像是 RAG 系统中的"知识仓库"。
2.1 为什么选择 Milvus?
目前主流向量数据库有很多,例如:
| 数据库 | 特点 |
|---|---|
| FAISS | Facebook 开源,适合本地开发 |
| Chroma | 轻量级,适合快速验证 |
| PgVector | PostgreSQL 插件 |
| Elasticsearch | 同时支持全文检索和向量检索 |
| Milvus | 企业级、高性能、支持混合检索 |
Milvus 的优势主要体现在以下几个方面:
- 支持十亿级向量存储;
- 支持多种 ANN 索引算法;
- 支持
稠密向量和稀疏向量; - 原生支持 Hybrid Search;
- 支持
Reranker重排; - 支持分布式部署。
对于生产环境而言,Milvus 是目前非常成熟的解决方案。
第三章 Milvus 两阶段执行流程(重点)
Milvus 的整个工作流程可以分为两个阶段:
- 索引阶段(离线)
- 检索阶段(在线)
二者共同构成了整个向量检索系统。
整体流程如下:
下面分别介绍两个阶段。
3.1 第一阶段:索引阶段(Indexing)
索引阶段主要完成数据入库。
流程如下:
整个阶段可以拆分为六个步骤。
(1)加载文档
系统首先读取知识库中的各种文件,例如:
- 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 会进入在线检索阶段。
整体流程如下:
整个过程主要包括五个步骤。
(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 默认推荐的稠密向量索引。
可以把它理解为一张多层导航地图。
整个搜索过程不是遍历所有节点,而是逐层缩小搜索范围。
例如:
第一层:
全国地图
↓
上海附近
第二层:
上海
↓
浦东新区
第三层:
浦东新区
↓
目标餐厅
这种搜索方式可以快速定位目标,大幅减少计算量。
相比暴力搜索:
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 系统普遍采用的检索方案。
更多推荐




所有评论(0)