文章目录
-
- 一、向量数据库是什么?
-
- [1.1 先看一个日常场景](#1.1 先看一个日常场景)
- [1.2 传统数据库 vs 向量数据库](#1.2 传统数据库 vs 向量数据库)
- [1.3 从内存到向量数据库](#1.3 从内存到向量数据库)
- 二、Milvus:开源向量数据库
-
- [2.1 Milvus 是什么?](#2.1 Milvus 是什么?)
- [2.2 核心概念速览](#2.2 核心概念速览)
- [2.3 索引为什么重要?](#2.3 索引为什么重要?)
- [2.4 RAG 的完整工作流](#2.4 RAG 的完整工作流)
- [三、实战:AI 日记本 Diary](#三、实战:AI 日记本 Diary)
-
- [3.1 架构设计](#3.1 架构设计)
- [3.2 环境准备](#3.2 环境准备)
- [3.3 第一步:连接 Zilliz Cloud + 创建 Collection](#3.3 第一步:连接 Zilliz Cloud + 创建 Collection)
- [3.4 第二步:创建 Collection 和索引](#3.4 第二步:创建 Collection 和索引)
- [3.5 第三步:向量化并插入数据](#3.5 第三步:向量化并插入数据)
- [3.6 第四步:语义搜索](#3.6 第四步:语义搜索)
- [3.7 第五步:RAG 完整流程(检索 + LLM 生成)](#3.7 第五步:RAG 完整流程(检索 + LLM 生成))
- [四、Zilliz Cloud:全托管 Milvus 云服务](#四、Zilliz Cloud:全托管 Milvus 云服务)
-
- [4.1 Zilliz 是什么?](#4.1 Zilliz 是什么?)
- [4.2 为什么选择 Zilliz Cloud?](#4.2 为什么选择 Zilliz Cloud?)
- [4.3 从自建到云端的代码差异](#4.3 从自建到云端的代码差异)
- 五、全文总结
- 六、核心知识点复盘
本文带你从零理解向量数据库的核心概念,通过一个完整的 AI 日记本 项目,手把手学会用 Milvus 向量数据库和 Zilliz Cloud 云服务,实现语义搜索和 RAG(检索增强生成)。
一、向量数据库是什么?
1.1 先看一个日常场景
你手机里的相册 App,搜索"猫"就能找到所有猫的照片。这背后不是靠文件名匹配,而是靠 语义理解------它知道照片里"有猫"。传统数据库做不到这件事,向量数据库可以。
1.2 传统数据库 vs 向量数据库
| 对比维度 | 传统数据库(MySQL 等) | 向量数据库(Milvus 等) |
|---|---|---|
| 存什么 | 结构化数据(数字、字符串) | 高维向量 + 结构化字段 |
| 怎么查 | WHERE id = 1 精确匹配 |
向量相似度搜索,找"最像"的 |
| 查询方式 | 关键词、ID、条件筛选 | 语义:"最近心情好的日记" |
| 典型场景 | 订单系统、用户管理 | AI Agent 记忆、知识库、推荐系统 |
打个比方:传统数据库像图书馆的索引卡片------你必须知道书名或作者才能找到书。向量数据库像一位懂你的图书管理员------你说"那本讲外星人和小男孩友谊的书",他就能找到《E.T.》的相关资料。
1.3 从内存到向量数据库
在学习向量数据库之前,你可能用过更简单的方案:
方案一:Loader + Splitter(文档加载与切分)
这是 RAG 系统的第一步:加载文档(PDF、网页、Markdown),切成小段(chunk)。比如一篇 5000 字的文章,切成 10 个 500 字的片段。
原文 → Loader(加载) → Splitter(切分) → 多个 chunk
方案二:内存向量数据库
切分后的 chunk 转成向量,直接存在内存里(比如用 Python 的 numpy 数组或 Node.js 的 Float32Array)。查询时逐一计算相似度。
js
// 内存向量检索(仅适合小数据量)
const memoryVectors = [
{ vector: [0.1, 0.2, ...], text: '今天天气很好' },
{ vector: [0.5, 0.6, ...], text: '完成了一个项目' },
];
function search(queryVector, k) {
return memoryVectors
.map(item => ({
...item,
score: cosineSimilarity(queryVector, item.vector)
}))
.sort((a, b) => b.score - a.score)
.slice(0, k);
}
这个方案的问题很明显 :数据量一大就慢到没法用(每次查询要遍历全部向量,时间复杂度 O(n))。而且程序重启数据就丢了。这就引出了我们的主角------Milvus。
二、Milvus:开源向量数据库
2.1 Milvus 是什么?
Milvus 是一款专为处理海量高维向量数据而设计的开源向量数据库。在 AI Agent 产品中,Milvus 承担的角色类似于 Web 应用中 MySQL 的角色------它是 AI 记忆和知识的"存储引擎"。
#mermaid-svg-bKDRkgf0WkfGUnGC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bKDRkgf0WkfGUnGC .error-icon{fill:#552222;}#mermaid-svg-bKDRkgf0WkfGUnGC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bKDRkgf0WkfGUnGC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bKDRkgf0WkfGUnGC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bKDRkgf0WkfGUnGC .marker.cross{stroke:#333333;}#mermaid-svg-bKDRkgf0WkfGUnGC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bKDRkgf0WkfGUnGC p{margin:0;}#mermaid-svg-bKDRkgf0WkfGUnGC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bKDRkgf0WkfGUnGC .cluster-label text{fill:#333;}#mermaid-svg-bKDRkgf0WkfGUnGC .cluster-label span{color:#333;}#mermaid-svg-bKDRkgf0WkfGUnGC .cluster-label span p{background-color:transparent;}#mermaid-svg-bKDRkgf0WkfGUnGC .label text,#mermaid-svg-bKDRkgf0WkfGUnGC span{fill:#333;color:#333;}#mermaid-svg-bKDRkgf0WkfGUnGC .node rect,#mermaid-svg-bKDRkgf0WkfGUnGC .node circle,#mermaid-svg-bKDRkgf0WkfGUnGC .node ellipse,#mermaid-svg-bKDRkgf0WkfGUnGC .node polygon,#mermaid-svg-bKDRkgf0WkfGUnGC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bKDRkgf0WkfGUnGC .rough-node .label text,#mermaid-svg-bKDRkgf0WkfGUnGC .node .label text,#mermaid-svg-bKDRkgf0WkfGUnGC .image-shape .label,#mermaid-svg-bKDRkgf0WkfGUnGC .icon-shape .label{text-anchor:middle;}#mermaid-svg-bKDRkgf0WkfGUnGC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bKDRkgf0WkfGUnGC .rough-node .label,#mermaid-svg-bKDRkgf0WkfGUnGC .node .label,#mermaid-svg-bKDRkgf0WkfGUnGC .image-shape .label,#mermaid-svg-bKDRkgf0WkfGUnGC .icon-shape .label{text-align:center;}#mermaid-svg-bKDRkgf0WkfGUnGC .node.clickable{cursor:pointer;}#mermaid-svg-bKDRkgf0WkfGUnGC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bKDRkgf0WkfGUnGC .arrowheadPath{fill:#333333;}#mermaid-svg-bKDRkgf0WkfGUnGC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bKDRkgf0WkfGUnGC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bKDRkgf0WkfGUnGC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bKDRkgf0WkfGUnGC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bKDRkgf0WkfGUnGC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bKDRkgf0WkfGUnGC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bKDRkgf0WkfGUnGC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bKDRkgf0WkfGUnGC .cluster text{fill:#333;}#mermaid-svg-bKDRkgf0WkfGUnGC .cluster span{color:#333;}#mermaid-svg-bKDRkgf0WkfGUnGC div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bKDRkgf0WkfGUnGC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bKDRkgf0WkfGUnGC rect.text{fill:none;stroke-width:0;}#mermaid-svg-bKDRkgf0WkfGUnGC .icon-shape,#mermaid-svg-bKDRkgf0WkfGUnGC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bKDRkgf0WkfGUnGC .icon-shape p,#mermaid-svg-bKDRkgf0WkfGUnGC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bKDRkgf0WkfGUnGC .icon-shape .label rect,#mermaid-svg-bKDRkgf0WkfGUnGC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bKDRkgf0WkfGUnGC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bKDRkgf0WkfGUnGC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bKDRkgf0WkfGUnGC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} AI Agent
用户提问
Milvus 向量库
语义检索结果
LLM 生成回答
Web应用
用户请求
MySQL
精确查询结果
一句话理解:Web 应用把业务数据存 MySQL 做 CRUD,AI Agent 把知识、记忆存 Milvus 做语义检索。
2.2 核心概念速览
| Milvus 概念 | 类比 MySQL | 说明 |
|---|---|---|
| Collection | Table(表) | 数据集合,定义字段结构 |
| Field | Column(列) | 字段,支持 VarChar、FloatVector、Array 等类型 |
| Vector Field | 无对应 | 特殊字段,存高维向量(如 1024 维) |
| Index | Index(索引) | 加速向量检索,不用索引会 O(n) 全量扫描 |
| Metric Type | 无对应 | 相似度计算方式:COSINE(余弦)、L2(欧氏距离)、IP(内积) |
2.3 索引为什么重要?
没有索引时,每次查询都要把库里全部向量和查询向量逐一算相似度------O(n) 时间复杂度。想象图书馆没有分类系统,每次找书都要把所有书架翻一遍。
Milvus 支持多种索引类型,核心思路是用空间换时间:
js
// 常见的索引类型
IndexType.IVF_FLAT // 聚簇索引:先把向量分组(聚类),查询时只搜最近的几个组,毫秒级
IndexType.AUTOINDEX // 自动选择最优索引策略,适合不确定场景时使用
类比 :字典的拼音索引让你不用从第一页翻到最后一页;图书馆的"文学馆/小说/科幻"分类让你不用每个书架都找一遍。向量索引做的是同一件事------迅速缩小搜索范围。
2.4 RAG 的完整工作流
RAG(检索增强生成)是向量数据库最经典的应用模式:
① 文档向量化(Embedding)
原始文本 → Embedding 模型 → 向量数组 [0.12, -0.34, 0.56, ...]
↓
② 存入向量数据库(Milvus)
Collection: { id, content, vector }
↓
③ 用户提问时
用户 query → 向量化 → 在 Milvus 中做相似度匹配
↓
④ 检索出最相关的 N 条文档
results: [{ content: '...', score: 0.95 }, ...]
↓
⑤ 拼接 Prompt 发给大模型
"基于以下资料回答问题:{检索结果}\n\n问题:{用户问题}"
↓
⑥ 大模型生成回答
核心价值:大模型本身不知道你的私有文档内容,但通过向量检索把相关文档"喂"给它,它就临时"学会"了这些知识------不需要微调模型。
三、实战:AI 日记本 Diary
我们来实现一个 AI 日记本,它有两层存储:
- MySQL 层(概念说明):存日记的 CRUD,处理结构化数据------标题、日期、分类
- Milvus 层(代码实战):存日记内容的向量,支持语义检索------"最近心情好的日记有哪些?"
3.1 架构设计
┌─────────────────────────────────────────────────┐
│ AI 日记本 │
├──────────────┬──────────────────────────────────┤
│ MySQL │ Milvus │
│ 结构化存储 │ 向量存储 │
├──────────────┼──────────────────────────────────┤
│ id │ id (主键) │
│ title │ content (日记正文) │
│ content │ date (日期) │
│ mood │ mood (心情) │
│ created_at │ tags (标签数组) │
│ │ vector (1024维向量) ← AI能力关键 │
└──────────────┴──────────────────────────────────┘
核心设计思想:结构化查询走 MySQL("查 3 月份的日记"),语义查询走 Milvus("最近做了哪些开心的事?")。两者互补,不是替代关系。
3.2 环境准备
我们使用 Zilliz Cloud(Milvus 的全托管云服务),免去自己搭建服务器的麻烦。
bash
# 安装依赖
npm install @zilliz/milvus2-sdk-node @langchain/openai dotenv
.env 配置文件:
env
# OpenAI API(用于生成 Embedding 和 LLM 回答)
OPENAI_API_KEY=your-api-key
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
MODEL_NAME=qwen-plus
EMBEDDING_MODEL_NAME=text-embedding-v3
# Zilliz Cloud 连接信息
MILVUS_ADDRESS=https://your-cluster.zillizcloud.com
MILVUS_TOKEN=your-zilliz-token
3.3 第一步:连接 Zilliz Cloud + 创建 Collection
先从最基础的连接和集合创建开始。这是整个流程的"地基"。
js
// index.mjs --- 完整流程:连接、建表、建索引、插入、查询
import 'dotenv/config'
import {
MilvusClient, // 客户端(C/S 架构,类似 MySQL 的 mysql2)
MetricType, // 相似度计算方式
IndexType, // 索引类型
DataType // 字段数据类型
} from '@zilliz/milvus2-sdk-node';
import { OpenAIEmbeddings } from '@langchain/openai'
// ============ 配置 ============
const ADDRESS = process.env.MILVUS_ADDRESS; // Zilliz Cloud 地址
const TOKEN = process.env.MILVUS_TOKEN; // API Key
const COLLECTION_NAME = 'ai_dairy';
const VECTOR_DIM = 1024; // text-embedding-3-small 输出 1024 维向量
// ============ Embedding 模型初始化 ============
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDING_MODEL_NAME,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
dimension: VECTOR_DIM
});
// ============ Milvus 客户端初始化 ============
const client = new MilvusClient({
address: ADDRESS,
token: TOKEN
});
// 封装向量化函数:输入文本,输出 1024 维向量数组
const getEmbedding = async (text) => {
return await embeddings.embedQuery(text);
}
代码思路解析:
MilvusClient采用 C/S 架构 ------客户端连接远程的 Milvus/Zilliz 服务端,类似mysql2连接 MySQL。OpenAIEmbeddings负责把文字转成向量。同一个模型输出的向量维度是固定的(text-embedding-v3是 1024 维)。getEmbedding是我们封装的工具函数,后续所有"文字→向量"的操作都通过它完成。
3.4 第二步:创建 Collection 和索引
在 Milvus 中创建 Collection 类似于在 MySQL 中建表,但需要额外定义向量字段:
js
async function main() {
// --- 连接 ---
console.log("正在连接 Zilliz Cloud...");
const checkHealthy = await client.checkHealth();
if (!checkHealthy.isHealthy) {
console.error('连接失败', checkHealthy.reasons);
return;
}
console.log('连接成功,集群状态正常');
// --- 创建 Collection(类比 MySQL 建表)---
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 // 向量维度 = 1024
},
{
name: 'content', // 日记正文
data_type: DataType.VarChar,
max_length: 5000
},
{
name: 'date', // 日期
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, // 最多 10 个标签
max_length: 50
}
]
});
console.log('✅ Collection 创建成功');
// --- 创建索引(加速向量检索)---
await client.createIndex({
collection_name: COLLECTION_NAME,
field_name: 'vector', // 给 vector 字段建索引
index_type: IndexType.IVF_FLAT, // 聚簇索引
metric_type: MetricType.COSINE, // 余弦相似度
});
console.log('✅ 索引创建成功');
// --- ★ 重要:加载 Collection 到内存 ---
await client.loadCollection({
collection_name: COLLECTION_NAME,
});
console.log('✅ Collection 已加载到内存,可以查询');
⚠️ 关键步骤 :创建 Collection 和索引后,必须调用
loadCollection()将它加载到内存,否则后续的搜索操作会报错。这类似于 MySQL 中表创建后需要ANALYZE TABLE让索引生效。
3.5 第三步:向量化并插入数据
准备好 5 篇模拟日记,逐篇向量化后批量插入:
js
// --- 模拟日记数据 ---
const diaryContents = [
{
id: 'diary_001',
content: '今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。',
date: '2026-01-10',
mood: 'happy',
tags: ['生活', '散步']
},
{
id: 'diary_002',
content: '今天工作很忙,完成了一个重要的项目里程碑。团队合作很愉快,感觉很有成就感。',
date: '2026-01-11',
mood: 'excited',
tags: ['工作', '成就']
},
{
id: 'diary_003',
content: '周末和朋友去爬山,天气很好,心情也很放松。享受大自然的感觉真好。',
date: '2026-01-12',
mood: 'relaxed',
tags: ['户外', '朋友']
},
{
id: 'diary_004',
content: '今天学习了 Milvus 向量数据库,感觉很有意思。向量搜索技术真的很强大。',
date: '2026-01-12',
mood: 'curious',
tags: ['学习', '技术']
},
{
id: 'diary_005',
content: '晚上做了一顿丰盛的晚餐,尝试了新菜谱。家人都说很好吃,很有成就感。',
date: '2026-01-13',
mood: 'proud',
tags: ['美食', '家庭']
}
];
// --- 批量向量化(每条日记独立 embedding)---
console.log('正在生成向量...');
const diaryData = await Promise.all(
diaryContents.map(async (diary) => ({
...diary,
vector: await getEmbedding(diary.content) // 文本 → 1024维向量
}))
);
// --- 批量插入 ---
const insertResult = await client.insert({
collection_name: COLLECTION_NAME,
data: diaryData // 直接用 JSON 数组,不需要写 SQL
});
console.log(`✅ ${insertResult.insert_cnt} 条记录插入成功`);
代码思路解析:
Promise.all+map并行向量化:每条日记独立调用 Embedding API,5 条日记一起处理而不是逐条等待,提升效率。- 向量化的是
content字段 :AI 按日记的语义内容去匹配,不是按日期或标签。 - 插入直接用 JSON :不需要写 SQL,Milvus SDK 的
insert()接受 JavaScript 对象数组。
3.6 第四步:语义搜索
这是核心环节------用自然语言查询,找到语义最相近的日记:
js
// --- 语义搜索 ---
const query = '我想看看关于户外活动的日记';
console.log(`🔍 查询: "${query}"`);
// Step 1: 将查询文本向量化
const queryVector = await getEmbedding(query);
// Step 2: 在 Milvus 中搜索最相似的向量
const searchResult = await client.search({
collection_name: COLLECTION_NAME,
vector: queryVector, // 查询向量
limit: 2, // 返回 Top 2
metric_type: MetricType.COSINE, // 余弦相似度
output_fields: ['id', 'content', 'date', 'mood', 'tags'] // 返回字段
});
// Step 3: 打印结果
console.log(`📋 找到 ${searchResult.results.length} 条结果:\n`);
searchResult.results.forEach((item, index) => {
console.log(`${index + 1}. 【相似度: ${item.score.toFixed(4)}】`);
console.log(` 日期: ${item.date} | 心情: ${item.mood}`);
console.log(` 标签: ${item.tags?.join(', ')}`);
console.log(` 内容: ${item.content}\n`);
});
}
运行结果示例:
🔍 查询: "我想看看关于户外活动的日记"
📋 找到 2 条结果:
1. 【相似度: 0.8932】
日期: 2026-01-12 | 心情: relaxed
标签: 户外, 朋友
内容: 周末和朋友去爬山,天气很好,心情也很放松。享受大自然的感觉真好。
2. 【相似度: 0.7851】
日期: 2026-01-10 | 心情: happy
标签: 生活, 散步
内容: 今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。
关键理解 :查询中没有出现"爬山"或"散步"这两个词,但 Milvus 通过语义匹配找到了这两条。因为"户外活动"在语义上和"爬山""散步"最接近。这就是向量数据库和传统关键词搜索的本质区别。
3.7 第五步:RAG 完整流程(检索 + LLM 生成)
有了向量检索,我们再接上大模型,让 AI 基于日记内容回答用户问题:
js
// rag.mjs --- RAG 完整流程
import 'dotenv/config'
import { MilvusClient, MetricType } from '@zilliz/milvus2-sdk-node';
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai'
// ============ 配置 ============
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
const COLLECTION_NAME = 'ai_dairy';
const VECTOR_DIM = 1024;
// ============ 初始化 Embedding 模型 ============
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDING_MODEL_NAME,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
dimension: VECTOR_DIM
});
// ============ 初始化大模型(用于生成回答)============
const model = new ChatOpenAI({
temperature: 0.1, // 低温度让回答更稳定、更贴近检索结果
model: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_BASE_URL }
});
// ============ Milvus 客户端 ============
const client = new MilvusClient({ address: ADDRESS, token: TOKEN });
const getEmbedding = async (text) => {
return await embeddings.embedQuery(text);
}
// ============ R 步骤:检索(Retrieve)============
async function retrieveDiaries(question, k = 2) {
try {
const queryVector = await getEmbedding(question);
const searchResult = await client.search({
collection_name: COLLECTION_NAME,
vector: queryVector,
limit: k,
metric_type: MetricType.COSINE,
output_fields: ['id', 'content', 'date', 'mood', 'tags']
});
return searchResult.results;
} catch (err) {
console.error('检索出错:', err.message);
return [];
}
}
// ============ G 步骤:增强 + 生成(Augment + Generate)============
async function answerDiaryQuestion(question, k = 2) {
console.log('='.repeat(50));
console.log(`❓ 问题: ${question}`);
console.log('='.repeat(50));
// Step 1: 检索相关日记
console.log('🔍 正在检索相关日记...');
const retrievedDiaries = await retrieveDiaries(question, k);
if (retrievedDiaries.length === 0) {
return '没有找到相关日记,试试换个问法吧~';
}
// 打印检索结果
retrievedDiaries.forEach((diary, i) => {
console.log(` 日记${i + 1} 【相似度: ${diary.score.toFixed(4)}】`);
console.log(` 内容: ${diary.content}\n`);
});
// Step 2: 拼接上下文(Augment)
const context = retrievedDiaries
.map((diary, i) => `
[日记${i + 1}]
日期: ${diary.date}
心情: ${diary.mood}
标签: ${diary.tags?.join(', ')}
内容: ${diary.content}
`.trim())
.join('\n\n---\n\n');
// Step 3: 构造 Prompt,让大模型基于检索内容回答(Generate)
const prompt = `你是一个温暖贴心的 AI 日记助手。基于用户的日记内容回答问题,用亲切自然的语言。
请根据以下日记内容回答问题:
${context}
用户问题: ${question}
回答要求:
1. 如果日记中有相关信息,请结合日记内容给出详细温暖的回答
2. 可以总结多篇日记的内容,找出共同点或趋势
3. 如果日记中没有相关信息,请温和告知用户
4. 用第一人称"你"来称呼日记的作者
5. 回答要有同理心,让用户感到被理解和关心
AI 助手的回答:`;
console.log('🤖 AI 正在生成回答...\n');
const response = await model.invoke(prompt);
console.log('💬 ' + response.content);
return response.content;
}
// ============ 主流程 ============
async function main() {
console.log('连接到 Milvus...');
await client.connectPromise;
console.log('✅ 已连接\n');
await answerDiaryQuestion('我最近做了什么让我感到快乐的事情?', 2);
}
main().catch(console.error);
完整流程解析:
用户提问:"我最近做了什么让我感到快乐的事情?"
│
▼
① Retrieve(检索)
将问题向量化 → Milvus 搜索 → 返回 Top 2 最相关日记
│
▼
② Augment(增强)
将检索到的日记内容拼接成结构化的上下文
│
▼
③ Generate(生成)
将上下文 + 用户问题 + 角色设定 拼成 Prompt
│
▼
④ 大模型生成回答(基于检索到的真实日记内容)
"根据你的日记,最近让你感到快乐的事情有:
去公园散步赏花(1月10日),以及和朋友一起爬山(1月12日)..."
这就是 RAG 的核心价值------大模型回答的不是"编造"的内容,而是基于你的真实日记内容。检索到的文档就是大模型的"参考资料"。
四、Zilliz Cloud:全托管 Milvus 云服务
4.1 Zilliz 是什么?
Zilliz Cloud 是基于开源 Milvus 构建的全托管向量数据库云服务。通俗地说:就像你用云数据库 RDS 而不是自己在 ECS 上装 MySQL 一样------Zilliz 帮你搞定部署、运维、扩容,你只需要拿到连接地址和 API Key 就能用。
4.2 为什么选择 Zilliz Cloud?
| 对比维度 | 自建 Milvus | Zilliz Cloud |
|---|---|---|
| 部署 | 需要 Docker/K8s、配置存储、网络 | 注册即用,5 分钟拿到连接地址 |
| 运维 | 自己负责升级、备份、监控 | 全托管,自动升级和备份 |
| 扩容 | 手动加节点、重新平衡数据 | 弹性伸缩,按需付费 |
| 免费额度 | 取决于你的服务器 | 提供免费 Starter 套餐,适合学习 |
4.3 从自建到云端的代码差异
代码层面几乎零差异 ------只需要把 address 换成 Zilliz Cloud 的连接地址,加上 token 认证即可:
js
// 自建 Milvus
const client = new MilvusClient({
address: 'localhost:19530'
});
// Zilliz Cloud(只需改地址 + 加 token)
const client = new MilvusClient({
address: 'https://your-cluster.zillizcloud.com', // 云端地址
token: 'your-api-key-here' // API Key 认证
});
其余所有代码------创建 Collection、建索引、插入数据、向量搜索------完全一样。
五、全文总结
本文从"为什么需要向量数据库"出发,经历了以下完整学习路径:
- 概念理解:向量数据库 ≠ 传统数据库,它解决的是语义相似度搜索问题,不是精确匹配问题。
- 核心原理:文档 → Embedding 模型 → 向量 → 存入 Milvus → 查询时同样向量化 → 余弦相似度匹配 → 返回最相关内容。
- 实战演练:通过 AI 日记本项目,完成了连接 Zilliz Cloud → 建 Collection → 建索引 → 插入向量化数据 → 语义搜索 → RAG 对话的完整流程。
- 架构思维:MySQL 管结构化 CRUD,Milvus 管语义检索,两者互补而非替代。
向量数据库是 AI 应用的基础设施,就像传统应用离不开 MySQL 一样,未来的 AI Agent 产品也离不开 Milvus 这样的向量存储。
六、核心知识点复盘
| 知识点 | 要点 |
|---|---|
| 向量是什么 | Embedding 模型将文本映射成的浮点数数组(如 1024 维),语义相近的文本向量距离近 |
| Milvus Collection | 类比 MySQL 的 Table,需要定义字段结构,关键是包含一个 FloatVector 字段 |
| 向量索引 | 没有索引 = O(n) 全量扫描;IVF_FLAT 聚簇索引将查询降到毫秒级 |
| 相似度度量 | COSINE(余弦相似度)最常用,值越接近 1 越相似;还有 L2(欧氏距离)、IP(内积) |
| loadCollection | 创建索引后必须加载到内存,否则搜索失败------这是最容易遗漏的步骤 |
| RAG 三步骤 | Retrieve(检索)→ Augment(增强 Prompt)→ Generate(大模型生成) |
| MySQL vs Milvus | 不是替代关系:MySQL 管精确的结构化查询,Milvus 管模糊的语义查询 |
本文基于 Milvus + Zilliz Cloud + LangChain 技术栈撰写,所有代码均可运行。