LLM 应用中的长期记忆¶
长期记忆允许智能体在多次对话中记住重要信息。LangMem 提供了从聊天中提取有意义的细节、存储它们并用于改善未来互动的方法。其核心在于,LangMem 中的每个记忆操作都遵循相同的模式:
- 接收对话和当前记忆状态
- 提示 LLM 以决定如何扩展或整合记忆状态
- 返回更新后的记忆状态
最好的记忆系统通常是针对特定应用的。在设计您的系统时,以下问题可以作为有用的指南:
- 什么内容:您的智能体应该学习哪种类型的内容:事实/知识?过去事件的摘要?规则和风格?
- 何时(以及由谁)应该形成记忆?
- 记忆应该存储在何处?(在提示中?语义存储中?)。这在很大程度上决定了它们将如何被回忆。
记忆类型¶
LLM 应用中的记忆可以反映人类记忆的某些结构,每种类型在构建自适应、具备上下文感知能力的系统中都扮演着独特的角色。
记忆类型 | 用途 | 智能体示例 | 人类示例 | 典型存储模式 |
---|---|---|---|---|
语义记忆 | 事实与知识 | 用户偏好;知识三元组 | 知道 Python 是一门编程语言 | 资料卡或集合 |
情景记忆 | 过往经历 | 少样本示例;过去对话的摘要 | 记得第一天上班的情景 | 集合 |
程序性记忆 | 系统行为 | 核心个性和响应模式 | 知道如何骑自行车 | 提示规则或集合 |
语义记忆:事实与知识¶
语义记忆存储了支撑智能体响应的基本事实和其他信息。语义记忆的两种常见表示形式是集合(用于记录可在运行时搜索的无限量知识)和资料卡(用于记录遵循严格模式且易于按用户或智能体查找的任务特定信息)。
集合¶
集合是大多数人想象智能体长期记忆时的第一印象。在这种类型中,记忆以单个文档或记录的形式存储。对于每个新对话,记忆系统可以决定向存储中插入新的记忆。
使用集合类型的记忆会给更新记忆状态的过程增加一些复杂性。系统必须将新信息与先前的信念进行协调,通过*删除*/*作废*或*更新*/*整合*现有记忆。如果系统过度提取,当您的智能体需要搜索存储时,可能会导致记忆的精确度降低。如果提取不足,则可能导致召回率低。LangMem 使用一种记忆丰富化过程,力求在记忆创建和整合之间取得平衡,同时允许您(开发者)自定义指令以进一步调整两者的强度。
最后,记忆的相关性不仅仅是语义相似性。回忆应该将相似性与记忆的“重要性”以及记忆的“强度”结合起来,后者是记忆最近/频繁被使用情况的函数。
提取语义记忆为集合
设置
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."
# ),
# ),
# ]
资料卡¶
另一方面,资料卡则适用于特定任务。资料卡是一个表示当前状态的单一文档,例如用户使用应用的主要目标、他们偏好的称呼和响应风格等。当新信息到达时,它会更新现有文档而不是创建新文档。当您只关心最新状态并希望避免记住无关信息时,这种方法是理想的。
使用资料卡管理用户偏好
设置
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",
# ],
# ),
# )
根据您将如何使用数据来选择使用资料卡还是集合:当您需要快速访问当前状态以及对可存储信息类型有数据要求时,资料卡表现出色。它们也易于呈现给用户进行手动编辑。当您希望在多次互动中跟踪知识而无信息损失,并且希望根据上下文而不是每次都回忆特定信息时,集合非常有用。
情景记忆:过往经历¶
情景记忆将成功的互动保存为学习示例,以指导未来的行为。与存储事实的语义记忆不同,情景记忆捕捉了互动的完整上下文——情境、导致成功的思考过程以及该方法为何有效。这些记忆帮助智能体从经验中学习,根据以往的成功经验调整其响应。
定义和提取情景
设置
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",
# ),
# )
# ]
程序性记忆:系统指令¶
程序性记忆编码了智能体应如何行为和响应。它始于定义核心行为的系统提示,然后通过反馈和经验不断演进。随着智能体与用户的互动,它会完善这些指令,学习哪些方法在不同情况下最有效。
根据反馈优化提示
设置
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.
写入记忆¶
记忆可以通过两种方式形成,每种方式都适用于不同的需求。主动形成发生在对话期间,当关键上下文出现时能够立即更新。后台形成发生在互动之间,允许进行更深层次的模式分析而不影响响应时间。这种双重方法让您在响应速度和深度学习之间取得平衡。
形成类型 | 延迟影响 | 更新速度 | 处理负载 | 用例 |
---|---|---|---|---|
主动 | 较高 | 立即 | 响应期间 | 关键上下文更新 |
后台 | 无 | 延迟 | 调用之间/之后 | 模式分析、摘要 |
主动形成¶
您可能希望您的智能体在“热路径”中保存记忆。这种主动的记忆形成发生在对话期间,当关键上下文出现时能够立即更新。这种方法易于实现,并让智能体自己选择如何存储和更新其记忆。然而,它会给用户互动增加可感知的延迟,并为智能体满足用户需求增加了另一个障碍。
请查看“热路径”快速入门示例,了解如何使用此技术。
潜意识形成¶
“潜意识”记忆形成指的是在对话发生后(或在一段时间不活动后)提示 LLM 对话进行反思,从而发现模式和提取见解,而不会减慢即时互动或增加智能体工具选择决策的复杂性。这种方法非常适合确保提取信息的更高召回率。
请查看“后台”快速入门示例,了解如何使用此技术。
集成模式¶
LangMem 的记忆工具按两个层次的集成模式组织:
1. 核心 API¶
LangMem 的核心是提供无副作用地转换记忆状态的函数。这些原语是记忆操作的构建块:
这些核心函数不依赖于任何特定的数据库或存储系统。您可以在任何应用程序中使用它们。
2. 有状态集成¶
上一层依赖于 LangGraph 的长期记忆存储。这些组件使用上述核心 API 来转换存储中存在的记忆,并在新对话信息传入时根据需要进行更新/插入或删除:
如果您正在使用 LangGraph Platform 或 LangGraph OSS,请使用这些组件,因为这是为您的智能体添加记忆能力的简便方法。
存储系统¶
存储是可选的
请记住,LangMem 的核心功能是围绕不需要任何特定存储层的理念构建的。这里描述的存储功能是 LangMem 与 LangGraph 的高级集成的一部分,当您需要内置持久性时非常有用。
在使用 LangMem 的有状态操作符或平台服务时,存储系统建立在 LangGraph 的存储原语之上,提供了一种灵活而强大的方式来组织和访问记忆。该存储系统围绕两个概念设计:
记忆命名空间¶
记忆被组织到命名空间中,这允许对数据进行自然分割:
- 多级命名空间:按组织、用户、应用程序或任何其他层次结构对记忆进行分组。
- 上下文键:在其命名空间内唯一标识记忆。
- 结构化内容:存储带有元数据的丰富结构化数据,以实现更好的组织。
分层组织记忆
命名空间可以包含模板变量(例如 `"{user_id}"`),这些变量在运行时从 `RunnableConfig` 中的 `configurable` 字段填充。请参阅如何动态配置命名空间的示例,或查阅 NamespaceTemplate 参考文档了解更多详情。
灵活检索¶
如果您使用托管 API 之一,LangMem 将直接与 LangGraph 的 BaseStore 接口集成,用于记忆存储和检索。存储系统支持多种检索记忆的方式:
有关存储功能的更多详细信息,请参阅 LangGraph 存储文档。