LangGraph Memory记忆系统:告别”金鱼脑”
你有没有遇到过这种情况:跟AI助手聊了半天,它突然忘了你叫啥?这就是典型的”金鱼脑”——七秒记忆,刚说完就忘。
今天咱们来聊聊 LangGraph 的 Memory 记忆系统,让你的 AI Agent 真正拥有”记性”。
引言:为什么需要记忆? 想象一下你走进一家咖啡店,店员问你要喝什么。你说:”来杯拿铁,少糖。”店员记下了。你又说:”哦对了,用燕麦奶。”店员点头。接着你补充:”还有,我叫小明,是老顾客了,上次存的积分记得用上。”
如果这位店员是个”金鱼脑”,会发生什么?
你刚说完燕麦奶,他就忘了你要的是拿铁
你说积分的时候,他问你:”您叫什么名字来着?”
等你坐下来,他又来问:”先生您要点什么?”
这就是没有记忆系统的 AI Agent 的写照。
短期记忆 vs 长期记忆 人类记忆分两种:
短期记忆(Short-term Memory)
临时保存当前对话的信息
容量有限,通常 5-9 个项目
持续几分钟到几小时
例如:记住对话中提到的名字、偏好
长期记忆(Long-term Memory)
持久保存重要信息
容量几乎无限
可以保存数天、数月甚至永久
例如:用户的名字、常用偏好、历史对话
在 AI Agent 中,这两种记忆分别对应:
记忆类型
LangGraph 实现
使用场景
短期记忆
InMemorySaver
开发调试、单次对话
长期记忆
SqliteSaver / PostgresSaver
生产环境、持久化存储
第一部分:InMemorySaver——开发者的”草稿纸” 什么是 InMemorySaver? InMemorySaver 是 LangGraph 提供的最简单的记忆存储方式。顾名思义,它把记忆存在内存 里。
优点:
配置简单,一行代码搞定
速度快,内存读写效率极高
不需要额外依赖
缺点:
程序重启,记忆归零
无法跨进程共享
不适合生产环境
代码示例:第一个”有记忆”的 Agent 让我们从一个最简单的例子开始——一个能记住用户偏好的助手:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 from langgraph.graph import StateGraph, MessagesState, START, ENDfrom langgraph.checkpoint.memory import InMemorySaverfrom langchain_openai import ChatOpenAImemory = InMemorySaver() model = ChatOpenAI(model="gpt-4o-mini" ) def call_model (state: MessagesState ): response = model.invoke(state["messages" ]) return {"messages" : [response]} builder = StateGraph(MessagesState) builder.add_node("agent" , call_model) builder.add_edge(START, "agent" ) builder.add_edge("agent" , END) graph = builder.compile (checkpointer=memory) config = {"configurable" : {"thread_id" : "conversation_1" }} response1 = graph.invoke( {"messages" : [{"role" : "user" , "content" : "你好,我叫小明" }]}, config ) print ("AI:" , response1["messages" ][-1 ].content)response2 = graph.invoke( {"messages" : [{"role" : "user" , "content" : "我叫什么名字?" }]}, config ) print ("AI:" , response2["messages" ][-1 ].content)
运行结果:
1 2 AI: 你好小明!很高兴认识你。 AI: 你叫小明呀,我记得的。
见证奇迹的时刻! 第二轮对话中,AI 没有问你”你是谁”,而是准确地记住了你叫小明。这就是记忆系统的魔力。
关键概念:thread_id 你可能注意到代码里的 thread_id="conversation_1"。这是什么意思?
thread_id 是记忆的”身份证” 。每个唯一的 thread_id 对应一份独立的记忆空间。
1 2 3 4 5 config_a = {"configurable" : {"thread_id" : "user_a_chat" }} config_b = {"configurable" : {"thread_id" : "user_b_chat" }}
这样,用户 A 和用户 B 的记忆是隔离的,互不干扰。
可视化记忆状态 想知道当前记忆里存了什么?可以用 get_state 方法:
1 2 3 4 5 state = graph.get_state(config) print ("消息历史:" , len (state.values["messages" ]), "条" )for msg in state.values["messages" ]: print (f"- {msg.type } : {msg.content[:50 ]} ..." )
输出示例:
1 2 3 4 5 消息历史: 4 条 - human: 你好,我叫小明 - ai: 你好小明!很高兴认识你。 - human: 我叫什么名字? - ai: 你叫小明呀,我记得的。
第二部分:SqliteSaver——轻量级持久化 为什么需要持久化? 用 InMemorySaver 开发调试没问题,但生产环境有两个致命问题:
程序重启,记忆丢失 ——用户明天再聊,AI 又成陌生人了
多进程无法共享 ——API 服务通常多个 worker,每个 worker 的记忆是独立的
解决方案:持久化存储。
SqliteSaver 登场 SqliteSaver 使用 SQLite 数据库存储记忆,解决了上述两个问题:
优点:
数据持久化,重启不丢
单文件数据库,零配置
支持多进程读写
轻量级,无需额外服务
缺点:
并发性能不如 PostgreSQL
不适合超大规模数据
代码示例:持久化的记忆 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from langgraph.checkpoint.sqlite import SqliteSaverfrom langgraph.graph import StateGraph, MessagesState, START, ENDfrom langchain_openai import ChatOpenAIimport sqlite3conn = sqlite3.connect("chat_memory.db" , check_same_thread=False ) memory = SqliteSaver(conn) model = ChatOpenAI(model="gpt-4o-mini" ) def call_model (state: MessagesState ): response = model.invoke(state["messages" ]) return {"messages" : [response]} builder = StateGraph(MessagesState) builder.add_node("agent" , call_model) builder.add_edge(START, "agent" ) builder.add_edge("agent" , END) graph = builder.compile (checkpointer=memory) config = {"configurable" : {"thread_id" : "persistent_chat_1" }} response = graph.invoke( {"messages" : [{"role" : "user" , "content" : "记住,我的最爱是抹茶拿铁" }]}, config ) print ("AI:" , response["messages" ][-1 ].content)
关键区别: 现在关闭程序,重新运行,AI 依然记得你的最爱是抹茶拿铁。
查看 SQLite 数据库 好奇数据是怎么存的?用命令行看看:
1 2 3 4 5 sqlite3 chat_memory.db ".tables" sqlite3 chat_memory.db "SELECT * FROM checkpoints;"
进阶:异步支持 生产环境通常使用异步,LangGraph 也提供了异步版本:
1 2 3 4 5 6 from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaverasync with AsyncSqliteSaver.from_conn_string("chat_memory.db" ) as memory: graph = builder.compile (checkpointer=memory)
第三部分:实战——认识你的”老朋友” 现在让我们做一个更实用的例子:一个能记住用户偏好的个性化助手。
场景设定 想象一个咖啡订购助手,它需要记住:
完整代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 from langgraph.graph import StateGraph, MessagesState, START, ENDfrom langgraph.checkpoint.sqlite import SqliteSaverfrom langchain_openai import ChatOpenAIfrom typing import TypedDict, Annotatedimport sqlite3import operatorclass CoffeeState (MessagesState ): user_name: str favorite_coffee: str preferences: Annotated[list , operator.add] conn = sqlite3.connect("coffee_memory.db" , check_same_thread=False ) memory = SqliteSaver(conn) model = ChatOpenAI(model="gpt-4o-mini" ) def understand_user (state: CoffeeState ): messages = state["messages" ] last_message = messages[-1 ].content extraction_prompt = f""" 分析用户的消息,提取以下信息(JSON格式): - user_name: 用户的名字(如果有) - favorite_coffee: 提到的咖啡种类(如果有) - preferences: 口味偏好(如少糖、燕麦奶等,数组格式) 如果某项不存在,设为 null。 用户消息:{last_message} """ extraction = model.invoke([ {"role" : "system" , "content" : "你是一个信息提取助手,只返回JSON。" }, {"role" : "user" , "content" : extraction_prompt} ]) import json try : info = json.loads(extraction.content) updates = {} if info.get("user_name" ): updates["user_name" ] = info["user_name" ] if info.get("favorite_coffee" ): updates["favorite_coffee" ] = info["favorite_coffee" ] if info.get("preferences" ): updates["preferences" ] = info["preferences" ] return updates except : return {} def generate_response (state: CoffeeState ): user_name = state.get("user_name" , "顾客" ) favorite = state.get("favorite_coffee" , "" ) prefs = state.get("preferences" , []) system_msg = f"""你是咖啡助手。当前服务顾客:{user_name} 已知信息:最爱{favorite if favorite else '待定' } ,偏好{', ' .join(prefs) if prefs else '无' } 语气友好,可以适当提及已知信息让顾客感到亲切。""" response = model.invoke( [{"role" : "system" , "content" : system_msg}] + state["messages" ] ) return {"messages" : [response]} builder = StateGraph(CoffeeState) builder.add_node("understand" , understand_user) builder.add_node("respond" , generate_response) builder.add_edge(START, "understand" ) builder.add_edge("understand" , "respond" ) builder.add_edge("respond" , END) graph = builder.compile (checkpointer=memory) config = {"configurable" : {"thread_id" : "coffee_lover_123" }} print ("=== 第一次对话 ===" )result = graph.invoke( {"messages" : [{"role" : "user" , "content" : "你好,我是张伟,喜欢喝美式" }]}, config ) print ("AI:" , result["messages" ][-1 ].content)print ("\n=== 第二次对话(重启程序后)===" )conn2 = sqlite3.connect("coffee_memory.db" , check_same_thread=False ) memory2 = SqliteSaver(conn2) graph2 = builder.compile (checkpointer=memory2) result2 = graph2.invoke( {"messages" : [{"role" : "user" , "content" : "今天想换换口味" }]}, config ) print ("AI:" , result2["messages" ][-1 ].content)print ("\n=== 第三次对话 ===" )result3 = graph2.invoke( {"messages" : [{"role" : "user" , "content" : "加点燕麦奶" }]}, config ) print ("AI:" , result3["messages" ][-1 ].content)print ("\n=== 第四次对话(检查是否记住所有信息)===" )result4 = graph2.invoke( {"messages" : [{"role" : "user" , "content" : "我是谁?我喜欢什么?" }]}, config ) print ("AI:" , result4["messages" ][-1 ].content)
运行结果示例 1 2 3 4 5 6 7 8 9 10 11 === 第一次对话 === AI: 你好张伟!欢迎光临,美式咖啡是个不错的选择,需要我为你准备一杯吗? === 第二次对话(重启程序后) === AI: 嗨张伟!今天想尝试点什么新口味呢?你平时最喜欢美式,要不要试试我们的冷萃或者加点风味糖浆? === 第三次对话 === AI: 没问题!美式加燕麦奶,这是个健康的搭配。需要加糖吗? === 第四次对话(检查是否记住所有信息) === AI: 你是张伟呀!你喜欢喝美式咖啡,今天还加了燕麦奶。需要我现在为你做一杯吗?
看到没有? 即使程序重启,AI 依然记得:
这就是持久化记忆的威力!
第四部分:记忆管理——什么时候该清空? 记忆不是越多越好 想象你的房间——东西越多,找东西越难。AI 的记忆也一样:
对话太长 :Token 消耗增加,成本上升
信息过多 :模型可能”迷失”在信息中
旧信息干扰 :三年前的偏好可能不再适用
策略一:限制对话长度 1 2 3 4 5 6 7 8 9 def trim_messages (messages, max_count=10 ): """只保留最近 N 条消息""" return messages[-max_count:] def call_model (state: MessagesState ): trimmed = trim_messages(state["messages" ], 10 ) response = model.invoke(trimmed) return {"messages" : [response]}
策略二:总结压缩 当对话太长时,可以总结历史,只保留关键信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from langchain_core.messages import SystemMessagedef summarize_history (messages ): """将长对话总结为关键信息""" summary_prompt = f""" 总结以下对话的关键信息(用户偏好、重要事实): {messages} 用一句话概括。 """ summary = model.invoke([{"role" : "user" , "content" : summary_prompt}]) return SystemMessage(content=f"[历史摘要] {summary.content} " ) if len (state["messages" ]) > 20 : summary = summarize_history(state["messages" ][:15 ]) new_messages = [summary] + state["messages" ][15 :]
策略三:清空记忆 有时候,用户就是想”重新开始”。
1 2 3 4 5 new_config = {"configurable" : {"thread_id" : "conversation_new" }} memory.delete(thread_id="conversation_old" )
策略四:定期归档 对于需要长期保存但不常访问的记忆,可以定期归档到外部存储:
1 2 3 4 5 def archive_old_memories (thread_id, days=30 ): """将旧记忆归档到外部存储""" old_state = memory.get(thread_id)
总结与下篇预告 今天学了什么?
为什么需要记忆 ——没有记忆的 AI 就像金鱼,七秒就忘
短期记忆 InMemorySaver ——开发调试的好帮手
长期记忆 SqliteSaver ——轻量级持久化方案
实战应用 ——记住用户偏好的咖啡助手
记忆管理 ——清空、限制、总结,避免记忆过载
记忆系统的选择指南
场景
推荐方案
理由
开发调试
InMemorySaver
简单快捷,零配置
单机应用
SqliteSaver
持久化,开箱即用
生产服务
PostgresSaver
高并发,可扩展
分布式系统
RedisSaver
共享状态,低延迟
下篇预告 《LangGraph 状态管理:打造复杂多轮对话 Agent》
我们将深入探讨:
如何设计复杂的状态结构
条件边(Conditional Edges)的高级用法
人机协作:Human-in-the-loop 模式
子图(Subgraphs):模块化你的 Agent
思考题: 如果你要做一个”心理咨询师 AI”,需要记住用户的哪些信息?是越详细越好吗?欢迎在评论区讨论。
延伸阅读:
文章首发于 Channing’s AI Lab ,转载请联系作者。