09 - Memory — Agent的记忆系统!从短期记忆到长期记忆,一篇搞定
适合前后端/测试等有编程基础的同学,手把手教你让AI拥有“记忆”
前言
通过前两节课的学习,我们已经掌握了LangChain的Model I/O(统一调用大模型)和Chain(构建执行流程)。现在你的AI应用已经能“听懂话”、“会干活”了。
但它有一个致命的问题——鱼的记忆。
回想一下第6节课我们讲过的内容:大模型的API是无状态的,每次调用都是独立的,它完全不记得上一次聊了什么。
在第6节课,我们手动封装了一个MemoryChat类来管理对话历史。现在进入LangChain的世界,我们要用更优雅、更强大的方式来解决这个问题。
这就是今天的主角——Memory(记忆系统)。
一句话定义:LangChain的Memory组件通过结构化的方式存储、管理对话历史,让AI具备“记忆能力”。
全文约6000字,代码均可直接复制运行。
一、为什么AI需要记忆?
1.1 无状态API的本质
大模型的API调用是无状态的——每次请求都是独立的,模型不会主动记住任何之前的信息。
问题重现:
# 第一轮对话response1=llm.invoke("我叫小明")print(response1.content)# "你好小明!很高兴认识你。"# 第二轮对话(独立请求)response2=llm.invoke("我叫什么名字?")print(response2.content)# "抱歉,我不知道你的名字。"AI完全“失忆”了。这让多轮对话变得极其尴尬。
1.2 Memory的核心作用
LangChain的Memory组件通过两个核心动作解决这个问题:
| 动作 | 说明 |
|---|---|
| 存储(Save) | 将每一轮的用户输入和AI输出保存到指定存储介质(内存、数据库等) |
| 提取(Load) | 新一轮对话时,从存储介质中提取历史对话,注入到Prompt中供LLM参考 |
Memory能帮我们解决的实际问题:
- 避免重复提问:你说过“我叫小明”,之后AI能直接叫出你的名字
- 支撑复杂任务:让AI“先梳理需求,再生成方案”,它能记住中间结果
- 简化交互:不用每次提问都把前因后果说一遍
1.3 短期记忆 vs 长期记忆
LangChain的记忆系统分为两个层级:
| 记忆类型 | 作用范围 | 存储方式 | 典型场景 |
|---|---|---|---|
| 短期记忆(Short-term Memory) | 当前会话/线程内 | 内存/检查点 | 多轮对话、跟踪对话状态 |
| 长期记忆(Long-term Memory) | 跨会话、跨用户 | 向量数据库/持久化存储 | 记住用户偏好、历史知识 |
💡通俗理解:短期记忆就像你正在进行的聊天——聊完就忘了;长期记忆就像你的通讯录——每次打开都能找到。
二、LangChain的四种核心记忆类型
LangChain提供了多种Memory实现,适用于不同场景。下面我们逐一学习最常用的四种。
2.1 ConversationBufferMemory — 全量记忆
核心机制:保存所有对话历史,像一个不断增长的记事本。
优点:实现简单,信息完整无丢失。
缺点:对话增长会导致Token消耗巨大。
适用场景:对话轮次少、内容短的场景。
fromlangchain.memoryimportConversationBufferMemory# 初始化记忆memory=ConversationBufferMemory()# 保存对话上下文memory.save_context({"input":"你好啊,AI小助手。"},{"output":"嗨!有什么我可以帮助你的?"})memory.save_context({"input":"我叫小明"},{"output":"你好小明!很高兴认识你。"})# 加载记忆history=memory.load_memory_variables({})print(history)# 输出: {'history': 'Human: 你好啊,AI小助手。\nAI: 嗨!有什么我可以帮助你的?\nHuman: 我叫小明\nAI: 你好小明!很高兴认识你。'}# 也可以直接获取消息对象列表messages=memory.chat_memory.messagesprint(messages)# 输出: [HumanMessage(content='你好啊,AI小助手。'), AIMessage(content='嗨!有什么我可以帮助你的?'), ...]与Chain结合使用:
fromlangchain.chainsimportLLMChainfromlangchain.promptsimportPromptTemplatefromlangchain.memoryimportConversationBufferMemory# 定义Prompt模板(注意:变量名必须是"history")template=""" 你是一个友好的AI助手。 对话历史: {history} 人类:{input} AI:"""prompt=PromptTemplate(input_variables=["history","input"],template=template)# 创建带记忆的Chainmemory=ConversationBufferMemory()chain=LLMChain(llm=llm,prompt=prompt,memory=memory,verbose=True# 开启可以看到内部执行过程)# 多轮对话print(chain.predict(input="你好,我叫小明"))print(chain.predict(input="我叫什么名字?"))# AI会记得!2.2 ConversationBufferWindowMemory — 窗口记忆
核心机制:只保留最近K轮对话,像一个滑动窗口。
优点:有效控制上下文长度,Token消耗可控。
缺点:会丢失窗口外的历史信息。
适用场景:只需要近期对话上下文的场景。
fromlangchain.memoryimportConversationBufferWindowMemory# k=2 表示只保留最近2轮对话(每轮=1次Human+1次AI)memory=ConversationBufferWindowMemory(k=2)memory.save_context({"input":"第1轮:你好"},{"output":"你好!"})memory.save_context({"input":"第2轮:我叫小明"},{"output":"你好小明!"})memory.save_context({"input":"第3轮:我喜欢编程"},{"output":"太好了!"})# 加载记忆——只会看到最近2轮history=memory.load_memory_variables({})print(history['history'])# 输出只包含第2轮和第3轮,第1轮已被遗忘2.3 ConversationSummaryMemory — 摘要记忆
核心机制:调用LLM将历史对话总结为摘要,用摘要替代完整历史。
优点:大幅压缩Token消耗。
缺点:摘要可能导致细节丢失。
适用场景:长对话且关注整体脉络的场景。
fromlangchain.memoryimportConversationSummaryMemory# 初始化摘要记忆(需要传入LLM用于生成摘要)memory=ConversationSummaryMemory(llm=llm)# 多轮对话后,记忆会自动生成摘要memory.save_context({"input":"你好,我叫小明,是一名软件工程师"},{"output":"你好小明!很高兴认识你。"})memory.save_context({"input":"我主要做后端开发,用Python"},{"output":"Python后端开发很有前景!"})memory.save_context({"input":"最近在学习AI Agent"},{"output":"AI Agent是当前最热门的方向!"})# 加载记忆——看到的是摘要而不是完整对话history=memory.load_memory_variables({})print(history['history'])# 输出类似: "小明是一名软件工程师,专注于Python后端开发,目前正在学习AI Agent。"2.4 VectorStoreRetrieverMemory — 向量检索记忆
核心机制:将对话存入向量数据库,按语义相关性检索最相关的记忆。
优点:能从超长历史中检索关键信息,支持跨会话记忆。
缺点:依赖向量数据库,实现较复杂。
适用场景:对话历史极长,需要根据语义检索关键记忆的场景。
fromlangchain.memoryimportVectorStoreRetrieverMemoryfromlangchain.vectorstoresimportChromafromlangchain.embeddingsimportOpenAIEmbeddings# 1. 创建向量数据库embedding=OpenAIEmbeddings()vectorstore=Chroma(collection_name="conversation_memory",embedding_function=embedding)# 2. 创建VectorStoreRetrieverMemory# retriever会检索最相关的top-k条记忆memory=VectorStoreRetrieverMemory(retriever=vectorstore.as_retriever(search_kwargs={"k":3}),memory_key="history"# 注入Prompt时的变量名)# 3. 保存记忆(自动向量化存储)memory.save_context({"input":"我喜欢看科幻电影,特别是《星际穿越》"},{"output":"《星际穿越》是一部经典!"})memory.save_context({"input":"我住在北京,经常去朝阳公园跑步"},{"output":"朝阳公园环境很好,适合跑步"})memory.save_context({"input":"我是Python开发者,有5年经验"},{"output":"5年Python经验很资深了!"})# 4. 检索相关记忆(基于语义相似度,而非关键词匹配)# 问"推荐一部科幻片" → 自动检索到关于《星际穿越》的记忆relevant=memory.load_memory_variables({"input":"推荐一部科幻片"})print(relevant['history'])# 输出包含《星际穿越》相关的记忆三、四种记忆类型对比总结
| 记忆类型 | 核心机制 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| ConversationBufferMemory | 保存全部对话 | 实现简单,信息完整 | Token消耗巨大 | 短对话 |
| ConversationBufferWindowMemory | 只保留最近K轮 | 控制Token消耗 | 丢失窗口外历史 | 只需近期上下文 |
| ConversationSummaryMemory | LLM生成摘要 | 大幅压缩Token | 细节可能丢失 | 长对话、关注整体脉络 |
| VectorStoreRetrieverMemory | 向量检索相关记忆 | 支持超长历史、语义检索 | 实现复杂,需向量库 | 极长对话、跨会话记忆 |
四、实战:构建带记忆的“智能客服Agent”
把前面学的知识综合起来,构建一个真正能用的智能客服Agent。
需求:
- 用户首次对话时,Agent收集用户信息(姓名、偏好)
- Agent能记住这些信息,在后续对话中调用
- 使用
ConversationSummaryMemory管理长对话
fromlangchain_openaiimportChatOpenAIfromlangchain.chainsimportLLMChainfromlangchain.promptsimportPromptTemplatefromlangchain.memoryimportConversationSummaryMemory# 1. 初始化模型llm=ChatOpenAI(model="deepseek-v4-flash",api_key="你的Key",base_url="https://api.deepseek.com",temperature=0.7)# 2. 定义系统Prompttemplate=""" 你是一位专业的智能客服助手,名叫"小智"。 你的职责是耐心、友好地回答用户的问题,并记住用户告诉你的所有信息。 以下是对话摘要(历史记忆): {history} 人类:{input} 小智:"""prompt=PromptTemplate(input_variables=["history","input"],template=template)# 3. 创建带摘要记忆的Chainmemory=ConversationSummaryMemory(llm=llm,memory_key="history")chain=LLMChain(llm=llm,prompt=prompt,memory=memory,verbose=False)# 4. 模拟多轮对话defchat_with_agent(user_input):response=chain.predict(input=user_input)returnresponse# 测试对话print("客服小智:您好!我是智能客服小智,有什么可以帮您?")# 第一轮:用户自我介绍user1="你好,我叫王芳,想咨询一下你们的产品"print(f"用户:{user1}")print(f"小智:{chat_with_agent(user1)}\n")# 第二轮:用户问具体问题(Agent应该记得用户的名字)user2="我上次问的那个智能手表,现在有优惠吗?"print(f"用户:{user2}")print(f"小智:{chat_with_agent(user2)}\n")# 第三轮:继续追问(Agent应该记得之前的对话脉络)user3="好的,那帮我下单一个吧"print(f"用户:{user3}")print(f"小智:{chat_with_agent(user3)}\n")# 查看当前记忆摘要print("--- 当前记忆摘要 ---")print(memory.load_memory_variables({})['history'])运行效果:
客服小智:您好!我是智能客服小智,有什么可以帮您? 用户:你好,我叫王芳,想咨询一下你们的产品 小智:您好王芳!很高兴为您服务。请问您想了解我们哪款产品呢? 用户:我上次问的那个智能手表,现在有优惠吗? 小智:王芳您好!您之前问的智能手表现在确实有优惠活动,目前打8折。 用户:好的,那帮我下单一个吧 小智:好的王芳!我这就帮您下单,请问您需要什么颜色的? --- 当前记忆摘要 --- 王芳是一位咨询产品的客户,她询问了智能手表,并决定下单购买。Agent成功记住了用户的名字“王芳”以及之前的对话脉络!
五、生产环境最佳实践
5.1 会话隔离:使用session_id
在实际生产中,你需要为每个用户/会话维护独立的记忆,而不是所有用户共享一个记忆。
# 用字典管理多个会话的记忆session_memories={}defget_memory_for_session(session_id):ifsession_idnotinsession_memories:session_memories[session_id]=ConversationBufferMemory()returnsession_memories[session_id]# 每个用户使用自己的session_iduser1_memory=get_memory_for_session("user_001")user2_memory=get_memory_for_session("user_002")5.2 持久化存储
默认的Memory只存在内存中,程序重启就丢失了。生产环境需要持久化。
# 使用Redis存储对话历史(示例)importredisfromlangchain.memoryimportConversationBufferMemoryfromlangchain.memory.chat_message_historiesimportRedisChatMessageHistory# 使用Redis作为存储后端history=RedisChatMessageHistory(session_id="user_001",url="redis://localhost:6379/0")memory=ConversationBufferMemory(chat_memory=history,return_messages=True)5.3 Token消耗监控
使用tiktoken监控Token消耗,防止超出上下文窗口。
importtiktokendefcount_tokens(text,model="cl100k_base"):encoding=tiktoken.get_encoding(model)returnlen(encoding.encode(text))# 在每次调用前检查history=memory.load_memory_variables({})['history']token_count=count_tokens(history)print(f"当前记忆消耗Token:{token_count}")iftoken_count>100000:# 超过阈值print("警告:记忆过长,考虑切换到SummaryMemory")5.4 记忆类型选择决策树
对话轮次少(<10轮)? → 是 → ConversationBufferMemory(简单够用) → 否 ↓ 需要记住所有细节? → 是 → ConversationBufferWindowMemory(控制窗口大小) → 否 ↓ 关注整体脉络而非细节? → 是 → ConversationSummaryMemory(摘要压缩) → 否 ↓ 对话极长、需要语义检索? → 是 → VectorStoreRetrieverMemory(向量检索)六、实战小练习(作业)
练习:构建一个“个性化美食推荐Agent”
需求:
- Agent能记住用户的基本信息(姓名、口味偏好、忌口)
- 用户每次问“推荐餐厅”时,Agent基于记忆中的偏好进行推荐
- 使用
ConversationSummaryMemory管理记忆 - 模拟至少5轮对话,验证Agent能记住跨轮次的信息
提示代码框架:
fromlangchain.memoryimportConversationSummaryMemory# 1. 初始化模型和记忆llm=ChatOpenAI(...)memory=ConversationSummaryMemory(llm=llm,memory_key="history")# 2. 定义Prompt模板(包含记忆和历史)template=""" 你是一位美食推荐专家。 对话摘要: {history} 用户:{input} 专家:"""prompt=PromptTemplate(...)# 3. 创建Chainchain=LLMChain(llm=llm,prompt=prompt,memory=memory)# 4. 多轮对话测试# 第1轮:用户自我介绍 + 口味偏好# 第2轮:推荐餐厅(基于偏好)# 第3轮:用户补充信息(忌口)# 第4轮:再次推荐(基于更新后的偏好)# 第5轮:验证Agent记住了所有信息结语
今天这节课,我们全面学习了LangChain的Memory记忆系统:
| 知识点 | 核心内容 |
|---|---|
| 为什么需要Memory | 大模型无状态,需要记忆组件维持对话连贯性 |
| 短期 vs 长期记忆 | 短期记忆管理当前会话,长期记忆跨会话持久化 |
| ConversationBufferMemory | 全量保存,适合短对话 |
| ConversationBufferWindowMemory | 滑动窗口,控制Token消耗 |
| ConversationSummaryMemory | LLM生成摘要,适合长对话 |
| VectorStoreRetrieverMemory | 向量检索,支持超长历史和语义检索 |
| 生产最佳实践 | 会话隔离、持久化存储、Token监控 |
下节课(第10节),我们将进入LangChain的另一个核心模块——Retrieval(检索),学习如何让Agent连接外部知识库,构建RAG(检索增强生成)应用!
如果觉得有帮助,欢迎点赞、收藏、评论三连!我们下节课见!
📌 本文是《AI Agent开发实战》课程第9节的完整内容,系列文章持续更新中,关注我不迷路!
