跳到内容

LLM 应用中的长期记忆

长期记忆允许智能体在对话中记住重要信息。LangMem 提供了从聊天中提取有意义的细节、存储它们并利用它们来改善未来交互的方法。其核心是,LangMem 中的每个记忆操作都遵循相同的模式

  1. 接受对话和当前记忆状态
  2. 提示 LLM 决定如何扩展或整合记忆状态
  3. 回复更新后的记忆状态

最佳记忆系统通常是特定于应用的。在设计您的系统时,以下问题可以作为有用的指南

  1. 您的智能体应该学习什么类型的内容:事实/知识?过去事件的摘要?规则和风格?
  2. 记忆应该在何时形成(以及应该形成记忆)
  3. 记忆应该存储在哪里?(在提示中?语义存储中?)。这在很大程度上决定了它们将如何被召回。

记忆类型

LLM 应用中的记忆可以反映人类记忆的一些结构,每种类型在构建自适应、上下文感知系统中都发挥着独特的作用

记忆类型 目的 智能体示例 人类示例 典型存储模式
语义 事实与知识 用户偏好;知识三元组 知道 Python 是一种编程语言 配置文件或集合
情景 过往经历 少量示例;过往对话摘要 记住你上班的第一天 集合
程序 系统行为 核心个性和响应模式 知道如何骑自行车 提示规则或集合

语义记忆:事实与知识

语义记忆存储了支撑智能体响应的基本事实和其他信息。语义记忆的两种常见表示形式是集合(用于记录无限量的知识以便在运行时搜索)和配置文件(用于记录遵循严格模式的任务特定信息,便于用户或智能体查找)。

集合

当人们想到智能体的长期记忆时,集合是他们最常想到的。在这种类型中,记忆以单个文档或记录的形式存储。对于每个新对话,记忆系统都可以决定向存储中插入新记忆。

使用集合类型的记忆会增加更新记忆状态的复杂性。系统必须将新信息与先前的信念进行协调,这可能涉及删除/无效化更新/整合现有记忆。如果系统过度提取,这可能导致当您的智能体需要搜索存储时,记忆的精确度降低。如果提取不足,则可能导致召回率低。LangMem 使用记忆丰富过程,力求平衡记忆的创建和整合,同时允许您(开发者)自定义指令以进一步调整每种记忆的强度。

最后,记忆的相关性不仅仅是语义相似性。召回应该结合相似性、记忆的“重要性”以及记忆的“强度”,而强度是其最近/频繁使用程度的函数。

Collection update process

将语义记忆提取为集合
设置

API:create_memory_manager

from langmem import create_memory_manager

manager = create_memory_manager(
    "anthropic:claude-3-5-sonnet-latest",
    instructions="Extract all noteworthy facts, events, and relationships. Indicate their importance.",
    enable_inserts=True,
)

# Process a conversation to extract semantic memories
conversation = [
    {"role": "user", "content": "I work at Acme Corp in the ML team"},
    {"role": "assistant", "content": "I'll remember that. What kind of ML work do you do?"},
    {"role": "user", "content": "Mostly NLP and large language models"}
]
memories = manager.invoke({"messages": conversation})
# Example memories:
# [
#     ExtractedMemory(
#         id="27e96a9d-8e53-4031-865e-5ec50c1f7ad5",
#         content=Memory(
#             content="[IMPORTANT] User prefers to be called Lex (short for Alex) and appreciates"
#             " casual, witty communication style with relevant emojis."
#         ),
#     ),
#     ExtractedMemory(
#         id="e2f6b646-cdf1-4be1-bb40-0fd91d25d00f",
#         content=Memory(
#             content="[BACKGROUND] Lex is proficient in Python programming and specializes in developing"
#             " AI systems with a focus on making them sound more natural and less corporate."
#         ),
#     ),
#     ExtractedMemory(
#         id="c1e03ebb-a393-4e8d-8eb7-b928d8bed510",
#         content=Memory(
#             content="[HOBBY] Lex is a competitive speedcuber (someone who solves Rubik's cubes competitively),"
#             " showing an interest in both technical and recreational puzzle-solving."
#         ),
#     ),
#     ExtractedMemory(
#         id="ee7fc6e4-0118-425f-8704-6b3145881ff7",
#         content=Memory(
#             content="[PERSONALITY] Based on communication style and interests, Lex appears to value authenticity,"
#             " creativity, and technical excellence while maintaining a fun, approachable demeanor."
#         ),
#     ),
# ]

配置文件

另一方面,配置文件是针对特定任务的良好限定范围。配置文件是一个表示当前状态的单一文档,例如用户使用应用程序的主要目标、他们偏好的名称和响应风格等。当新信息到来时,它会更新现有文档而不是创建一个新文档。当您只关心最新状态并希望避免记住多余信息时,这种方法是理想的选择。

Profile update process

使用配置文件管理用户偏好
设置

API:create_memory_manager

from langmem import create_memory_manager
from pydantic import BaseModel


class UserProfile(BaseModel):
    """Save the user's preferences."""
    name: str
    preferred_name: str
    response_style_preference: str
    special_skills: list[str]
    other_preferences: list[str]


manager = create_memory_manager(
    "anthropic:claude-3-5-sonnet-latest",
    schemas=[UserProfile],
    instructions="Extract user preferences and settings",
    enable_inserts=False,
)

# Extract user preferences from a conversation
conversation = [
    {"role": "user", "content": "Hi! I'm Alex but please call me Lex. I'm a wizard at Python and love making AI systems that don't sound like boring corporate robots 🤖"},
    {"role": "assistant", "content": "Nice to meet you, Lex! Love the anti-corporate-robot stance. How would you like me to communicate with you?"},
    {"role": "user", "content": "Keep it casual and witty - and maybe throw in some relevant emojis when it feels right ✨ Also, besides AI, I do competitive speedcubing!"},
]
profile = manager.invoke({"messages": conversation})[0]
print(profile)
# Example profile:
# ExtractedMemory(
#     id="6f555d97-387e-4af6-a23f-a66b4e809b0e",
#     content=UserProfile(
#         name="Alex",
#         preferred_name="Lex",
#         response_style_preference="casual and witty with appropriate emojis",
#         special_skills=[
#             "Python programming",
#             "AI development",
#             "competitive speedcubing",
#         ],
#         other_preferences=[
#             "prefers informal communication",
#             "dislikes corporate-style interactions",
#         ],
#     ),
# )

根据您将如何使用数据来选择配置文件和集合:当您需要快速访问当前状态并且对可以存储的信息类型有数据要求时,配置文件表现出色。它们也易于呈现给用户进行手动编辑。当您希望在多次交互中跟踪知识而不会丢失信息,并且希望有上下文地(而不是每次都)召回特定信息时,集合非常有用。

情景记忆:过往经历

情景记忆将成功的交互保留为学习示例,以指导未来的行为。与存储事实的语义记忆不同,情景记忆捕获交互的完整上下文——情境、导致成功的思维过程以及为什么该方法有效。这些记忆帮助智能体从经验中学习,根据过去有效的方法调整其响应。

定义和提取情景
设置

API:create_memory_manager

from pydantic import BaseModel, Field
from langmem import create_memory_manager

class Episode(BaseModel):
    """An episode captures how to handle a specific situation, including the reasoning process
    and what made it successful."""

    observation: str = Field(
        ..., 
        description="The situation and relevant context"
    )
    thoughts: str = Field(
        ...,
        description="Key considerations and reasoning process"
    )
    action: str = Field(
        ...,
        description="What was done in response"
    )
    result: str = Field(
        ...,
        description="What happened and why it worked"
    )

manager = create_memory_manager(
    "anthropic:claude-3-5-sonnet-latest",
    schemas=[Episode],
    instructions="Extract examples of successful interactions. Include the context, thought process, and why the approach worked.",
    enable_inserts=True,
)

# Example conversation
conversation = [
    {"role": "user", "content": "What's a binary tree? I work with family trees if that helps"},
    {"role": "assistant", "content": "A binary tree is like a family tree, but each parent has at most 2 children. Here's a simple example:\n   Bob\n  /  \\\nAmy  Carl\n\nJust like in family trees, we call Bob the 'parent' and Amy and Carl the 'children'."},
    {"role": "user", "content": "Oh that makes sense! So in a binary search tree, would it be like organizing a family by age?"},
]
# Extract episode(s)
episodes = manager.invoke({"messages": conversation})
# Example episode:
# [
#     ExtractedMemory(
#         id="f9194af3-a63f-4d8a-98e9-16c66e649844",
#         content=Episode(
#             observation="User struggled debugging a recursive "
#                         "function for longest path in binary "
#                         "tree, unclear on logic.",
#             thoughts="Used explorer in treehouse village "
#                      "metaphor to explain recursion:\n"
#                      "- Houses = Nodes\n"
#                      "- Bridges = Edges\n"
#                      "- Explorer's path = Traversal",
#             action="Reframed problem using metaphor, "
#                    "outlined steps:\n"
#                    "1. Check left path\n"
#                    "2. Check right path\n"
#                    "3. Add 1 for current position\n"
#                    "Highlighted common bugs",
#             result="Metaphor helped user understand logic. "
#                    "Worked because it:\n"
#                    "1. Made concepts tangible\n"
#                    "2. Created mental model\n"
#                    "3. Showed key steps\n"
#                    "4. Pointed to likely bugs",
#         ),
#     )
# ]

程序记忆:系统指令

程序记忆编码了智能体应该如何行为和响应。它从定义核心行为的系统提示开始,然后通过反馈和经验进化。当智能体与用户交互时,它会完善这些指令,学习哪种方法最适合不同的情况。

Instructions update process

根据反馈优化提示
设置

API: create_prompt_optimizer

from langmem import create_prompt_optimizer

optimizer = create_prompt_optimizer(
    "anthropic:claude-3-5-sonnet-latest",
    kind="metaprompt",
    config={"max_reflection_steps": 3}
)
prompt = "You are a helpful assistant."
trajectory = [
    {"role": "user", "content": "Explain inheritance in Python"},
    {"role": "assistant", "content": "Here's a detailed theoretical explanation..."},
    {"role": "user", "content": "Show me a practical example instead"},
]
optimized = optimizer.invoke({
    "trajectories": [(trajectory, {"user_score": 0})], 
    "prompt": prompt
})
print(optimized)
# You are a helpful assistant with expertise in explaining technical concepts clearly and practically. When explaining programming concepts:

# 1. Start with a brief, practical explanation supported by a concrete code example
# 2. If the user requests more theoretical details, provide them after the practical example
# 3. Always include working code examples for programming-related questions
# 4. Pay close attention to user preferences - if they ask for a specific approach (like practical examples or theory), adapt your response accordingly
# 5. Use simple, clear language and break down complex concepts into digestible parts

# When users ask follow-up questions or request a different approach, immediately adjust your explanation style to match their preferences. If they ask for practical examples, provide them. If they ask for theory, explain the concepts in depth.

写入记忆

记忆可以通过两种方式形成,每种方式都适用于不同的需求。主动形成发生在对话期间,可以在关键上下文出现时立即更新。背景形成发生在交互之间,允许更深层次的模式分析而不影响响应时间。这种双重方法使您可以在响应速度和彻底学习之间取得平衡。

形成类型 延迟影响 更新速度 处理负载 用例
主动 更高 即时 响应期间 关键上下文更新
背景 延迟 调用之间/之后 模式分析、摘要

Hot path vs background memory processing

有意识形成

您可能希望您的智能体“在热路径中”保存记忆。这种主动记忆形成发生在对话期间,可以在关键上下文出现时立即更新。这种方法易于实现,并允许智能体自身选择如何存储和更新其记忆。然而,它会给用户交互增加可感知的延迟,并且会给智能体满足用户需求的能力增加一个障碍。

请查看“热路径”快速入门,了解如何使用此技术的示例。

潜意识形成

“潜意识”记忆形成指的是在对话发生后(或在非活动一段时间后)提示 LLM 反思对话的技术,找出模式并提取见解,而不会减慢即时交互或增加智能体工具选择决策的复杂性。这种方法非常适合确保提取信息的更高召回率。

请查看“背景”快速入门,了解如何使用此技术的示例。

整合模式

LangMem 的记忆工具分为两层整合模式

1. 核心 API

LangMem 的核心功能是提供转换记忆状态而不产生副作用的函数。这些原语是记忆操作的构建块

  • 记忆管理器:基于新的对话信息,提取新记忆,更新或删除过时记忆,并从现有记忆中整合和归纳
  • 提示优化器:根据对话信息(可选反馈)更新提示规则和核心行为

这些核心功能不依赖于任何特定的数据库或存储系统。您可以在任何应用中使用它们。

2. 有状态整合

下一层依赖于 LangGraph 的长期记忆存储。这些组件使用上述核心 API 来转换存储中存在的记忆,并在新的对话信息到来时根据需要进行插入/删除

如果您正在使用 LangGraph Platform 或 LangGraph OSS,请使用这些功能,因为它们是为您的智能体添加记忆能力的简单方法。

存储系统

存储是可选的

请记住,LangMem 的核心功能围绕着不需要任何特定存储层而构建。此处描述的存储功能是 LangMem 与 LangGraph 更高层集成的一部分,当您需要内置持久性时非常有用。

当使用 LangMem 的有状态操作符或平台服务时,存储系统建立在 LangGraph 的存储原语之上,提供了一种灵活而强大的方式来组织和访问记忆。存储系统围绕两个概念设计

记忆命名空间

记忆被组织到命名空间中,从而实现数据的自然分段

  • 多级命名空间:按组织、用户、应用程序或任何其他层次结构对记忆进行分组
  • 上下文键:在命名空间内唯一标识记忆
  • 结构化内容:存储带有元数据的丰富结构化数据,以便更好地组织
分层组织记忆
# Organize memories by organization -> configurable user -> context
namespace = ("acme_corp", "{user_id}", "code_assistant")

命名空间可以包含模板变量(例如"{user_id}"),这些变量将在运行时从RunnableConfig中的configurable字段填充。有关示例,请参阅如何动态配置命名空间,或参阅NamespaceTemplate参考文档了解更多详细信息。

灵活检索

如果您使用其中一个托管 API,LangMem 将直接与 LangGraph 的BaseStore接口集成,用于记忆存储和检索。存储系统支持多种检索记忆的方式

有关存储功能的更多详细信息,请参阅LangGraph 存储文档

评论