向量数据库从零到实战:用 Milvus + Zilliz 构建 AI 日记语义检索系统

文章目录

    • 一、向量数据库是什么?
      • [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);
}

代码思路解析

  1. MilvusClient 采用 C/S 架构 ------客户端连接远程的 Milvus/Zilliz 服务端,类似 mysql2 连接 MySQL。
  2. OpenAIEmbeddings 负责把文字转成向量。同一个模型输出的向量维度是固定的(text-embedding-v3 是 1024 维)。
  3. 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} 条记录插入成功`);

代码思路解析

  1. Promise.all + map 并行向量化:每条日记独立调用 Embedding API,5 条日记一起处理而不是逐条等待,提升效率。
  2. 向量化的是 content 字段 :AI 按日记的语义内容去匹配,不是按日期或标签。
  3. 插入直接用 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、建索引、插入数据、向量搜索------完全一样


五、全文总结

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

  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 技术栈撰写,所有代码均可运行。

相关推荐
m沐沐1 小时前
【深度学习】循环神经网络RNN——结构、原理与长期依赖问题解析
人工智能·pytorch·python·rnn·深度学习·算法·机器学习
硬核子牙1 小时前
不要小瞧y=wx+b
人工智能·chatgpt·程序员
workflower1 小时前
情境感知系统
人工智能·机器学习·设计模式·自然语言处理·机器人
Gauss松鼠会1 小时前
Prometheus 实现 openGauss 的指标监控(二)Prometheus 中添加 openGauss指标
网络·数据库·oracle·prometheus·opengauss·经验总结
kisloy2 小时前
【爬虫入门第2讲】浏览器开发者工具完全指南
人工智能·爬虫·tensorflow
打工的小李2 小时前
AI干部画像:从海量材料中自动提炼标签,告别“凭感觉”
人工智能
Cloud云卷云舒2 小时前
PolarDB(阿里云)VS HaishanDB(海山数据库,移动云)的AI能力全面对比
数据库·阿里云·ai-native·polardb·haishandb
Chengbei112 小时前
云安全漏洞挖掘SKILL、一站式云漏洞挖掘工具,支持S3爆破、IMDS探测、K8s检测与AK/SK权限利用
前端·人工智能·网络安全·云原生·容器·kubernetes·系统安全
snowfoootball2 小时前
基于 LangBot + NapCatQQ的QQ AI Bot实战记录
人工智能