写在前面:之前我们做 RAG,向量存在内存里(
MemoryVectorStore),数据一重启就没了。老师说:"内存向量存储是学习用的,生产环境要用真正的向量数据库。" 今天的主角是 Milvus(Zilliz Cloud)——一个专为海量高维向量数据设计的开源数据库。我们做了一个"AI 日记本":把日记存进 Milvus,然后问 AI "我最近做了什么快乐的事",AI 自动检索相关日记、结合内容、生成温暖的回答。当看到 AI 准确地说出"你 1 月 10 日去公园散步很开心,1 月 13 日做晚饭很有成就感"时,我整个人都惊了——这不就是 AI 版的"懂你"吗?
一、从内存到真正的向量数据库
1.1 之前的问题
之前我们做 RAG,用的是 MemoryVectorStore:
const vectorStore = await MemoryVectorStore.fromDocuments(documents, embeddings);
老师说:
"内存向量数据库——学习用,轻量。生产环境要用真正的向量数据库,像 MySQL 之于 Web 应用一样。"
内存向量存储的问题:
| 问题 | 说明 | 比喻 |
|---|---|---|
| 数据不持久 | 服务重启,数据全丢 | 写在沙子上,潮水一冲就没了 |
| 容量有限 | 受内存限制,不能存太多 | 书包就这么大,装不了太多书 |
| 不能共享 | 多台服务器不能共用 | 你这本笔记只有你自己能看 |
1.2 Milvus 是什么?
老师说:
"Milvus 是一款开源的向量数据库,专为处理海量高维向量数据而设计。AI Agent 产品都会使用 Milvus 这样的 Vector Store。像 Web 应用会把数据存在 MySQL 里面,基于 CRUD 实现各种业务功能。Agent 会把知识、记忆放在 Milvus 数据库中,对知识做语义检索、增删改查等各种功能。"
MySQL : Web 应用 = Milvus : AI 应用
| 对比 | MySQL | Milvus |
|---|---|---|
| 存什么 | 结构化数据(用户、订单) | 高维向量(文本的语义向量) |
| 查什么 | WHERE name LIKE '%关键词%' | 余弦相似度找语义相近的向量 |
| 索引方式 | B+ 树 | IVF_FLAT、AUTOINDEX 等向量索引 |
| 典型应用 | 电商、CMS | RAG、推荐系统、AI 记忆 |
我们用 Zilliz Cloud——基于 Milvus 的全托管云服务,不用自己部署服务器。
二、连接 Milvus 并创建集合
2.1 连接 Zilliz Cloud
看 main.mjs:
import { MilvusClient } from '@zilliz/milvus2-sdk-node';
import dotenv from 'dotenv/config';
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
const COLLECTION_NAME = 'ai_dairy';
const VECTOR_DIM = 1024;
const client = new MilvusClient({
address: ADDRESS,
token: TOKEN,
});
async function main() {
const checkHealth = await client.checkHealth();
if (!checkHealth.isHealthy) {
console.error('链接失败', checkHealth.reasons);
return;
}
console.log('链接成功,集群状态正常。');
}
MilvusClient 是连接 Milvus 的客户端,checkHealth() 检查服务是否正常。
2.2 创建集合(Collection)
老师说:
"MySQL 里叫表(Table),Milvus 里叫集合(Collection)。"
// 先删除旧集合(如果有),再创建
try {
await client.dropCollection({ collection_name: COLLECTION_NAME });
} catch (_) {}
await client.createCollection({
collection_name: COLLECTION_NAME,
fields: [
{ name: 'id', data_type: DataType.VarChar, max_length: 50, is_primary_key: true },
{ name: 'vector', data_type: DataType.FloatVector, dim: VECTOR_DIM },
{ name: 'content', data_type: DataType.VarChar, max_length: 5000 },
{ name: 'data', data_type: DataType.VarChar, max_length: 50 },
{ name: 'mood', data_type: DataType.VarChar, max_length: 50 },
{ name: 'tags', data_type: DataType.Array, element_type: DataType.VarChar, max_capacity: 10, max_length: 50 },
]
});
集合的字段定义就像 MySQL 建表时的字段声明:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | VarChar | 主键,唯一标识 |
| vector | FloatVector(1024) | 日记内容的语义向量 |
| content | VarChar | 日记原文 |
| data | VarChar | 日记日期 |
| mood | VarChar | 心情标签 |
| tags | Array of VarChar | 标签数组 |
2.3 创建索引
await client.createIndex({
collection_name: COLLECTION_NAME,
field_name: 'vector',
index_type: IndexType.IVF_FLAT,
metric_type: MetricType.COSINE,
});
await client.loadCollection({
collection_name: COLLECTION_NAME,
});
老师说:
"没有索引时,每次查询都要把库里的向量和查询的向量逐一算相似度(O(n)),数据量大了,慢得没法用。就像去图书馆找一本《三体》——没有索引,每本都要翻一下。有索引:文学馆 → 小说 → 科幻,迅速缩小范围。"
IVF_FLAT 是一种聚类索引,把相似的向量聚在一起,搜索时先找最近的聚类,再在聚类里细找——从 O(n) 变成 O(log n)。
三、插入数据:把日记向量化存进去
3.1 准备日记内容
const diaryContents = [
{
id: 'diary_001',
content: '今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。',
data: '2026-01-10',
mood: 'happy',
tags: ['生活', '散步']
},
{
id: 'diary_002',
content: '今天工作很忙,完成了一个重要的项目里程碑。团队合作很愉快,感觉很有成就感。',
data: '2026-01-11',
mood: 'excited',
tags: ['工作', '成就']
},
{
id: 'diary_003',
content: '周末和朋友去爬山,天气很好,心情也很放松。享受大自然的感觉真好。',
data: '2026-01-12',
mood: 'relaxed',
tags: ['户外', '朋友']
},
{
id: 'diary_004',
content: '今天学习了 Milvus 向量数据库,感觉很有意思。向量搜索技术真的很强大。',
data: '2026-01-12',
mood: 'curious',
tags: ['学习', '技术']
},
{
id: 'diary_005',
content: '晚上做了一顿丰盛的晚餐,尝试了新菜谱。家人都说很好吃,很有成就感。',
data: '2026-01-13',
mood: 'proud',
tags: ['美食', '家庭']
}
];
3.2 向量化并插入
const diaryData = await Promise.all(
diaryContents.map(async (diary) => ({
...diary,
vector: await getEmbeddings(diary.content)
}))
);
const insertResult = await client.insert({
collection_name: COLLECTION_NAME,
data: diaryData,
});
console.log(insertResult.insert_cnt, '条记录成功插入。');
每条日记在入库前,先通过 Embedding 模型把 content 转成 1024 维向量。 这样后面搜索时,就能用"语义"而不是"关键词"来查找了。
四、语义搜索:问一个自然语言问题
4.1 搜索函数
async function retrieveRelatedDiaries(question, k = 2) {
const queryVector = await getEmbeddings(question);
const searchResult = await client.search({
collection_name: COLLECTION_NAME,
vector: queryVector,
limit: k,
metric_type: MetricType.COSINE,
output_fields: ['id', 'content', 'data', 'mood', 'tags'],
});
return searchResult.results;
}
搜索流程:
用户问:"我最近做了什么快乐的事情?"
↓ Embedding 模型
"快乐" → [1024 维向量]
↓ Milvus 搜索
在向量库里找余弦相似度最高的 2 条
↓ 返回
日记 001(散步,相似度 0.92)和 日记 005(做饭,相似度 0.85)
4.2 搜索结果的相似度评分
searchResult.results.forEach((item, index) => {
console.log(`${index + 1}. [Score: ${item.score.toFixed(4)}]`);
console.log(`Content: ${item.content}`);
});
输出:
1. [Score: 0.9234]
Content: 今天天气很好,去公园散步了,心情愉快……
2. [Score: 0.8543]
Content: 晚上做了一顿丰盛的晚餐,尝试了新菜谱……
相似度得分(Score)越接近 1,说明和问题语义越相关。 0.92 表示"去公园散步"和"快乐"高度相关。
五、RAG 增强生成:AI 结合日记回答问题
5.1 构建 Prompt
const content = retrievedDiaries
.map((diary, i) => `
[日记 ${i+1}]
日期: ${diary.data}
心情: ${diary.mood}
标签: ${diary.tags?.join(', ')}
内容: ${diary.content}
`).join('\n\n----\n\n');
const prompt = `你是一个温暖贴心的 AI 日记助手。基于用户的日记内容回答问题。
${content}
用户问题:${question}
回答要求:
1. 如果日记中有相关信息,请结合日记内容给出详细、温暖的回答。
2. 可以总结多篇日记的内容,找出共同点或趋势。
3. 用第二人称"你"来称呼日记的作者。
AI 助手的回答:`;
const response = await model.invoke(prompt);
console.log(response.content);
5.2 AI 的回答
当问题为 "我最近做了什么快乐的事情?" 时,AI 的回答大概是这样:
"我翻看了你最近的日记,发现你做了不少快乐的事情呢!🎉
1 月 10 日,你趁着好天气去公园散了步,看到花儿盛开,心情非常愉快 🌸 1 月 13 日,你挑战了新菜谱,做了一顿丰盛的晚餐,家人都说好吃,你特别有成就感 👨🍳
看起来你是一个懂得从生活小事中获取快乐的人。继续保持这种热爱生活的态度吧!😊"
这就是 RAG 的完整流程:检索(Retrieval)→ 增强(Augmented)→ 生成(Generation)。
六、完整的数据流
用户问:"我最近做了什么快乐的事情?"
↓
1. 问题向量化(Embedding)
↓
2. Milvus 语义搜索(余弦相似度)
↓
3. 找到最相关的 2 篇日记
↓
4. 把日记内容组装成 Prompt
↓
5. LLM 基于日记生成回答
↓
输出温暖的回答
七、总结:Milvus = AI 应用的"MySQL"
| 概念 | 说明 |
|---|---|
| Milvus | 开源向量数据库,存高维向量 |
| Zilliz Cloud | Milvus 的全托管云服务 |
| Collection | 类似 MySQL 的表 |
| Field | 字段定义,含类型和约束 |
| Vector | 1024 维的 Float 数组 |
| Index | IVF_FLAT 等索引,加速搜索 |
| Cosine Similarity | 余弦相似度,度量语义距离 |
| Score | 相似度评分,越接近 1 越相关 |
| RAG | 检索 + 增强 + 生成 |
从 MemoryVectorStore 到 Milvus,是从"玩具"到"产品"的跨越。真正生产级的 RAG 应用,都需要一个像 Milvus 这样的向量数据库来持久化存储和高效检索知识。
写在最后
今天最大的收获,是把向量存储从内存搬到了真正的向量数据库里。以前做的 RAG 都是"一次性"的——数据重启就没了。现在有了 Milvus,日记存进去就"永久"了,随时可以问 AI "我最近心情怎么样"、"我学到什么新东西了"。
而且 Milvus 的 SDK 用起来和 MySQL 的驱动很像——连接、建表、插数据、查数据,思路是一样的。如果你会 MySQL,学会 Milvus 只是换几个 API 名字的事。
下次面试官问你:"生产环境怎么做 RAG?"
你可以淡定地说:
"生产环境的 RAG 需要用真正的向量数据库,比如 Milvus(或 Zilliz Cloud)。流程是:① 将知识文档通过 Embedding 模型转成高维向量,存入 Milvus Collection;② 建索引(如 IVF_FLAT)加速搜索;③ 用户提问时,把问题也向量化,在 Milvus 中做余弦相似度搜索,找到最相关的 K 条记录;④ 把检索到的内容作为上下文组装成 Prompt,交给 LLM 生成回答。MemoryVectorStore 适合学习和原型验证,Milvus 适合生产部署。"
然后看着面试官满意的表情,心里默念:这波,又稳了。
本文所有代码示例均来自课堂学习资料,真实可运行。