第13课:LangChain|ConversationBufferMemory【彻底搞懂对话内存的核心原理与演进】
摘要 本文系统梳理了LangChain记忆组件的演进历程,重点解析了ConversationBufferMemory的设计缺陷及其被InMemoryChatMessageHistory替代的原因。旧版ConversationBufferMemory存在接口耦合、语义混乱等问题,而新版通过BaseChatMessageHistory接口实现存储与业务逻辑解耦。InMemoryChatMessageH

文章目录
课程导读 & 学习目标
在前面的课程中,我们已经系统掌握了LangChain的记忆体系。第12课我们学习了新版记忆架构的核心组件——BaseChatMessageHistory和RunnableWithMessageHistory。但有一个关键问题始终没有深入回答:在RunnableWithMessageHistory这个新架构中,对话历史的存储基座到底是什么?谁在真正“装”这些消息?
答案就是BaseChatMessageHistory的具体实现——InMemoryChatMessageHistory。然而,如果你查阅旧版LangChain的教程或代码,会发现一个频繁出现的名字:ConversationBufferMemory。它曾是LangChain早期版本中最核心的记忆组件,承载了无数学员的第一个带记忆对话应用。
今天这节课的核心任务,就是系统梳理ConversationBufferMemory的演进历程,同时更深入理解新版架构中InMemoryChatMessageHistory作为其替代者的定位。可以说,ConversationBufferMemory已经完成了它的历史使命,但理解它的设计思路,可以帮助你更顺畅地过渡到新版架构,也为阅读存量代码打下基础。
通过类比生活中“备忘录”与“对话记录本”的区别,我们先理解一个朴素的设计:聊天历史本质上就是按照时间顺序不断追加消息的一个“篮子”。
前置知识与环境准备
1.1 环境沿用
继续使用前几课的langchain_course虚拟环境(Python 3.10+)。如果你是从头开始,请参考第1课的虚拟环境创建方式。
1.2 依赖安装
# 激活虚拟环境
source venv/bin/activate # Mac/Linux
# venv\Scripts\activate # Windows
# 基础依赖
pip install langchain==0.3.7 langchain-core==0.3.21 langchain-openai==0.2.8 python-dotenv==1.0.1
# LangChain Classic 包(用于查看旧版 API 对比)
pip install langchain-classic
# 验证安装
python -c "from langchain_core.chat_history import InMemoryChatMessageHistory; print('✓ 新版记忆组件导入成功')"
1.3 API密钥与模型选择
沿用前几课的.env配置(智谱GLM-4或Ollama本地模型)。本节课的记忆演示涉及多轮对话交互,建议使用本地Ollama模型以零成本体验完整功能。
1.4 上节课回顾与本课定位
在第12课中,我们详细拆解了新版记忆架构的演化逻辑,介绍了BaseChatMessageHistory接口和RunnableWithMessageHistory包装器的工作原理。本节课则专注于一件事:深入理解InMemoryChatMessageHistory(也就是旧版ConversationBufferMemory在新时代的继承者)的设计哲学与工程实践。如果你在学习新版API时看到“内存消息历史”这类术语,它就是Buffer Memory在新版本中的最佳别名。
核心概念深度拆解
2.1 为什么需要“记忆基座”——从“一次性工人”到“有记忆的伙伴”
大模型本身是一种“金鱼”,如果你连续问同一个人的同一个名字,第二轮模型就会直接忘记第一轮讲过的信息。记忆系统回答两个根本问题:存储什么和如何召回。一个最简单的解决方案,就是把每次对话的原句完整保存下来,按照时间顺序排列。下次提问时,把这整本“备忘录”一股脑塞给模型,让它自己从中找出之前说过什么。这就是**Buffer Memory(缓冲内存)**的核心思想:它是一种“对话抄录员”,一字不差地把所有对话记录下来。
基于这种需求,一张最简单的记忆组件表就浮现出来。早期LangChain的ConversationBufferMemory核心功能无外乎是存储和读取:提供save_context()把当前的用户输入和AI输出保存起来;提供load_memory_variables()把历史记录的纯文本版本返回给调用方;而clear()则清空所有记录。
补充词汇:
ConversationBufferWindowMemory——对话缓冲窗口内存。它与ConversationBufferMemory类似,但只保留最近的K轮对话。一“轮”包括用户的输入和AI的即时回复。这对于控制Token消耗非常有用。ConversationSummaryMemory则在一个更高维度上用LLM自动生成摘要来总结对话,将长对话压缩成几行关键信息。
2.2 记忆体系的构成与分层路径
LangChain的对话存储经历了几个层次:
- 你看到的业务层:
ConversationBufferMemory、ConversationSummaryMemory。这些是上层API提供的对话记忆组合方案。 - 底层的存储基座:
BaseChatMessageHistory与它的各种实现(InMemoryChatMessageHistory、RedisChatMessageHistory、SQLChatMessageHistory等)。
新旧版本在存储逻辑上有根本差异。旧版ConversationBufferMemory内存中维护的是一个纯文本字符串buffer。在memory.load_memory_variables({})时,返回的是如下格式:
Human: 我叫李明
AI: 你好李明,很高兴认识你
Human: 我是软件工程师
AI: 太棒了,我也喜欢软件领域
这种把消息类型(人/AI)用字符前缀硬编码的方式,与模型API期待的角色完全割裂。新版BaseChatMessageHistory``messages列表存储的是HumanMessage、AIMessage等真正的消息对象,更加符合现代聊天模型的训练数据格式。
2.3 ConversationBufferMemory的源码结构(历史版本)
ConversationBufferMemory继承自BaseMemory抽象基类。它的核心存储是一个名为buffer的字符串或消息列表。当return_messages=False(默认)时,buffer是纯文本;当return_messages=True时,buffer是消息对象列表。这就是ConversationBufferMemory的两种人脸——String模式和Chat模式。
实际上,ConversationBufferMemory的“两个面孔”:return_messages=False时,它会帮你把所有消息拼接成字符串,很容易与Completion模型(即text-davinci-003这种非Chat模型)一起调用。ConversationBufferMemory(return_messages=True)后,buffer存储的是消息对象列表,与聊天模型的输入结构天然对齐。旧版的这个设计让很多初学者理解起来非常吃力,因为明明是同一类记忆组件,却要引入不同的返回格式。
2.4 Deprecated时间线——为什么它被标记废弃
从v0.1.17开始,LangChain官方在ConversationBufferMemory的代码中引入废弃警告(DeprecationWarning):LangChainDeprecationWarning: The class ConversationBufferMemory was deprecated in LangChain 0.3.1 and will be removed in 1.0。
LangChain官方在v0.3.1中正式将ConversationBufferMemory标为废弃。原因很清晰:
- 延续了旧版BaseMemory的错误拼凑:旧版BaseMemory把消息历史和Memory的业务耦合在一个接口中。新版通过
BaseChatMessageHistory和RunnableWithMessageHistory完全解耦。 - 函数语义令人困惑的
buffer机制:return_messages的存在又造成了学习成本翻倍。 - 无法支持流式原生特性:新版Runnable体系可以轻松用
stream,旧版链被强绑定。
注意:新版LangChain官方API参考中已经完整列出了从
ConversationBufferMemory迁移到RunnableWithMessageHistory的详细示例。
2.5 新版存储基座的角色——InMemoryChatMessageHistory
新版推荐的存储基座是继承了BaseChatMessageHistory接口的InMemoryChatMessageHistory。它的内部存储是一个消息列表,提供add_messages、clear和只读属性messages。它的设计思路极其干净。
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
# 创建实例
history = InMemoryChatMessageHistory()
# 添加消息(推荐批量添加)
history.add_messages([
HumanMessage(content="我叫李明"),
AIMessage(content="你好,李明!")
])
# 查看所有历史
for msg in history.messages:
print(f"{msg.type}: {msg.content}")
InMemoryChatMessageHistory与RunnableWithMessageHistory配合的完整流程在第12课中已经详细展示过。它扮演的角色就是把记忆存储与记忆管理完全解耦。这种设计的最大优势是:将来你可以轻松替换存储后端(文件、Redis、数据库中),业务链核心代码零修改。
底层运行原理剖析
3.1 记忆写入与读取的完整数据流
当你用ConversationBufferMemory写一个历史记录时,典型的数据流动如下:
- 用户输入:
{"input": "我叫李明"} - 调用的是
memory.save_context({"input": r"human的原始输入字段"}, {"output": "模型返回AIMessage"}) - 默认拼接方式:将"Human: 用户输入"和"AI: 模型输出"拼接在一起,添加一个换行符分隔。
- 存入
self.buffer(如果是返回return_messages=False)或存入self.chat_memory.messages(如果是return_messages=True模式)。
这条链路到了新版反而简洁很多:RunnableWithMessageHistory会自动从invoke执行后,将本次的HumanMessage和AIMessage保存到BaseChatMessageHistory实例中,不需要手动调用任何save_context。
3.2 return_messages 的本质剖析
ConversationBufferMemory的return_messages参数,它控制的是load_memory_variables({})返回值的格式。如果return_messages=True,返回:{"history": [HumanMessage(...), AIMessage(...)]}。主要用于聊天模型的输入;如果为False(默认),返回:{"history": "Human: 我叫李明\nAI: 你好!"}。这主要是为旧的文本补全模型设计的过渡方案。
新版架构中,InMemoryChatMessageHistory的messages属性永远返回消息对象列表,没有任何类似return_messages的冗余配置,输出格式完全由Parser按需解析。
核心API/组件源码解读
4.1 InMemoryChatMessageHistory(对标旧BufferMemory)
InMemoryChatMessageHistory是所有内存聊天历史存储的标准实现:
add_messages(messages: Sequence[BaseMessage]) -> None:批量添加多条消息,生产环境更可靠,能有效减少存储的后端调用次数。clear() -> None:清空当前会话历史。messages属性(只读):返回当前存储的所有消息对象。
4.2 ConversationBufferMemory(旧版)的残留API
如果你在旧版LangChain 0.2.x中使用ConversationBufferMemory,通常会看到以下方法:
save_context(inputs: Dict[str, Any], outputs: Dict[str, str]):保存用户输入和AI输出。load_memory_variables(inputs: Dict[str, Any]) -> Dict[str, Any]:加载内存历史,返回字典,键为memory_key(默认是history)。clear():清空缓冲。
4.3 新旧API映射表
| 旧设计 | 新设计 |
|---|---|
ConversationBufferMemory(return_messages=False) |
InMemoryChatMessageHistory + StrOutputParser(手动拼接Human/AI前缀) |
ConversationBufferMemory(return_messages=True) |
InMemoryChatMessageHistory 直接配合 ChatPromptTemplate |
save_context(...) |
RunnableWithMessageHistory.run 自动保存,无需手动调用 |
buffer |
InMemoryChatMessageHistory.messages |
memory.load_memory_variables({})["history"] |
模板中自动注入 history_messages_key |
| ConversationSummaryMemory(基于LLM的总结) | 暂无官方直接替代,但可以通过自定义回调或LangChain的摘要链模拟实现 |
手把手项目实战教学
实战一:InMemoryChatMessageHistory 基础操作
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
history = InMemoryChatMessageHistory()
history.add_messages([
HumanMessage(content="我叫李明,是一名软件工程师"),
AIMessage(content="你好李明!请问有什么可以帮你的?")
])
history.add_message(HumanMessage(content="你还记得我做什么工作吗?"))
print("完整历史记录:")
for msg in history.messages:
print(f"{msg.type}: {msg.content}")
运行结果会显示三条消息,与旧版BufferMemory存储的效果完全一致,但比旧版Buffered Memory结构更简单。
实战二:ConversationBufferWindowMemory 基础使用
from langchain.memory import ConversationBufferWindowMemory
# 只保留最近2轮对话
memory = ConversationBufferWindowMemory(k=2, return_messages=True)
# 模拟三轮对话
memory.save_context({"input": "第一轮:我叫李明"}, {"output": "AI:你好"})
memory.save_context({"input": "第二轮:我是软件工程师"}, {"output": "AI:知道了"})
memory.save_context({"input": "第三轮:我喜欢打篮球"}, {"output": "AI:记住了"})
# 注意:此时 memory 中只保留了第2轮和第3轮的消息
print(memory.load_memory_variables({}))
⚠️ 注意:这段代码适用于需要回顾旧版API逻辑或阅读旧代码的情况。新版开发应优先考虑
InMemoryChatMessageHistory。
实战三:总结旧版BufferMemory代码(if encountered)
from langchain.memory import ConversationBufferMemory
memory = ConversationBufferMemory(return_messages=True)
memory.save_context({"input": "我叫李明"}, {"output": "你好李明"})
memory.save_context({"input": "我是一名工程师"}, {"output": "太棒了"})
print(memory.load_memory_variables({}))
完整可运行Python代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""LangChain 第13课:ConversationBufferMemory完整演示"""
import os
from dotenv import load_dotenv
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.messages import HumanMessage, AIMessage
load_dotenv()
def demo_in_memory():
print("新版InMemoryChatMessageHistory 实践")
history = InMemoryChatMessageHistory()
history.add_messages([
HumanMessage(content="我叫李明,是一名软件工程师"),
AIMessage(content="你好,李明!很高兴认识你。")
])
history.add_message(HumanMessage(content="你还记得我做什么工作吗?"))
for msg in history.messages:
print(f"{msg.type}: {msg.content}")
def demo_legacy_window():
print("\n旧版ConversationBufferWindowMemory 快照")
from langchain.memory import ConversationBufferWindowMemory
memory = ConversationBufferWindowMemory(k=2, return_messages=True)
memory.save_context({"input": "第一轮:我叫李明"}, {"output": "AI:你好"})
memory.save_context({"input": "第二轮:我是软件工程师"}, {"output": "AI:知道了"})
memory.save_context({"input": "第三轮:我喜欢打篮球"}, {"output": "AI:记住了"})
print("保留的最远两轮记忆:", memory.load_memory_variables({}))
if __name__ == "__main__":
demo_in_memory()
demo_legacy_window()
环境依赖安装命令
# 激活虚拟环境
source venv/bin/activate # Mac/Linux
# venv\Scripts\activate # Windows
# 安装所有依赖
pip install langchain==0.3.7 langchain-core==0.3.21 langchain-openai==0.2.8 python-dotenv==1.0.1
# 仅当需要查看旧版API时才安装
pip install langchain-classic
# Redis 依赖(生产级持久化)
pip install redis
常见报错坑点与避坑方案
坑1:ImportError: cannot import name 'ConversationBufferMemory' from 'langchain.memory'
现象:新环境中导入旧版内存组件失败。
原因:LangChain 0.3.x 已经把ConversationBufferMemory从主框架中移入langchain-classic。
解决方案:换用新架构的InMemoryChatMessageHistory替代。
坑2:使用了ConversationBufferMemory(return_messages=True)但传入文本模型后报错
现象:LLM类型不匹配,提示非法消息期望字符串。
原因:Completion模型只接受字符串,不接受消息对象列表。
解决方案:设置return_messages=False后手动拼接字符串,或直接换用法最新模型体系ChatOpenAI。
坑3:memory.save_context未生效
现象:明明调用了多次save_context,但load_memory_variables发现历史仍然空空如也。
原因:默认的memory_key与链不同步,或在传递过程中手动覆盖了输入。
解决方案:检查ConversationChain中的memory_key,默认为history;如果你想改,必须显式定义。
坑4:ConversationBufferWindowMemory的k值单位混淆
现象:希望记住3轮对话,但只记住了1.5轮。
原因:窗口内存中的k代表存储的交互实例(messages对数)。一“轮”包括用户的输入和AI的一条回复。
解决方案:窗口内存的交互单位是(My turn + AI turn)。
本节核心知识点总结
📌 ConversationBufferMemory 是旧版LangChain中最核心的记忆组件,作用是把对话历史无压缩地存储为纯文本或消息列表,适合短时对话。由于架构演进,它已在v0.3.1中被标记为废弃,并在1.0中完全移除。
📌 存储方式的对比:旧BufferMemory通过参数return_messages切换消息格式,模糊不清;新版InMemoryChatMessageHistory只存储消息对象,配合Parser统一解析。
📌 ConversationBufferWindowMemory 是与BufferMemory并列但增加窗口截断功能的变体,使用滑动窗口仅保留最近几轮对话。它是旧版中控制token消耗最直接的工具。
📌 迁移推荐:全新项目统一采用InMemoryChatMessageHistory + RunnableWithMessageHistory架构。这是LangChain认为唯一面向未来的对话记忆方案。
课后练习题
选择题
1. ConversationBufferMemory在新版LangChain中的状态是?
A. 是推荐使用的标准记忆组件。
B. 已被标记为废弃(deprecated),将在1.0中移除。
C. 只能在0.1.x版本中使用。
D. 从0.3.x开始得到了全面增强。
答案及解析:B。ConversationBufferMemory在v0.3.1被标记为废弃,并将在1.0中彻底移除,推荐迁移到InMemoryChatMessageHistory或RunnableWithMessageHistory。
2. ConversationBufferWindowMemory 的 k 参数代表什么含义?
A. 保留的历史消息总条数。
B. 保留的最近k轮对话(每轮=用户输入+AI回复)。
C. 保留的 Token 总数。
D. 保留的时间周期。
答案及解析:B。k代表窗口大小,即保留的最近k组(Human + AI)完整对话。
3. 在新版LangChain中,哪个类负责内存消息的存储?
A. ConversationBufferMemory
B. BaseMemory
C. InMemoryChatMessageHistory
D. ConversationChain
答案及解析:C。新版记忆架构的核心存储基类是BaseChatMessageHistory,而它的内存实现就是InMemoryChatMessageHistory。ConversationChain和ConversationBufferMemory都属于被废弃的旧模块。
简答题
4. 请描述return_messages参数在ConversationBufferMemory中的作用。
参考答案:
return_messages=False(默认):load_memory_variables返回的是一个字符串,格式为"Human: ...\nAI: ...",适用于Completion模型。return_messages=True:返回的是一个List[BaseMessage],直接与Chat模型的输入对齐。这一机制在过渡期解决了两类模型兼容问题,但也增加了理解的复杂度。
实践题
5. 将以下ConversationBufferMemory代码迁移到新版LangChain架构。
旧代码:
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI
memory = ConversationBufferMemory(return_messages=True)
conversation = ConversationChain(llm=ChatOpenAI(), memory=memory)
conversation.predict(input="我叫李明")
conversation.predict(input="我叫什么名字?")
答案:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-3.5-turbo")
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="history"),
("human", "{input}")
])
chain = prompt | llm | StrOutputParser()
store = {}
def get_session_history(session_id: str):
if session_id not in store:
store[session_id] = ChatMessageHistory()
return store[session_id]
conversation = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key="input",
history_messages_key="history"
)
config = {"configurable": {"session_id": "test_user"}}
conversation.invoke({"input": "我叫李明"}, config)
conversation.invoke({"input": "我叫什么名字?"}, config)
解析:生成新版架构中的ChatMessageHistory作用于MessagesPlaceholder,与RunnableWithMessageHistory完全解耦,存储格式清爽。旧predict被映射到了invoke方法。
🔗《30节课 LangChain 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~
更多推荐


所有评论(0)