☰
【三个月 AI Agent 实战学习】Day 30:对话中的短期记忆管理 —— 让 Agent 记住上下文
2026/10/8 14:25:30 网站建设 项目流程

Day 30:对话中的短期记忆管理 —— 让 Agent 记住上下文

欢迎来到第三十天!在构建多轮对话的 Agent 时,如何有效管理对话历史是一个核心问题。如果将所有历史消息原封不动地发送给模型,Token 消耗会迅速膨胀,甚至超出上下文窗口。今天我们将学习两种经典的短期记忆管理策略:Buffer Memory(缓冲记忆)和Summary Memory(摘要记忆),并通过实验对比它们在 Token 消耗和信息保留方面的差异。这将为后续构建带长期记忆的客服 Agent 打下基础。


一、今日学习目标

  1. 理解短期记忆在多轮对话中的重要性:模型需要记住之前的对话内容才能做出连贯回复。
  2. 掌握 LangChain 中ConversationBufferMemory和ConversationSummaryMemory的使用方法。
  3. 学会在对话链中集成记忆组件,实现有状态的聊天机器人。
  4. 通过实验对比不同记忆策略的 Token 消耗和信息保留能力。
  5. 了解如何手动管理消息列表(裁剪、摘要)以实现自定义的短期记忆。

二、详细实现步骤

步骤 1:理解短期记忆的本质

在纯 API 调用中,多轮对话是通过维护一个消息列表(messages)来实现的。每轮对话时,需要将完整的历史消息(或适当裁剪后的历史)发送给模型。短期记忆管理的核心问题就是:如何存储和利用对话历史,同时控制 Token 消耗。

LangChain 提供了多种记忆组件,可以自动管理这些历史消息,并与链无缝集成。今天重点介绍两种:

  • ConversationBufferMemory:简单地将所有历史消息保存在缓冲区中,每轮对话时完整地发送给模型。优点是信息完整,缺点是 Token 消耗随对话轮数线性增长,容易超出上下文窗口。
  • ConversationSummaryMemory:使用 LLM 将历史对话压缩成一段简短的摘要,每轮只发送摘要而不是完整历史。优点是 Token 消耗稳定,缺点是会丢失细节信息。
步骤 2:环境准备

确保已安装langchain、langchain-openai。如果尚未安装,执行:

pipinstalllangchain langchain-openai

新建short_term_memory_demo.py,导入所需模块:

importosfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromlangchain.memoryimportConversationBufferMemory,ConversationSummaryMemoryfromlangchain.chainsimportConversationChainfromlangchain_core.promptsimportChatPromptTemplate,MessagesPlaceholder load_dotenv()llm=ChatOpenAI(model="deepseek-chat",api_key=os.getenv("DEEPSEEK_API_KEY"),base_url="https://api.deepseek.com",temperature=0.7)
步骤 3:使用 ConversationBufferMemory

ConversationBufferMemory会存储所有对话消息。我们可以将其插入到一个带有MessagesPlaceholder的提示词模板中,然后构建ConversationChain。

# 创建缓冲记忆buffer_memory=ConversationBufferMemory(return_messages=True)# 定义提示词模板,包含历史消息占位符prompt_template=ChatPromptTemplate.from_messages([("system","你是一个友好的聊天机器人。"),MessagesPlaceholder(variable_name="history"),("human","{input}")])# 创建对话链buffer_chain=ConversationChain(llm=llm,memory=buffer_memory,prompt=prompt_template,verbose=True# 打印详细过程,便于观察)# 进行几轮对话print("===== ConversationBufferMemory 对话 =====")response1=buffer_chain.invoke({"input":"我叫小明,我喜欢打篮球。"})print("AI:",response1["response"])response2=buffer_chain.invoke({"input":"你还记得我叫什么吗?"})print("AI:",response2["response"])response3=buffer_chain.invoke({"input":"我刚才说我喜欢什么运动?"})print("AI:",response3["response"])

观察输出:模型能够准确记住用户的名字和喜好。同时,verbose=True会打印每次发送给模型的完整提示词,你会看到历史消息被完整地包含在history中。

步骤 4:检查 Token 消耗

我们可以通过回调或手动查看消息长度来估算 Token 消耗。为了简单,我们可以在对话后访问buffer_memory.chat_memory.messages查看历史消息。

print("\n缓冲记忆中的消息数:",len(buffer_memory.chat_memory.messages))formsginbuffer_memory.chat_memory.messages:print(f"{msg.type}:{msg.content}")

随着对话轮数增加,消息列表会越来越长,Token 消耗也随之增加。

步骤 5:使用 ConversationSummaryMemory

ConversationSummaryMemory会使用 LLM 将历史对话总结成一段摘要,并在后续对话中只发送摘要,而不是完整历史。

# 创建摘要记忆summary_memory=ConversationSummaryMemory(llm=llm,return_messages=True,max_token_limit=100# 摘要的最大 Token 数(可选))# 定义提示词模板(历史部分使用摘要)summary_prompt_template=ChatPromptTemplate.from_messages([("system","你是一个友好的聊天机器人。"),MessagesPlaceholder(variable_name="history"),("human","{input}")])summary_chain=ConversationChain(llm=llm,memory=summary_memory,prompt=summary_prompt_template,verbose=True)# 进行同样的对话print("\n===== ConversationSummaryMemory 对话 =====")response1=summary_chain.invoke({"input":"我叫小明,我喜欢打篮球。"})print("AI:",response1["response"])response2=summary_chain.invoke({"input":"你还记得我叫什么吗?"})print("AI:",response2["response"])response3=summary_chain.invoke({"input":"我刚才说我喜欢什么运动?"})print("AI:",response3["response"])

观察输出:模型仍然能够回答正确,因为摘要保留了关键信息(名字和爱好)。但此时发送给模型的历史部分不再是完整的对话,而是一段摘要文本。你可以通过verbose=True看到提示词中的history是一段总结,而不是多条消息。

步骤 6:对比 Token 消耗

我们可以粗略计算两种记忆策略下的 Token 消耗。由于verbose=True会打印提示词,但为了自动化,我们可以访问记忆中的内容并计算 Token。

# 计算缓冲记忆的总 Token(近似)fromlangchain_core.messagesimportHumanMessage,AIMessageimporttiktoken enc=tiktoken.get_encoding("cl100k_base")defcount_tokens_in_messages(messages):total=0formsginmessages:total+=len(enc.encode(msg.content))returntotal buffer_tokens=count_tokens_in_messages(buffer_memory.chat_memory.messages)print(f"\n缓冲记忆中的 Token 数:{buffer_tokens}")# 查看摘要记忆中的历史(通常是摘要字符串)summary_history=summary_memory.chat_memory.messages summary_tokens=count_tokens_in_messages(summary_history)print(f"摘要记忆中的 Token 数:{summary_tokens}")

通常,摘要记忆的 Token 数远小于缓冲记忆,特别是在多轮对话后。

步骤 7:手动实现对话历史裁剪(可选进阶)

除了使用 LangChain 的记忆组件,你也可以手动管理消息列表,实现更灵活的裁剪策略。例如,只保留最近 N 条消息,或者当 Token 超过阈值时自动总结前文。

以下是一个简单的手动裁剪函数:

deftrim_messages(messages,max_tokens=2000):"""保留最近的消息,使总 Token 数不超过 max_tokens"""enc=tiktoken.get_encoding("cl100k_base")total=0trimmed=[]# 从后往前累加,直到接近上限formsginreversed(messages):msg_tokens=len(enc.encode(msg.content))iftotal+msg_tokens>max_tokens:breaktrimmed.insert(0,msg)total+=msg_tokensreturntrimmed

你可以使用这个函数在每轮对话前裁剪消息列表,控制 Token 消耗。


三、常见问题与调试

Q1:ConversationBufferMemory在多轮对话后 Token 超限怎么办?
→ 可以使用ConversationBufferWindowMemory(只保留最近 K 轮对话),或者使用摘要记忆。也可以手动实现滑动窗口裁剪。

Q2:ConversationSummaryMemory的摘要是否准确?
→ 摘要质量取决于 LLM 的能力和max_token_limit的设置。如果摘要过于简短,可能会丢失重要细节。你可以调整max_token_limit或使用自定义摘要提示词。

Q3:如何同时保留最近几轮完整对话和早期摘要?
→ 可以使用混合策略:保留最近 N 轮完整消息,同时对更早的消息进行摘要。这需要自定义记忆组件,但 LangChain 的ConversationSummaryBufferMemory正是为此设计,它结合了缓冲和摘要,保留最近的消息并总结更早的内容。

Q4:记忆组件在多用户场景下如何处理?
→ 每个用户应有独立的记忆实例。在实际应用中,可以使用数据库存储会话状态,根据用户 ID 加载对应的记忆。LangChain 提供了多种集成,如RedisChatMessageHistory、SQLChatMessageHistory等,可以将消息持久化到外部存储。

Q5:在 Agent 中,记忆是如何与工具调用结合的?
→ 在后续课程中,我们会使用AgentExecutor结合记忆组件,使 Agent 在多轮对话中记住之前的工具调用结果和用户上下文。通常做法是将记忆作为memory参数传入AgentExecutor,或者在每次调用时手动将历史消息添加到输入中。

Q6:Token 计算不准确怎么办?
→ 使用tiktoken的cl100k_base编码对 DeepSeek 模型是近似估算。更精确的计算可以使用 DeepSeek 官方提供的 Token 计算工具(如果有)。在实际项目中,通常使用 API 返回的usage字段来精确统计。


四、今日总结与作业

今天你完成了:

  • ✅ 理解了短期记忆在多轮对话中的作用。
  • ✅ 使用ConversationBufferMemory实现了带记忆的聊天机器人。
  • ✅ 使用ConversationSummaryMemory实现了基于摘要的记忆,并对比了 Token 消耗。
  • ✅ 学习了手动裁剪消息列表的方法。
  • ✅ 了解了混合记忆策略(如ConversationSummaryBufferMemory)的概念。

今日作业(必做):

  1. 使用ConversationBufferWindowMemory(只保留最近 2 轮对话)重新实现聊天机器人,观察它在多轮对话中的表现,并计算 Token 消耗。
  2. 尝试使用ConversationSummaryBufferMemory,设置max_token_limit=50,进行多轮对话(至少 5 轮),观察摘要如何动态更新,以及模型能否记住早期信息。
  3. 编写一个函数manage_memory(messages, strategy, max_tokens),根据参数选择不同策略(buffer、summary、window)来管理消息列表,并返回处理后的消息列表。测试不同策略的效果。
  4. (思考题)在构建带记忆的客服 Agent 时,你会选择哪种记忆策略?为什么?请结合客服场景的特点(如需要记住用户订单号、历史投诉等)进行说明。

明日预告:我们将构建一个带记忆的聊天机器人,结合 RAG 和记忆组件,实现一个能记住用户偏好并基于知识库回答的智能助手。请做好准备!

有任何问题欢迎随时提问!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询