
知识库构建
向量嵌入
我们前面已经把文本分块的所有内容全部实现完毕。接下来我们开始学习向量嵌入的相关知识点。
向量嵌入(Embedding) 是将非结构化数据(如单词、句子、图像或音频)转换为一列数字(向量)的过程。这个向量是高维空间中的一个点,并且具有这样的性质:语义上相似的对象,在向量空间中的距离也更近。

向量嵌入模块重点包含四大部分内容,其中核心的三种相似度计算方式分别为:余弦相似度 、欧式距离 、点积距离 。这三种方法的核心作用一致,都是用来判断两个向量之间的相似程度。
我们在之前语义分块中,已经带大家体验过向量嵌入的完整流程,也成功下载部署了嵌入模型BGE。也实操过:将一段普通文本输入嵌入模型,经过模型计算后,最终可以得到一组对应的向量数据。
这套流程的核心意义非常关键:原本不可计算、非数字化的文本,通过嵌入模型的转换,变成了可以被计算机识别、运算的数字化向量。只要文本完成向量化,我们就可以通过对比向量的相似度,精准判断两段文本的语义含义是否相近。
简单总结:余弦相似度、欧式距离、点积距离,就是我们用来对比向量相似度、判断文本语义相似度的三种核心算法。
除了三种相似度算法,本章最后我们还会讲解向量数据库。我们已经多次提到向量的概念,而向量需要专门的数据库进行存储管理,后续我们会通过向量数据库,持久化保存嵌入模型生成的所有文本向量。
余弦相似度
接下来我们重点学习第一个核心知识点:余弦相似度。
余弦相似度 是计算两个向量之间夹角的余弦值。余弦距离 (Cosine distance) 就是用 1 减去这个获得的余弦相似度。取值范围
计算公式:
- 逻辑:只看向量方向夹角,忽略向量长度(模长)。
- 适用场景:
- 绝大多数文本 Embedding(OpenAI、BGE、Sentence‑BERT 等);
- 文本语义匹配,只关心语义方向,不关心向量长度;
- 取值范围:
,越接近 1 代表语义越相似,数值越大越相似。
- LlamaIndex 默认用它,是工业界 RAG 标准方案。
余弦相似度的核心逻辑: 通过计算两个向量的夹角大小 ,判断向量相似度。它的取值范围为 -1, 1 。数值越接近 1,代表两个向量、两段文本的语义相似度越高;数值越小,语义相似度越低。
这里重点强调: 在LlamaIndex框架中,计算向量相似度的默认算法就是余弦相似度,也是工业界RAG项目的主流标准方案。
了解完理论之后,我们在PyCharm中编写代码,手动实现余弦相似度的计算逻辑。新建Python文件,命名为similarity.py,专门用于计算两段文本的向量相似度。
我们自定义核心方法,命名为embedding_similarity,依靠嵌入模型实现文本相似度计算。
方法传入两个参数:sentence1、sentence2,均为字符串类型,用于接收两段待对比的文本;方法最终返回一个浮点型数值,也就是两段文本的相似度结果。
**代码实现第一步:加载嵌入模型。**模型默认存储在硬盘中,使用前必须加载到内存。
首先完成模块导入,从llama_index框架中导入对应的相似度工具与嵌入模型工具:从llama_index.core.base.embeddings.base导入SimilarityMode,从llama_index.embeddings.huggingface导入HuggingFaceEmbedding,同时导入全局配置文件config。
随后初始化嵌入模型对象embed_model,指定model_name参数,直接读取config配置文件中预先定义好的model_path模型路径,加载我们本地的嵌入模型。
第二步:计算文本向量。 分别对两段文本进行向量化处理,调用embed_model.get_text_embedding()方法,分别生成vec1、vec2两组向量数据。
第三步:计算向量相似度。 调用embed_model.similarity()方法,传入两组向量,同时可以指定mode相似度计算模式。
大家可以点开源码查看,LlamaIndex框架已经提前封装好了三种相似度计算模式,分别对应:余弦相似度、点积距离、欧式距离。我们默认使用余弦相似度模式,无需手动修改。
python
class SimilarityMode(str, Enum):
"""Modes for similarity/distance."""
DEFAULT = "cosine"
DOT_PRODUCT = "dot_product"
EUCLIDEAN = "euclidean"
最后将计算得到的相似度结果similarity返回,核心方法编写完成。
similarity.py
python
from llama_index.core.base.embeddings.base import SimilarityMode
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
import config
def embedding_similarity(
sentence1: str,
sentence2: str,
mode: SimilarityMode = SimilarityMode.DEFAULT
) -> float:
"""
计算两个文本之间的语义相似度,基于HuggingFace嵌入模型生成向量后计算相似度
Args:
sentence1: 第一个待比对的文本句子
sentence2: 第二个待比对的文本句子
mode: 相似度计算模式,默认为DEFAULT;
SimilarityMode.DEFAULT:默认余弦相似度
SimilarityMode.DOT_PRODUCT:点积相似度
SimilarityMode.COSINE:余弦相似度
Returns:
float: 相似度得分,取值一般[-1,1];值越大代表两个文本语义越接近
"""
# 1. 初始化HuggingFace嵌入模型实例
# model_path 从config读取,支持本地模型路径或者huggingface模型名称
embed_model = HuggingFaceEmbedding(model_name=config.model_path)
# 2. 将输入文本转换为高维向量(embedding向量)
# 文本语义被编码成浮点数数组,语义相近的文本向量夹角更小
vec1 = embed_model.get_text_embedding(sentence1)
vec2 = embed_model.get_text_embedding(sentence2)
# 3. 调用模型内置方法,根据两个向量计算相似度
# 底层会根据传入的mode选择余弦/点积等算法
similarity = embed_model.similarity(vec1, vec2, mode=mode)
return similarity
接下来编写测试代码,在if __name__ == "__main__"入口函数中测试效果。
我们定义两段毫无关联的文本:
第一段文本:"我是一个学生,我今年上大一了。"
第二段文本:"陕西省的省会是西安。"
调用embedding_similarity方法,传入两段文本,打印最终相似度结果。
python
if __name__ == "__main__":
# 测试样例:两个语义完全无关的句子
sentence1 = "我是一个学生,今年上大一了"
sentence2 = "陕西省的省会是西安"
# 输出相似度分数,无关文本得分会比较低
print(embedding_similarity(sentence1, sentence2))
执行代码,模型首次加载速度较慢,加载完成后向量计算速度很快。最终打印出的相似度结果为0.3325左右。
结合余弦相似度取值范围[-1,1]的规则可以判断:这两段文本的相似度数值极低,几乎没有语义关联,和我们的主观认知完全一致。
这里延伸一个实战重点:在后续向量检索 的业务场景中,我们可以自定义相似度阈值 ,比如设置阈值为0.6或0.8。凡是低于阈值的检索结果,直接判定为无关内容并剔除,以此保证RAG检索的精准度。
以上就是余弦相似度的全部理论讲解与代码实操内容。
欧氏距离
看完余弦相似度、余弦距离之后,我们接下来学习欧氏距离,它同样是用来计算向量之间相似度的方法。
欧氏距离 ,就是把 "文档向量" 和 "查询向量" 当成高维空间里的两个点,算它们之间的直线距离;距离越小,说明文本越相似。取值范围
- 计算公式:
- 逻辑:计算向量空间两点的直线距离,数值越小越相似 (和余弦 / 点积相反)。
- 适用场景:
- 图像、数值特征向量;
- 很少用于文本 Embedding,文本向量高维下欧氏距离区分度很差;
- 向量库内部会自动转换距离为相似度打分,业务层不用手动换算。
欧氏距离的核心逻辑:把 "文档向量" 和 "查询向量" 当作高维空间里的两个点,计算两点之间的直线距离。距离越小,代表文本越相似。
欧氏距离的取值范围和余弦相似度不一样,它的特点是:数值越小,相似度越高。
再来看它的适用场景: 欧氏距离更适合图像、数值特征向量。很少用于文本 Embedding ,文本向量处在高维空间下,欧氏距离的区分度很差。所以欧氏距离并不擅长处理文本相似度排序。不过我们依然需要学习它。后续 RAG 知识库的内容不局限于纯文本,**还可以存放图片、音频、视频等多媒体数据,这时候欧氏距离就可以发挥作用,用来计算多媒体特征向量之间的相似度。**以上就是欧氏距离的理论部分。
接下来我们来看欧氏距离的代码实现,实现方式十分简单。我们前面已经知道,LlamaIndex提供了SimilarityMode,封装好了多种距离、相似度的计算模式,我们只需要切换传入的参数即可。
我们需要对之前写好的方法做调整:给函数增加一个mode参数,参数类型为SimilarityMode,同时设置默认值。如果调用的时候不传入mode参数,就默认使用余弦相似度。
在调用相似度计算的方法内部,把传入的mode参数传递给底层的similarity()函数。
想要计算欧氏距离,我们直接调用embedding_similarity方法,传入两段文本,再指定mode=SimilarityMode.EUCLIDEAN,就可以完成欧氏距离的计算。
similarity.py
python
# LlamaIndex中为了把数值排序规则统一起来,改成了结果取负数
print(embedding_similarity(sentence1, sentence2,
mode=SimilarityMode.EUCLIDEAN))
我们执行代码,把之前余弦相似度的打印语句注释掉,运行欧氏距离的测试代码。运行之后我们会发现一个现象:理论上欧氏距离的取值范围是 ,计算出来应该是正数,但这里输出结果却是负数。代码本身没有写错,这是
LlamaIndex框架内部的处理逻辑。
bash
C:\Users\A1983\miniconda3\envs\RAG\python.exe E:\RAGBasic\similarity.py
Loading weights: 100%|██████████| 391/391 [00:00<00:00, 22393.60it/s]
-1.1177726225276081
我们打开源码查看,进入similarity方法的实现。当mode为欧氏距离的时候,源码里会在计算结果前面加上一个负号。
python
def similarity(
embedding1: Embedding,
embedding2: Embedding,
mode: SimilarityMode = SimilarityMode.DEFAULT,
) -> float:
"""Get embedding similarity."""
if mode == SimilarityMode.EUCLIDEAN:
# Using -euclidean distance as similarity to achieve same ranking order
return -float(np.linalg.norm(np.array(embedding1) - np.array(embedding2)))
elif mode == SimilarityMode.DOT_PRODUCT:
return np.dot(embedding1, embedding2)
else:
product = np.dot(embedding1, embedding2)
norm = np.linalg.norm(embedding1) * np.linalg.norm(embedding2)
return product / norm
为什么框架要特意加上负号? 余弦相似度、点积距离的规则都是:数值越大,相似度越高 ;但欧氏距离的规则是:数值越小,相似度越高,二者排序逻辑是相反的。
框架给欧氏距离结果加上负号,就是为了统一排序逻辑。经过取负处理之后,欧氏距离的结果也变成 "数值越大,相似度越高"。
后续做向量检索时,我们用查询向量去检索向量库,会得到多条结果,每条结果都有对应的打分。统一成 "数值越大相似度越高" 的规则之后,排序处理就会变得简单。
这里需要大家重点注意: 使用LlamaIndex计算欧氏距离,得到负数结果不要觉得奇怪,这是框架特意做的处理,目的就是统一排序逻辑。
到这里,欧氏距离的理论和代码实操就全部讲解完成。
点积距离
接下来我们学习点积距离 。点积距离就是使用两个向量的点积来衡量相似度,数值越大,代表相似度越高。
点积距离(Dot Product Distance) 其实就是:用两个向量的点积来衡量相似度,值越大越相似。
- 计算公式:
- 逻辑:同时兼顾向量方向 + 向量长度。
- 适用场景:
- 做过归一化(L2 Norm)的向量:归一化后点积 = 余弦相似度;
- 模型输出向量自带长度表征重要性的场景(部分自研 Embedding);
- 注意:未归一化向量不能随便用,长向量会天然得分更高,干扰语义匹配。
点积距离和余弦相似度的排序逻辑是一致的,同样遵循数值越大、相似度越高的规则。
我们来看点积距离的计算逻辑:将两个向量对应维度的元素两两相乘,同时兼顾向量方向 与向量长度。 如果两个向量方向相同,相乘之后得到的点积结果就会更大。
这里有一个非常关键的知识点:当向量完成归一化处理之后,点积距离的计算结果就等价于余弦相似度。
那什么是向量归一化?我们举一个二维向量例子,比如向量(3, 4)。 归一化的操作就是把向量里的每一个分量,除以该向量的模长 。 模长的计算方式:对各分量平方求和之后再开根号。 对于向量(3,4):模长 。 用向量每一个元素除以模长:
,
。 归一化之后得到新向量
(0.6, 0.8)。 原始向量(3,4)和归一化之后的(0.6,0.8),二者代表的方向完全一致,仅仅是向量的长度发生了改变。
所以对于已经做过归一化的向量,点积距离的结果就等于余弦相似度。 我们使用的BGE‑M3嵌入模型,输出的向量本身就已经完成了归一化处理。 这就意味着,如果我们使用BGE‑M3模型计算点积距离,得到的结果会和余弦相似度几乎相等。
接下来编写点积距离的测试代码,实现非常简单。 直接调用embedding_similarity函数,传入两段文本,指定参数mode=SimilarityMode.DOT_PRODUCT。
similarity.py
python
print(embedding_similarity(sentence1, sentence2,
mode=SimilarityMode.DOT_PRODUCT))
我们把欧氏距离的打印语句注释掉,放开余弦相似度的打印代码,运行程序对比两组结果。 运行后可以看到,两个结果小数点后 8 位都完全一致,精度极高,几乎完全相等,正好印证我们前面的理论:归一化向量下,点积距离等价于余弦相似度。
这里需要特别注意:如果向量没有做归一化,二者结果就不会相等。余弦相似度的取值范围固定在-1,1,但未归一化向量的点积距离可以得到很大的数值,向量长度会干扰语义匹配的结果。
到此,点积距离的理论和代码实现就讲解完毕。 我们已经学习完三种向量相似度计算方式:余弦相似度、欧氏距离、点积距离。后续我们拿到嵌入模型输出的向量之后,就可以自由选用这三种算法来计算文本之间的相似度。
向量数据库
接下来我们学习向量数据库。前面我们已经了解嵌入模型,可以把一段文本转换成向量。生成好的向量,需要一个专门的地方来存储,这就引出了向量数据库。
什么是向量数据库?
向量数据库是专门存储、索引、检索高维Embedding向量的专用数据库,核心作用是快速做相似度匹配,弥补传统关系型数据库不擅长高维浮点数数组比对的短板。
python
[
-0.03807787224650383,
-0.002669620094820857,
-0.00038058828795328736,
0.031184565275907516,
-0.02876528911292553,
..................,
0.030514752492308617,
0.03327701613306999,
-0.01747242547571659,
-0.029959438368678093,
-0.00964059866964817
]
**我们之前学过很多关系型数据库,例如MySQL、Redis、SQLite,这些数据库能不能存储向量?**答案是可以存储,但是效率很差。向量是高维浮点数组,小数位数很长。关系型数据库虽然可以存下向量数据,但是做相似度检索的时候性能远不如向量数据库。所以我们要使用更适配的工具,也就是向量数据库,以此提升检索效率。
现在市面上向量数据库的种类非常多,像Milvus、Chroma、Pinecone等等,行业内百花齐放,没有一款产品能够一统江湖。
常见的向量数据库有如下种类:
HNSW:工业最主流,速度、精度均衡(Milvus、Chroma、Pinecone)FAISS IVF:Facebook 开源,适合批量大规模数据Annoy、DiskANN:适合磁盘大容量存储
这里我们选用Milvus,大家可以自行查阅它的中文官方文档,文档里包含安装、SDK 使用、核心概念等内容。
快速入门 | Milvus 文档
https://milvus.io/docs/zh/quickstart.md这里我们只梳理核心概念,不逐行阅读文档,我们的主线是学习 RAG,和 RAG 关联不大的内容简单过一遍即可。
Milvus 与 MySQL 核心概念对比
接下来对比Milvus向量数据库和MySQL关系型数据库的核心概念。 在MySQL中,我们有数据表、行记录、列字段,还有分区、分表、磁盘存储等概念。 到了Milvus里面,对应概念叫做Collection集合、Entity实体、Field字段、Partition分区等。只是命名不一样,底层逻辑一一对应。向量数据库的优势,就是专门优化高维浮点数组的处理。
| Milvus | MySQL |
|---|---|
Collection 集合 |
Table 数据表 |
Entity 实体 |
Row 行记录 |
Field 字段 |
Column 列 |
Partition 分区 |
分表 / 分区表 |
Segment |
磁盘数据文件 |
Shard 分片 |
分库分表 |
Proxy |
MySQL Proxy 网关 |
QueryNode |
查询执行器 |
etcd 元数据 |
系统库 information_schema |
| 对象存储 | 磁盘数据目录 |
Milvus 与 MySQL 的区别
再看二者的区别:
| 对比项 | 传统关系型数据库 | 向量数据库 |
|---|---|---|
| 存储对象 | 字符串、数字、表格 | 高维浮点向量 |
| 查询方式 | 精确匹配、模糊关键词 | 近似相似度匹配 |
| 适用场景 | 订单、用户、业务台账 | RAG 知识库、图文检索、推荐 |
| 高维性能 | 上万维检索极慢,全量遍历 | 内置索引算法,亿级向量秒查 |
- 存储对象:传统关系型数据库存储字符串、数字、表格;向量数据库存储高维浮点向量。
- 查询方式 :MySQL 使用等于、
like做精确匹配、模糊查询;向量数据库做近似相似度匹配。 - 适用场景 :关系型数据库适合订单、用户、业务台账;向量数据库适合 RAG 知识库、图文检索、推荐系统。
- 数据量级:关系型数据库一般几万、几十万,百万级就要考虑分库分表;向量数据库可以轻松支撑千万、亿级向量。
部署 Milvus 向量数据库
了解完概念与区别,我们开始安装向量数据库。这里我们使用Milvus‑Lite 。 Milvus‑Lite是 Milvus 的轻量嵌入式版本,类比MySQL和SQLite的关系。它不需要启动独立服务,不需要额外配置,数据直接保存在本地文件当中,非常适合本地学习开发。
核心特点:
| 特性 | 说明 |
|---|---|
| 无需部署 | 不需要启动独立服务,随 Python 代码自动运行 |
| 零配置 | 无需 Docker、Kubernetes 或复杂的配置文件 |
| 文件存储 | 数据直接保存在本地文件中(如 .db 文件) |
| 完全兼容 | 与标准 Milvus 使用相同的 API(pymilvus) |
安装命令:
bash
pip install milvus-lite pymilvus -i https://pypi.tuna.tsinghua.edu.cn/simple
这里有两个包:
-
milvus‑lite:轻量版 Milvus 数据库本体; -
pymilvus:Milvus 官方 Python SDK,是 Python 程序和 Milvus 数据库之间的桥梁,封装好了调用接口,我们不用写原生底层语句。
PyMilvus 是 Milvus 向量数据库的官方 Python SDK(软件开发工具包),它提供了与 Milvus 服务进行交互的 Python API 接口。
简单理解:PyMilvus 是 Python 程序与 Milvus 数据库之间的 "翻译官" 和 "桥梁"。
┌─────────────────┐
│ Milvus‑Lite 包 │
└─────────────────┘
┌──────────────┐
│ Milvus 引擎(C++ 核心) │
└──────────────┘
┌─────────────────────┐
│ PyMilvus (Python SDK) │
└─────────────────────┘
标准 Milvus 部署:
┌─────────────┐ gRPC ┌─────────────────┐
│ PyMilvus │◄─────────────►│ Milvus 服务 │
│ (Python端) │ │ (独立进程/容器) │
└─────────────┘ └─────────────────┘
复制命令,在 PyCharm 终端执行安装。执行完成之后,可以用pip list查看安装结果。 可以看到我们安装了milvus‑lite 3.0 版本,同时pymilvus也安装完成,版本同样为 3.0。 至此,Milvus‑Lite 数据库以及 Python 调用 SDK 就全部安装完毕。
向量数据库常见操作
前面我们已经成功安装好了milvus-lite以及pymilvus依赖库,接下来我们编写代码,实操感受向量数据库的完整使用流程。
我们在项目中新建Python文件,命名为milvus.py。后续所有和Milvus向量数据库相关的操作,包括创建数据库、创建集合、数据增删改查,全部统一写在这个文件中。后续我们做向量数据入库、向量检索功能时,直接调用该文件的方法即可。
| 概念 | 类比 | 在本示例中的体现 |
|---|---|---|
Database(库) |
一个SQLite文件 / 目录 |
./milvus_demo.db |
Collection(集合) |
一张表 | my_collection |
Schema(模式) |
表结构 | 主键 id、向量 vector、标量 title/content/category |
Vector(向量) |
语义坐标 | N 维浮点数组 |
Metric(距离度量) |
相似度算法 | COSINE(余弦相似度) |
Insert |
写入行 | insert_data() |
Search |
按向量找最近邻 | search() |
Query |
按条件过滤 | query_by_filter() |
Delete |
按条件删除 | delete_by_filter() |
首先我们编写第一个核心方法:连接本地Milvus数据库。
我们安装的是Milvus-Lite轻量版本,最大的特点就是无需手动启动服务,安装完成后可直接连接使用。
我们定义connect方法,传入形参db_path,用来指定本地数据库的存储路径。该方法的返回值为MilvusClient对象,也就是pymilvus提供的核心客户端类。
首先我们声明一个全局变量client,变量类型为MilvusClient,初始赋值为None。在方法内部通过global client声明引用全局变量,实例化MilvusClient(db_path)完成数据库连接,最后将客户端对象返回。这就是本地Milvus数据库的连接逻辑。
python
from pymilvus import MilvusClient
# 全局Milvus客户端实例,初始为None
# 用于保存数据库连接对象,避免重复创建连接
client: MilvusClient = None
def connect(dbpath: str) -> MilvusClient:
"""
初始化并连接本地Milvus数据库(文件模式)
Args:
dbpath: Milvus本地数据库文件路径,本地文件模式下,会在该路径生成数据库文件
Returns:
MilvusClient: Milvus客户端连接对象,可用于建集合、插入向量、查询检索等操作
Note:
使用global关键字修改全局变量client,后续其他函数可以直接使用这个全局client对象;
每次调用connect()会覆盖旧的client连接,重新建立新连接。
"""
# 声明使用全局变量client
global client
# 创建MilvusClient实例,本地文件模式,传入数据库文件路径
# MilvusClient 是pymilvus的高层API,简化向量库操作
client = MilvusClient(str(dbpath))
# 返回已经建立好的客户端连接对象
return client
if __name__ == "__main__":
# 示例:连接本地milvus数据库
# connect("./milvus_db")
pass
要点
Milvus‑Lite的 "库" 是目录,不是单个.sqlite文件- 开发阶段用「删目录」比
drop_collection更稳,尤其在 Windows 上
第二步,编写获取全局客户端 的方法get_client()。方法逻辑非常简单,直接返回我们初始化完成的全局client客户端对象,方便后续所有方法统一调用。
python
def get_client() -> MilvusClient:
"""
获取当前已经初始化好的Milvus客户端实例
Returns:
MilvusClient: 全局的数据库连接对象
Warning:
调用前必须先执行connect() / create_db(),否则返回None
"""
return client
第三步,编写创建数据库 方法create_db(),传入参数为数据库名称db_name,返回值同样是MilvusClient客户端对象。
python
def create_db(dbname: str) -> MilvusClient:
"""
基于配置的基础路径,拼接数据库文件名,创建并连接Milvus数据库
Args:
dbname: 数据库名称,会自动拼接后缀 .db
Returns:
MilvusClient: 建立完成的Milvus客户端连接对象
Example:
create_db("rag_knowledge")
实际文件路径:config.base_path / "rag_knowledge.db"
"""
# 拼接完整数据库文件路径:基础路径 + dbname + .db后缀
db_file_path = config.base_path / f"{dbname}.db"
# 调用connect建立连接,返回客户端对象
return connect(db_file_path)
我们需要提前导入项目全局配置文件config,读取预设的基础路径config.base_path,拼接数据库路径,最终路径格式为:基础路径 + 数据库名 + .db后缀,通过拼接好的路径调用connect方法,完成数据库创建与连接。
接下来我们编写入口测试代码,在if __name__ == "__main__"中调用create_db方法,创建名为bit的数据库,执行代码测试效果。
python
if __name__ == "__main__":
# 使用示例
create_db("bit")
milvus_client = get_client()
此时我们的项目目录中,会生成一个bit.db文件夹,这就是我们的本地向量数据库文件。

我们可以对比关系型数据库MySQL的逻辑理解:MySQL中先创建database数据库,再创建table数据表;而在Milvus向量数据库中,创建完数据库目录后,内部的collections集合,就等价于MySQL的数据表。当前新建的数据库为空,暂时没有任何集合数据。
同时目录中会生成一个.lock锁文件,作用是保证数据库访问唯一性 ,限制单个进程、单个程序对数据库进行操作,避免多进程读写冲突,保障数据安全。
数据库创建完成后,我们继续编写下一个核心方法:切换数据库 ,对应MySQL中的use db语句。
定义use_db方法,传入数据库名称db_name,返回值为MilvusClient客户端。方法内部拼接数据库完整路径,调用connect方法,实现数据库的切换连接。
python
def use_db(dbname: str) -> MilvusClient:
"""
打开已存在的Milvus数据库,建立连接
和create_db语义区分:create_db偏向"新建",use_db偏向"打开已有库"
底层逻辑完全一致:文件不存在时Milvus也会自动创建。
Args:
dbname: 数据库名称,自动拼接 .db 后缀
Returns:
MilvusClient: 数据库客户端连接对象
"""
# 拼接数据库完整路径
db_file_path = config.base_path / f"{dbname}.db"
# 建立连接,覆盖全局client
return connect(db_file_path)
这个方法的作用主要用于多数据库场景: 比如我们可以创建bit.db、bit2.db多个向量数据库,默认会连接最新创建的数据库,通过use_db方法可以自由切换指定数据库。
我们编写测试代码,先创建bit数据库、再创建bit2数据库,最后调用use_db("bit"),即可成功切换回bit数据库,测试效果正常。
最后我们编写列出所有数据库 的方法list_dbs(),对应MySQL中查看所有数据库的语句。
该方法返回值为List[str]字符串列表,需要提前从typing导入List类型注解。方法内部遍历项目路径下所有后缀为.db的数据库文件,提取数据库名称并返回。
python
def list_dbs() -> List[str]:
"""
扫描配置的base_path目录,列出所有本地Milvus数据库文件名(不带.db后缀)
Returns:
List[str]: 数据库名称列表,例如 ["bit", "knowledge_base"]
"""
# glob匹配所有*.db文件;p.stem 获取文件名去掉后缀,如 "bit.db" → "bit"
return [p.stem for p in config.base_path.glob("*.db")]
调用该方法,就可以自动罗列出我们当前项目中所有创建好的向量数据库,比如bit、bit2,实现了向量数据库的查询展示功能。
以上就是Milvus向量数据库的连接、创建、切换、查询所有数据库的基础核心操作。
我们已经成功创建好数据库 DB ,接下来我们继续新建 collection,也就是给向量数据库新建数据表。
我们开始编写第六个方法:创建 collection 。对应 MySQL 里面的 create table 建表操作。
我们定义方法命名为create_collection,方法传入两个参数:
-
第一个参数是
name,代表集合名称; -
第二个参数是
dimension,类型为int,代表向量维度,方法无返回值。
方法内部第一步,先通过 get_client() 获取全局客户端。
在建表之前,我们需要先做判断:如果当前数据库中已经存在同名 collection,我们先执行删除操作。通过 client.has_collection(name) 判断集合是否存在,如果存在,就调用 client.drop_collection(collection_name=name) 将原有集合删除。
删除完成之后,我们再调用 client.create_collection() 创建全新的集合。
python
def create_collection(name: str, dimension: int) -> None:
"""
创建Milvus集合(等价于关系型数据库的表)
如果集合已经存在,会先删除旧集合,再新建,实现覆盖重建。
Args:
name: 集合名称,向量库的表名
dimension: 向量维度,代表每条向量的元素个数,例如embedding模型输出768维则填768
Note:
1. 集合内部存储向量数据,每条数据包含向量、元数据字段;
2. 这里采用MilvusClient简化API,使用默认索引配置;
3. 注意:调用前必须已经调用connect/create_db/use_db完成数据库连接,否则get_client()返回None会抛异常。
"""
# 获取全局已经初始化好的Milvus客户端
client = get_client()
# 判断该集合是否已经存在
if client.has_collection(name):
# 集合存在则先删除旧集合,清空旧的向量数据
client.drop_collection(collection_name=name)
# 创建新集合,指定集合名和向量维度
client.create_collection(collection_name=name, dimension=dimension)
写完创建集合的方法后,我们继续编写第七个方法:删除 collection,对应 MySQL 的删表操作。
python
def drop_collection(name: str) -> None:
"""
删除指定的Milvus集合(向量数据表)
删除后集合内全部向量数据永久丢失,不可恢复。
Args:
name: 要删除的集合名称
Warning:
调用前必须完成数据库连接;删除操作不可逆,请谨慎使用。
"""
# 获取客户端对象,执行删除集合操作
get_client().drop_collection(collection_name=name)
我们在 bit 数据库中新建一个集合,集合名称命名为 01,向量维度设置为 1024维。

最后我们测试删除功能,保留 bit.db 数据库,执行删除集合的方法,即可成功删除刚刚创建的 01 集合。
前面我们成功创建了 collection 集合,也就是向量数据库的数据表。接下来我们对标 MySQL 的增删改查常规操作,带大家实操学习:这些基础操作在 Milvus 向量数据库中是如何实现的。
首先我们学习第一步:新增数据、插入数据。
我们自定义声明一个 insert 方法。MilvusClient 底层本身就封装好了 insert 插入方法,我们可以直接通过 get_client().insert() 调用。
python
def insert(
self,
collection_name: str,
data: Union[Dict, List[Dict]],
timeout: Optional[float] = None,
partition_name: Optional[str] = "",
**kwargs,
) -> Dict:
"""Insert data into the collection.
If the Milvus Client was initiated without an existing Collection, the first dict passed
in will be used to initiate the collection.
Args:
data (List[Dict[str, any]]): A list of dicts to pass in. If list not provided, will
cast to list.
timeout (float, optional): The timeout to use, will override init timeout. Defaults
to None.
Raises:
DataNotMatchException: If the data has missing fields an exception will be thrown.
MilvusException: General Milvus error on insert.
Returns:
Dict: Number of rows that were inserted and the inserted primary key list.
"""
# If no data provided, we cannot input anything
if isinstance(data, dict):
data = [data]
msg = "wrong type of argument 'data',"
msg += f"expected 'Dict' or list of 'Dict', got '{type(data).__name__}'"
if not isinstance(data, list):
raise TypeError(msg)
if len(data) == 0:
return {"insert_count": 0, "ids": []}
conn = self._get_connection()
# Insert into the collection.
res = conn.insert_rows(
collection_name,
data,
partition_name=partition_name,
timeout=timeout,
context=self._generate_call_context(**kwargs),
**kwargs,
)
return OmitZeroDict(
{
"insert_count": res.insert_count,
"ids": res.primary_keys,
"cost": res.cost,
}
)
我们来看底层 insert 方法的核心参数:
-
第一个参数是
self,为方法本身; -
第二个参数是
collection_name,用来指定具体的集合,对应MySQL中的数据表; -
第三个参数是
data,代表需要插入集合中的数据内容; -
第四个参数是
timeout,也就是超时时间。
因为插入数据属于磁盘写入操作,和数据库连接、数据写入逻辑一致,都需要配置超时时间;最后还有 partition_name 分区参数,向量数据库支持分区机制,相同集合下可以划分不同分区,我们暂不演示分区用法。
基于底层原生方法,我们对插入功能进行简单封装。 方法传入 collection_name 字符串参数,用来指定操作的目标数据表。
python
def insert(collection_name: str, rows: List[Dict[str, Any]]) -> Dict:
"""
向Milvus集合(向量数据表)批量插入数据
Args:
collection_name: 目标集合名称
rows: 待插入的数据列表,每一个字典代表一条记录
字典key必须匹配集合定义的字段名,包含向量字段与元数据字段
示例:[{"id": 1, "vector": [0.1,0.2,...], "text": "示例文本"}]
Returns:
Dict: Milvus返回的插入结果,包含插入成功的数量、id列表等信息
Note:
1. 调用前必须完成数据库连接,否则get_client()返回None会抛出异常
2. 向量维度必须和创建集合时指定的dimension保持一致,否则会报错
3. 本地文件模式不支持多进程并发写入,多进程同时插入会报database is locked
4. 数据字典中id字段如果不指定,Milvus会自动生成主键id
"""
# 获取全局Milvus客户端,执行批量插入
return get_client().insert(collection_name=collection_name, data=rows)
接下来我们编写测试数据,定义文本内容,通过嵌入模型生成向量。调用 embed_model.get_text_embedding() 方法,传入文本,生成对应的向量数据 vector。
向量生成完成后,我们开始构建插入数据集。指定目标集合为刚刚创建的 01,手动构造列表格式数据,内部为字典结构:自定义 id=1 作为主键,传入生成好的 vector 向量字段,同时保留原始 text 文本字段。
python
if __name__ == "__main__":
# 1. 打开/创建本地milvus数据库
use_db("bit")
# 2. 定义集合名称,向量维度(假设embedding模型输出768维)
coll_name = "01"
vec_dim = 1024
# 3. 创建集合:如果已存在则删除重建
create_collection(coll_name, dimension=vec_dim)
# 4. 定义测试原始文本
test_text = "这是一条用于测试向量数据库的示例文本内容"
# 5. 调用嵌入模型生成文本向量
print("开始生成文本向量,模型加载计算会稍慢...")
embed_model = HuggingFaceEmbedding(model_name=config.model_path)
vector = embed_model.get_text_embedding(test_text)
# 6. 构造插入数据集:列表,内部字典,包含id、vector、原始text字段
insert_data = [
{
"id": 1, # 手动指定主键id
"vector": vector, # 模型生成的向量数据
"text": test_text # 保存原始文本元数据
}
]
# 7. 执行插入操作
print("开始插入向量数据...")
insert_res = insert(collection_name=coll_name, rows=insert_data)
# 8. 打印插入返回结果,验证插入成功
print("插入结果:", insert_res)
# 预期输出示例:{'insert_count': 1, 'ids': [1]}
# 说明:
# 插入成功后,本地数据库文件目录会发生变化:
# 1. 生成partitions分区文件夹,内部有000001开头的数据文件,和id=1对应,持久化向量数据
# 2. 存在wr预写日志文件,类似MySQL预写日志,保证数据持久化可靠性
执行代码,模型会先加载并计算文本向量,过程会稍微缓慢,加载完成后即可完成数据插入。执行成功后,控制台返回字典格式的成功响应,显示插入条数为 1,代表单条数据插入成功。
python
开始生成文本向量,模型加载计算会稍慢...
Loading weights: 100%|██████████| 391/391 [00:00<00:00, 6140.22it/s]
开始插入向量数据...
插入结果: {'insert_count': 1, 'ids': [1]}
数据插入成功后,我们查看本地数据库文件的变化:集合目录下生成了 partitions 分区文件夹,内部生成编号从 000001 开始的数据文件,和我们插入的数据 ID 对应,这就是我们持久化到磁盘的数据集。同时目录中存在wal 文件,对应 MySQL 的预写日志机制,是向量数据库的数据持久化保障机制。

我们继续查看核心配置文件:manifest.json 记录字段名称、向量索引类型、版本回滚配置等元数据信息;
python
{
"active_wal_number": 2,
"current_seq": 1,
"index_specs": {
"vector": {
"build_params": {},
"field_name": "vector",
"index_type": "AUTOINDEX",
"metric_type": "COSINE",
"search_params": {}
}
},
"manifest_format_version": 2,
"partitions": {
"_default": {
"data_files": [
"data/data_000001_000001.parquet"
],
"delta_files": []
}
},
"schema_version": 1,
"version": 2
}
schema.json 对应数据表结构。
python
{
"collection_name": "01",
"enable_dynamic_field": true,
"fields": [
{
"auto_id": false,
"default_value": null,
"dim": null,
"dtype": "int64",
"element_type": null,
"is_primary": true,
"max_capacity": null,
"max_length": null,
"name": "id",
"nullable": false
},
{
"auto_id": false,
"default_value": null,
"dim": 1024,
"dtype": "float_vector",
"element_type": null,
"is_primary": false,
"max_capacity": null,
"max_length": null,
"name": "vector",
"nullable": false
}
],
"properties": {},
"schema_format_version": 2,
"version": 1
}
这里大家可以发现一个关键点:我们代码中插入了 id、vector、text 三个字段,但默认表结构中只保留 id 和 vector 两个核心字段,这是向量数据库的标准特性,向量和主键是默认核心字段,文本属于自定义拓展字段。
接下来我们批量插入数据,注释掉原有删表、建表代码,避免重复清空集合,新增两组不同文本数据,批量插入三条数据。执行代码时需要先指定数据库,否则程序无法识别操作库。
python
if __name__ == "__main__":
# 1. 指定打开本地milvus数据库,必须先指定数据库,否则无法识别操作的库
use_db("bit")
# 2. 集合名称
coll_name = "01"
# ========== 注释掉原有删表、建表代码,防止重复清空集合 ==========
# vec_dim = 1024
# create_collection(coll_name, dimension=vec_dim)
# 3. 定义两组不同的测试文本,共3条测试数据
test_texts = [
"人工智能大模型可以完成文本理解、生成等多种任务",
"向量数据库专门用于存储和检索高维向量数据",
"RAG检索增强生成结合大模型与私有知识库实现问答"
]
# 4. 批量生成向量,构造插入数据集
embed_model = HuggingFaceEmbedding(model_name=config.model_path)
insert_data = []
for idx, text in enumerate(test_texts, start=2):
print(f"正在生成第{idx}条文本向量:{text}")
vector = embed_model.get_text_embedding(text)
insert_data.append({
"id": idx, # 主键id,接着上一轮id=1往后顺延:2、3、4
"vector": vector,
"text": text
})
# 5. 执行批量插入
print("\n开始批量插入多条向量数据...")
insert_res = insert(collection_name=coll_name, rows=insert_data)
# 6. 打印插入返回结果,验证批量插入成功
print("批量插入结果:", insert_res)
# 预期输出:{'insert_count': 3, 'ids': [2, 3, 4]}
执行成功后可以看到,数据 ID 自动递增,从原有 ID=1 顺延生成新的编号,三条数据全部插入完成。至此,Milvus 数据插入功能 完全实现。

数据插入完成后,我们继续实现数据查询功能,也就是通过 ID 查询数据。
编写查询方法,指定目标 collection_name,传入需要查询的 ID 数组,同时配置 fields 查询字段参数,对标 MySQL 的 SELECT 字段查询逻辑,可以指定查询部分字段或全量字段。方法最终返回字典列表格式的查询结果。
python
from typing import List, Dict, Any, Optional
def get(collection_name: str, ids: List[int], output_fields: Optional[List[str]] = None) -> Dict:
"""
根据主键ID查询Milvus集合中的数据记录
Args:
collection_name: 目标集合名称
ids: 需要查询的主键id列表,可以传入多个id批量查询
output_fields: 需要返回的字段列表,不传则返回全部字段;
示例:["id", "vector", "text"]
Returns:
Dict: 查询结果字典,包含data数组,每一项是对应id的完整记录
Note:
1. 调用前必须完成数据库连接,否则get_client()返回None会抛出异常
2. 传入的ids必须是集合中已存在的主键id,不存在的id不会返回对应数据
3. 本地文件模式下,查询会读取磁盘持久化的partitions分区数据
"""
# 获取全局Milvus客户端,执行按id查询
return get_client().get(collection_name=collection_name, ids=ids, output_fields=output_fields)
首次执行查询会出现数据释放报错,原因是: 向量数据库数据持久化在磁盘中,程序运行时读取数据需要先加载到内存。我们需要先调用 get_client().load_collection() 方法,将目标集合数据加载到内存,再执行查询操作。
python
if __name__ == "__main__":
use_db("bit")
coll_name = "01"
client.load_collection(collection_name=coll_name)
# 方式1:不指定fields,Milvus默认只返回 id、vector,拿不到自定义text字段
records = get_by_ids(coll_name, ids=[1,2])
# 方式2:显式声明需要的全部字段:id、vector、text,获取完整数据
# records = get_by_ids(coll_name, ids=[1, 2], fields=["id", "vector", "text"])
print("查询完整结果:", records)
加载完成后执行查询,初步查询结果只返回了id 和 vector 两个字段,无法查询到我们自定义的 text 文本字段。
这里是大家必须掌握的核心重点 :如果 fields 参数不主动声明,Milvus 默认只返回主键 id 和向量 vector 两个核心字段;如果需要查询自定义拓展字段(如 text),必须手动在 fields 中声明所有需要查询的字段。
我们补全 fields 参数,传入 id、vector、text 三个字段,重新执行查询。此时可以完整查询出包含文本、向量、主键的全量数据。
到这里,我们就完整实现了 Milvus 向量数据库的数据插入 与按ID查询数据两大核心功能。
我们已经实现了Milvus向量数据库的普通数据查询。大家可以思考一个问题:如果Milvus只支持ID查询、或者带过滤条件的查询,那它和传统关系型数据库就没有本质区别了。
我们之前提到过,使用向量数据库最核心的价值,就是能够快速完成向量相似度查询。如果仅仅做ID查询、条件过滤,是远远发挥不出向量数据库优势的。所以接下来,我们来封装向量数据库的向量检索能力。
这是我们第十个功能:向量查询。 我们封装一个方法,命名为search_by_text。
**这个方法的设计目标:**我们提前把大量文本通过嵌入模型转换成向量,存入数据库。查询的时候,只需要输入一段文本,就可以快速检索出语义相近的数据,不需要每次查询的时候现场计算向量,这也是引入向量数据库的关键意义。
我们先看pymilvus底层自带的search方法,在它的基础之上封装我们自己的检索方法。
python
def search(
self,
collection_name: str,
data: Optional[Union[List[list], list]] = None,
filter: str = "",
limit: int = 10,
output_fields: Optional[List[str]] = None,
search_params: Optional[dict] = None,
timeout: Optional[float] = None,
partition_names: Optional[List[str]] = None,
anns_field: Optional[str] = None,
ranker: Optional[Union[Function, FunctionScore]] = None,
function_chains: Optional[Union[FunctionChain, List[FunctionChain]]] = None,
highlighter: Optional[Highlighter] = None,
ids: Optional[Union[List[int], List[str], str, int]] = None,
search_aggregation: Optional[SearchAggregation] = None,
**kwargs,
) -> List[List[dict]]:
"""Search for a query vector/vectors.
In order for the search to process, a collection needs to have been either provided
at init or data needs to have been inserted.
Args:
data (Optional[Union[List[list], list]]): The vector/vectors/embedding
list to search.
limit (int, optional): How many results to return per search. Defaults to 10.
filter(str, optional): A filter to use for the search. Defaults to None.
output_fields (List[str], optional): List of which field values to return. If None
specified, only primary fields including distances will be returned.
search_params (dict, optional): The search params to use for the search.
ranker (Function, optional): The ranker to use for the search.
function_chains (FunctionChain or List[FunctionChain], optional): Function chain or
function chains to apply to ordinary search. Mutually exclusive with ranker.
timeout (float, optional): Timeout to use, overides the client level assigned at init.
Defaults to None.
ids (Optional[Union[List[int], List[str], str, int]]): The ids to use for the search.
Defaults to None.
search_aggregation (SearchAggregation, optional): Hierarchical bucket aggregation spec.
Mutually exclusive with group_by_field. When set, `limit` is ignored and the root
SearchAggregation.size controls top-level bucket count.
Raises:
ValueError: The collection being searched doesnt exist. Need to insert data first.
Returns:
List[List[dict]]: A nested list of dicts containing the result data. Embeddings are
not included in the result data.
"""
validate_param("collection_name", collection_name, str)
# Convert EmbeddingList objects to flat arrays if present
if isinstance(data, list) and data and isinstance(data[0], EmbeddingList):
data = [emb_list.to_flat_array() for emb_list in data]
kwargs["is_embedding_list"] = True
conn = self._get_connection()
kwargs = self._with_cluster_id(kwargs)
return conn.search(
collection_name=collection_name,
data=data,
ids=ids,
anns_field=anns_field or "",
param=search_params or {},
expression=filter,
limit=limit,
output_fields=output_fields,
partition_names=partition_names,
expr_params=kwargs.pop("filter_params", {}),
timeout=timeout,
ranker=ranker,
function_chains=function_chains,
highlighter=highlighter,
search_aggregation=search_aggregation,
context=self._generate_call_context(**kwargs),
**kwargs,
)
做了什么
- 在
my_collection中做近似最近邻搜索(ANN) - 返回与
query_vector余弦距离最近的 Top N - 附带
title、content、category等标量字段
我们来看它的关键参数:
第一个参数是collection_name,指定操作的集合;
第二个关键参数是data,它接收的是查询内容的向量表示。**这里有一个Milvus的参数约定:**就算只查询一条,也要把向量放在外层数组里面。比如我们把查询文本转为1024维向量之后,需要再套一层列表传入。
除此之外还有filter过滤条件、limit返回结果数量、ids限定查询ID范围。 filter过滤条件,在后续RAG项目里会用到,本次我们暂时不重点讲解。重点关注limit,它用来控制返回相似度最高的结果条数。举个例子,集合里面存放20条数据,我们设置limit=5,就会返回相似度排名前5条的数据。
了解完底层search的参数之后,我们开始封装自己的search_by_text方法。
方法入参设计:
-
collection_name:字符串,指定目标集合; -
limit:int类型,默认值设为5,控制返回结果条数; -
text:字符串,用户输入的查询文本; -
output_fields:列表类型,默认值为None,用来控制查询返回哪些字段。
底层search返回值类型是嵌套字典的列表,我们直接沿用这个返回类型。
方法内部逻辑:我们传入的是原始文本,需要先调用嵌入模型,把文本转换成向量。所以需要引入嵌入模型对象。 调用embed_model.get_text_embedding(text)得到文本向量,然后按照Milvus的要求,在外层再套一层列表,得到vectors。
之后调用客户端的search方法,传入collection_name、data=vectors、limit、output_fields,完成向量检索。至此,我们的文本向量查询方法就定义完成。
python
# 声明向量嵌入模型,使用配置文件指定的模型路径
embed_model = HuggingFaceEmbedding(model_name=config.model_path)
def search_by_text(
collection_name: str,
limit: int = 5,
text: str = "",
fields: Optional[List[str]] = None,
) -> List[List[dict]]:
"""
根据输入文本做向量相似度检索,将文本转为向量后在Milvus集合中做相似性搜索
Args:
collection_name: 目标集合名称
limit: 返回匹配结果的最大条数,默认返回前5条
text: 用户输入的查询文本,会被模型转为向量用于检索
fields: 需要返回的字段列表,不传则默认只返回id、vector;
需要自定义元数据(如text)必须显式传入,示例:["id", "vector", "text"]
Returns:
List[List[dict]]: 检索结果,外层列表对应查询向量,内层列表为相似度匹配的多条结果;
每条字典包含距离score、id以及指定的output_fields字段
Note:
1. 调用前必须完成数据库连接,否则get_client()返回None会抛出异常
2. ⚠️ 检索前集合需要加载到内存,否则会出现数据释放报错;
底层search同样需要load_collection加载磁盘数据到内存
3. 若不指定fields,Milvus仅返回主键id和vector向量,自定义扩展字段不会返回
4. 内部会调用embed_model把输入文本生成向量,传入search接口执行相似度查询
"""
# 将查询文本转为向量,search接口要求传入向量列表,故封装为列表
vectors = [embed_model.get_text_embedding(text)]
# 执行向量相似度检索
return client.search(
collection_name=collection_name,
data=vectors,
limit=limit,
output_fields=fields,
)
python
if __name__ == "__main__":
# 1. 打开数据库,必须先指定数据库
use_db("bit")
coll_name = "01"
client.load_collection(collection_name=coll_name)
# ========== 批量插入4条测试数据 ==========
# 4条语义各不相同的测试句子,前两句语义相近,后两句差异大
test_sentences = [
"小明是一个大帅哥。",
"小明长得很好看。",
"今天中午吃点什么呢?",
"我们正在学习外国的知识。"
]
insert_data = []
start_id = 4 # 设置起始ID为4,id自增:4、5、6、7
for idx, text in enumerate(test_sentences, start=start_id):
print(f"正在生成向量:{text}")
vec = embed_model.get_text_embedding(text)
insert_data.append({
"id": idx,
"vector": vec,
"text": text
})
# 执行批量插入
print("\n开始批量插入4条测试数据...")
insert_res = insert(collection_name=coll_name, rows=insert_data)
print("插入结果:", insert_res)
# ========== 向量检索测试 ==========
query_text = "有很多很多女孩喜欢小明"
print(f"\n执行检索,查询文本:{query_text}")
# 调用向量检索函数,limit=2,返回最相似的2条;指定返回id、vector、text字段
search_result = search_by_text(
collection_name=coll_name,
limit=2,
text=query_text,
fields=["id", "vector", "text"]
)
# 打印检索结果
print("\n===== 向量检索返回结果 =====")
for res_item in search_result[0]:
print(f"id: {res_item['id']}")
print(f"文本: {res_item['text']}")
print(f"向量距离distance: {res_item['distance']}")
print("-" * 50)
# 预期:返回两条和小明相关的句子
# distance越小,代表语义相似度越高,相似度高的排在前面
方法写完之后,我们构造测试数据集来验证功能。我们准备4条语义各不相同的句子:
-
- 小明是一个大帅哥。
-
- 小明长得很好看。
-
- 今天中午吃点什么呢?
-
- 我们正在学习外国的知识。
可以看到,前两句话语义相近,后两句话语义和前两句差异很大。我们的测试目标:输入一句和"小明"相关的查询文本,验证search_by_text能否把语义最相近的两条数据检索出来。
接下来批量插入测试数据。设置起始ID为4,循环遍历文本列表。每一条文本,调用嵌入模型生成向量,构造字典数据,包含id、vector、text字段,调用insert方法插入集合01,ID自增。执行代码,4条测试数据成功写入数据库。
完成数据插入,我们编写测试查询。查询文本设置为:"有很多很多女孩喜欢小明"。 调用search_by_text,传入集合名01、查询文本、设置limit=2。 预期结果:返回"小明是一个大帅哥"、"小明长得很好看"这两条语义最接近的数据。
python
C:\Users\A1983\miniconda3\envs\RAG\python.exe E:\RAGBasic\milvus.py
Loading weights: 100%|██████████| 391/391 [00:00<00:00, 27896.39it/s]
正在生成向量:小明是一个大帅哥。
正在生成向量:小明长得很好看。
正在生成向量:今天中午吃点什么呢?
正在生成向量:我们正在学习外国的知识。
开始批量插入4条测试数据...
插入结果: {'insert_count': 4, 'ids': [4, 5, 6, 7]}
执行检索,查询文本:有很多很多女孩喜欢小明
===== 向量检索返回结果 =====
id: 5
文本: 小明长得很好看。
向量距离distance: 0.8037629127502441
--------------------------------------------------
id: 4
文本: 小明是一个大帅哥。
向量距离distance: 0.7597196102142334
--------------------------------------------------
进程已结束,退出代码为 0
注意:执行查询之前,要先调用load_collection,把集合加载到内存。执行查询,打印返回结果。
查看返回结果,结果里面会有distance字段,这个代表向量距离:距离越小,代表两条文本语义相似度越高。我们可以看到,和查询文本语义相近的两条数据被优先返回,我们的向量检索方法验证通过。
这里要理解向量数据库带来的优势: 传统语义检索,每次查询都要现场计算所有文本的向量,再计算相似度,非常耗时。 使用向量数据库之后,把向量计算工作前置:提前把文本转为向量存入数据库。后续检索的时候,只需要把用户输入文本转为向量,直接调用向量检索接口,就可以快速得到相似度最高的结果。把向量计算和相似度检索拆分成两个独立步骤。
到这里,我们把Milvus向量数据库整套基础操作全部讲解完毕:创建数据库、创建集合、插入数据、ID查询、向量相似度检索。