nestjs+langchain:Memory

0 阅读11分钟

书接上文

一、Memory

Memory概述

Memory,是LangChain中用于多轮对话中保存和管理上下文信息(比如文本、图像、音频等)的组件。它让应用能够记住用户之前说了什么,从而实现对话的 上下文感知能力 ,为构建真正智能和上下文感知的链式对话系统提供了基础。

设计理念

image.png

  1. 输入问题:({"question": ...})
  2. 读取历史消息:从Memory中READ历史消息({"past_messages": [...]})
  3. 构建提示(Prompt):读取到的历史消息和当前问题会被合并,构建一个新的Prompt
  4. 模型处理:构建好的提示会被传递给语言模型进行处理。语言模型根据提示生成一个输出。
  5. 解析输出:输出解析器通过正则表达式 regex("Answer: (.*)")来解析,返回一个回答({"answer":...})给用户
  6. 得到回复并写入Memory:新生成的回答会与当前的问题一起写入Memory,更新对话历史。Memory会存储最新的对话内容,为后续的对话提供上下文支持。

InMemoryChatMessageHistory

  • 属于 BaseChatMessageHistory 的实现类
  • 存储位置:进程内存(RAM)
  • 特点:速度极快、不落地、重启服务就丢失
  • 用途:开发调试、单会话临时对话、演示项目

agents.service.ts

// 1. 导入内存历史记录和消息类型
import { HumanMessage } from 'node_modules/@langchain/core/dist/messages/human.cjs';
import { AIMessage } from 'node_modules/@langchain/core/dist/messages/ai.cjs';
import { InMemoryChatMessageHistory } from 'node_modules/@langchain/core/dist/chat_history.cjs';

async chatMsgHistory() {
    // 2. 创建内存历史记录实例
    const history = new InMemoryChatMessageHistory();

    // 3. 添加消息
    await history.addMessage(new AIMessage('我是一个无所不能的小智'));
    await history.addMessage(
      new HumanMessage('你好,我叫小明,请介绍一下你自己'),
    );
    await history.addMessage(new HumanMessage('我是谁呢?'));

    // 4. 获取历史消息列表
    // 注意:这是一个异步操作
    const messages = await history.getMessages();

    console.log('History Messages:', messages);

    // 5. 调用 LLM (直接传入消息数组)
    // LLM 会根据整个对话历史生成回复
    const response = await this.llm.invoke(messages);

    console.log('LLM Response:', response.content);
  }
输出结果:
History Messages: [
  AIMessage {
    "content": "我是一个无所不能的小智",
    "additional_kwargs": {},
    "response_metadata": {},
    "tool_calls": [],
    "invalid_tool_calls": []
  },
  HumanMessage {
    "content": "你好,我叫小明,请介绍一下你自己",
    "additional_kwargs": {},
    "response_metadata": {}
  },
  HumanMessage {
    "content": "我是谁呢?",
    "additional_kwargs": {},
    "response_metadata": {}
  }
]
LLM Response: 你好呀小明!我是小智,一个随时准备帮你解答问题、陪你聊天的人工智能助手。无论是学习上的难题、生活中的小困惑,还是单纯想找个人聊聊天,我都在这里哦!

至于你嘛,你可是刚刚告诉我你叫小明呢~不过如果你愿意多聊聊,我可以记住更多关于你的事情,比如你的兴趣爱好、最近在忙什么,这样下次聊天就能更贴心啦! 😊

BufferMemory和ConversationChain

ConversationChain

ConversationChain 是 LangChain 中最基础、最常用的链(Chain)之一,专门用于构建有状态的对话机器人。它的核心作用是:将用户输入 + 历史对话记忆 + 系统提示词 组合在一起,发送给 LLM,并自动更新记忆。

核心工作原理

ConversationChain 内部主要包含三个组件:

  1. LLM (大语言模型) :负责生成回复。
  2. Memory (记忆模块) :负责存储和检索之前的对话历史(如 BufferMemory)。
  3. Prompt (提示词模板) :默认使用 DEFAULT_PROMPT,它将 {history} 和 {input} 填入模板中。

执行流程:

  1. 用户输入 input
  2. Chain 从 Memory 中加载历史对话 (history)。
  3. Chain 将 history 和 input 填入 Prompt 模板。
  4. 将完整的 Prompt 发送给 LLM。
  5. LLM 生成回复。
  6. Chain 将本次的 input 和 LLM 的 output 保存回 Memory,以便下一轮对话使用。

BufferMemory

你用 ConversationChain 时,必须传 BufferMemory,它自动帮你做 4 件事:

  1. 自动存对话:用户说的话、AI 回复的话,自动存起来
  2. 自动拼文本:把对话历史拼成 {history} 字符串,直接塞进你的提示词模板
  3. 自动联动链:和 LLMChain 深度绑定,一行代码都不用手动写
  4. 自动读记忆:每次调用 chain.invoke(),自动把历史传给 AI

使用示例

agents.service.ts

// 1. 导入内存历史记录和消息类型
import { HumanMessage } from 'node_modules/@langchain/core/dist/messages/human.cjs';
import { AIMessage } from 'node_modules/@langchain/core/dist/messages/ai.cjs';
import { ConversationChain } from '@langchain/classic/chains';
import { BufferMemory } from '@langchain/classic/memory';

async bufferM() {
    // 2. 创建记忆
    const memory = new BufferMemory({
      memoryKey: 'history',
      returnMessages: true, // 推荐设置为 true,以便更好地处理聊天模型
    });

    // 3. 初始化 ConversationChain
    const chain = new ConversationChain({
      llm: this.llm,
      memory: memory,
      // ConversationChain 有默认的 Prompt,你也可以自定义
      // prompt: customPrompt
    });

    // 4. 调用链 (注意:invoke 是异步的)
    let res = await chain.invoke({ input: '我的名字叫Tom' });
    console.log('res 1:', res);

    res = await chain.invoke({ input: '我的名字叫什么' });
    console.log('res 2:', res);
  }

输出结果:

res 1: {
  response: '你好Tom!很高兴认识你呀~ 😊 有什么我可以帮你解答或者聊些什么呢?比如兴趣爱好、科技趣闻,或者最近发生的有趣事情?'
}
res 2: { response: '你的名字叫Tom呀~ 😊 刚才你告诉我的,需要我记住什么其他信息吗?' }

BufferWindowMemory

该记忆类会 保存一段时间内对话交互 的列表, 仅使用最近 K 个交互 。这样就使缓存区不会变得太大。

特点

  1. 适合长对话场景。
  2. 与 Chains/Models 无缝集成
  3. 支持两种返回格式(通过 return_messages 参数控制输出格式)
  • return_messages=True 返回消息对象列表(List[BaseMessage])
  • return_messages=False (默认) 返回拼接的 纯文本字符串
1.导入
import { BufferWindowMemory } from '@langchain/classic/memory';

async runWindowMemoryExample() {
    // 2. 定义模版
    // 注意:变量名必须与 invoke 时传入的 key 一致,这里使用 {history} 和 {question}
    const template = `以下是人类与AI之间的友好对话描述。AI表现得很健谈,并提供了大量来自其上下文的
                        具体细节。如果AI不知道问题的答案,它会表示不知道。
                        当前对话:
                        {history}
                        Human: {question}
                        AI:`;

    // 3. 定义提示词模版
    const promptTemplate = PromptTemplate.fromTemplate(template);

    // 5. 实例化 BufferWindowMemory 对象,设定窗口阈值 k=1
    // k=1 意味着只保留最近的一轮对话(一问一答)
    const memory = new BufferWindowMemory({
      memoryKey: 'history',
      k: 1,
      returnMessages: true, // 对于 ChatModel,建议设为 true
    });

    // 6. 定义 LLMChain
    const conversationWithSummary = new ConversationChain({
      llm: this.llm,
      prompt: promptTemplate,
      memory: memory,
      //   verbose: true, // 开启调试日志
    });

    // 7. 执行链(第一次提问)
    console.log('--- Round 1 ---');
    const respon1 = await conversationWithSummary.invoke({
      question: '你好,我是孙小空',
    });
    console.log('Response 1:', respon1);

    // 8. 执行链(第二次提问)
    console.log('--- Round 2 ---');
    const respon2 = await conversationWithSummary.invoke({
      question: '我还有两个师弟,一个是猪小戒,一个是沙小僧',
    });
    console.log('Response 2:', respon2);

    // 9. 执行链(第三次提问)
    console.log('--- Round 3 ---');
    const respon3 = await conversationWithSummary.invoke({
      question: '我今年高考,竟然考上了1本',
    });
    console.log('Response 3:', respon3);

    // 10. 执行链(第四次提问)
    // 由于 k=1,此时内存中只保留了 Round 3 的对话。
    // Round 1 和 Round 2 的信息已经被丢弃。
    // 因此,AI 很可能不知道我叫什么,或者只能根据 Round 3 的上下文瞎编。
    console.log('--- Round 4 ---');
    const respon4 = await conversationWithSummary.invoke({
      question: '我叫什么?',
    });
    console.log('Response 4:', respon4);

    // 验证内存状态
    const history = await memory.loadMemoryVariables({});
    console.log('Current Memory History:', history);
  }

输出结果:

--- Round 1 ---
Response 1: {
  response: '你好呀,孙小空!这名字听起来好有趣,是不是有种“齐天大圣”的调皮感呢?很高兴认识你,有什么想聊的或者想问的吗?我随时准备着和你展开一场有趣的对话哦!'
}
--- Round 2 ---
Response 2: {
  response: '这组合太有意思啦!感觉就是“迷你版”的取经小团队呀。猪小戒,一听这名字就知道肯定和贪吃脱不了关系,说不定还特别憨态可掬呢;沙小僧呢,是不是特别老实忠厚,总是默默地跟着你们呀。他们是不是也和你一样有着有趣的故事呀?'
}
--- Round 3 ---
Response 3: {
  response: '这太厉害啦!一本可是很多学子梦寐以求的呢!考上一本意味着你之前的努力都得到了超棒的回报呀。你考上的哪个学校呀,是不是自己一直心心念念的那所?快和我分享分享这份喜悦~'
}
--- Round 4 ---
Response 4: { response: '我们才刚开始聊天呢,我还不知道你的名字呀。你愿意告诉我你的名字吗?这样我们就能更好地认识彼此啦!' }
Current Memory History: {
  history: [
    HumanMessage {
      "content": "我叫什么?",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "content": "我们才刚开始聊天呢,我还不知道你的名字呀。你愿意告诉我你的名字吗?这样我们就能更好地认识彼此啦!",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    }
  ]
}

ConversationTokenBufferMemory

是 LangChain 中一种基于 Token 数量控制 的对话记忆机制。如果字符数量超出指定数目,它会切掉这个对话的早期部分,以保留与最近的交流相对应的字符数量。

特点:

  • Token 精准控制
  • 原始对话保留
1.导入
import { ConversationTokenBufferMemory } from '@langchain/classic/memory';

async runTokenBufferMemoryExample() {
    // 2. 定义 ConversationTokenBufferMemory 对象
    const memory = new ConversationTokenBufferMemory({
      llm: this.llm,
      maxTokenLimit: 20, // 设置 token 上限 (注意:驼峰命名法)
      returnMessages: true, // 推荐设置为 true,以便返回消息对象
    });

    // 添加对话
    // saveContext 接受两个参数: input (用户) 和 output (AI)
    await memory.saveContext(
      { input: '你好吗?' },
      { output: '我很好,谢谢!' },
    );

    await memory.saveContext(
      { input: '今天天气如何?' },
      { output: '晴天,25度' },
    );

    // 查看当前记忆
    // loadMemoryVariables 返回一个对象,键由 memoryKey 决定(默认为 'history')
    const history = await memory.loadMemoryVariables({});

    console.log('Current Memory History:', history);
  }

输出结果:

Current Memory History: {
  history: [
    AIMessage {
      "content": "晴天,25度",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    }
  ]
}

ConversationSummaryMemory

前⾯的⽅式发现,如果全部保存下来太过浪费,截断时⽆论是按照 对话条数 还是 token 都是⽆法保证既节省内存⼜保证对话质量的。 ConversationSummaryMemory是 LangChain 中一种 智能压缩对话历史 的记忆机制,它通过大语言模型(LLM)自动生成对话内容的 精简摘要 ,而不是存储原始对话文本。这种记忆方式特别适合长对话和需要保留核心信息的场景。

特点:

  • 摘要生成
  • 动态更新
  • 上下文优化
1.导入
import { ConversationSummaryMemory } from '@langchain/classic/memory';

  async runSummaryMemoryExample() {
    // 2. 初始化 ConversationSummaryMemory 实例
    // 注意:这里不需要传入外部的 chatHistory,memory 会内部管理其状态
    const memory = new ConversationSummaryMemory({
      llm: this.llm,
      memoryKey: 'history', // 默认键名
      returnMessages: true, // 推荐返回消息对象,便于后续处理
    });

    // 3. 模拟第一轮对话并保存
    // saveContext 会触发 LLM 生成初始摘要或更新现有摘要
    await memory.saveContext(
      { input: '你好,你是谁?' },
      { output: '我是AI助手小智' },
    );

    // 4. 查看当前摘要状态
    // loadMemoryVariables 返回包含摘要的对象
    const historyAfterFirstRound = await memory.loadMemoryVariables({});
    console.log('History after 1st round:', historyAfterFirstRound);

    // 5. 模拟第二轮对话并保存
    await memory.saveContext(
      { input: '我的名字叫小明' },
      { output: '很高兴认识你,小明' },
    );

    // 6. 查看更新后的摘要状态
    // 此时摘要应该包含了前两轮对话的核心信息
    const historyAfterSecondRound = await memory.loadMemoryVariables({});
    console.log('History after 2nd round:', historyAfterSecondRound);
  }

输出结果:

History after 1st round: {
  history: [
    SystemMessage {
      "content": "人类询问AI是谁,AI回答自己是AI助手小智。",
      "additional_kwargs": {},
      "response_metadata": {}
    }
  ]
}
History after 2nd round: {
  history: [
    SystemMessage {
      "content": "人类询问AI是谁,AI回答自己是AI助手小智。人类告诉AI自己的名字叫小明,AI表示很高兴认识小明。",
      "additional_kwargs": {},
      "response_metadata": {}
    }
  ]
}

ConversationSummaryBufferMemory

在保留最近 对话原始记录 的同时,对较早的对话内容进行 智能摘要 。

特点:

  • 保留最近N条原始对话:确保最新交互的完整上下文
  • 摘要较早历史:对超出缓冲区的旧对话进行压缩,避免信息过载
  • 平衡细节与效率:既不会丢失关键细节,又能处理长对话
//导入
import { ConversationSummaryBufferMemory } from '@langchain/classic/memory';

async runSummaryBufferMemoryExample() {
    // 1. 定义 ConversationSummaryBufferMemory 对象
    const memory = new ConversationSummaryBufferMemory({
      llm: this.llm,
      maxTokenLimit: 40, // 设置 Token 上限,超过此值会触发摘要压缩
      returnMessages: true, // 推荐设置为 true,返回消息对象数组
      memoryKey: 'history', // 默认键名
    });

    // 2. 保存消息上下文
    // 注意:每次 saveContext 都会检查是否超过 maxTokenLimit,如果超过则触发 LLM 进行摘要
    await memory.saveContext(
      { input: '你好,我的名字叫小明' },
      { output: '很高兴认识你,小明' },
    );

    await memory.saveContext(
      { input: '李白是哪个朝代的诗人' },
      { output: '李白是唐朝诗人' },
    );

    await memory.saveContext(
      { input: '唐宋八大家里有苏轼吗?' },
      { output: '有' },
    );

    // 3. 读取内容
    // 返回的对象中包含摘要后的历史记录
    const historyVariables = await memory.loadMemoryVariables({});
    console.log(
      'Memory Variables (Summary + Recent Messages):',
      historyVariables,
    );

    // 4. 读取底层原始/混合消息历史
    // 在 JS 中,通过 memory.chatHistory.getMessages() 获取
    // 注意:ConversationSummaryBufferMemory 会在内部维护一个混合了“摘要消息”和“最近原始消息”的列表
    const messages = await memory.chatHistory.getMessages();
    console.log('Chat History Messages:', messages);
  }

输出结果:

Memory Variables (Summary + Recent Messages): {
  history: [
    SystemMessage {
      "content": "The human introduces themselves as Xiaoming and the AI responds in a friendly manner, saying it's glad to meet Xiaoming. Xiaoming asks which dynasty the poet Li Bai belonged to, and the AI replies that Li Bai was a poet of the Tang Dynasty.",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "content": "很高兴认识你,小明",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    },
    HumanMessage {
      "content": "李白是哪个朝代的诗人",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "content": "李白是唐朝诗人",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    },
    HumanMessage {
      "content": "唐宋八大家里有苏轼吗?",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "content": "有",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_calls": [],
      "invalid_tool_calls": []
    }
  ]
}
Chat History Messages: [
  AIMessage {
    "content": "很高兴认识你,小明",
    "additional_kwargs": {},
    "response_metadata": {},
    "tool_calls": [],
    "invalid_tool_calls": []
  },
  HumanMessage {
    "content": "李白是哪个朝代的诗人",
    "additional_kwargs": {},
    "response_metadata": {}
  },
  AIMessage {
    "content": "李白是唐朝诗人",
    "additional_kwargs": {},
    "response_metadata": {},
    "tool_calls": [],
    "invalid_tool_calls": []
  },
  HumanMessage {
    "content": "唐宋八大家里有苏轼吗?",
    "additional_kwargs": {},
    "response_metadata": {}
  },
  AIMessage {
    "content": "有",
    "additional_kwargs": {},
    "response_metadata": {},
    "tool_calls": [],
    "invalid_tool_calls": []
  }
]