当AI Agent有了“记忆”,它就不再是简单的对话工具,而成为了一个懂你的伙伴。

最近我在开发一个AI日记本应用,发现传统的MySQL很难实现“语义搜索”——比如你想找“心情愉快的日子”,但日记里并没有这个词。这时,向量数据库就派上了用场。本文我将分享如何使用Milvus(以及全托管服务Zilliz)结合OpenAI Embeddings,快速构建一个具备记忆与检索能力的AI日记助手。


一、为什么需要向量数据库?

我们熟悉的MySQL、PostgreSQL擅长存储结构化数据,通过idLIKE进行精确或模糊匹配。但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_ADDRESS=https://your-instance.zillizcloud.com
MILVUS_TOKEN=your-api-token
OPENAI_API_KEY=sk-xxx
OPENAI_BASE_URL=https://api.openai.com/v1   # 或代理地址
EMBEDDINGS_MODEL_NAME=text-embedding-3-small
MODEL_NAME=gpt-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_FLATHNSW等。

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支持多种数据类型,我们定义一个更完整的schema:

const 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生成Embedding

import { 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与调用LLM

import { 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) =>
        `[日记${i+1}] 日期:${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不再是一个冷冰冰的问答机,而是真正“读过”你的日记,成为了懂你的朋友。


八、踩坑与优化建议

  1. 索引创建时机:最好在插入数据前创建索引,否则插入后需手动构建。
  2. loadCollection:每次操作前确保集合已加载,否则搜索报错。
  3. 维度一致:向量维度必须与Embedding模型输出维度一致,否则插入失败。
  4. 批量插入:插入大量数据时,分批进行(每批几百条),避免超时。
  5. 连接管理:使用client.connectPromise确保连接完成再操作(SDK 2.x版本)。
  6. 相似度度量COSINE适合文本,IP(内积)和L2也有各自适用场景,根据数据特点选择。

九、总结与展望

通过本文,你已经掌握了:

  • 向量数据库的核心概念与Milvus基本操作
  • 如何结合Embedding模型将文本向量化
  • 如何实现语义搜索和RAG问答

我们构建的AI日记本只是一个起点,同样的架构可以应用到客服知识库、推荐系统、AI Agent记忆系统等众多场景。当AI拥有了“长期记忆”,它的能力边界将被大大拓展。

最后送大家一句心得: “数据决定智能的上限,向量数据库就是承载这些数据的骨骼。”  愿你的AI应用都能拥有聪明而持久的记忆。

 

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐