问题
我们以往在使用豆包或者DeepSeek时,在一些长的上下文环境中AI模型是可以记住我们之前提供的一些信息的。那么,AI大模型是怎么记忆这些信息的。
我们先从一段简单的代码入手看看。
while True:
user_input = input("请输入 'quit' 退出程序: ")
if user_input == 'quit':
print("程序结束。")
break
else:
print(chain.invoke({'text': user_input}))
我先输入”你好,我是大白。你是谁?“,接着后续我又继续问”我是谁?“。。。结果歇菜了,他并不知道我是谁。可见,原生的单纯的对话式问答,AI大模型并不会自动记忆。
那么,Langchain框架到底是怎么记忆的?
ChatMessageHistory
基础的内存存储。所有消息存在于程序运行时内存中,服务重启则记忆消失。
代码
# 创建消息历史记录,存储组件
# add_user_message() 添加用户的输入信息
# add_ai_message() 添加存储大模型的回复信息
# .messages 属性获取所有历史消息
chat_history = ChatMessageHistory()
while True:
user_input = input("用户:")
if user_input == "exit":
break
# 添加用户输入
chat_history.add_user_message(user_input)
# 访问LLM时,chat_history.messages 获取所有的历史消息
response = chain.invoke({'messages': chat_history.messages})
print("chat_history:",chat_history.messages)
print(f"大模型回复》》》:{response}")
# 将大模型的回复加入历史记录
chat_history.add_ai_message(response)
📢⚠️:这里大模型的回复也要加入历史记录,后续大模型对之前的上下文才会有更完善的记忆。
运行
可见,AI大模型确实记住了我们之前对话的相关信息。我们继续exit程序,重新问一些上轮对话的相关内容。
可见,ChatMessageHistory确实是内存存储。当程序重新运行之后,之前的记忆已然清空。
RedisChatMessageHistory
持久化存储。将消息序列化后存入Redis,实现跨请求、跨进程甚至跨服务重启的记忆持久化。
代码
- 构建数据库记忆
# pip install redis
# session_id 识别用户 0.3版本中 redis_url 访问路径
# history = RedisChatMessageHistory(session_id="my_session_id", redis_url="redis://localhost:6379")
# langchain 1.0 url
history = RedisChatMessageHistory(session_id="my_session_id1", url="redis://localhost:6379")
运行
当此运行,显然是没问题的。
我们重新启动程序继续问上次相关的信息,发现这次记忆记住了。可见,RedisChatMessageHistory确实是持久化存储的。
RunnableWithMessageHistory
会话管理器。 它本身不存储消息,而是一个“装饰器”或“包装器”,其核心创新在于引入了session_id的概念,能够根据每次交互的上下文,动态地从你指定的存储后端(比如前两者)中获取或创建对应会话的历史记录,并将其注入到本次调用链中。它是构建多租户SaaS服务或用户粘性型对话产品的利器,该设计将会话的逻辑管理与存储的物理实现解耦,未来你可以轻松将存储从Redis切换到PostgreSQL或MongoDB,而业务代码几乎不变。
一个最简单的应用就是在多轮对话中可以用RunnableWithMessageHistory来隔离不同用户的记忆。
代码
RunnableWithMessageHistory
# 历史对话记忆
def get_session_history(session_id):
return RedisChatMessageHistory(
session_id=session_id,
url="redis://localhost:6379",
ttl=300 # 300秒表示5分钟过期,这个添加记忆持续时间 5分钟
)
# 串联历史记录(记忆) 和 链
# 将对话历史自动集成到模型调用链中,解决了聊天机器人在上下文连续性
# 和多用户支持中的核心问题
chatbot_with_his = RunnableWithMessageHistory(
# 执行流程
chain,
# 记忆存储的位置的函数
get_session_history,
input_messages_key="input",
)
会话模拟
# 模拟了两个用户的会话 ,"session_id": "用户的唯一标识"
# user123对话
resp_user123 = chatbot_with_his.invoke(
{"input": "我是user123,我喜欢音乐,你能给我推荐一首轻音乐吗?"},
# config 配置信息 configurable {"session_id": "user123"} 配置的是用户的身份信息
config={
"configurable": {"session_id": "user123"},
},
)
# 大模型回复
print("用户123的回答:", resp_user123)
print("==================")
# jack对话
resp_jack = chatbot_with_his.invoke(
{"input": "我是jack,我热爱运动,篮球是我的最爱,你猜我喜欢哪个NBA球星?"},
config={"configurable": {"session_id": "jack"}}
)
print("用户jack的回答:", resp_jack)
print("==================")
# user123再次提问
resp1_user123 = chatbot_with_his.invoke(
{"input": "我喜欢什么?"},
config={"configurable": {"session_id": "user123"}}
)
print("用户123的回答:", resp1_user123)
print("==================")
resp_jack1 = chatbot_with_his.invoke(
{"input": "我喜欢什么?"},
config={"configurable": {"session_id": "jack"}}
)
print("用户jack的回答:", resp_jack1)
分析
可见,RunnableWithMessageHistory隔离了不同用户的记忆。其核心机制:
-
get_session_history:灵魂函数。当遇到一个session_id时,请调用我这个函数来获取对应的历史存储对象。 -
config:
- {“configurable”: {“session_id”: “...”}}是传递会话上下文的标准方式。session_id是你的业务逻辑生成的唯一标识,可以是用户ID、设备ID、聊天线程ID等。
- config对象会在调用链中向下传递,RunnableWithMessageHistory在关键时刻拦截它,取出 session_id并调用 get_session_history 函数。
-
会话记忆的完全自动化管理,开发者只需关注业务和
session_id的生成。我们并不需要手动调用add_message。在每次invoke时RunnableWithMessageHistory自动执行以下流程:-
根据
config中的session_id, 通过get_session_history拿到本次对话的专属ChatMessageHistory对象。 -
将该对象中的所有历史消息取出,注入到
PromptTemplate的MessagesPlaceholder(即history变量)中。 -
执行基础链(LLM调用)。
-
调用成功后,自动将本次的“用户输入”和“AI输出”作为一组消息,追加回那个
ChatMessageHistory对象。
-
对照表
以下是三种方式的详细对比,帮助你快速决策:
| 特性维度 | ChatMessageHistory | RedisChatMessageHistory | RunnableWithMessageHistory |
|---|---|---|---|
| 核心本质 | 内存存储容器 | 持久化存储实现 | 会话管理装饰器 |
| 存储位置 | 应用程序进程内存 | Redis数据库 | 依赖传入的ChatMessageHistory实例(可以是前两者之一) |
| 数据持久性 | 无,进程退出即丢失 | 有,可配置持久化策略 | 由传入的底层存储实例决定 |
| 并发与分布式 | 不支持,多进程/多实例间隔离 | 支持,所有实例共享同一数据源 | 完美支持,是处理多用户并发的推荐模式 |
| 应用场景 | 1. 原型验证与快速测试 2. 单次、无状态的脚本任务 3. 不需要历史记录的简单工具链 | 1. 需要历史回溯的生产级单用户应用 2. 简单的、基于内存缓存的会话共享 | 1. 多用户对话服务(如客服机器人、AI社交产品) 2. 需要严格按会话隔离的复杂Agent应用 3. 未来需要灵活更换存储后端的架构 |
| 接入复杂度 | 极低,开箱即用 | 中等,需部署和维护Redis | 略高,需理解session_id的传递与管理机制 |