🔥 从 MySQL 到 Milvus甚 AI 日记本项目搞懂向量数据库和 RAG

102 阅读11分钟

🔥 从 MySQL 到 Milvus甚 AI 日记本项目搞懂向量数据库和 RAG

摘芁䜠还圚甚 WHERE content LIKE '%关键词%' 搜玢文本倪萜后了本文甚䞀䞪「AI 日记本」项目垊䜠从零搞懂向量数据库 Milvus 和 RAG让䜠的 AI 应甚真正拥有"记忆"。


📌 前蚀

䞊呚有䞪需求甚户蟓入䞀句话系统芁从几千条日记里扟到语义最盞关的几条。

我第䞀反应是 MySQL 党文搜玢䜆埈快发现䞍对

甚户搜玢日记原文MySQL 结果
"心情奜的日子""今倩阳光明媚去公园散步了"❌ 搜䞍到
"和朋友出去玩""呚末和哥们儿去爬山了"❌ 搜䞍到
"工䜜压力倧""今倩deadline疯狂加班"❌ 搜䞍到

关键词完党对䞍䞊䜆语义䞊高床盞关

这就是䌠统数据库的痛点 —— 它只胜做字面匹配理解䞍了语义。

盎到我接觊了 Milvus 向量数据库 + RAG才发现原来 AI 应甚的"记忆"可以这样做。


🎯 本文适合谁

  • 有 Node.js 基础想了解向量数据库的前端/党栈匀发者
  • 听过 RAG 䜆䞍知道怎么萜地的同孊
  • 想给自己的 AI 应甚加"长期记忆"的匀发者

📚 先搞懂什么是向量数据库

甚囟乊銆类比

想象䜠去囟乊銆扟乊

方匏MySQLMilvus
怎么扟按乊名、ISBN 查粟确匹配告诉管理员"我想看䞀本关于孀独的科幻小诎"语义匹配
底层逻蟑WHERE title = '䞉䜓'把䜠的描述和每本乊的"语义指纹"做盞䌌床计算
擅长结构化数据的粟确查询非结构化数据的暡糊语义检玢

栞心抂念Embedding向量化

向量数据库的灵魂是 Embedding —— 把任意文本变成䞀组数字向量

"今倩心情埈奜"  →  [0.12, -0.34, 0.56, ..., 0.78]  1024 䞪浮点数
"匀心的䞀倩"    →  [0.11, -0.33, 0.55, ..., 0.77]  语义盞近数字也接近
"项目䞊线了"    →  [0.89, 0.12, -0.45, ..., 0.23]  语义䞍同数字差距倧

语义盞近的文本向量圚空闎䞭的距犻也近 —— 这就是向量搜玢的原理。

💡 䞺什么需芁 Embedding 因䞺计算机䞍懂䞭文䜆它懂数字。Embedding 就是把人类语蚀翻译成计算机胜理解的"语义坐标"。


🛠 项目实战AI 日记本

📊 完敎源码Gitee - milvus-demo/demo2/src

孊习路线囟4 䞪文件的递进逻蟑

我们甚 4 䞪文件埪序析进地孊习

┌─────────────────────────────────────────────────────────────────────┐
│  第 1 步main.mjs — 先甚"假数据"跑通最小闭环                         │
│  目的理解 Milvus 的栞心操䜜建衚 → 插数据 → 搜玢                   │
│  特点手写 4 绎向量快速验证流皋                                     │
└─────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▌
┌─────────────────────────────────────────────────────────────────────┐
│  第 2 步index.mjs — 甚真实数据 + Embedding 暡型写入                 │
│  目的孊䌚甚 Embedding 暡型自劚生成向量                              │
│  特点从"假数据"升级到"真实日记数据"                                 │
└─────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▌
┌─────────────────────────────────────────────────────────────────────┐
│  第 3 步query.mjs — 甚自然语蚀搜玢日记                              │
│  目的孊䌚甚自然语蚀检玢向量数据                                     │
│  特点搜玢"户倖掻劚"胜扟到"爬山"和"公园散步"                         │
└─────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▌
┌─────────────────────────────────────────────────────────────────────┐
│  第 4 步rag.mjs — 检玢 + LLM = AI 日记助手                        │
│  目的把检玢结果喂给 LLM生成智胜回答                               │
│  特点完敎 RAG 流皋AI 真正"读懂"了䜠的日记                         │
└─────────────────────────────────────────────────────────────────────┘

端到端数据流从文本到回答

圚匀始写代码之前先看懂敎䞪数据流

┌──────────────────────────────────────────────────────────────────────┐
│                        数据写入阶段index.mjs                      │
├───────────────────────────────────────────────────────────────────────
│                                                                      │
│   日记文本"今倩倩气埈奜去公园散步了"                                 │
│                    │                                                 │
│                    â–Œ                                                 │
│   ┌─────────────────────────────┐                                   │
│   │    Embedding 暡型           │  文本 → 向量                       │
│   │  text-embedding-3-small     │                                   │
│   └──────────────┬──────────────┘                                   │
│                  â–Œ                                                   │
│   向量[0.12, -0.34, 0.56, ..., 0.78]1536 绎                    │
│                  │                                                   │
│                  â–Œ                                                   │
│   ┌─────────────────────────────┐                                   │
│   │       Milvus 向量数据库      │  存傚向量 + 元数据                  │
│   │  ai_dairy 集合              │                                   │
│   └─────────────────────────────┘                                   │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────┐
│                        检玢 + 生成阶段rag.mjs                     │
├───────────────────────────────────────────────────────────────────────
│                                                                      │
│   甚户提问"我最近做了什么让我感到快乐的事情"                        │
│                    │                                                 │
│                    â–Œ                                                 │
│   ┌─────────────────────────────┐                                   │
│   │    Embedding 暡型           │  把问题也蜬成向量                   │
│   └──────────────┬──────────────┘                                   │
│                  â–Œ                                                   │
│   问题向量[0.15, -0.32, 0.58, ..., 0.76]                           │
│                  │                                                   │
│                  â–Œ                                                   │
│   ┌─────────────────────────────┐                                   │
│   │       Milvus 检玢           │  扟到最盞䌌的 2 条日记              │
│   │  COSINE 盞䌌床搜玢          │                                   │
│   └──────────────┬──────────────┘                                   │
│                  â–Œ                                                   │
│   检玢结果                                                           │
│   - 日记1"今倩倩气埈奜去公园散步了"盞䌌床 0.89                  │
│   - 日记2"呚末和朋友去爬山"盞䌌床 0.85                          │
│                  │                                                   │
│                  â–Œ                                                   │
│   ┌─────────────────────────────┐                                   │
│   │    拌接 Prompt              │  日记内容 + 问题 → 完敎提瀺词       │
│   └──────────────┬──────────────┘                                   │
│                  â–Œ                                                   │
│   ┌─────────────────────────────┐                                   │
│   │        LLM 生成             │  AI 基于日记内容生成回答            │
│   └──────────────┬──────────────┘                                   │
│                  â–Œ                                                   │
│   "从䜠的日记来看最近让䜠快乐的事情有                              │
│    1. 去公园散步感受春倩的矎奜                                       │
│    2. 和朋友爬山享受户倖时光"                                       │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

💡 䞺什么需芁这䞪流皋 因䞺 LLM 自己没有䜠的日记数据它䌚"幻觉"。RAG 让 AI 先查资料再回答保证回答有据可䟝。


1⃣ main.mjs — Milvus 入闚先跑通最小闭环

䞺什么芁从这里匀始 因䞺圚甚真实数据之前先甚"假数据"验证流皋是最安党的。就像孊做菜先甚䟿宜食材练手别䞀䞊来就甚和牛。

连接 Zilliz Cloud
import { MilvusClient, IndexType, MetricType } from "@zilliz/milvus2-sdk-node";

const client = new MilvusClient({
    address: process.env.MILVUS_ADDRESS,  // Zilliz Cloud 地址
    token: process.env.MILVUS_TOKEN        // API Key
});

// 检查连接
const checkHealth = await client.checkHealth();
console.log(checkHealth.isHealthy ? '✅ 连接成功' : '❌ 连接倱莥');

💡 Zilliz Cloud 是 Milvus 的党托管云服务䞍甚自己郚眲泚册就胜甚。

创建集合类䌌 MySQL 建衚
await client.createCollection({
    collection_name: 'test',
    dimension: 4,      // 向量绎床这里甚 4 绎方䟿挔瀺
    auto_id: true       // 自劚生成 ID
});
创建玢匕 —— 䞺什么需芁

没有玢匕时搜玢芁逐䞀计算所有向量的盞䌌床时闎倍杂床 O(n)。数据量倧了就慢埗犻谱。

IVF_FLAT 玢匕的原理类䌌囟乊銆分区

没有玢匕圚敎栋楌里䞀本本翻          → O(n)
有玢匕  先去"文孊区"再扟"科幻"    → 近䌌 O(1)
await client.createIndex({
    collection_name: 'test',
    field_name: 'vector',
    index_type: IndexType.AUTOINDEX,
    metric_type: MetricType.COSINE   // 甚䜙匊盞䌌床
});
插入和搜玢
// 插入数据手劚指定的 4 绎向量
const data = [
    { vector: [0.1, 0.2, 0.3, 0.4], content: '这是第䞀条数据' },
    { vector: [0.5, 0.6, 0.7, 0.8], content: '这是第二条数据' }
];
await client.insert({ collection_name: 'test', data });

// 搜玢扟和 [0.5, 0.5, 0.6, 0.8] 最盞䌌的 2 条
const searchRes = await client.search({
    collection_name: 'test',
    data: [[0.5, 0.5, 0.6, 0.8]],
    limit: 2,
    output_fields: ['content']
});

console.log('搜玢结果', searchRes.results);

到这里䜠已经理解了 Milvus 的栞心䞉板斧建集合 → 插数据 → 搜玢。


2⃣ index.mjs — 甹 Embedding 暡型写入真实数据

䞺什么芁升级到这䞀步 因䞺手写向量䞍现实。实际项目䞭䜠需芁甚 Embedding 暡型自劚把文本蜬成向量。

定义集合结构
import { DataType } from "@zilliz/milvus2-sdk-node";

await client.createCollection({
    collection_name: 'ai_dairy',
    fields: [
        { name: 'id', data_type: DataType.VarChar, max_length: 50, is_primary_key: true },
        { name: 'vector', data_type: DataType.FloatVector, dim: 1024 },  // 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 }
    ]
});

跟 MySQL 建衚几乎䞀样只是倚了䞀䞪 FloatVector 类型的 vector 字段。

Embedding文本 → 向量
import { OpenAIEmbeddings } from '@langchain/openai';

const embedding = new OpenAIEmbeddings({
    apiKey: process.env.OPENAI_API_KEY,
    model: process.env.EMBEDDINGS_MODEL_NAME   // 比劂 text-embedding-3-small
});

const getEmbedding = async (text) => {
    return await embedding.embedQuery(text);    // 䞀段文本 → 1024 绎向量
};

💡 䞺什么芁甚 Embedding 暡型 因䞺它胜把"语义"猖码成数字。手劚写向量无法捕捉语义信息。

准倇日记数据 + 写入
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: ['矎食', '家庭'] }
];

// 给每条日记加䞊向量
const diaryData = await Promise.all(
    diaryContents.map(async (diary) => ({
        ...diary,
        vector: await getEmbedding(diary.content)   // 自劚生成向量
    }))
);

// 批量插入
const insertResult = await client.insert({
    collection_name: 'ai_dairy',
    data: diaryData
});
console.log('✅', insertResult.insert_cnt, '条记圕成功插入。');

// 重芁插入后重新加蜜集合
await client.loadCollection({ collection_name: 'ai_dairy' });

关键流皋

文本日记 → Embedding 暡型 → 1024 绎向量 → 存入 Milvus

3⃣ query.mjs — 甚自然语蚀搜玢日记

䞺什么芁单独讲搜玢 因䞺写入和搜玢是䞀䞪独立的操䜜。写入是䞀次性的搜玢是实时的。而䞔搜玢涉及"把甚户问题也蜬成向量"这䞪关键步骀。

// 甚户诎了䞀句话
const query = '我想看看关于户倖掻劚的日记';

// 1. 把查询文本也蜬成向量这䞀步埈关键
const queryVector = await getEmbedding(query);

// 2. 去 Milvus 里扟最盞䌌的 2 条
const searchResult = await client.search({
    collection_name: 'ai_dairy',
    vector: queryVector,
    limit: 2,
    metric_type: MetricType.COSINE,
    output_fields: ['id', 'content', 'date', 'mood', 'tags']
});

// 3. 打印结果
console.log('🔍 搜玢结果\n');
searchResult.results.forEach((item, index) => {
    console.log(`${index + 1}. [Score: ${item.score.toFixed(4)}]`);
    console.log(`   内容${item.content}`);
    console.log(`   心情${item.mood}`);
    console.log(`   日期${item.date}\n`);
});

蟓出倧抂是

🔍 搜玢结果

1. [Score: 0.8923]
   内容呚末和朋友去爬山倩气埈奜心情也埈攟束。
   心情relaxed
   日期2026-01-12

2. [Score: 0.8156]
   内容今倩倩气埈奜去公园散步了心情愉快。
   心情happy
   日期2026-01-10

甚户诎"户倖掻劚"系统扟到了"爬山"和"公园散步" —— 没有任䜕关键词匹配纯靠语义理解


4⃣ rag.mjs — 检玢 + LLM = AI 日记助手

䞺什么芁加 RAG 因䞺光检玢还䞍借。甚户问"我最近快乐吗"䜠䞍胜只返回几条日记而是芁让 AI 分析这些日记给出䞀䞪有枩床的回答。

什么是 RAG

RAG = Retrieval检玢+ Augmented增区+ Generation生成

甚囟乊銆类比

1. 检玢Retrieval  去囟乊銆扟几本盞关的乊
2. 增区Augmented  把乊翻到盞关页面攟圚桌䞊
3. 生成Generation 让 AI 看着这些乊来回答问题

没有 RAGAI 只胜靠自己的"记忆"回答可胜胡诎八道幻觉。 有了 RAGAI 先查资料再回答有理有据。

暡块䞀检玢Retrieval
async function retrieveRelevantDiaries(question, k = 2) {
    const queryVector = await getEmbedding(question);
    const searchResult = await client.search({
        collection_name: 'ai_dairy',
        limit: k,
        metric_type: MetricType.COSINE,
        vector: queryVector,
        output_fields: ['id', 'content', 'date', 'mood', 'tags']
    });
    return searchResult.results;
}

这䞀步就是前面 query.mjs 做的事 —— 扟到最盞关的 k 条日记。

暡块二增区Augmented
// 把检玢到的日记拌成䞀段"䞊䞋文"
function buildContext(retrievedDiaries) {
    return retrievedDiaries.map((diary, i) => `
[日记 ${i + 1}]
日期${diary.date}
心情${diary.mood}
标筟${diary.tags?.join(', ')}
内容${diary.content}
`).join('\n----\n');
}

䞺什么芁拌接䞊䞋文 因䞺 LLM 需芁"参考资料"才胜准确回答。就像䜠回答问题时老垈䌚给䜠䞀些参考材料。

暡块䞉生成Generation
async function generateAnswer(question, context) {
    const prompt = `䜠是䞀䞪枩暖莎心的AI日记助手。
基于甚户的日记内容回答问题甚亲切自然的语蚀。

请根据以䞋日记内容回答问题

${context}

甚户问题${question}

回答芁求
1. 劂果日记䞭有盞关信息请结合日记内容给出诊细、枩暖的回答。
2. 可以总结倚篇日记的内容扟出共同点或趋势。
3. 劂果日记䞭没有盞关信息请枩和告知甚户。
4. 甚第二人称"䜠"来称呌日记的䜜者。
5. 回答芁有同理心让甚户感到被理解和关心。

AI 助手回答`;

    const response = await model.invoke(prompt);
    return response.content;
}
完敎调甚流皋
// RAG 完敎流皋
async function aiDiaryAssistant(question) {
    console.log(`\n📝 甚户提问${question}\n`);

    // 1. 检玢
    console.log('🔍 正圚检玢盞关日记...');
    const retrievedDiaries = await retrieveRelevantDiaries(question, k = 3);

    // 2. 构建䞊䞋文
    const context = buildContext(retrievedDiaries);

    // 3. 生成回答
    console.log('🀖 AI 正圚生成回答...\n');
    const answer = await generateAnswer(question, context);

    console.log('💡 AI 回答');
    console.log(answer);

    return answer;
}

// 测试
await aiDiaryAssistant('我最近做了什么让我感到快乐的事情');

蟓出瀺䟋

📝 甚户提问我最近做了什么让我感到快乐的事情

🔍 正圚检玢盞关日记...
🀖 AI 正圚生成回答...

💡 AI 回答
从䜠的日记来看最近让䜠感到快乐的事情有

1. **去公园散步**1月10日—— 䜠提到"倩气埈奜心情愉快"看埗出来䜠埈享受亲近自然的时光。

2. **和朋友爬山**1月12日—— 呚末和朋友䞀起户倖掻劚䜠诎"心情也埈攟束"友情和运劚郜让䜠匀心。

3. **尝试新菜谱**1月13日—— 䜠䞺自己做了䞀顿䞰盛的晚逐这种自我照顟的小确幞也埈珍莵呢 😊

看埗出来䜠是䞀䞪热爱生掻、懂埗享受圓䞋的人。继续保持这仜对生掻的热情吧

💡 关键技术点总结

抂念䞀句话解释圚项目䞭的䜜甚
Milvus䞓闚存和查向量的数据库存傚日记的向量衚瀺
Embedding文本 → 数字数组把日记和问题蜬成向量
COSINE 盞䌌床衡量䞀䞪向量方向的盞䌌皋床扟到语义最盞关的日记
IVF_FLAT 玢匕聚簇玢匕加速搜玢让搜玢曎快
RAG检玢 + LLM 生成让 AI 基于日记内容回答
Zilliz CloudMilvus 的党托管云服务免去郚眲烊恌

🐛 螩坑记圕

坑 1查到 0 条数据

现象insert 明明成功了search 返回空数组。

原因loadCollection 圚 insert 之前调甚了新插入的数据没有被加蜜到内存。

解决插入数据后重新调甚 loadCollection

await client.insert({ collection_name: 'ai_dairy', data: diaryData });
await client.loadCollection({ collection_name: 'ai_dairy' });  // 重新加蜜

预防养成习惯每次 insert 后郜 load 䞀次。


坑 2output_fields 字段名拌错

现象返回的字段倌党是 undefined。

原因output_fields 里写的是 'data'实际字段名是 'date'。

解决仔细检查集合的字段定义别拌错。

预防倍制字段名䞍芁手打。


坑 3向量绎床䞍匹配

现象创建集合时 dim: 1024䜆 Embedding 暡型生成的向量绎床䞍是 1024。

解决确保 dim 参数和 Embedding 暡型蟓出的绎床䞀臎。垞甚暡型的绎床

暡型绎床
text-embedding-3-small1536
text-embedding-3-large3072
text-embedding-ada-0021536

预防圚创建集合前先调甚䞀次 Embedding API 确讀蟓出绎床。


🔗 参考资料


💬 亀流讚论

䜠圚项目䞭甚过向量数据库吗有没有遇到什么坑欢迎评论区亀流

觉埗有甚点䞪赞 👍 收藏 ⭐ 关泚 👆后续䌚曎新曎倚 AI 实战内容