长期记忆

长期记忆的其它使用场景示例

除了页面提到的“保存用户姓名”,长期记忆在复杂智能体应用中还有以下典型用法:

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_preferenceget_user_history 工具。当用户说“我不喜欢吃辣”时,LLM 发现这属于“偏好”,于是主动调用写入工具;当用户问“推荐个餐厅”时,LLM 意识到需要参考历史,于是主动调用读取工具。
  • 本质:这是一种“按需读写”。它的好处是节省 Token,只有在 LLM 觉得有用时才去翻看“陈年旧账”。

2. 主动式:系统自动注入(无感记忆)

在这种模式下,不需要 LLM 判断,程序员在代码层面就搞定了。

  • 实现方式:在进入 StateGraph 的第一个节点(通常是 Assistant 节点)之前,程序先根据 user_idStore 里把该用户的所有标签、偏好、历史习惯全部取出来。
  • 注入点:将取出的信息直接塞进 System Prompt(系统提示词)。
  • System Prompt 示例:“你是一个助手。已知用户信息:{memories}。请结合这些信息回答。”
  • 场景:适用于“个性化人设”。例如用户一上线,AI 就知道他是个“资深程序员”且“喜欢简洁回复”,无需 LLM 额外调用工具。

3. 反思式:异步记忆加工(后台自动运行)

这是最高级的形式,通常在对话结束后或在后台节点运行。

  • 逻辑:LLM 并不在对话过程中读写记忆。而是在对话结束后,由一个专门的 “反思节点 (Reflection Node)” 扫描整个对话记录(短期记忆),总结出有价值的信息,然后存入长期记忆。
  • 场景
  • 用户聊了 10 句,AI 总结出:“用户最近在备考雅思,且对口语比较焦虑”。
  • 这条总结被存入 Store。下次用户再来,AI 已经拿到了这个总结。

总结:到底谁决定“需求”?

  1. 产品经理/开发者决定“能记住什么”:你通过定义工具的 args_schemadescription 来告诉 AI:“你可以记住用户的饮食偏好、职业、纪念日”。
  2. 大模型决定“什么时候记/读”:基于你给工具写的 description(描述)。如果描述写得好(例如:“当你发现用户提到长期习惯或特定偏好时,请调用此工具”),LLM 的触发就会非常精准。

进阶场景:如果记忆太多了怎么办?

如果长期记忆有 1000 条,你不可能全部塞进 System Prompt。这时就必须用到你在页面 二、长期记忆 中看到的 向量检索 (Vector Search)

  1. 用户提问。
  2. 程序拦截问题,去 Store 里做一次向量搜索。
  3. 只把搜索到的“最相关”的前 3 条记忆塞进上下文。
  4. 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 更有条理,可以维护一个“记忆索引”

实现思路:

  1. 每当 AI 创建一个新的 category(比如 diet),就在一个固定的位置(如 (user_id, "index"))记录下这个类别。
  2. 在对话开始时,系统自动把“当前已有的记忆类别”注入到 Tool 的描述里。

AI 的思考过程:

  • 用户问:“我不舒服,想喝点粥。”
  • AI 观察工具描述:我有 [diet, medical, coding] 三个维度的记忆。
  • AI 判断:这明显和 diet 相关,调用 get_memory(category="diet")

方案三:双层检索架构(自动路由)

这是目前最智能的实现方式,结合了你提到的“动态分类”:

  1. 意图识别层:用户输入 -> LLM 判断意图。
  2. 动态路径生成:如果意图是“订餐”,LLM 自动推断相关的 namespace 可能叫 dietfood
  3. 多路召回
  • 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。

实践建议: 对于“不喜欢吃辣”这种例子,最好的对接方式是“语义搜索”。因为用户下次可能会问“推荐个川菜馆”,此时 store.search 发现“川菜”和“辣”在向量空间里很近,自然就能把那条记忆抓取出来提醒 AI:“注意,虽然你在搜川菜馆,但用户之前说过他不爱吃辣”。


Backlinks