书接上文。
一、Memory
Memory概述
Memory,是LangChain中用于多轮对话中保存和管理上下文信息(比如文本、图像、音频等)的组件。它让应用能够记住用户之前说了什么,从而实现对话的 上下文感知能力 ,为构建真正智能和上下文感知的链式对话系统提供了基础。
设计理念
- 输入问题:({"question": ...})
- 读取历史消息:从Memory中READ历史消息({"past_messages": [...]})
- 构建提示(Prompt):读取到的历史消息和当前问题会被合并,构建一个新的Prompt
- 模型处理:构建好的提示会被传递给语言模型进行处理。语言模型根据提示生成一个输出。
- 解析输出:输出解析器通过正则表达式 regex("Answer: (.*)")来解析,返回一个回答({"answer":...})给用户
- 得到回复并写入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 内部主要包含三个组件:
- LLM (大语言模型) :负责生成回复。
- Memory (记忆模块) :负责存储和检索之前的对话历史(如
BufferMemory)。 - Prompt (提示词模板) :默认使用
DEFAULT_PROMPT,它将{history}和{input}填入模板中。
执行流程:
- 用户输入
input。 - Chain 从
Memory中加载历史对话 (history)。 - Chain 将
history和input填入 Prompt 模板。 - 将完整的 Prompt 发送给 LLM。
- LLM 生成回复。
- Chain 将本次的
input和 LLM 的output保存回Memory,以便下一轮对话使用。
BufferMemory
你用 ConversationChain 时,必须传 BufferMemory,它自动帮你做 4 件事:
- 自动存对话:用户说的话、AI 回复的话,自动存起来
- 自动拼文本:把对话历史拼成
{history}字符串,直接塞进你的提示词模板 - 自动联动链:和
LLMChain深度绑定,一行代码都不用手动写 - 自动读记忆:每次调用
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 个交互 。这样就使缓存区不会变得太大。
特点:
- 适合长对话场景。
- 与 Chains/Models 无缝集成
- 支持两种返回格式(通过 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": []
}
]