第一次使用 Milvus:给我的 AI 知识库装上“记忆”

第一次使用 Milvus:给我的 AI 知识库装上"记忆"

系列:《从 0 打造我的本地 AI 知识库:Obsidian + Ollama + Milvus + RAG + MCP + Agent》

第 04 篇

上一篇:《我终于搞懂 Embedding:为什么一句话可以变成 1024 个数字?》

本文关键词:Milvus Milvus Lite Vector Database 向量数据库 Collection Cosine Similarity Python


上一篇,我们已经解决了一个非常关键的问题:

text 复制代码
文字
 ↓
Embedding
 ↓
1024维向量

例如:

text 复制代码
MySQL MVCC 是多版本并发控制机制

经过 Embedding 模型以后,会变成一串数字:

text 复制代码
[-0.0655, -0.0861, -0.0121, ...]

但是新的问题来了。

这些向量应该放在哪里?

如果只有一条数据,我们完全可以:

python 复制代码
vectors = []

然后把向量放进去。

但如果我的 Obsidian 以后有:

text 复制代码
100篇
1000篇
10000篇

甚至每篇文章又被切成几十个 Chunk。

很快就会变成:

text 复制代码
几十万个向量

这时候,简单的 Python List 显然不够用了。

于是,我需要一个专门负责:

保存向量 + 搜索相似向量

的数据库。

这就是:

Vector Database【向量数据库】

而我这次选择的是:

Milvus


一、Milvus 到底是什么?

第一次看到 Milvus 的时候,我其实把它理解成:

"是不是一个专门存 Embedding 的数据库?"

这个理解虽然不算错,但还不够准确。

更准确地说:

Milvus 是一个专门用于向量数据存储和相似度检索的向量数据库。

普通数据库擅长:

text 复制代码
id = 1001
name = Tom
age = 20

然后查询:

sql 复制代码
SELECT *
FROM user
WHERE age = 20;

而向量数据库主要解决的是:

text 复制代码
给我一个向量

↓

帮我找到数据库中
与它最相似的向量。

例如:

text 复制代码
用户问题
    ↓
Embedding
    ↓
[0.12, -0.32, 0.45, ...]
    ↓
Milvus
    ↓
找最相似的向量

这正好就是我的 RAG 所需要的能力。


二、为什么不用 MySQL?

这是一个很好的问题。

我本身就是 Java 后端开发,第一反应其实是:

"我已经会 MySQL 了,为什么不直接用 MySQL?"

当然,现在很多数据库已经具备向量搜索能力。

但是在这个项目里,我希望真正理解:

向量数据库到底是干什么的。

所以第一版我选择专门的向量数据库 Milvus。

这样可以把概念拆得非常清楚:

text 复制代码
MySQL
 ↓
传统结构化数据

Milvus
 ↓
向量数据

当然,实际生产系统里到底选 MySQL、PostgreSQL + pgvector、Milvus、Elasticsearch 等,要根据规模和场景决定。

但对于我这个学习型项目:

Milvus 非常适合拿来理解 Vector Search。


三、我为什么选择 Milvus Lite?

如果直接部署完整的 Milvus 集群,事情一下子就复杂起来了。

可能涉及:

text 复制代码
Docker
Kubernetes
Etcd
对象存储
消息系统
Milvus Server

但我现在只是:

text 复制代码
Mac M1
16GB
个人项目

完全没必要一开始就搞这么复杂。

所以我选择:

Milvus Lite

它可以直接嵌入到 Python 项目里。

我的目标非常简单:

text 复制代码
Mac
 │
 ├── Ollama
 │
 ├── Python
 │
 └── Milvus Lite

甚至不需要先部署一个庞大的数据库集群。

这对于学习和小规模个人知识库来说非常方便。


四、先准备 Python 环境

我的项目目录:

text 复制代码
local-ai-kb/
├── .venv/
├── data/
├── obsidian/
└── ...

进入项目:

bash 复制代码
cd ~/local-ai-kb

启动虚拟环境:

bash 复制代码
source .venv/bin/activate

然后安装 Milvus Lite:

bash 复制代码
pip install -U "pymilvus[milvus-lite]"

安装完成以后,就可以开始写第一段代码。


五、创建我的第一个 Milvus Collection

我先创建:

text 复制代码
milvus_test.py

代码:

python 复制代码
from pymilvus import MilvusClient

DB_PATH = "./data/milvus.db"
COLLECTION_NAME = "knowledge_base"
VECTOR_DIMENSION = 1024

client = MilvusClient(DB_PATH)

if client.has_collection(collection_name=COLLECTION_NAME):
    client.drop_collection(collection_name=COLLECTION_NAME)

client.create_collection(
    collection_name=COLLECTION_NAME,
    dimension=VECTOR_DIMENSION,
    metric_type="COSINE"
)

print("================================")
print("Milvus 初始化成功")
print("================================")
print("数据库:", DB_PATH)
print("Collection:", COLLECTION_NAME)
print("向量维度:", VECTOR_DIMENSION)
print("距离算法:", "COSINE")

运行:

bash 复制代码
python milvus_test.py

最终看到:

text 复制代码
================================
Milvus 初始化成功
================================
数据库: ./data/milvus.db
Collection: knowledge_base
向量维度: 1024
距离算法: COSINE

到这里,我的第一个向量数据库已经创建出来了。


六、这里出现了一个新概念:Collection

第一次接触 Milvus 时,我看到:

python 复制代码
COLLECTION_NAME = "knowledge_base"

一开始不知道:

Collection 是什么?

可以先简单理解成:

Collection ≈ 一组相关向量数据的集合。

如果类比传统数据库:

text 复制代码
MySQL
  ↓
Database
  ↓
Table

Milvus:

text 复制代码
Milvus
  ↓
Collection
  ↓
Vector Data

比如以后我可以设计:

text 复制代码
knowledge_base
interview_questions
blog_knowledge
novel_knowledge

第一阶段,我只需要:

text 复制代码
knowledge_base

就够了。


七、为什么必须指定 1024?

这里和上一篇的 Embedding 就连接起来了。

上一篇我们已经知道:

text 复制代码
Qwen3-Embedding 0.6B
        ↓
     1024维

那么 Milvus 就必须知道:

我要保存的向量到底是多少维?

所以:

python 复制代码
VECTOR_DIMENSION = 1024

如果 Embedding 模型输出:

text 复制代码
1024个数字

那么 Milvus Collection 就应该配置:

text 复制代码
dimension = 1024

它们必须匹配。

也就是:

text 复制代码
Embedding 模型
       │
       │ 1024维
       ▼
    Milvus
       │
       │ 1024维
       ▼
   保存向量

如果以后换成其他 Embedding 模型,而它输出的是:

text 复制代码
768维

那么对应的 Collection 设计也需要匹配。

这也是为什么:

Embedding 模型一旦确定,向量维度就是整个向量数据库设计中的重要参数。


八、Cosine 又出现了

上一篇讲 Embedding 的时候,我们已经认识了:

text 复制代码
Cosine Similarity
【余弦相似度】

现在它又出现了:

python 复制代码
metric_type="COSINE"

这是什么意思?

就是告诉 Milvus:

以后进行向量搜索时,我希望使用 Cosine 这一类相似度/距离度量。

于是:

text 复制代码
用户问题
   ↓
Embedding
   ↓
问题向量
   ↓
Milvus
   ↓
Cosine
   ↓
找到相似向量

整个链路就连接起来了。


九、Milvus 现在还没有"记忆"

这里我要特别强调一下。

运行完:

bash 复制代码
python milvus_test.py

并不代表知识库已经有知识了。

现在只是:

text 复制代码
Milvus
 ↓
创建了一个空的 Collection

就像:

text 复制代码
你创建了一个数据库

但里面:

text 复制代码
没有数据

所以现在的状态是:

text 复制代码
knowledge_base

      空
      ↓
     [ ]

下一步,我们才真正给它放入第一条知识。


十、把我的第一篇 Markdown 变成向量

我准备了一篇测试笔记:

text 复制代码
obsidian/test.md

内容:

markdown 复制代码
# MySQL MVCC

MVCC 是 Multi-Version Concurrency Control,
即多版本并发控制。

InnoDB 通过 Undo Log 和 Read View 实现 MVCC。

MVCC 可以在保证事务隔离性的同时,
减少读操作和写操作之间的锁竞争。

现在要做的事情就是:

text 复制代码
test.md
   ↓
读取 Markdown
   ↓
Embedding
   ↓
1024维向量
   ↓
Milvus

十一、第一次向 Milvus 插入数据

我创建:

text 复制代码
insert_test.py

代码:

python 复制代码
import requests
from pymilvus import MilvusClient

OLLAMA_URL = "http://localhost:11434/api/embed"

EMBEDDING_MODEL = "qwen3-embedding:0.6b"

DB_PATH = "./data/milvus.db"

COLLECTION_NAME = "knowledge_base"

FILE_PATH = "./obsidian/test.md"


# 1. 读取 Markdown
with open(FILE_PATH, "r", encoding="utf-8") as f:
    text = f.read()

print("读取文件:")
print(FILE_PATH)

print("文本长度:", len(text))


# 2. 调用 Embedding 模型
response = requests.post(
    OLLAMA_URL,
    json={
        "model": EMBEDDING_MODEL,
        "input": text
    }
)

response.raise_for_status()

data = response.json()

embedding = data["embeddings"][0]

print("Embedding 维度:", len(embedding))


# 3. 连接 Milvus
client = MilvusClient(DB_PATH)


# 4. 准备数据
data = [{
    "id": 1,
    "vector": embedding,
    "text": text,
    "source": FILE_PATH
}]


# 5. 插入
result = client.insert(
    collection_name=COLLECTION_NAME,
    data=data
)


print("================================")
print("数据插入 Milvus 成功")
print("================================")

print("插入结果:", result)

运行:

bash 复制代码
python insert_test.py

得到:

text 复制代码
读取文件:
./obsidian/test.md

文本长度:147

Embedding 维度:1024

================================
数据插入 Milvus 成功
================================

插入结果:
{'insert_count': 1, 'ids': [1]}

这一刻,我的 Milvus 终于不再是空的。


十二、Milvus 里面现在到底有什么?

现在可以把数据库想象成:

text 复制代码
knowledge_base
│
└── id = 1
    │
    ├── vector
    │   └── [1024个数字]
    │
    ├── text
    │   └── MySQL MVCC......
    │
    └── source
        └── ./obsidian/test.md

这里我故意保存了三个东西:

text 复制代码
vector
text
source

为什么?

因为:

vector

用于:

text 复制代码
相似度搜索

text

用于:

text 复制代码
找到以后,把原始知识取出来

source

用于:

text 复制代码
告诉 AI:

这段知识来自哪个文件?

所以一条知识实际上变成了:

text 复制代码
┌─────────────────────────┐
│ ID                      │
├─────────────────────────┤
│ Vector                  │
│ [1024个数字]            │
├─────────────────────────┤
│ Text                    │
│ MySQL MVCC......        │
├─────────────────────────┤
│ Source                  │
│ obsidian/test.md        │
└─────────────────────────┘

这就开始有点像真正的知识库了。


十三、第一次查询,我遇到了一个坑

接下来我当然想:

"那我能不能搜索一下?"

于是我第一次查询的时候遇到了:

text 复制代码
Collection 'knowledge_base'
is in state 'released';
call load() before search/get/query

刚看到这个错误,我还有点懵。

是不是数据库坏了?

是不是 Milvus 没启动?

都不是。


十四、Collection 的 Load 是什么?

Milvus 为了进行搜索,需要把 Collection 加载到可用于搜索的状态。

所以在搜索之前,需要:

python 复制代码
client.load_collection(
    collection_name=COLLECTION_NAME
)

于是:

text 复制代码
创建 Collection
       ↓
插入数据
       ↓
Collection
       ↓
Load
       ↓
Search

这个错误反而让我真正理解了一件事情:

Milvus 的"数据存在"和"当前可搜索状态"不是完全一回事。


十五、第一次真正的向量搜索

现在准备一个问题:

text 复制代码
MySQL 的 MVCC 是什么?

首先:

text 复制代码
问题
 ↓
Embedding
 ↓
1024维向量

然后:

text 复制代码
问题向量
 ↓
Milvus
 ↓
Search

代码:

python 复制代码
import requests
from pymilvus import MilvusClient

OLLAMA_EMBED_URL = "http://localhost:11434/api/embed"

EMBEDDING_MODEL = "qwen3-embedding:0.6b"

DB_PATH = "./data/milvus.db"

COLLECTION_NAME = "knowledge_base"


question = "MySQL 的 MVCC 是什么?"


# 1. 问题 Embedding
response = requests.post(
    OLLAMA_EMBED_URL,
    json={
        "model": EMBEDDING_MODEL,
        "input": question
    }
)

response.raise_for_status()

query_vector = response.json()["embeddings"][0]


# 2. 连接 Milvus
client = MilvusClient(DB_PATH)


# 3. 加载 Collection
client.load_collection(
    collection_name=COLLECTION_NAME
)


# 4. 搜索
results = client.search(
    collection_name=COLLECTION_NAME,
    data=[query_vector],
    limit=3,
    output_fields=[
        "text",
        "source"
    ]
)


# 5. 打印结果
for result in results[0]:

    print("相似度:", result["distance"])

    print("来源:", result["entity"]["source"])

    print("内容:")

    print(result["entity"]["text"])

运行以后,我第一次真正看到了:

text 复制代码
================================
用户问题
================================

MySQL 的 MVCC 是什么?

问题向量维度:1024

Collection 加载成功

================================
向量搜索结果
================================

相似度:
0.8407926559448242

来源:
./obsidian/test.md

内容:

# MySQL MVCC

MVCC 是 Multi-Version Concurrency Control,

即多版本并发控制。

InnoDB 通过 Undo Log 和 Read View 实现 MVCC。

MVCC 可以在保证事务隔离性的同时,

减少读操作和写操作之间的锁竞争。

十六、这一刻,RAG 的前半部分终于跑通了

回头看整个过程:

text 复制代码
                     test.md
                        │
                        ▼
               Qwen3-Embedding
                     0.6B
                        │
                        ▼
                  1024维向量
                        │
                        ▼
                     Milvus
                        │
                        │
用户问题 ──→ Embedding ─┘
                        │
                        ▼
                   相似度搜索
                        │
                        ▼
                    test.md

这其实已经是:

Retrieval【检索】

了。

还没有生成最终答案。

但我们已经完成了 RAG 中非常关键的一半:

text 复制代码
RAG
│
├── Retrieval   ← 现在已经跑通
│
└── Generation  ← 下一步

十七、Milvus 在整个系统中的位置

现在整个架构可以画成:

text 复制代码
                    Obsidian
                       │
                       ▼
                    Markdown
                       │
                       ▼
              Qwen3-Embedding 0.6B
                       │
                       ▼
                  1024维 Vector
                       │
                       ▼
                 ┌───────────┐
                 │  Milvus   │
                 │  Lite     │
                 └─────┬─────┘
                       │
                  Vector Search
                       │
                       ▼
                  相关知识片段
                       │
                       ▼
                  Qwen3.5 9B
                       │
                       ▼
                    AI回答

注意:

Milvus 本身不会回答问题。

它只负责:

帮我找到相关知识。

真正负责回答的是:

text 复制代码
Qwen3.5 9B

十八、到这里,我终于理解了"向量数据库"

以前我对向量数据库的理解非常模糊。

现在可以用一句非常简单的话总结:

向量数据库就是帮我们保存向量,并快速找到与目标向量相似的数据。

在我的项目里面:

text 复制代码
Embedding

负责:
"把文字变成向量"
Milvus

负责:
"保存向量 + 找相似向量"
Qwen3.5

负责:
"根据找到的知识生成答案"

三者职责完全不同。


十九、但现在这个知识库还有一个巨大问题

目前我的数据库只有:

text 复制代码
test.md

一篇文章。

这当然没有任何实际意义。

真正的 Obsidian 可能是:

text 复制代码
Obsidian
│
├── Java
│   ├── HashMap.md
│   ├── ConcurrentHashMap.md
│   └── ThreadPool.md
│
├── JVM
│   ├── G1.md
│   ├── GC.md
│   └── JVM内存.md
│
├── MySQL
│   ├── MVCC.md
│   ├── 索引.md
│   ├── 事务.md
│   └── 锁.md
│
├── Redis
│
├── Spring
│
└── AI

如果还是:

python 复制代码
FILE_PATH = "./obsidian/test.md"

一个一个手动处理:

text 复制代码
test1.md
test2.md
test3.md
test4.md
...

那我迟早会疯掉。

所以接下来真正的问题变成:

如何自动处理整个 Obsidian?


二十、而且还有另一个问题:一篇文章不能永远是一个向量

假设以后有一篇:

text 复制代码
JVM 垃圾回收机制.md

有:

text 复制代码
10000 字

我把整篇文章直接做一次 Embedding:

text 复制代码
10000字
 ↓
一个向量

虽然能工作,但效果未必好。

因为用户问:

G1 的 Remembered Set 是干什么的?

而整个 10000 字文章里面可能包含:

text 复制代码
G1
CMS
Parallel GC
Young GC
Old GC
Remembered Set
Write Barrier
Region

一个向量把这么多知识全部混在了一起。

这就会影响检索的精准度。

所以真正的 RAG 还需要一个非常重要的步骤:

Chunk【文本分块】

也就是:

text 复制代码
一篇长文章
      ↓
切成多个知识片段
      ↓
Chunk 1
Chunk 2
Chunk 3
Chunk 4
      ↓
分别 Embedding
      ↓
分别保存到 Milvus

这样:

text 复制代码
"G1 的 Remembered Set 是什么?"

就更容易精准找到:

text 复制代码
G1 / Remembered Set

相关的那个 Chunk。


二十一、这一篇我真正学到了什么?

如果把今天的内容压缩成一张图:

text 复制代码
                    Markdown
                       │
                       ▼
                  Embedding
                       │
                       ▼
                1024维 Vector
                       │
                       ▼
                    Milvus
                       │
              ┌────────┴────────┐
              │                 │
           保存向量          相似度搜索
                                │
                                ▼
                           相关知识

其中:

组件 作用
Markdown 保存我的原始知识
Embedding 把文字转换成向量
Vector 数字化的语义表示
Milvus 保存和搜索向量
Cosine 衡量向量相似程度

到这里:

RAG 的 Retrieval 部分,我终于亲手跑通了。


下一篇

下一篇进入整个项目真正的关键:

《第一次完整 RAG:让本地大模型真正"读懂"我的知识库》

我们终于可以把:

text 复制代码
用户问题
    ↓
Embedding
    ↓
Milvus
    ↓
找到相关知识
    ↓
Qwen3.5 9B
    ↓
最终答案

全部连接起来。

也就是说,下一篇之后,我就不再只是拥有:

一个会搜索向量的数据库。

而是真正拥有:

一个能够根据我的 Obsidian 知识回答问题的本地 AI。

这也是整个系列从"AI 基础组件实验"正式进入:

RAG 实战

的转折点。

相关推荐
YOLO数据集集合1 小时前
天空反无人机检测数据集 | 无人机检测 多旋翼识别 固定翼识别 低空安防 目标检测9063期
人工智能·yolo·目标检测·计算机视觉·无人机·反无人机
一木 之林1 小时前
插件、MCP、Skill 的区别?
java·c++·人工智能
JiMoKuangXiangQu1 小时前
在 AllWinner T507 上部署 Qwen2.5-0.5B 大语言模型
人工智能·llama.cpp·推理引擎
szarron1 小时前
RF Demo Kit|NanoVNA 射频演示测试板完整上手教程,滤波器、衰减器、SOLT 校准学习板
开发语言·人工智能·学习·php·射频工程·频谱仪
IT·陈寒1 小时前
JavaScript性能优化完全指南
人工智能·大模型·api·创业·变现·简历优化
tedcloud1231 小时前
Wand-Enhancer:如何搭建一套远程开发与测试环境
前端·人工智能·macos·开源·流程图
薛定谔的猫-菜鸟程序员1 小时前
端侧免费大模型实测:MiniCPM5-2B-Q4_K_M 架构拆解与 4GB 显卡实测
人工智能·大模型·agent·hermes·minicpm5-2b
小海豚儿1 小时前
没有反馈的 Loop,只是更贵的重试
人工智能·ai编程
用户302822530681 小时前
Agent Skill工程:如何把一次成功运行提炼成可测试的方法
人工智能