LangChain 会话记忆核心:记忆管理策略
写在前面
多轮对话会导致历史消息超出模型上下文限制(如DeepSeek的128K),因此需要记忆管理策略。LangChain提供三种方案:修剪(移除首尾部分消息)、删除(永久删除状态快照)和总结摘要(推荐,将早期消息压缩为摘要,再与近期消息拼接)。文章重点演示了SummarizationMiddleware中间件的使用:设置触发条件(消息超过3条就进行总结摘要)和保留条数(保留最近1条对话),结合InMemorySaver实现checkpointer快照和create_agent创建带记忆的智能体。测试表明,经过多轮对话后,模型能通过消息摘要记住用户的关键信息(如“花哥”“爱猫”“爱爬山”),有效缓解上下文溢出问题。
目录
一. 为什么需要这个记忆管理策略?
1. 原因
二. 具体的策略有哪些?
1. 修剪
2. 删除
3. 总结摘要(推荐)
三. 总结摘要的具体代码实现
1. 代码实现
2. 效果测试、信息解读
一. 为什么需要这个记忆管理策略?
1. 原因
多轮对话会导致历史消息越来越多,最终超出模型的上下文限制(DeepSeek的上下文不能超过128K),langchain提供了一些记忆管理的策略来解决这个问题。
二. 具体的策略有哪些?
1. 修剪
拿到消息历史后,先移除前N条或后N条消息,再调用模型。
举例:如下图,当新消息来了,发现快要超出上下文限制了,修剪(去除)掉上面老旧的几条历史对话。图中就是修剪掉了ABCD这四条消息,然后将下面几条新的消息发送给了大模型。
缺点:如果为了不让上下文超限,就简单地修剪掉几条旧对话,虽然能暂时解决上下文超限问题,但很容易导致重要记忆丢失。
2. 删除
永久删除AgentState的快照。
举例:如下图,干脆直接删掉了之前的几个对话,这样是解决了上下文溢出的问题、节省了快照内存。
缺点:快照直接没了,后续想回滚都不行,风险较大。
3. 总结摘要(推荐)
先总结历史消息中的早期消息,得到消息摘要,然后用消息摘要和最近的消息形成消息列表,再调用模型。
举例:当新消息来了,发现之前的历史消息太多,要超出上下文限制了,此时应该再调用一个“总结摘要模型”,此时就能将之前的历史消息,总结成一个消息S。
优点:理论上,当我们压缩完,后续消息又多了,我们还可以调用“总结摘要模型”,对其再一次进行压缩。以此类推,可以无限压缩。
三. 总结摘要的具体代码实现
1. 代码实现
from aiohttp.web_middlewares import middleware # 1、导入消息摘要中间件的包、初始化消息摘要中间件 from langchain.agents.middleware import SummarizationMiddleware # 加载环境变量(如果不这么做,无法获取我们配置的ApiKey) from dotenv import load_dotenv load_dotenv() middleware = SummarizationMiddleware( model="deepseek-chat", trigger=("messages",3), # 触发时机:当消息超过3条时,进行总结摘要 keep=("messages",1) # 保留的会话数:1 ) # 2、导入checkpointer所需的包、初始化checkpointer from langgraph.checkpoint.memory import InMemorySaver checkpointer = InMemorySaver() # 基于内存存储,为了方便此处就不采用持久化存储了 # 3、创建智能体,并设置消息摘要中间件、checkpointer from langchain.agents import create_agent agent = create_agent( model="deepseek-chat", middleware=[middleware], checkpointer=checkpointer, ) # 4、测试效果:配置一个thread_id,并进行多轮对话 config = {"configurable":{"thread_id":"thread_3"}} # 开始多轮对话 from langchain.messages import HumanMessage agent.invoke({"messages":[HumanMessage(content="你好,我是花哥")]},config) agent.invoke({"messages":[HumanMessage(content="我喜欢讲故事")]},config) agent.invoke({"messages":[HumanMessage(content="我最喜欢的动物是猫")]},config) agent.invoke({"messages":[HumanMessage(content="我最喜欢的运动是爬山")]},config) # 测试记忆效果 final_response = agent.invoke({"messages":[HumanMessage(content="你还记得我吗?")]},config) # 打印响应结果 for message in final_response["messages"]: message.pretty_print()2. 效果测试、信息解读
================================ Human Message ================================= Here is a summary of the conversation to date: ## SESSION INTENT 用户“花哥”的主要目标是进行关于猫的故事互动。用户已补充信息:最喜欢的运动是爬山。AI正在尝试将“猫”与“爬山”主题结合,并引导用户从三种互动方式(用户讲、共同续写、AI讲)中选择一种继续推进。 ## SUMMARY - 用户“花哥”喜欢讲故事、喜欢猫、喜欢爬山。 - AI提议将猫与爬山主题结合,并重点建议了“共同续写”方式(例如一只爱爬山的猫,或爬山时遇到神秘的猫),以及AI可以先讲一个《喜欢爬山的猫》的故事作为开场。 - AI询问用户:是否先从用户的想法开始,还是由AI先抛砖引玉。用户尚未做出最终选择,仍处于互动方式确认阶段。 ## ARTIFACTS None ## NEXT STEPS 等待花哥从AI的最新提议中做出选择:是用户自己提供开头共同续写,还是由AI先讲一个关于喜欢爬山的猫的故事。 ================================ Human Message ================================= 你还记得我吗? ================================== Ai Message ================================== 哈哈,当然记得你,花哥!你爱讲故事、爱猫、爱爬山,咱们正一起琢磨怎么把这三样有意思的事串起来呢~现在请你做个决定——咱们是 **“你来开头,我来续”**(你给个关于猫与爬山的故事开头),还是 **“我来抛砖引玉”**(我先讲一个《喜欢爬山的猫》的故事)?选一个,咱们这就开讲!🐱⛰️- 由于我们之前进行了4轮对话,也就是产生了8条历史对话(一问一答),此时超出了我们设置的trigger(历史对话超过3条就进行总结摘要)
- 响应结果中,我们能看到Here is a summary of the conversation to date字样,此时说明的确触发了消息摘要
- 从最后一行,确实能看出,大模型的确通过消息摘要,记住了我们之前说的四个因素。
以上就是本篇文章的全部内容,喜欢的话可以留个免费的关注呦~~~
