【AI】LangChain Memory解析:让AI记住对话上下文
本文会从"没有 Memory 的痛点"开始,循序渐进地介绍 LangChain 提供的 10 种 Memory 类型,从最简单的全量存储,到智能的摘要压缩,再到专业的实体追踪和向量检索。
LangChain Memory解析:让AI记住对话上下文
前言
上三篇文章跟着宋红康老师的教程学了 LangChain 的入门、Model I/O 和 Chains 模块,这次继续学习第四章 Memory(记忆)模块。
你肯定体验过这样的场景:和 ChatGPT 聊天时,它能记住你之前说过的话,甚至记住你上次提到的名字、偏好。但如果你直接调用 OpenAI 的 API,每次请求都是独立的,AI 根本不记得你是谁。这就是为什么我们需要 Memory 模块——让 AI 像人一样,能记住对话的上下文。
本文会从"没有 Memory 的痛点"开始,循序渐进地介绍 LangChain 提供的 10 种 Memory 类型,从最简单的全量存储,到智能的摘要压缩,再到专业的实体追踪和向量检索。内容比较长,但每个类型都配有代码示例,力求讲透每种 Memory 的适用场景和技术细节。
目录
- 一、为什么需要 Memory?
- 二、基础 Memory 模块
- 三、限制对话长度的 Memory
- 四、智能压缩的 Memory
- 五、专业场景的 Memory
- 六、Memory 选型指南
- 七、总结
- 系列推荐
一、为什么需要 Memory?
先看个没有 Memory 的例子:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
prompt_template = ChatPromptTemplate.from_messages([
("system", "你是一个与人类对话的机器人。"),
("human", "问题:{question}")
])
# 第一轮对话
messages = prompt_template.invoke({"question": "北京有什么好吃的?"})
response = llm.invoke(messages)
print(response.content)
# 手动添加历史消息到 prompt
from langchain_core.messages import AIMessage
prompt_template.messages.append(AIMessage(content=response.content))
# 第二轮对话
messages = prompt_template.invoke({"question": "上海呢?"})
response = llm.invoke(messages)
print(response.content)
痛点显而易见:
- 手动管理历史消息:每次对话后都要
append(AIMessage(...)),非常繁琐 - 无限增长的上下文:对话越长,prompt 越长,Token 消耗越大,成本越高
- 缺乏灵活性:想要滑动窗口、摘要压缩、实体追踪?自己写代码吧
这就是 Memory 模块要解决的问题:自动管理对话历史,提供灵活的存储策略,平衡上下文完整性和成本效率。
二、基础 Memory 模块
2.1 ChatMessageHistory:最底层的消息存储
ChatMessageHistory 是最基础的消息存储容器,就像一个 list,用来存放用户消息和 AI 消息:
from langchain.memory import ChatMessageHistory
history = ChatMessageHistory()
history.add_user_message("你好")
history.add_ai_message("你好!有什么我可以帮助你的吗?")
print(history.messages)
# 输出:
# [HumanMessage(content='你好'),
# AIMessage(content='你好!有什么我可以帮助你的吗?')]
核心方法:
add_user_message(text): 添加用户消息add_ai_message(text): 添加 AI 消息messages: 返回所有消息列表(HumanMessage和AIMessage对象)
适用场景: 当你需要完全自定义消息管理逻辑时使用,但通常我们会用更高级的封装。
2.2 ConversationBufferMemory:完整对话记忆
ConversationBufferMemory 是最常用的 Memory,它会完整保存所有对话历史:
示例 1:字符串格式输出
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory()
memory.save_context(inputs={"input": "你好"}, outputs={"output": "你好!有什么我可以帮助你的吗?"})
memory.save_context(inputs={"input": "我想了解 LangChain"}, outputs={"output": "LangChain 是一个..."})
print(memory.load_memory_variables({}))
# 输出:{'history': 'Human: 你好\nAI: 你好!有什么我可以帮助你的吗?\nHuman: 我想了解 LangChain\nAI: LangChain 是一个...'}
关键 API:
save_context(inputs, outputs): 保存一轮对话load_memory_variables({}): 加载记忆内容,默认返回字符串格式
示例 2:消息列表格式输出
memory = ConversationBufferMemory(return_messages=True)
memory.save_context({"input": "你好"}, {"output": "你好!"})
print(memory.load_memory_variables({}))
# 输出:{'history': [HumanMessage(content='你好'), AIMessage(content='你好!')]}
参数说明:
return_messages=True: 返回消息对象列表,适合与ChatPromptTemplate配合
示例 3:与 LLMChain 集成(记住名字)
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate
prompt = PromptTemplate(
input_variables=['history', 'question'],
template="以下是历史对话:\n{history}\n问题:{question}"
)
memory = ConversationBufferMemory()
chain = LLMChain(llm=llm, prompt=prompt, memory=memory)
# 第一轮:告诉名字
response1 = chain.invoke({"question": "我叫小明"})
print(response1) # 输出:你好,小明!
# 第二轮:询问名字
response2 = chain.invoke({"question": "我叫什么名字?"})
print(response2) # 输出:你叫小明
核心机制: Memory 会在每次调用 chain.invoke() 时:
- 调用前:通过
load_memory_variables()加载历史对话到{history}变量 - 调用后:通过
save_context()自动保存本轮对话
示例 4:自定义 memory_key
memory = ConversationBufferMemory(memory_key="chat_history")
prompt = PromptTemplate(
input_variables=['chat_history', 'question'],
template="历史:\n{chat_history}\n问题:{question}"
)
# 注意:prompt 中的变量名要和 memory_key 对应
示例 5:与 ChatPromptTemplate 集成
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个与人类对话的机器人。"),
MessagesPlaceholder(variable_name='history'), # 占位符,用于插入历史消息
("human", "问题:{question}")
])
memory = ConversationBufferMemory(memory_key='history', return_messages=True)
chain = LLMChain(llm=llm, prompt=prompt, memory=memory)
response = chain.invoke({"question": "你好"})
重要提示: 使用 ChatPromptTemplate 时:
- 必须设置
return_messages=True - 使用
MessagesPlaceholder(variable_name='history')插入历史消息 memory_key要和MessagesPlaceholder的variable_name对应
PromptTemplate vs ChatPromptTemplate 在 Memory 中的差异:
| 对比维度 | PromptTemplate | ChatPromptTemplate |
|---|---|---|
| 历史消息格式 | 字符串格式 "Human: ...\nAI: ..." |
消息对象列表 [HumanMessage, AIMessage] |
| Memory 参数 | return_messages=False(默认) |
return_messages=True(必须) |
| 占位符 | 直接用 {history} |
需要 MessagesPlaceholder(variable_name='history') |
| 消息保存时机 | 调用后保存 | 调用后保存 |
2.3 ConversationChain:简化版对话链
ConversationChain 是一个简化的封装,自动创建 ConversationBufferMemory 和 LLMChain(来自 02-基础Memory模块的使用.ipynb):
示例 1:使用自定义 prompt
from langchain.chains import ConversationChain
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个与人类对话的机器人。"),
MessagesPlaceholder(variable_name='history'),
("human", "{input}")
])
chain = ConversationChain(llm=llm, prompt=prompt)
response = chain.invoke({"input": "你好"})
示例 2:使用默认 prompt
chain = ConversationChain(llm=llm)
# 默认 prompt 包含 {input} 和 {history} 两个变量
response = chain.invoke({"input": "你好"})
适用场景: 对话轮次较少、依赖完整上下文的场景(如简单的聊天机器人)。但注意成本问题:对话越长,每次调用的 Token 消耗越大。
三、限制对话长度的 Memory
3.1 ConversationBufferWindowMemory:滑动窗口记忆
问题引入: ConversationBufferMemory 会无限增长历史消息,导致:
- 内存占用大
- Token 消耗高(每次请求都要发送全部历史)
- 容易超出模型的上下文限制(如 GPT-4 的 8k tokens)
解决方案: ConversationBufferWindowMemory 只保留最近的 K 轮对话:
示例 1:窗口大小 k=2(字符串格式)
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k=2)
memory.save_context({"input": "第1轮"}, {"output": "回复1"})
memory.save_context({"input": "第2轮"}, {"output": "回复2"})
memory.save_context({"input": "第3轮"}, {"output": "回复3"})
print(memory.load_memory_variables({}))
# 输出:{'history': 'Human: 第2轮\nAI: 回复2\nHuman: 第3轮\nAI: 回复3'}
# 第1轮被丢弃了!
示例 2:集成到 LLM(客服场景)
prompt = ChatPromptTemplate.from_messages([
("system", "你是客服助手。"),
MessagesPlaceholder(variable_name='history'),
("human", "{question}")
])
memory = ConversationBufferWindowMemory(k=2, memory_key='history', return_messages=True)
chain = LLMChain(llm=llm, prompt=prompt, memory=memory)
chain.invoke({"question": "我叫孙小空"})
chain.invoke({"question": "我有一个师弟"})
chain.invoke({"question": "我叫什么?"})
# 输出:抱歉,我并不知道你的名字
# 因为 k=2,只记住了后两轮,第一轮的名字被忘记了
适用场景: 需要保持对话连贯性,但不需要完整历史的场景(如客服、简单聊天)。
取舍: 牺牲了长期记忆,换取了成本控制和性能优化。
3.2 ConversationTokenBufferMemory:令牌限制记忆
更精准的控制: 按 Token 数量而非轮次来限制记忆:
示例 1:max_token_limit=10
from langchain.memory import ConversationTokenBufferMemory
memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=10)
memory.save_context({"input": "你好,你是谁?"}, {"output": "我是 AI 助手。"})
print(memory.load_memory_variables({}))
# 输出:{'history': ''}
# 因为这一轮对话的 Token 数超过 10,被完全丢弃
示例 2:max_token_limit=30
memory = ConversationTokenBufferMemory(llm=llm, max_token_limit=30)
memory.save_context({"input": "你好"}, {"output": "你好!"})
memory.save_context({"input": "介绍一下你自己"}, {"output": "我是一个 AI 助手..."})
print(memory.load_memory_variables({}))
# 输出:保留了部分最近的对话,Token 总数不超过 30
工作原理:
- 每次
save_context()时,计算当前所有消息的 Token 总数 - 如果超过
max_token_limit,从最早的消息开始删除,直到满足限制
适用场景: 严格的 Token 预算控制(如按 Token 收费的场景)。
注意事项: 需要传入 llm 参数,用于计算 Token 数量。
四、智能压缩的 Memory
4.1 ConversationSummaryMemory:摘要式记忆
更聪明的压缩: 用 LLM 生成对话摘要,而不是简单地丢弃历史:
示例 1:从空历史开始
from langchain.memory import ConversationSummaryMemory
memory = ConversationSummaryMemory(llm=llm)
memory.save_context({"input": "你好"}, {"output": "怎么了"})
print(memory.load_memory_variables({}))
# 输出:{'history': 'The human greets the AI in Chinese, saying "hello". The AI responds by asking "what\'s up?" in Chinese.'}
# LLM 自动生成了英文摘要!
示例 2:从已有历史初始化
from langchain.memory import ChatMessageHistory
history = ChatMessageHistory()
history.add_user_message("你好,你是谁?")
history.add_ai_message("我是 AI 助手。")
memory = ConversationSummaryMemory.from_messages(
llm=llm,
chat_memory=history,
return_messages=True
)
print(memory.buffer)
# 输出:对话的摘要(由 LLM 生成)
核心机制:
- 每次
save_context()时,LLM 会阅读当前摘要 + 新对话,生成新的摘要 from_messages()会保留原始消息到chat_memory,方便后续重新生成摘要
适用场景: 需要长对话记忆,但希望压缩 Token 消耗的场景(如长期客服、顾问)。
取舍: 摘要可能丢失细节,但保留了关键信息;每次生成摘要会额外消耗 Token。
4.2 ConversationSummaryBufferMemory:混合式记忆
最佳实践: 结合摘要和完整消息,兼顾细节和压缩:
示例 1:max_token_limit=40
from langchain.memory import ConversationSummaryBufferMemory
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=40,
return_messages=True
)
memory.save_context({"input": "你好"}, {"output": "你好!"})
memory.save_context({"input": "介绍一下你自己"}, {"output": "我是 AI 助手..."})
memory.save_context({"input": "你能做什么?"}, {"output": "我可以回答问题..."})
print(memory.load_memory_variables({}))
# 输出:
# {'history': [
# SystemMessage(content='对前几轮对话的摘要...'), # 早期对话的摘要
# AIMessage(content='我可以回答问题...'), # 最近的完整消息
# HumanMessage(content='你能做什么?')
# ]}
示例 2:对比 max_token_limit=100
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=100,
return_messages=True
)
# 同样的对话
memory.save_context({"input": "你好"}, {"output": "你好!"})
memory.save_context({"input": "介绍一下你自己"}, {"output": "我是 AI 助手..."})
memory.save_context({"input": "你能做什么?"}, {"output": "我可以回答问题..."})
print(memory.load_memory_variables({}))
# 输出:所有消息都是完整的,没有生成摘要
示例 3:电商客服场景
prompt = ChatPromptTemplate.from_messages([
("system", "你是电商客服助手。"),
MessagesPlaceholder(variable_name='history'),
("human", "{question}")
])
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=100,
memory_key='history',
return_messages=True
)
chain = LLMChain(llm=llm, prompt=prompt, memory=memory)
chain.invoke({"question": "我的订单号是 12345"})
chain.invoke({"question": "什么时候发货?"})
chain.invoke({"question": "可以退货吗?"})
chain.invoke({"question": "我的订单号是多少来着?"})
# AI 能回答:你的订单号是 12345
# 因为即使前面的对话被压缩成摘要,订单号这个关键信息会被保留
工作原理:
- 保留最近几轮完整消息(Token 数在
max_token_limit内) - 将更早的消息压缩成摘要(通过 LLM 生成)
- 最终返回:摘要(SystemMessage)+ 最近的完整消息
适用场景: 最推荐的 Memory 类型!平衡了细节保留和成本控制,适用于大多数生产环境。
五、专业场景的 Memory
5.1 ConversationEntityMemory:实体追踪记忆
场景引入: 在电商、医疗等场景中,我们更关心实体(人名、产品、症状)及其属性,而不是完整对话。
示例对比:
ConversationSummaryMemory:
摘要:患者主诉头痛和高血压,有青霉素过敏史...
ConversationEntityMemory:
{
"症状": "头痛",
"血压": "140/90",
"过敏药物": "青霉素"
}
示例:超级英雄对话
from langchain.memory import ConversationEntityMemory
memory = ConversationEntityMemory(llm=llm)
chain = ConversationChain(llm=llm, memory=memory, verbose=True)
chain.invoke("我叫蜘蛛侠。我的好朋友包括钢铁侠、美国队长和绿巨人。")
chain.invoke("我住在纽约。")
# 查看实体存储
print(memory.entity_store.store)
# 输出:
# {
# '蜘蛛侠': '蜘蛛侠的好朋友包括钢铁侠、美国队长和绿巨人。蜘蛛侠住在纽约。',
# '纽约': '蜘蛛侠住在纽约。',
# '钢铁侠': '钢铁侠是蜘蛛侠的好朋友。',
# '美国队长': '美国队长是蜘蛛侠的好朋友。',
# '绿巨人': '绿巨人是蜘蛛侠的好朋友。'
# }
工作原理:
- LLM 自动提取对话中的实体(人名、地点、物品)
- 为每个实体维护一个描述,记录相关信息
- 后续对话中,如果提到某个实体,会加载该实体的描述到上下文
适用场景:
- 电商:追踪产品名称、订单号、用户偏好
- 医疗:追踪症状、药物、过敏史
- CRM:追踪客户姓名、公司、需求
优势:
- 压缩了无关信息(如寒暄),保留了关键实体
- 适合高风险领域(医疗、法律),结构化数据更易审查
5.2 ConversationKGMemory:知识图谱记忆
更进一步: 不仅提取实体,还提取实体之间的关系(来自 03-其他Memory模块的使用.ipynb):
from langchain.memory import ConversationKGMemory
memory = ConversationKGMemory(llm=llm)
memory.save_context({"input": "向山姆问好"}, {"output": "山姆是谁"})
memory.save_context({"input": "山姆是我的朋友"}, {"output": "好的"})
# 查看知识图谱
print(memory.kg.get_triples())
# 输出:
# [KnowledgeTriple(subject='山姆', predicate='是', object_='我的朋友')]
工作原理:
- LLM 提取三元组:
(头实体, 关系, 尾实体) - 构建知识图谱,存储实体和关系
- 查询时,通过图谱推理相关信息
适用场景:
- 社交网络:追踪人际关系(朋友、同事、家人)
- 企业 CRM:追踪公司间的合作关系、竞争关系
- 智能助手:推理用户的社交圈和偏好
示例场景: “山姆是我的朋友,山姆喜欢打篮球” → 推理:我的朋友喜欢打篮球 → 推荐:篮球相关内容
5.3 VectorStoreRetrieverMemory:向量检索记忆
终极武器: 用向量数据库存储对话,通过语义相似度检索历史(来自 03-其他Memory模块的使用.ipynb):
from langchain.memory import VectorStoreRetrieverMemory
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
# 初始化向量存储
embeddings_model = OpenAIEmbeddings()
vectorstore = FAISS.from_texts(
["我最喜欢的食物是披萨", "我住在北京"],
embeddings_model
)
# 创建检索器
retriever = vectorstore.as_retriever(search_kwargs=dict(k=1))
# 创建 Memory
memory = VectorStoreRetrieverMemory(retriever=retriever)
# 语义检索
result = memory.load_memory_variables({"prompt": "我最喜欢的食物是"})
print(result)
# 输出:{'history': 'Human: 我最喜欢的食物是披萨'}
# 即使查询不是完全匹配,也能通过语义相似度找到相关历史
工作原理:
- 将每轮对话嵌入成向量,存储到向量数据库(FAISS、Pinecone 等)
- 查询时,将当前问题也嵌入成向量
- 通过余弦相似度检索最相关的 K 条历史
适用场景:
- 超长对话:几百轮、几千轮对话,传统 Memory 无法处理
- 语义搜索:不关心时间顺序,关心语义相关性(如"上次讨论的那个 bug")
- 多轮复杂咨询:法律、医疗、技术支持,需要检索历史相似案例
优势:
- 不受对话长度限制(向量数据库可以存储海量数据)
- 语义检索比关键词匹配更智能
劣势:
- 成本较高(需要向量化模型、向量数据库)
- 检索结果可能不按时间顺序,需要额外处理
六、Memory 选型指南
根据场景选择合适的 Memory 类型:
| Memory 类型 | 适用场景 | Token 效率 | 实现复杂度 | 关键参数 |
|---|---|---|---|---|
| ConversationBufferMemory | 短对话、需要完整上下文(如简单聊天) | 低 | 简单 | return_messages, memory_key |
| ConversationBufferWindowMemory | 中等长度对话、只需最近上下文(如客服) | 中 | 简单 | k(窗口大小) |
| ConversationTokenBufferMemory | 严格 Token 预算控制 | 高 | 中等 | max_token_limit |
| ConversationSummaryMemory | 长对话、关注关键点而非细节 | 高 | 中等 | llm |
| ConversationSummaryBufferMemory | 推荐! 平衡细节和压缩(大多数生产环境) | 很高 | 中等 | max_token_limit, llm |
| ConversationEntityMemory | 追踪特定实体(人名、产品、症状) | 高 | 复杂 | llm |
| ConversationKGMemory | 复杂关系推理(社交网络、知识图谱) | 高 | 复杂 | llm |
| VectorStoreRetrieverMemory | 超长对话、语义搜索(技术支持、法律咨询) | 很高 | 复杂 | retriever, k |
选型建议:
- 快速原型:先用
ConversationBufferMemory,简单直接 - 生产环境:推荐
ConversationSummaryBufferMemory,性价比最高 - 成本敏感:用
ConversationBufferWindowMemory或ConversationTokenBufferMemory - 专业场景:
- 电商/医疗 →
ConversationEntityMemory - 社交/CRM →
ConversationKGMemory - 超长历史/语义检索 →
VectorStoreRetrieverMemory
- 电商/医疗 →
七、总结
Memory 模块是 LangChain 的核心功能之一,让 AI 能够像人一样记住对话上下文。本文介绍了 10 种 Memory 类型,从最基础的完整存储,到智能的摘要压缩,再到专业的实体追踪和向量检索。
核心要点:
-
基础 Memory:
ChatMessageHistory:最底层的消息容器ConversationBufferMemory:完整保存所有对话,简单但成本高ConversationChain:简化版封装,快速原型开发
-
限制长度:
ConversationBufferWindowMemory:滑动窗口,按轮次限制ConversationTokenBufferMemory:按 Token 数量精准控制
-
智能压缩:
ConversationSummaryMemory:LLM 生成摘要,保留关键信息ConversationSummaryBufferMemory:混合式,最推荐的生产方案
-
专业场景:
ConversationEntityMemory:实体追踪,适合电商、医疗ConversationKGMemory:知识图谱,适合关系推理VectorStoreRetrieverMemory:向量检索,适合超长对话
实战建议:
- 开发阶段:用
ConversationBufferMemory快速验证功能 - 上线前:切换到
ConversationSummaryBufferMemory,平衡成本和效果 - 特殊场景:根据业务需求选择专业 Memory(实体/知识图谱/向量检索)
注意事项:
- PromptTemplate vs ChatPromptTemplate:后者需要
return_messages=True和MessagesPlaceholder - memory_key 对应:确保 Memory 的
memory_key和 Prompt 的变量名一致 - Token 成本:摘要式 Memory 会额外调用 LLM,需考虑成本
- 数据持久化:本文的示例都是内存存储,生产环境需要持久化到数据库或文件
下一篇文章我们继续学习 LangChain 的其他模块,敬请期待!
更多推荐


所有评论(0)