从零搭建AI日记本:Milvus向量数据库实战指南
当AI Agent有了“记忆”它就不再是简单的对话工具而成为了一个懂你的伙伴。最近我在开发一个AI日记本应用发现传统的MySQL很难实现“语义搜索”——比如你想找“心情愉快的日子”但日记里并没有这个词。这时向量数据库就派上了用场。本文我将分享如何使用Milvus以及全托管服务Zilliz结合OpenAI Embeddings快速构建一个具备记忆与检索能力的AI日记助手。一、为什么需要向量数据库我们熟悉的MySQL、PostgreSQL擅长存储结构化数据通过id或LIKE进行精确或模糊匹配。但AI应用中的数据往往是非结构化的——文本、图像、音频。当我们希望根据语义来检索时比如“查找关于户外活动的日记”传统数据库就力不从心了。向量数据库应运而生。它将数据转换成高维向量通过计算向量间的距离余弦相似度、欧氏距离等来实现语义相似性检索。像Milvus这样的系统专为海量高维向量设计已经成为AI Agent记忆系统的标配。如果说MySQL是结构化数据的仓库那Milvus就是向量数据的图书馆——你不再需要记住书名关键词只需描述你想看的内容它就能帮你找到“懂你”的那本书。二、Milvus与Zilliz你的向量数据库选择Milvus开源向量数据库支持十亿级向量检索提供多种索引IVF_FLAT、HNSW等底层用C实现性能强悍。Zilliz基于Milvus的全托管云服务免运维提供API Key接入适合快速开发和线上部署。本文使用Zilliz免费版省去自建集群的繁琐。注册后你会获得一个ADDRESS如https://xxxx.zillizcloud.com和TOKEN填入.env即可。三、环境准备npm install zilliz/milvus2-sdk-node langchain/openai dotenv创建.env文件MILVUS_ADDRESShttps://your-instance.zillizcloud.com MILVUS_TOKENyour-api-token OPENAI_API_KEYsk-xxx OPENAI_BASE_URLhttps://api.openai.com/v1 # 或代理地址 EMBEDDINGS_MODEL_NAMEtext-embedding-3-small MODEL_NAMEgpt-4o-mini四、初次接触连接与基础操作我们先写一个简单脚本连接Zilliz创建集合插入两条测试向量并执行一次搜索。4.1 连接与健康检查import { MilvusClient, IndexType, MetricType } from zilliz/milvus2-sdk-node; import dotenv/config; const client new MilvusClient({ address: process.env.MILVUS_ADDRESS, token: process.env.MILVUS_TOKEN, }); const check await client.checkHealth(); if (!check.isHealthy) throw new Error(连接失败);4.2 创建集合Collection在Milvus中集合相当于MySQL的表。我们定义一个4维向量字段仅为演示自动生成ID。const COLLECTION_NAME test; const DIMENSION 4; await client.createCollection({ collection_name: COLLECTION_NAME, dimension: DIMENSION, auto_id: true, });4.3 创建索引——让查询快如闪电没有索引的向量检索是全量遍历O(n)数据量一大就慢得无法接受。索引好比书的目录能大幅缩小搜索范围。await client.createIndex({ collection_name: COLLECTION_NAME, field_name: vector, index_type: IndexType.AUTOINDEX, // 自动选择最优索引 metric_type: MetricType.COSINE, // 相似度度量 });AUTOINDEX是Milvus 2.3提供的智能索引会基于数据自动调整新手友好。生产环境可根据数据量选用IVF_FLAT、HNSW等。4.4 插入数据Milvus的数据插入非常灵活直接传入JSON数组即可无需写SQL。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: COLLECTION_NAME, data });4.5 执行向量搜索const searchRes await client.search({ collection_name: COLLECTION_NAME, data: [[0.5, 0.5, 0.6, 0.8]], // 查询向量 limit: 2, output_fields: [content], }); console.log(searchRes.results);你会得到按相似度排序的结果并附带score得分越接近1表示越相似。五、进阶AI日记本的核心——文本向量化上面的例子用的是手工构造的向量现实中我们需要将文本转换为向量。这通常由Embedding模型完成如OpenAI的text-embedding-3-small。5.1 定义集合结构带丰富元数据我们的日记不仅有内容还有日期、心情、标签等结构化字段。Milvus支持多种数据类型我们定义一个更完整的schemaconst COLLECTION_NAME ai_dairy; const VECTOR_DIM 1024; // 与embedding模型维度一致 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: 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, max_length: 50 }, ] });这里用到了Array类型来存储标签非常方便。5.2 使用LangChain生成Embeddingimport { OpenAIEmbeddings } from langchain/openai; const embeddings new OpenAIEmbeddings({ apiKey: process.env.OPENAI_API_KEY, model: process.env.EMBEDDINGS_MODEL_NAME, dimensions: VECTOR_DIM, }); async function getEmbedding(text) { return await embeddings.embedQuery(text); }5.3 插入带有向量的日记假设我们有5条日记内容我们遍历每条调用getEmbedding生成向量然后批量插入。const diaryData await Promise.all( diaryContents.map(async (diary) ({ ...diary, vector: await getEmbedding(diary.content), })) ); await client.insert({ collection_name: COLLECTION_NAME, data: diaryData });注意插入前需要先loadCollection将集合加载到内存否则搜索会报错。await client.loadCollection({ collection_name: COLLECTION_NAME });六、语义搜索用自然语言找日记现在用户输入“我想看看关于户外活动的日记”我们先将问题向量化然后去Milvus中搜索。const query 我想看看关于户外活动的日记; const queryVector await getEmbedding(query); const result await client.search({ collection_name: COLLECTION_NAME, vector: queryVector, limit: 2, metric_type: MetricType.COSINE, output_fields: [id, content, date, mood, tags] });输出的结果会包含相似度得分并且按得分降序排列。你可能会发现即使日记中没有“户外”二字只要内容提到“爬山”、“散步”等也会被检索出来——这就是语义检索的魅力。七、RAG让大模型结合记忆来回答RAG检索增强生成是目前AI应用的主流范式。我们检索出相关的日记然后作为上下文喂给大模型让它生成一个既基于事实又有人情味的回答。7.1 检索相关日记封装一个函数async function retrieveRelevantDiaries(question, k 2) { const vec await getEmbedding(question); const res await client.search({ collection_name: COLLECTION_NAME, vector: vec, limit: k, output_fields: [id, content, date, mood, tags] }); return res.results; }7.2 构建Prompt与调用LLMimport { ChatOpenAI } from langchain/openai; const model new ChatOpenAI({ temperature: 0.1, model: process.env.MODEL_NAME, }); async function answerDiaryQuestion(question) { const diaries await retrieveRelevantDiaries(question, 2); if (!diaries.length) return 没有找到相关日记; const context diaries.map((d, i) [日记${i1}] 日期:${d.date} 心情:${d.mood} 内容:${d.content} ).join(\n\n); const prompt 你是一个温暖的AI日记助手。根据以下日记内容回答用户问题 ${context} 用户问题${question} 要求 - 如果日记中有相关信息请结合给出详细回答。 - 用“你”称呼日记作者语气亲切。 - 如果信息不足请温和地说明。 回答 ; const response await model.invoke(prompt); console.log(response.content); }7.3 效果演示当用户问“我最近做了什么让我感到快乐的事情”时系统会检索出得分最高的日记比如“完成项目里程碑”和“散步赏花”然后大模型会总结出这些快乐时刻并给予温暖的回应。这时候AI不再是一个冷冰冰的问答机而是真正“读过”你的日记成为了懂你的朋友。八、踩坑与优化建议索引创建时机最好在插入数据前创建索引否则插入后需手动构建。loadCollection每次操作前确保集合已加载否则搜索报错。维度一致向量维度必须与Embedding模型输出维度一致否则插入失败。批量插入插入大量数据时分批进行每批几百条避免超时。连接管理使用client.connectPromise确保连接完成再操作SDK 2.x版本。相似度度量COSINE适合文本IP内积和L2也有各自适用场景根据数据特点选择。九、总结与展望通过本文你已经掌握了向量数据库的核心概念与Milvus基本操作如何结合Embedding模型将文本向量化如何实现语义搜索和RAG问答我们构建的AI日记本只是一个起点同样的架构可以应用到客服知识库、推荐系统、AI Agent记忆系统等众多场景。当AI拥有了“长期记忆”它的能力边界将被大大拓展。最后送大家一句心得“数据决定智能的上限向量数据库就是承载这些数据的骨骼。”愿你的AI应用都能拥有聪明而持久的记忆。