长期记忆
长期记忆的其它使用场景示例
除了页面提到的“保存用户姓名”,长期记忆在复杂智能体应用中还有以下典型用法:
A. 个性化风格偏好 (Personalized Styles)
智能体记录用户对输出格式的具体偏好,并在后续对话中自动应用。
- 场景:用户曾说“以后写代码请务必加上详细的中文注释”。
- 实现:智能体将此偏好存入
(user_id, 'preferences')。下次对话开始时,系统提示词(System Prompt)会自动注入这段记忆,无需用户重复要求。
B. 跨工具的经验学习 (Few-shot Learning)
智能体记录在处理复杂任务时的成功路径或失败教训。
- 场景:一个 SQL 智能体在查询某个复杂表结构时报错了,通过搜索找到了正确的 Join 方式。
- 实现:智能体将“表 A 和表 B 关联时应使用字段 X”这一经验存入 Store。下次遇到类似查询时,先通过向量搜索检索相关的“成功经验”。
C. 长期任务的状态跟踪 (Entity Memory)
在长达数天的研究任务中,记录对特定实体的认知。
- 场景:一个投研智能体在分析“特斯拉”这家公司。
- 实现:
- 第一天:存入特斯拉最新的财报数据。
- 第三天:存入特斯拉在某地区的建厂传闻。
- 第五天:当用户问“总结本周关于特斯拉的所有进展”时,智能体从 Store 中拉取该 namespace 下的所有条目。
D. 用户画像与关系维护 (CRM-like Memory)
记录与用户交流中的关键事实,建立情感连接。
- 场景:用户提过“我下周要去上海出差”。
- 实现:将此事件存入长期记忆。当下周用户再次上线时,智能体可以主动问候:“上海的出差还顺利吗?”
触发长期记忆
上面演示了用户信息的工具,提炼一下,你需要读写的一些内容(比如用户信息,偏好,一些经验等),确定要在哪个智能体里使用,就会主动传入该智能体,大模型会根据智能体里提供的工具的描述(有的甚至包含了明确的调用时机),判断是否需要调用工具,从而触发长期记忆的读写。
即:“需求”是预先定义的,但“时机”是动态触发的。
在 LangGraph 或任何基于 Tool Calling 的智能体架构中,长期记忆的使用确实主要依赖于你传入的工具,但如何使用可以分为三种不同的策略:
1. 被动式:大模型自主判断(即上文提到的 Tool 模式)
这是最常见、最灵活的方式。
- 逻辑:你给 Agent 装备了
save_preference和get_user_history工具。当用户说“我不喜欢吃辣”时,LLM 发现这属于“偏好”,于是主动调用写入工具;当用户问“推荐个餐厅”时,LLM 意识到需要参考历史,于是主动调用读取工具。 - 本质:这是一种“按需读写”。它的好处是节省 Token,只有在 LLM 觉得有用时才去翻看“陈年旧账”。
2. 主动式:系统自动注入(无感记忆)
在这种模式下,不需要 LLM 判断,程序员在代码层面就搞定了。
- 实现方式:在进入
StateGraph的第一个节点(通常是 Assistant 节点)之前,程序先根据user_id去Store里把该用户的所有标签、偏好、历史习惯全部取出来。 - 注入点:将取出的信息直接塞进 System Prompt(系统提示词)。
- System Prompt 示例:“你是一个助手。已知用户信息:{memories}。请结合这些信息回答。”
- 场景:适用于“个性化人设”。例如用户一上线,AI 就知道他是个“资深程序员”且“喜欢简洁回复”,无需 LLM 额外调用工具。
3. 反思式:异步记忆加工(后台自动运行)
这是最高级的形式,通常在对话结束后或在后台节点运行。
- 逻辑:LLM 并不在对话过程中读写记忆。而是在对话结束后,由一个专门的 “反思节点 (Reflection Node)” 扫描整个对话记录(短期记忆),总结出有价值的信息,然后存入长期记忆。
- 场景:
- 用户聊了 10 句,AI 总结出:“用户最近在备考雅思,且对口语比较焦虑”。
- 这条总结被存入
Store。下次用户再来,AI 已经拿到了这个总结。
总结:到底谁决定“需求”?
- 产品经理/开发者决定“能记住什么”:你通过定义工具的
args_schema和description来告诉 AI:“你可以记住用户的饮食偏好、职业、纪念日”。 - 大模型决定“什么时候记/读”:基于你给工具写的
description(描述)。如果描述写得好(例如:“当你发现用户提到长期习惯或特定偏好时,请调用此工具”),LLM 的触发就会非常精准。
进阶场景:如果记忆太多了怎么办?
如果长期记忆有 1000 条,你不可能全部塞进 System Prompt。这时就必须用到你在页面 二、长期记忆 中看到的 向量检索 (Vector Search):
- 用户提问。
- 程序拦截问题,去
Store里做一次向量搜索。 - 只把搜索到的“最相关”的前 3 条记忆塞进上下文。
- LLM 基于这 3 条记忆回答问题。
一个全自动记忆工厂例子如下,而不是靠分析对话里的信息来调工具使用记忆:
from langgraph.graph import StateGraph, MessagesState, START, END
from langchain_core.messages import SystemMessage
# 1. 自动注入节点:在对话开始前,自动查“档案”
def intro_memory_node(state: MessagesState, config: RunnableConfig, store: BaseStore):
user_id = config["configurable"].get("user_id")
# 自动去 Store 查这个人的长期记忆
# 甚至可以根据当前用户的问题进行【语义搜索】
last_message = state["messages"][-1].content
memories = store.search(("memories", user_id), query=last_message, limit=3)
memory_context = "\n".join([m.value["content"] for m in memories])
# 将记忆作为系统指令注入
system_prompt = f"你是助手。关于用户的长期背景:{memory_context}"
# 注意:这里并不修改 messages 历史,只在本次调用中加入上下文
return {"messages": [SystemMessage(content=system_prompt)]}
# 2. 自动反思节点:对话完事了,偷偷记笔记
def reflection_node(state: MessagesState, config: RunnableConfig, store: BaseStore):
user_id = config["configurable"].get("user_id")
# 让 LLM 总结这轮对话里是否有值得长期记住的“知识点”
# 这一步是全自动的,用户感知不到
last_user_msg = state["messages"][-2].content
last_ai_msg = state["messages"][-1].content
# 模拟 LLM 提取:假设它提取到了“用户正在学习 Python”
new_knowledge = f"用户最近动向:{last_user_msg}"
# 自动存入 Store
store.put(("memories", user_id), str(uuid.uuid4()), {"content": new_knowledge})
return state
# 3. 构建工作流
builder = StateGraph(MessagesState)
builder.add_node("load_memory", intro_memory_node)
builder.add_node("agent", call_model_node) # 正常的对话节点
builder.add_node("record_memory", reflection_node)
builder.add_edge(START, "load_memory")
builder.add_edge("load_memory", "agent")
builder.add_edge("agent", "record_memory")
builder.add_edge("record_memory", END)
非结构化记忆
前面提到的都是预先设计好的记忆,因此可以结构化,而你的智能体如果想足够“通用”,可能需要记住无限组合的用户偏好,或其它内容,这是不可能预设Schema的,确定要做这个方向的话,可能需要让大模型自己来判断,自己只提供一个key-value的结构和想要存储的不同类别而区分的namespace的tools就可以了。
比如记录用户偏好是很正常的智能体行为,但“我喜欢吃辣”属于偏好,“我喜欢深色模式”也是偏好,这样可能只能通过namespace来区分,甚至让AI连namespace动态生成也是可以的?当然
不管是动态生成key,还是动态生成namespace,都需要防止大模型随意生成语意相近的词。对于key,可以用去重处理,而namespace,因为它组成毕竟少一些,可以用限定词表加去重处理来简化。即你可以提前预设大部分类别,碰到无法归类的,再让大模型理解语意和去重的基础上创建新的类别即可
以下是一个简单示例,靠提示词让大模型自己决定什么时候应该记忆下来,以及自动分类,自动创建词条。
@tool
def save_user_insight(
content: str,
category: str, # 让 AI 决定类别,例如 "diet", "ui_style", "workflow"
importance: int # 甚至可以让 AI 评估重要程度
):
"""
当发现用户有价值的长效信息时调用。
category: 信息的分类标签,如 'lifestyle', 'technical_pref', 'personality'。
"""
# 动态构建命名空间:(用户ID, 大类)
dynamic_ns = (user_id, category)
store.put(dynamic_ns, str(uuid.uuid4()), {"content": content})
当用户说出“不喜欢吃辣”的时候,自动判断值得记忆下来,在回答以后和饮食类的问题的时候有用。因此可能创建一个diet的分类,然后存储口味偏好。后续聊天里有饮食或口味相关的意图的时候,该怎么去找根本就没有结构化过的记忆呢?
检索非结构化记忆
这是一个非常核心的问题:既然存储是动态的,检索时如何确保 AI 能准确找到对应的“抽屉”?
在 LangGraph 的长期记忆 (Store) 架构下,实现这种“动态存、自动读”的对接,通常有以下两种主流方案:
方案一:语义搜索(Vector Search)—— 最推荐的“模糊对接”
你不需要让 AI 记住自己开了哪些 namespace。你只需要利用 InMemoryStore 自带的向量检索能力,通过语义相似度把相关的“抽屉”都扫一遍。
实现思路:
当用户问“推荐个餐厅”时,你并不强制要求 AI 去查 diet 文件夹,而是进行一次全量或跨 namespace 的搜索。
@tool
def search_related_memory(query: str, runtime: ToolRuntime[Context]):
"""
当你需要了解用户的背景、偏好、历史习惯以提供个性化回答时调用。
query: 搜索关键词,如 '用户对食物的口味偏好'
"""
user_id = runtime.context.user_id
# 不指定具体的 category,而是基于语义在用户的所有记忆中搜索
# 这里的 search 会把 (user_id, "diet") 和 (user_id, "lifestyle") 里相关的都找出来
results = runtime.store.search(
(user_id,), # 搜索该用户下的所有子 namespace
query=query,
limit=5
)
if not results:
return "没有找到相关的用户偏好记录。"
return "\n".join([f"[{r.namespace[-1]}]: {r.value['content']}" for r in results])
方案二:元数据导航(Metadata/Index)—— 结构化的“精准对接”
如果你希望 AI 更有条理,可以维护一个“记忆索引”。
实现思路:
- 每当 AI 创建一个新的
category(比如diet),就在一个固定的位置(如(user_id, "index"))记录下这个类别。 - 在对话开始时,系统自动把“当前已有的记忆类别”注入到 Tool 的描述里。
AI 的思考过程:
- 用户问:“我不舒服,想喝点粥。”
- AI 观察工具描述:我有
[diet, medical, coding]三个维度的记忆。 - AI 判断:这明显和
diet相关,调用get_memory(category="diet")。
方案三:双层检索架构(自动路由)
这是目前最智能的实现方式,结合了你提到的“动态分类”:
- 意图识别层:用户输入 -> LLM 判断意图。
- 动态路径生成:如果意图是“订餐”,LLM 自动推断相关的
namespace可能叫diet或food。 - 多路召回:
store.get((user_id, "diet"), ...)—— 精准读。store.search((user_id,), query="饮食习惯")—— 语义补足。
总结:如何实现“自动对接”?
要让工具自动对接,关键不在于“名字对齐”,而在于“语义对齐”。
- 写入时:通过
save_user_insight里的category参数描述,引导 AI 使用标准的、有意义的词汇(如:请尽量使用 diet 而不是 my_food_likes)。 - 读取时:
- 如果你用 Tool:给 Tool 的
query参数极高的自由度,让 AI 用自然语言去搜。 - 如果你用 自动注入:在 Node 节点里,先用当前用户的问题去
store.search一遍,把搜到的东西直接喂给 Agent 的 System Prompt。
- 如果你用 Tool:给 Tool 的
实践建议:
对于“不喜欢吃辣”这种例子,最好的对接方式是“语义搜索”。因为用户下次可能会问“推荐个川菜馆”,此时 store.search 发现“川菜”和“辣”在向量空间里很近,自然就能把那条记忆抓取出来提醒 AI:“注意,虽然你在搜川菜馆,但用户之前说过他不爱吃辣”。
Backlinks