本文带你从零理解向量数据库的核心概念,通过一个完整的 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)。查询时逐一计算相似度。

// 内存向量检索(仅适合小数据量)
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 记忆和知识的"存储引擎"。

AI Agent

用户提问

Milvus 向量库

语义检索结果

LLM 生成回答

Web应用

用户请求

MySQL

精确查询结果

一句话理解:Web 应用把业务数据存 MySQL 做 CRUD,AI Agent 把知识、记忆存 Milvus 做语义检索。

2.2 核心概念速览

Milvus 概念类比 MySQL说明
CollectionTable(表)数据集合,定义字段结构
FieldColumn(列)字段,支持 VarChar、FloatVector、Array 等类型
Vector Field无对应特殊字段,存高维向量(如 1024 维)
IndexIndex(索引)加速向量检索,不用索引会 O(n) 全量扫描
Metric Type无对应相似度计算方式:COSINE(余弦)、L2(欧氏距离)、IP(内积)

2.3 索引为什么重要?

没有索引时,每次查询都要把库里全部向量和查询向量逐一算相似度——O(n) 时间复杂度。想象图书馆没有分类系统,每次找书都要把所有书架翻一遍。

Milvus 支持多种索引类型,核心思路是用空间换时间

// 常见的索引类型
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 的全托管云服务),免去自己搭建服务器的麻烦。

# 安装依赖
npm install @zilliz/milvus2-sdk-node @langchain/openai dotenv

.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

先从最基础的连接和集合创建开始。这是整个流程的"地基"。

// 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);
}

代码思路解析

  1. MilvusClient 采用 C/S 架构——客户端连接远程的 Milvus/Zilliz 服务端,类似 mysql2 连接 MySQL。
  2. OpenAIEmbeddings 负责把文字转成向量。同一个模型输出的向量维度是固定的(text-embedding-v3 是 1024 维)。
  3. getEmbedding 是我们封装的工具函数,后续所有"文字→向量"的操作都通过它完成。

3.4 第二步:创建 Collection 和索引

在 Milvus 中创建 Collection 类似于在 MySQL 中建表,但需要额外定义向量字段

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 篇模拟日记,逐篇向量化后批量插入:

  // --- 模拟日记数据 ---
  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} 条记录插入成功`);

代码思路解析

  1. Promise.all + map 并行向量化:每条日记独立调用 Embedding API,5 条日记一起处理而不是逐条等待,提升效率。
  2. 向量化的是 content 字段:AI 按日记的语义内容去匹配,不是按日期或标签。
  3. 插入直接用 JSON:不需要写 SQL,Milvus SDK 的 insert() 接受 JavaScript 对象数组。

3.6 第四步:语义搜索

这是核心环节——用自然语言查询,找到语义最相近的日记:

  // --- 语义搜索 ---
  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 基于日记内容回答用户问题:

// 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?

对比维度自建 MilvusZilliz Cloud
部署需要 Docker/K8s、配置存储、网络注册即用,5 分钟拿到连接地址
运维自己负责升级、备份、监控全托管,自动升级和备份
扩容手动加节点、重新平衡数据弹性伸缩,按需付费
免费额度取决于你的服务器提供免费 Starter 套餐,适合学习

4.3 从自建到云端的代码差异

代码层面几乎零差异——只需要把 address 换成 Zilliz Cloud 的连接地址,加上 token 认证即可:

// 自建 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、建索引、插入数据、向量搜索——完全一样


五、全文总结

本文从"为什么需要向量数据库"出发,经历了以下完整学习路径:

  1. 概念理解:向量数据库 ≠ 传统数据库,它解决的是语义相似度搜索问题,不是精确匹配问题。
  2. 核心原理:文档 → Embedding 模型 → 向量 → 存入 Milvus → 查询时同样向量化 → 余弦相似度匹配 → 返回最相关内容。
  3. 实战演练:通过 AI 日记本项目,完成了连接 Zilliz Cloud → 建 Collection → 建索引 → 插入向量化数据 → 语义搜索 → RAG 对话的完整流程。
  4. 架构思维: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 技术栈撰写,所有代码均可运行。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐