Appearance
智能体记忆
到现在为止,我们的智能体都是"金鱼脑"——重启就忘,换个会话就忘。本章讲怎么给智能体加记忆:短期记忆让它在一次会话里保持上下文,长期记忆让它跨会话记住用户偏好。
一、两种记忆
| 类型 | 范围 | 实现 | 类比 |
|---|---|---|---|
| 短期记忆 | 本次会话内 | checkpointer + thread_id | 工作记忆 |
| 长期记忆 | 跨会话/跨用户 | Store(InMemoryStore/PostgresStore) | 笔记本 |
人类记忆还有更细的分类,对应到智能体:
- 语义记忆:事实知识("用户叫张三")—— 存 Store。
- 情景记忆:过往事件("上周用户问过退货")—— 存 Store 带时间戳。
- 程序记忆:技能/习惯("用户喜欢简洁回复")—— 存 Store 的 profile 区。
二、短期记忆:checkpointer + thread_id
这块在工具智能体实战里提过,这里系统讲。
2.1 原理
LangGraph 在每个图节点执行后会存档当前状态(checkpoint)。下次用相同 thread_id 调用时,自动从上次的状态继续。所以"记忆"本质是状态快照。
2.2 用法
python
from langgraph.checkpoint.memory import MemorySaver
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
agent = create_react_agent(
model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
tools=[],
checkpointer=MemorySaver(),
)
config = {"configurable": {"thread_id": "user-001"}}
# 第一轮:告诉它名字
agent.invoke({"messages": [{"role": "user", "content": "我叫张三"}]}, config=config)
# 第二轮:换一次 invoke,但同一个 thread_id
r = agent.invoke({"messages": [{"role": "user", "content": "我刚才说我叫什么?"}]}, config=config)
print(r["messages"][-1].content) # 会答"张三"换 thread_id 就换"会话",互不干扰。MemorySaver 是进程内存储,重启进程丢失;生产环境用 PostgresSaver 等持久化方案。
2.3 查看历史快照
python
# 列出所有快照
for snapshot in agent.get_state_history(config):
print(snapshot.config["configurable"]["checkpoint_id"],
"消息数:", len(snapshot.values.get("messages", [])))get_state_history 可用于"时光倒流"——回到某个历史点重新执行。
三、长期记忆:Store
短期记忆绑死 thread_id,换会话就没了。长期记忆用 Store 跨会话共享。
3.1 InMemoryStore
最简单的 Store,进程内:
python
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()生产环境用 PostgresStore,用法一致,只是后端换数据库。
3.2 namespace 隔离
Store 用 namespace(命名空间)分层隔离数据,类似文件夹。典型做法:按用户隔离:
python
# 存:namespace 是元组,key 是字符串
store.put(("user", "zhangsan"), "name", {"value": "张三"})
store.put(("user", "zhangsan"), "preference", {"value": "喜欢简洁回复"})
# 取
item = store.get(("user", "zhangsan"), "name")
print(item.value) # {"value": "张三"}
# 按 namespace 前缀搜索
items = store.search(("user", "zhangsan"))
for it in items:
print(it.key, it.value)namespace 用元组(tuple)而非字符串,是为了层级匹配,类似 (部门, 小组, 成员)。
3.3 三层 API
| 方法 | 作用 |
|---|---|
put(namespace, key, value) | 写入/更新 |
get(namespace, key) | 精确取一条 |
search(namespace_prefix) | 按前缀模糊搜多条 |
delete(namespace, key) | 删除 |
注意 search 用的是前缀匹配,(user,) 能匹配 (user, zhangsan) 和 (user, lisi)。
四、让 agent 主动记忆
光有 store 还不够,agent 得自己决定记什么、何时记。做法是给它两个工具:save_memory 和 recall_memory。
4.1 定义记忆工具
python
from langchain_core.tools import tool
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
@tool
def save_memory(key: str, content: str) -> str:
"""把一条信息保存到长期记忆。key 是简短标识如 'name'/'hobby'。
content 是要记住的内容。"""
# namespace 写死为当前用户,实际可从 config 取
store.put(("user", "default"), key, {"content": content})
return f"已记住 {key}: {content}"
@tool
def recall_memory(key: str = "") -> str:
"""回忆长期记忆。key 留空则返回所有记忆。"""
if key:
item = store.get(("user", "default"), key)
return str(item.value) if item else "无此记忆"
items = store.search(("user", "default"))
return "\n".join(f"{i.key}: {i.value['content']}" for i in items)4.2 构建 agent 并在 prompt 里引导记忆
python
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI
PROMPT = """你是一个贴心的助手。
- 用户告诉你个人信息(名字、喜好、习惯)时,主动用 save_memory 记下来。
- 回答前如果有必要,用 recall_memory 回忆一下用户偏好。
- 简短交流不要每次都回忆,避免啰嗦。"""
agent = create_react_agent(
model=ChatOpenAI(model="gpt-4o-mini", temperature=0),
tools=[save_memory, recall_memory],
prompt=PROMPT,
checkpointer=MemorySaver(), # 短期记忆也要
)4.3 体验"跨会话记忆"
python
# 会话 1
config1 = {"configurable": {"thread_id": "session-1"}}
agent.invoke({"messages": [{"role": "user", "content": "我叫张三,喜欢简洁回复,不爱看长篇大论。"}]}, config1)
# 会话 2(不同 thread_id,但 store 是共享的)
config2 = {"configurable": {"thread_id": "session-2"}}
r = agent.invoke({"messages": [{"role": "user", "content": "你还记得我是谁吗?"}]}, config2)
print(r["messages"][-1].content)
# 会回答"你是张三,喜欢简洁回复",因为 agent 会先调 recall_memory注意:第二个会话的 thread_id 变了,短期记忆(messages 历史)没了,但 agent 通过 recall_memory 工具从 store 里"想起来了"。这就是长期记忆的魔力。
五、完整可运行示例
python
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
from langgraph.store.memory import InMemoryStore
store = InMemoryStore()
@tool
def save_memory(key: str, content: str) -> str:
"""保存长期记忆。key 如 'name'/'hobby'。"""
store.put(("user", "default"), key, {"content": content})
return f"已记住 {key}: {content}"
@tool
def recall_memory(key: str = "") -> str:
"""回忆长期记忆。key 畺空返回全部。"""
if key:
item = store.get(("user", "default"), key)
return str(item.value) if item else "无此记忆"
items = store.search(("user", "default"))
return "\n".join(f"{i.key}: {i.value['content']}" for i in items) if items else "暂无记忆"
PROMPT = """你是贴心助手。用户告诉你个人信息时用 save_memory 记下。
回答前必要时用 recall_memory 回忆。简短交流别每次都回忆。"""
agent = create_react_agent(
ChatOpenAI(model="gpt-4o-mini", temperature=0),
[save_memory, recall_memory],
prompt=PROMPT,
checkpointer=MemorySaver(),
)
# 会话 1:自报家门
agent.invoke({"messages": [{"role": "user", "content": "我叫张三,喜欢喝美式咖啡,最近在学LangGraph。"}]},
{"configurable": {"thread_id": "s1"}})
# 会话 2:换个 thread_id
r = agent.invoke({"messages": [{"role": "user", "content": "你还记得我喜欢喝什么吗?"}]},
{"configurable": {"thread_id": "s2"}})
print("助手:", r["messages"][-1].content)六、记忆膨胀与清理
长期记忆会越存越多,导致 recall_memory 返回超长文本。对策:
- 定期清理:在节点里按时间戳删旧记忆。
- 自动摘要:把多条老记忆压成一条摘要。
- 按需检索:用带语义搜索的 Store(
index=True),让search按相似度返回 Top-K 而非全部。LangGraph 的PostgresStore支持向量检索。
七、常见踩坑
1. 隐私问题
长期记忆会存用户敏感信息(姓名、地址、偏好)。生产环境务必:
- 提供用户"查看/删除我的记忆"功能。
- 敏感字段加密存储。
- prompt 里声明哪些信息不存(密码、支付信息)。
2. namespace 写错导致记忆串用户
namespace 用元组时,写错一层就会把 A 的记忆存到 B 名下。建议封装一个 get_namespace(config) 函数,从 config 取 user_id 拼 namespace,别到处硬编码。
3. agent 不主动记忆
模型可能"懒得调" save_memory,光嘴上答应"好的我记下了"实际没调工具。在 prompt 里强化:"必须用 save_memory 工具实际保存,不要只是口头说记住了。" 或者在节点后置钩子里检测用户是否给了新信息,强制调用 save。
4. MemorySaver 重启丢失
MemorySaver 是进程内存,服务一重启全没了。开发够用,生产必须换 PostgresSaver / SqliteSaver。
八、小结
- 短期记忆:
checkpointer+thread_id,存会话内 messages。 - 长期记忆:
Store+ namespace,跨会话共享。 - 让 agent 主动记忆:给它
save_memory/recall_memory工具 + prompt 引导。 - 注意隐私、清理、namespace 正确性、生产环境用持久化存储。
记忆是智能体"个性化"的关键。下一模块我们转向模型集成,看怎么把不同的大模型接进 LangGraph。