很多新手在开发 AI 应用时都会卡在一个地方:我的 AI 怎么老是“失忆”?明明上一句刚说过名字,下一句就忘了。
别急,LangGraph 早就帮你把“记忆”这件事安排得明明白白。
今天这篇干货,我用最通俗的大白话 + 每一行都能直接跑的代码,带你彻底搞懂短期记忆、长期记忆、消息修剪、对话总结。
只要你跟着敲一遍代码,以后任何 AI 记忆问题都难不倒你。
一、AI 的“记忆”到底是什么?一个生活例子你就懂了
想象你开了一家奶茶店,雇了一个机器人店员。
-
短期记忆
:就是机器人和同一个顾客在当前这一轮对话里,能记住顾客刚才说“我要一杯少冰的杨枝甘露”。只要对话不中断,它不会反复问你“要冰的吗?”
-
长期记忆
:哪怕顾客过了一个星期再次进店,机器人还记得“这位顾客上次说他喜欢少冰、多糖”。
在 LangGraph 里:
-
短期记忆
→ Checkpointer(检查点),按对话线程 ID 保存状态。
-
长期记忆
→ Store(存储),按用户 ID 或其他命名空间跨会话保存信息。
下面我们一个知识点一个知识点地手写代码 🔥
二、短期记忆(一):用内存实现“对话存档”——10 行核心代码搞定
先掌握最简单的 InMemorySaver,你就能明白短期记忆的底层原理。
2.1 代码前先讲人话(不着急看代码)
-
你定义一个状态(比如 messages 消息列表 + user_name 用户名)。
-
你画一张图(先问候节点 → 回复节点 → 结束)。
-
你编译图的时候,塞给它一个 checkpointer(检查点工具),同时给每个对话分配一个 thread_id。
-
之后每次调用 graph.invoke,只要 thread_id 一样,LangGraph 就会从数据库中把之前的状态读出来,自动合并新消息。
就像是打游戏时自动存档:你每次玩到某个进度,系统自动保存;下次你只要说“还是那个存档”,游戏就从那里继续。
2.2 完整代码(复制粘贴即可运行,注释多到“发指”)
# ---------- 1. 导入所需的工具 ----------
from typing import Annotated
from typing_extensions import TypedDict
import operator
# InMemorySaver 是短期记忆的“内存版”,适合开发调试
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
# ---------- 2. 定义状态(就是 AI 要记住的内容结构) ----------
class ChatState(TypedDict):
# messages 用了 operator.add,意思是每次新消息不是覆盖,而是追加到列表后面
messages: Annotated[list, operator.add]
user_name: str # 简单字段,直接覆盖保存
# ---------- 3. 定义图里的节点(每个节点就是一个执行步骤) ----------
def greeting_node(state: ChatState) -> dict:
"""第一步:打招呼"""
user_name = state.get("user_name", "访客")
print(f"[问候节点] 当前用户:{user_name}")
# 返回一条助手消息,会被追加到 messages 里
return {"messages": [("assistant", f"你好,{user_name}!我是你的 AI 助手。")]}
def respond_node(state: ChatState) -> dict:
"""第二步:根据用户最新消息回应"""
# 从已有的消息中找出所有用户发的消息
user_msgs = [msg for msg in state["messages"] if msg[0] == "user"]
if user_msgs:
last_msg = user_msgs[-1][1] # 最近一条用户消息的内容
user_name = state.get("user_name", "访客")
if "名字" in last_msg or "我叫" in last_msg:
reply = f"我知道你叫 {user_name},很高兴认识你!"
elif "天气" in last_msg:
reply = f"抱歉 {user_name},我查不到实时天气。"
else:
reply = f"我明白了,{user_name}。能多说一点吗?"
else:
reply = "我没有收到你的消息。"
return {"messages": [("assistant", reply)]}
# ---------- 4. 构建图 ----------
builder = StateGraph(ChatState)
builder.add_node("greeting", greeting_node)
builder.add_node("respond", respond_node)
builder.add_edge(START, "greeting")
builder.add_edge("greeting", "respond")
builder.add_edge("respond", END)
# ---------- 5. 关键:编译时加入 checkpointer ----------
memory = InMemorySaver() # 创建内存检查点
graph = builder.compile(checkpointer=memory)
# ---------- 6. 配置线程 ID —— 这是短期记忆的“身份证” ----------
config = {"configurable": {"thread_id": "chat_user_xiao_ming"}}
# ---------- 7. 第一轮对话:用户告诉 AI 名字 ----------
print("===== 第一轮对话 =====")
result1 = graph.invoke(
{"messages": [("user", "你好,我叫小明")], "user_name": "小明"},
config
)
for role, msg in result1["messages"]:
print(f"{role}: {msg}")
print("\n===== 第二轮对话(同一个线程,AI 还记得名字)=====")
result2 = graph.invoke(
{"messages": [("user", "你还记得我叫什么吗?")], "user_name": "小明"},
config
)
for role, msg in result2["messages"]:
print(f"{role}: {msg}")
# 试试用不同的 thread_id —— 那就是全新的对话
new_config = {"configurable": {"thread_id": "another_chat"}}
print("\n===== 新线程(不同 thread_id),AI 完全不认识 =====")
result3 = graph.invoke(
{"messages": [("user", "你还记得我叫什么吗?")], "user_name": "路人"},
new_config
)
for role, msg in result3["messages"]:
print(f"{role}: {msg}")
运行上面代码,你会看到:
-
同一 thread_id 下,第二轮 AI 会回答“我知道你叫小明”。
-
换一个 thread_id,AI 又是从头开始。
这就是短期记忆最核心的模式。生产环境里你不能只用内存(服务重启就丢了),所以下面我们换成 SQLite 数据库版。
三、短期记忆(二):用 SQLite 数据库——生产环境首选
SQLite 会把状态存成一个文件,你甚至可以把它放到服务器上,随时读取。
3.1 改动只有两处
-
导入 SqliteSaver 而不是 InMemorySaver
-
创建数据库连接,然后把 checkpointer 传给编译函数
完整代码(依然是复制即用):
import sqlite3
from typing import Annotated
from typing_extensions import TypedDict
import operator
# 注意:SqliteSaver 在 langgraph.checkpoint.sqlite 里
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import StateGraph, START, END
class ChatState(TypedDict):
messages: Annotated[list, operator.add]
user_name: str
def greeting_node(state: ChatState):
user_name = state.get("user_name", "访客")
return {"messages": [("assistant", f"嗨,{user_name}!我是你的数据库记忆助手。")]}
def respond_node(state: ChatState):
user_msgs = [m for m in state["messages"] if m[0] == "user"]
if not user_msgs:
return {"messages": [("assistant", "你说啥?")]}
last_msg = user_msgs[-1][1]
user_name = state.get("user_name", "访客")
if "名字" in last_msg or "我叫" in last_msg:
reply = f"好嘞,我记住你叫 {user_name} 了!"
else:
reply = f"收到,{user_name}。继续聊~"
return {"messages": [("assistant", reply)]}
# 1️⃣ 创建或连接到 SQLite 数据库文件
conn = sqlite3.connect("chat_memory.db", check_same_thread=False)
# 2️⃣ 创建 SqliteSaver 实例
saver = SqliteSaver(conn)
builder = StateGraph(ChatState)
builder.add_node("greeting", greeting_node)
builder.add_node("respond", respond_node)
builder.add_edge(START, "greeting")
builder.add_edge("greeting", "respond")
builder.add_edge("respond", END)
# 3️⃣ 编译时把 SQLite Saver 作为 checkpointer
graph = builder.compile(checkpointer=saver)
# 同一个 thread_id,数据会保留在 chat_memory.db 里,即使你关掉 Python 再重启
config = {"configurable": {"thread_id": "zhang_san"}}
print("第1次调用")
resp = graph.invoke({"messages": [("user", "我叫张三")], "user_name": "张三"}, config)
print(resp["messages"])
print("\n第2次调用(同线程)")
resp2 = graph.invoke({"messages": [("user", "我叫什么?")], "user_name": "张三"}, config)
print(resp2["messages"])
# 记得关闭连接
conn.close()
好处:即使程序重启,数据库里的 chat_memory.db 还在,所有对话历史不会丢。你可以把 SQLite 换成 PostgreSQL(生产更推荐),但思路完全一样。
四、长期记忆:跨会话记住用户——比女朋友记性还好
短期记忆限制在一个 thread_id 里。如果用户今天用手机聊,明天用电脑聊,你想让他不用重新输入“我喜欢喝冰美式”,就需要长期记忆。
LangGraph 的 Store 机制可以存任何键值对数据,并且支持命名空间(就像文件夹一样)。
4.1 用InMemoryStore给你看原理
下面的例子中,AI 会从存储里读取该用户的历史偏好,即使换了 thread_id,也能记住用户的名字。
from typing import Annotated, List
from langchain_core.messages import HumanMessage, AIMessage
from langgraph.graph import StateGraph, START
from langgraph.store.memory import InMemoryStore
# 定义状态(这里直接用消息列表)
class ChatState(TypedDict):
messages: Annotated[List, lambda x, y: x + y]
def chat_node(state: ChatState, *, store):
"""聊天节点,可以从 store 读取/写入长期记忆"""
# 假设当前用户ID(正常应该从 config 里取,这里演示写固定值)
user_id = "user_888"
# ---------- 读取长期记忆 ----------
# store.get((命名空间,), key) -> 返回一个 item,没有则 None
user_item = store.get(("users",), user_id)
if user_item:
user_info = user_item.value
print(f"[记忆] 读取到历史信息:{user_info}")
else:
user_info = {}
print("[记忆] 没有找到历史信息,这是第一次对话。")
# 获取用户最新消息
last_msg = state["messages"][-1].content if state["messages"] else ""
if "我叫" in last_msg:
# 提取简单名字(这里示意,生产请用正则)
name = last_msg.split("我叫")[-1].strip()[:10]
user_info["name"] = name
print(f"[记忆] 新记住名字:{name}")
# 保存回长期记忆
store.put(("users",), user_id, user_info)
# 生成回复
known_name = user_info.get("name", "朋友")
reply = f"你好,{known_name}!有什么想聊的?"
return {"messages": [AIMessage(content=reply)]}
# ---------- 创建记忆存储 ----------
memory_store = InMemoryStore()
builder = StateGraph(ChatState)
builder.add_node("chat", chat_node)
builder.add_edge(START, "chat")
# 关键:编译时传入 store=memory_store
graph = builder.compile(store=memory_store)
# 第一次对话:用户说名字
print("===== 第一次对话(thread_1)=====")
resp1 = graph.invoke({"messages": [HumanMessage(content="你好,我叫李雷")]})
print("AI:", resp1["messages"][-1].content)
# 第二次对话:换一个全新的 thread_id(但长期记忆依然有效)
# 注意:store 是全局的,不依赖于 config
print("\n===== 第二次对话,新 thread,但我们能通过 store 找回记忆 =====")
resp2 = graph.invoke({"messages": [HumanMessage(content="你还记得我叫什么吗?")]})
print("AI:", resp2["messages"][-1].content)
# 查看 store 里保存的内容
user_data = memory_store.get(("users",), "user_888")
print(f"\n[验证] 长期记忆存储:{user_data.value if user_data else '无'}")
输出示例:
- 第二次对话 AI 会说“你好,李雷!有什么想聊的?”——证明它跨会话记住了你。
4.2 生产环境:SQLite 版长期记忆(与检查点分开)
LangGraph 也提供了 SqliteStore,可以和 SqliteSaver 一起用,共享同一个数据库文件。但写法稍复杂,官方推荐用上下文管理器。下面给出可以直接复制运行的生产级例子,同时使用了短期记忆(checkpointer)和长期记忆(store),让你看到两者如何协同工作:
import sqlite3
import uuid
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.store.sqlite import SqliteStore
from langchain_core.messages import HumanMessage, AIMessage
DB_PATH = "my_agent_memory.db"
def call_model(state: MessagesState, config, *, store):
"""一个既能短期记忆又能长期记忆的节点"""
user_id = config["configurable"]["user_id"]
namespace = ("user_memories", user_id)
# 1. 长期记忆:检索之前的记忆
memories = store.search(namespace, query=str(state["messages"][-1].content))
mem_text = "\n".join([m.value["data"] for m in memories]) if memories else ""
system_hint = f"关于用户的历史信息:{mem_text}" if mem_text else ""
# 2. 检测“记住”指令并写入长期记忆
last_msg = state["messages"][-1].content
if "记住" in last_msg:
memory_content = f"用户说过:{last_msg}"
store.put(namespace, str(uuid.uuid4()), {"data": memory_content})
reply = f"好的,我记住了:{last_msg}"
else:
reply = f"收到消息:{last_msg}。{system_hint}(当前仅演示记忆能力)"
return {"messages": [AIMessage(content=reply)]}
# 同时初始化 checkpointer 和 store
with SqliteStore.from_conn_string(DB_PATH) as store, \
SqliteSaver.from_conn_string(DB_PATH) as checkpointer:
builder = StateGraph(MessagesState)
builder.add_node(call_model)
builder.add_edge(START, "call_model")
graph = builder.compile(checkpointer=checkpointer, store=store)
# 第一次对话:要求 AI 记住信息
config1 = {"configurable": {"thread_id": "1", "user_id": "alice"}}
res = graph.invoke({"messages": [HumanMessage(content="记住我家里养了一只猫叫胖丁")]}, config1)
print("AI:", res["messages"][-1].content)
# 第二次对话:新线程,但 user_id 相同,AI 能通过长期记忆召回
config2 = {"configurable": {"thread_id": "2", "user_id": "alice"}}
res2 = graph.invoke({"messages": [HumanMessage(content="我家猫叫什么?")]}, config2)
print("AI:", res2["messages"][-1].content)
这样,你的 AI 就拥有了双重记忆系统,既不会丢掉当前对话的上下文,也能跨会话记住用户的重要信息。
五、记忆管理进阶:不让 AI “撑爆”上下文窗口
即便是最好的 大模型 ,上下文长度也有限。如果你的短期记忆里攒了上千条消息,迟早会把模型“撑死”。
你需要学会修剪消息、删除消息和总结消息。
5.1 修剪消息(保留最后 N 个 token)
LangChain 提供了一个 trim_messages 函数,你可以设定最多保留多少个 token,它会自动去掉前面的旧消息。
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.messages.utils import trim_messages, count_tokens_approximately
# 假设我们有很长的一段对话历史
long_history = [
HumanMessage(content="第1条很长很长的消息..." * 20),
AIMessage(content="回复1" * 20),
HumanMessage(content="第2条也很长..." * 20),
AIMessage(content="回复2" * 20),
HumanMessage(content="最后一条,我是小明"),
]
# 修剪:只保留最后 100 个 token 左右,并且尽量以 human 消息结尾
trimmed = trim_messages(
long_history,
strategy="last",
token_counter=count_tokens_approximately,
max_tokens=100,
start_on="human", # 从 human 消息开始
end_on=("human", "tool") # 尽量结束在 human 或 tool 消息
)
print(f"原始消息数:{len(long_history)},修剪后:{len(trimmed)}")
for msg in trimmed:
print(msg.content[:50])
使用场景:在调用大模型之前的节点里调用 trim_messages,再传给 模型 。
5.2 删除消息(用RemoveMessage)
如果你的状态里用了 add_messages reducer,那么你可以返回一个 RemoveMessage 对象来删除特定消息。
from langchain_core.messages import RemoveMessage, HumanMessage
# 假设当前 state["messages"] 包含很多消息
# 你想删除第一条消息:
to_remove = RemoveMessage(id=state["messages"][0].id)
# 在节点中返回 {"messages": [to_remove]} 即可删除
5.3 总结消息(最优雅的长上下文策略)
当你觉得对话太长时,可以写一个 总结节点,用一个更便宜的小模型把前面几十条消息压缩成一段 摘要 ,然后用摘要替换掉原始消息。
def summarize_conversation(state):
messages = state["messages"]
if len(messages) < 10: # 消息太少就不要总结了
return {}
# 假设你有一个 summarize_model
summary_text = "用户问了关于天气和名字的问题,以及……" # 实际调用模型生成
# 删除旧消息,保留一个 SystemMessage 包含摘要
new_messages = [SystemMessage(content=f"之前的对话摘要:{summary_text}")] + messages[-4:]
return {"messages": new_messages}
小技巧:把总结节点放在对话进行到一定长度后自动触发,既节省 token 又不丢失关键信息。
六、文章总结(看完你可以带走什么?)
今天我们基本把 LangGraph 的记忆管理从头到尾过了一遍:
记忆类型 | 用途 | 核心组件 | 关键点 |
短期记忆 | 多轮对话、单次任务的上下文 | Checkpointer + thread_id | 状态自动存档,消息自动追加 |
长期记忆 | 跨会话的用户属性、知识库 | Store + 命名空间 | 独立于线程,按用户 ID 存取 |
消息修剪 | 防止上下文爆炸 | trim_messages | 保留最后若干个 token |
消息删除 | 精确控制状态大小 | RemoveMessage | 依赖 add_messages reducer |
对话总结 | 优雅压缩历史 | 自定义 summarize 节点 | 用低成本模型生成摘要 |
你现在能做什么?
-
直接复制文章里的代码,改一改就能用到你自己的客服机器人、个人助理、RAG Agent 里。
-
再也不用怕用户骂 AI “你怎么又忘了”。
-
面试官问你 LangGraph 记忆怎么实现,你可以自信地画出 checkpointer 和 store 两张牌。
记住一句话:短期记忆保证“当前聊得顺”,长期记忆保证“下次记得你”,两者结合才是真智能。
如果这篇文章帮到了你,不妨收藏下来,以后写 Agent 记忆功能时直接翻出代码示例修改。也欢迎你在评论区留言交流你在记忆管理上遇到的奇葩问题 → 我们一起踩坑,一起填坑。