从零构建 AI Agent 的 Memory 记忆系统(下篇)—— 基于 Milvus 的长期记忆与 RAG 检索

前言

在上篇中,我们通过"截断"和"总结"管理了短期记忆。但总结会导致细节丢失,截断更是直接遗忘。要让 Agent 真正"懂你",我们需要在外部挂载一个长期记忆库(向量数据库)

本期我们使用本地部署的 Milvus,通过将历史对话向量化(Embedding)存储,并在对话时进行语义检索(RAG),实现跨会话的长期记忆。

三、 搭建长期记忆库:Milvus 持久化存储

首先,我们需要把历史对话数据插入到 Milvus 中。我们设计一个 conversations 集合,包含:主键 id、向量 vector、文本内容 content、轮次 round 和时间戳 timestamp

3.1 初始化与数据插入

javascript

php 复制代码
import 'dotenv/config'
import { MilvusClient, DataType, MetricType, IndexType } from '@zilliz/milvus2-sdk-node'
import { OpenAIEmbeddings } from '@langchain/openai'

const COLLECTION_NAME = 'conversations'; // 集合
const VECTOR_DIM = 1024; // 维度

const embeddings = new OpenAIEmbeddings({
  apiKey: process.env.OPENAI_API_KEY,
  model: process.env.EMBEDDINGS_MODEL_NAME,
  configuration: { baseURL: process.env.OPENAI_API_BASE_URL },
  dimension: VECTOR_DIM
});

// 获取文本的向量表示
async function getEmbedding(text) {
  const result = await embeddings.embedQuery(text);
  return result;
}

const client = new MilvusClient({ address: 'localhost:19530' });

async function main() {
  try {
    console.log('连接到Milvus...');
    await client.connectPromise;
    
    // 1. 创建集合
    await client.createCollection({
      collection_name: COLLECTION_NAME,
      fields: [
        { name: "id", data_type: DataType.VarChar, max_length: 50, is_primary_key: true }, // uuid 唯一的 id
        { name: 'vector', data_type: DataType.FloatVector, dim: VECTOR_DIM },
        { name: 'content', data_type: DataType.VarChar, max_length: 5000 },
        { name: 'round', data_type: DataType.Int64 },
        { name: 'timestamp', data_type: DataType.VarChar, max_length: 100 }
      ]
    });
    
    // 2. 创建索引(IVF_FLAT 和 COSINE 相似度)
    await client.createIndex({
      collection_name: COLLECTION_NAME,
      field_name: 'vector', // 最频繁
      index_type: IndexType.IVF_FLAT,
      metric_type: MetricType.COSINE
    });
    
    await client.loadCollection({ collection_name: COLLECTION_NAME });

    // 3. 准备数据并插入
    const conversations = [
      { id: 'conv_001', content: '用户: 我叫赵六,是一名数据科学家\n助手: 很高兴认识你,赵六!...', round: 1, timestamp: new Date().toISOString() },
      // ... 更多历史对话
    ];

    // 向量化并组装
    const conversationData = await Promise.all(
      conversations.map(async (conv) => ({
        ...conv,
        vector: await getEmbedding(conv.content)
      }))
    );

    const insertResult = await client.insert({
      collection_name: COLLECTION_NAME,
      data: conversationData
    });
    console.log('数据插入成功');
  } catch (err) {
    console.error('操作失败:', err);
  }
}

四、 记忆检索(RAG):让 Agent 回忆过去

数据存入 Milvus 后,当用户提出新问题时,我们提取问题向量,去 Milvus 中寻找相似的历史记忆,并将其拼接到 Prompt 中。

4.1 语义检索

javascript

php 复制代码
async function retrieveRelevantConversations(query, k = 2) {
  try {
    const queryVector = await getEmbedding(query); // embedding
    const searchResult = await client.search({
      collection_name: COLLECTION_NAME,
      vector: queryVector,
      limit: k,
      metric_type: MetricType.COSINE,
      output_fields: ['id', 'content', 'round', 'timestamp']
    });
    return searchResult.results;
  } catch (err) {
    console.error('检索对话时出错', err.message);
    return [];
  }
}

4.2 闭环:检索 + 对话 + 重新持久化

在每一轮对话中,我们不仅需要检索长期记忆,还需要将新的问答对再次向量化插入 Milvus,让记忆不断生长。

javascript

javascript 复制代码
async function retrievalMemoryDemo() {
  // ... 连接 Milvus,初始化 history (InMemoryChatMessageHistory)
  const conversations = [
    { input: "我之前提到的机器学习项目进展如何?" },
    { input: "我周末经常去做什么?" },
    { input: "我的职业是什么?" }
  ];

  for (let i = 0; i < conversations.length; i++) {
    const { input } = conversations[i];
    const userMessage = new HumanMessage(input);
    
    console.log(`\n第${i + 1}轮对话`)
    console.log(`用户: ${input}`);
    
    // 1. 检索相关历史对话
    console.log(`\n[检索相关历史对话]`);
    const retrievedConversations = await retrieveRelevantConversations(input, 2);
    
    let relevantHistory = '';
    if (retrievedConversations.length > 0) {
      relevantHistory = retrievedConversations
        .map((conv, idx) => `[历史对话 ${idx + 1}] 轮次: ${conv.round}\n${conv.content}`)
        .join('\n\n----\n\n');
    } else {
      console.log('未找到相关历史对话');
    }
    
    // 2. 构造包含长期记忆的 Prompt
    const contextMessages = relevantHistory 
      ? [new HumanMessage(`相关历史对话:\n${relevantHistory}\n\n用户问题:${input}`)] 
      : [userMessage];
      
    const response = await model.invoke(contextMessages);
    console.log(response.content);
    
    // 3. 更新短期记忆
    await history.addMessage(userMessage);
    await history.addMessage(response);
    
    // 4. 会话持久化到 Milvus(长期记忆闭环)
    const conversationText = `用户: ${input} \n 助手: ${response.content}`;
    const convId = `conv_${Date.now()}_${i + 1}`; // 时间 + i 唯一ID (uuid)
    const convVector = await getEmbedding(conversationText);
    
    try {
      await client.insert({
        collection_name: COLLECTION_NAME,
        data: [{
          id: convId,
          content: conversationText,
          vector: convVector,
          round: i + 1,
          timestamp: new Date().toISOString()
        }]
      });
    } catch(err) {
      console.error(err);
    }
  }
}

五、 总结与架构展望

结合上下两篇,一个成熟的 Agent Memory 系统架构呼之欲出:

  1. 存储逻辑:内存(InMemory,极速但易失)、文件(FileSystem,单机持久化)、数据库(Milvus 向量库,海量检索与长久记忆)。

  2. 管理逻辑

    • 截断(Trim) :基于消息数量或 Token 精确计算,保留近期上下文。
    • 总结(Summary) :基于 LLM 压缩历史,保留核心信息且节省 Token。
    • 检索(RAG) :基于向量检索,实现无限上下文的"回忆"。

在实际开发中,我们可以这样组合:每 20 条对话触发一次总结,将总结存入 Milvus;当用户提问时,从 Milvus 取出相关对话历史,结合最近未被截断的 Memory,一起组装发给大模型。

这样,你的 Agent 不仅能记住眼前的对话,还能在几万字之后,依然记得你最初那句"我对海鲜过敏"。这就是 Harness 的 Memory 模块赋予 Agent 的灵魂所在。

相关推荐
_codeOH1 小时前
MCP Server 开发实战:从 0 到 1 构建自己的工具服务
人工智能·ai编程
VIP_CQCRE2 小时前
Visual Studio 接入 Ace Data Cloud:让 LMLocal 直接调用统一 AI 模型能力
ai编程·visual studio·openai兼容·ace data cloud·lmlocal
AINative软件工程3 小时前
LLM 应用的测试替身工程实践:用 Fake/Stub/Mock 让 AI 代码真正跑起来 CI
单元测试·llm·ai编程
adaierya3 小时前
用 AI 解决音频转换编程问题
开发语言·人工智能·python·分类·ai编程
GoGeekBaird15 小时前
从「跑完即销毁」到「用完再销毁」:云沙箱的一次进化
后端·github·ai编程
kyriewen16 小时前
AI 改祖传模块后,代码评审总绕不开两个问题
前端·程序员·ai编程
颜淡慕潇18 小时前
Loop已死,Graph永生?深度看懂:2026年AI工程的真正范式跃迁
ai编程