知识库的构建 [ 4 ]

知识库构建

向量嵌入

我们前面已经把文本分块的所有内容全部实现完毕。接下来我们开始学习向量嵌入的相关知识点。

向量嵌入(Embedding) 是将非结构化数据(如单词、句子、图像或音频)转换为一列数字(向量)的过程。这个向量是高维空间中的一个点,并且具有这样的性质:语义上相似的对象,在向量空间中的距离也更近

向量嵌入模块重点包含四大部分内容,其中核心的三种相似度计算方式分别为:余弦相似度欧式距离点积距离 。这三种方法的核心作用一致,都是用来判断两个向量之间的相似程度

我们在之前语义分块中,已经带大家体验过向量嵌入的完整流程,也成功下载部署了嵌入模型BGE。也实操过:将一段普通文本输入嵌入模型,经过模型计算后,最终可以得到一组对应的向量数据。

这套流程的核心意义非常关键:原本不可计算、非数字化的文本,通过嵌入模型的转换,变成了可以被计算机识别、运算的数字化向量。只要文本完成向量化,我们就可以通过对比向量的相似度,精准判断两段文本的语义含义是否相近。

简单总结:余弦相似度、欧式距离、点积距离,就是我们用来对比向量相似度、判断文本语义相似度的三种核心算法。

除了三种相似度算法,本章最后我们还会讲解向量数据库。我们已经多次提到向量的概念,而向量需要专门的数据库进行存储管理,后续我们会通过向量数据库,持久化保存嵌入模型生成的所有文本向量。

余弦相似度

接下来我们重点学习第一个核心知识点:余弦相似度

余弦相似度 是计算两个向量之间夹角的余弦值。余弦距离 (Cosine distance) 就是用 1 减去这个获得的余弦相似度。取值范围

计算公式:

  • 逻辑:只看向量方向夹角,忽略向量长度(模长)。
  • 适用场景:
    • 绝大多数文本 Embedding(OpenAI、BGE、Sentence‑BERT 等);
    • 文本语义匹配,只关心语义方向,不关心向量长度;
  • 取值范围:,越接近 1 代表语义越相似,数值越大越相似。
  • LlamaIndex 默认用它,是工业界 RAG 标准方案。

余弦相似度的核心逻辑: 通过计算两个向量的夹角大小 ,判断向量相似度。它的取值范围为 -1, 1 。数值越接近 1,代表两个向量、两段文本的语义相似度越高;数值越小,语义相似度越低。

这里重点强调:LlamaIndex框架中,计算向量相似度的默认算法就是余弦相似度,也是工业界RAG项目的主流标准方案。

了解完理论之后,我们在PyCharm中编写代码,手动实现余弦相似度的计算逻辑。新建Python文件,命名为similarity.py,专门用于计算两段文本的向量相似度。

我们自定义核心方法,命名为embedding_similarity,依靠嵌入模型实现文本相似度计算。

方法传入两个参数:sentence1sentence2,均为字符串类型,用于接收两段待对比的文本;方法最终返回一个浮点型数值,也就是两段文本的相似度结果。

**代码实现第一步:加载嵌入模型。**模型默认存储在硬盘中,使用前必须加载到内存。

首先完成模块导入,从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()方法,分别生成vec1vec2两组向量数据。

第三步:计算向量相似度。 调用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.60.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
]

**我们之前学过很多关系型数据库,例如MySQLRedisSQLite,这些数据库能不能存储向量?**答案是可以存储,但是效率很差。向量是高维浮点数组,小数位数很长。关系型数据库虽然可以存下向量数据,但是做相似度检索的时候性能远不如向量数据库。所以我们要使用更适配的工具,也就是向量数据库,以此提升检索效率。

现在市面上向量数据库的种类非常多,像MilvusChromaPinecone等等,行业内百花齐放,没有一款产品能够一统江湖。

常见的向量数据库有如下种类:

  • HNSW:工业最主流,速度、精度均衡(MilvusChromaPinecone
  • FAISS IVF:Facebook 开源,适合批量大规模数据
  • AnnoyDiskANN:适合磁盘大容量存储

这里我们选用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‑LiteMilvus‑Lite是 Milvus 的轻量嵌入式版本,类比MySQLSQLite的关系。它不需要启动独立服务,不需要额外配置,数据直接保存在本地文件当中,非常适合本地学习开发。

核心特点:

特性 说明
无需部署 不需要启动独立服务,随 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.dbbit2.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")]

调用该方法,就可以自动罗列出我们当前项目中所有创建好的向量数据库,比如bitbit2,实现了向量数据库的查询展示功能。

以上就是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
}

这里大家可以发现一个关键点:我们代码中插入了 idvectortext 三个字段,但默认表结构中只保留 idvector 两个核心字段,这是向量数据库的标准特性,向量和主键是默认核心字段,文本属于自定义拓展字段

接下来我们批量插入数据,注释掉原有删表、建表代码,避免重复清空集合,新增两组不同文本数据,批量插入三条数据。执行代码时需要先指定数据库,否则程序无法识别操作库。

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)

加载完成后执行查询,初步查询结果只返回了idvector 两个字段,无法查询到我们自定义的 text 文本字段。

这里是大家必须掌握的核心重点 :如果 fields 参数不主动声明,Milvus 默认只返回主键 id 和向量 vector 两个核心字段;如果需要查询自定义拓展字段(如 text),必须手动在 fields 中声明所有需要查询的字段。

我们补全 fields 参数,传入 idvectortext 三个字段,重新执行查询。此时可以完整查询出包含文本、向量、主键的全量数据。

到这里,我们就完整实现了 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,
        )

做了什么

  1. my_collection 中做近似最近邻搜索(ANN)
  2. 返回与 query_vector 余弦距离最近的 Top N
  3. 附带 titlecontentcategory 等标量字段

我们来看它的关键参数:

第一个参数是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_namedata=vectorslimitoutput_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条语义各不相同的句子:

    1. 小明是一个大帅哥。
    1. 小明长得很好看。
    1. 今天中午吃点什么呢?
    1. 我们正在学习外国的知识。

可以看到,前两句话语义相近,后两句话语义和前两句差异很大。我们的测试目标:输入一句和"小明"相关的查询文本,验证search_by_text能否把语义最相近的两条数据检索出来。

接下来批量插入测试数据。设置起始ID为4,循环遍历文本列表。每一条文本,调用嵌入模型生成向量,构造字典数据,包含idvectortext字段,调用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查询、向量相似度检索。

相关推荐
pnoker14 分钟前
MCP 落地工业平台:从大模型对话到设备点位
人工智能·物联网·智能体·mcp
Microsoft Word17 分钟前
AI Agent入门:从LLM、上下文和工具到Harness工程
大数据·人工智能·机器学习
tachibana220 分钟前
Agent 的长短期记忆系统
人工智能·ai·大模型·llm·agent
前端的阶梯24 分钟前
AI Agent 全景知识基础
人工智能
OpenMiniServer27 分钟前
基因的演化动力学——从复制、变异到生命复杂性的形成
人工智能
stuartevil29 分钟前
AI图生视频常见坑:参考图风格不一致怎么解决
人工智能·深度学习·音视频
星核0penstarry35 分钟前
HICOOL 2026深度解读:从四大仪式看北京硬科技生态的底层逻辑
大数据·人工智能·科技·ai·创业创新·ai编程
aichitang202435 分钟前
快乐泛函每一天:希尔伯特空间中的最佳逼近与正交投影定理
人工智能·考研·算法·ai·面试·泛函分析
Python大数据分析@37 分钟前
如何评价openworker办公agent?
人工智能