Skip to main content

Documentation Index

Fetch the complete documentation index at: https://nvd-54.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

记忆是一个记住先前交互信息的系统。对于 AI 智能体,记忆至关重要,因为它让智能体能够记住先前的交互、从反馈中学习并适应用户偏好。随着智能体处理更复杂的任务和更多的用户交互,这种能力对于效率和用户满意度都变得至关重要。 本概念指南涵盖两种记忆类型,基于其回忆范围:
  • 短期记忆,或线程范围的记忆,通过在会话中维护消息历史来跟踪正在进行的对话。LangGraph 将短期记忆作为智能体状态的一部分来管理。状态通过检查点器持久化到数据库中,以便线程可以随时恢复。短期记忆在图被调用或步骤完成时更新,并在每个步骤开始时读取状态。
  • 长期记忆跨会话存储用户特定或应用级数据,并在对话线程_之间_共享。它可以_在任何时间_和_在任何线程中_被回忆。记忆的作用域限定在任何自定义命名空间中,而不仅仅是单个线程 ID 内。LangGraph 提供存储参考文档)让你保存和回忆长期记忆。
短期 vs 长期

短期记忆

短期记忆让你的应用记住单个线程或对话中的先前交互。线程将一个会话中的多次交互组织在一起,类似于电子邮件将消息分组在同一对话中。 LangGraph 将短期记忆作为智能体状态的一部分来管理,通过线程范围的检查点持久化。此状态通常可以包括对话历史以及其他有状态数据,如上传的文件、检索到的文档或生成的工件。通过将这些存储在图的状态中,机器人可以访问给定对话的完整上下文,同时保持不同线程之间的分离。

管理短期记忆

对话历史是最常见的短期记忆形式,长对话对当今的大语言模型(LLM)构成挑战。完整的历史可能无法放入 LLM 的上下文窗口,导致不可恢复的错误。即使你的 LLM 支持完整的上下文长度,大多数 LLM 在长上下文中仍然表现不佳。它们会被陈旧或离题的内容”分散注意力”,同时响应时间更慢、成本更高。 聊天模型使用消息接受上下文,包括开发者提供的指令(系统消息)和用户输入(人类消息)。在聊天应用中,消息在人类输入和模型响应之间交替,导致消息列表随时间增长。由于上下文窗口有限且 Token 丰富的消息列表成本高昂,许多应用可以从使用手动移除或遗忘过时信息的技术中受益。 过滤 有关管理消息的常见技术的更多信息,参见添加和管理记忆指南。

长期记忆

LangGraph 中的长期记忆允许系统在不同的对话或会话之间保留信息。与线程范围的短期记忆不同,长期记忆保存在自定义”命名空间”中。 长期记忆是一个复杂的挑战,没有一刀切的解决方案。然而,以下问题提供了一个框架来帮助你导航不同的技术:
  • 记忆的类型是什么?人类使用记忆来记住事实(语义记忆)、经历(情节记忆)和规则(程序性记忆)。AI 智能体可以以同样的方式使用记忆。例如,AI 智能体可以使用记忆来记住关于用户的特定事实以完成任务。
  • 什么时候更新记忆? 记忆可以作为智能体应用逻辑的一部分更新(例如”在热路径上”)。在这种情况下,智能体通常在回复用户之前决定记住事实。或者,记忆可以作为后台任务更新(在后台/异步运行并生成记忆的逻辑)。我们在下方部分解释这些方法的权衡。
不同的应用需要各种类型的记忆。虽然类比并不完美,但检查人类记忆类型可以提供启发。一些研究(例如 CoALA 论文)甚至将这些人类记忆类型映射到 AI 智能体中使用的记忆类型。
记忆类型存储什么人类示例智能体示例
语义事实在学校学到的东西关于用户的事实
情节经历做过的事情过去的智能体行为
程序性指令本能或运动技能智能体系统提示

语义记忆

语义记忆,在人类和 AI 智能体中,都涉及保留特定的事实和概念。在人类中,它可以包括在学校学到的信息和对概念及其关系的理解。对于 AI 智能体,语义记忆通常用于通过记住过去交互中的事实或概念来个性化应用。
语义记忆与”语义搜索”不同,后者是一种使用”含义”(通常作为嵌入)查找相似内容的技术。语义记忆是心理学术语,指存储事实和知识,而语义搜索是一种基于含义而非精确匹配来检索信息的方法。
语义记忆可以用不同的方式管理:

配置文件

记忆可以是一个持续更新的”配置文件”,包含关于用户、组织或其他实体(包括智能体本身)的范围明确、具体的信息。配置文件通常只是一个 JSON 文档,包含你为领域选择的各种键值对。 记忆配置文件时,你需要确保每次都在更新配置文件。因此,你需要传入之前的配置文件并要求模型生成新的配置文件(或某些 JSON 补丁以应用到旧配置文件)。随着配置文件变大,这可能变得容易出错,并可能受益于将配置文件拆分为多个文档或在生成文档时使用严格解码以确保记忆模式保持有效。 更新配置文件

集合

另外,记忆可以是随时间不断更新和扩展的文档集合。每个单独的记忆可以更窄范围且更容易生成,这意味着你随时间丢失信息的可能性更低。对于 LLM 来说,为新信息生成_新_对象比将新信息与现有配置文件协调更容易。因此,文档集合往往在下游带来更高的召回率 然而,这将一些复杂性转移到了记忆更新上。模型现在必须_删除_或_更新_列表中的现有项,这可能很棘手。此外,一些模型可能默认过度插入,而其他模型可能默认过度更新。参见 Trustcall 包了解管理此问题的一种方式,并考虑评估(例如使用 LangSmith 等工具)来帮助你调整行为。 使用文档集合也将复杂性转移到了对列表的记忆搜索上。Store 目前支持语义搜索按内容过滤 最后,使用记忆集合可能使向模型提供全面上下文变得具有挑战性。虽然单个记忆可能遵循特定模式,但这种结构可能无法捕获记忆之间的完整上下文或关系。因此,在使用这些记忆生成响应时,模型可能缺少在统一配置文件方法中更容易获得的重要上下文信息。 更新列表 无论记忆管理方法如何,核心要点是智能体将使用语义记忆来扎根其响应,这通常会导致更个性化和相关的交互。

情节记忆

情节记忆,在人类和 AI 智能体中,都涉及回忆过去的事件或行为。CoALA 论文很好地阐述了这一点:事实可以写入语义记忆,而经历可以写入情节记忆。对于 AI 智能体,情节记忆通常用于帮助智能体记住如何完成任务。 在实践中,情节记忆通常通过少样本示例提示来实现,其中智能体从过去的序列中学习以正确执行任务。有时”展示”比”讲述”更容易,LLM 从示例中学习得很好。少样本学习让你通过用输入输出示例更新提示来”编程”你的 LLM,以说明预期行为。虽然可以使用各种最佳实践来生成少样本示例,但挑战通常在于根据用户输入选择最相关的示例。 注意,记忆存储只是将数据存储为少样本示例的一种方式。如果你想要更多开发者参与,或将少样本与你的评估框架更紧密地结合,你也可以使用 LangSmith 数据集来存储你的数据,并实现你自己的检索逻辑来根据用户输入选择最相关的示例。 参见这篇博客文章展示少样本提示以提高工具调用性能,以及这篇博客文章使用少样本示例将 LLM 与人类偏好对齐。

程序性记忆

程序性记忆,在人类和 AI 智能体中,都涉及记住用于执行任务的规则。在人类中,程序性记忆就像关于如何执行任务的内化知识,如通过基本运动技能和平衡来骑自行车。情节记忆则涉及回忆特定的经历,如第一次成功地不用辅助轮骑自行车或一次难忘的风景自行车骑行。对于 AI 智能体,程序性记忆是模型权重、智能体代码和智能体提示的组合,共同决定了智能体的功能。 在实践中,智能体修改其模型权重或重写其代码是相当少见的。然而,智能体修改自己的提示更为常见。 改进智能体指令的一种有效方法是通过”反思”或元提示。这涉及用其当前指令(例如系统提示)以及最近的对话或明确的用户反馈来提示智能体。然后智能体根据这些输入改进自己的指令。这种方法对于难以预先指定指令的任务特别有用,因为它允许智能体从交互中学习和适应。 例如,我们构建了一个推文生成器,使用外部反馈和提示重写来为 Twitter 生成高质量的论文摘要。在这种情况下,具体的摘要提示很难预先指定,但用户批评生成的推文并提供如何改进摘要过程的反馈相当容易。 下面的伪代码展示了如何使用 LangGraph 记忆存储来实现,使用存储保存提示,update_instructions 节点获取当前提示(以及从与用户对话中捕获的反馈 state["messages"]),更新提示,并将新提示保存回存储。然后 call_model 从存储中获取更新的提示并使用它生成响应。
# 使用指令的节点
def call_model(state: State, store: BaseStore):
    namespace = ("agent_instructions", )
    instructions = store.get(namespace, key="agent_a")[0]
    # 应用逻辑
    prompt = prompt_template.format(instructions=instructions.value["instructions"])
    ...

# 更新指令的节点
def update_instructions(state: State, store: BaseStore):
    namespace = ("instructions",)
    instructions = store.search(namespace)[0]
    # 记忆逻辑
    prompt = prompt_template.format(instructions=instructions.value["instructions"], conversation=state["messages"])
    output = llm.invoke(prompt)
    new_instructions = output['new_instructions']
    store.put(("agent_instructions",), "agent_a", {"instructions": new_instructions})
    ...
更新指令

写入记忆

智能体写入记忆有两种主要方法:“在热路径中””在后台” 热路径 vs 后台

在热路径中

在运行时创建记忆既有优势也有挑战。积极的一面是,这种方法允许实时更新,使新记忆可以立即用于后续交互。它还支持透明性,因为可以在创建和存储记忆时通知用户。 然而,这种方法也存在挑战。如果智能体需要新工具来决定将什么提交到记忆中,可能会增加复杂性。此外,关于保存什么到记忆的推理过程可能影响智能体延迟。最后,智能体必须在记忆创建和其他职责之间多任务处理,可能影响创建记忆的数量和质量。 例如,ChatGPT 使用 save_memories 工具将记忆作为内容字符串插入,在每条用户消息时决定是否以及如何使用此工具。参见我们的 memory-agent 模板作为参考实现。

在后台

将记忆创建作为单独的后台任务提供了多项优势。它消除了主应用中的延迟,将应用逻辑与记忆管理分离,并允许智能体更专注地完成任务。这种方法还提供了灵活的记忆创建时机以避免冗余工作。 然而,这种方法也有自己的挑战。确定记忆写入的频率变得至关重要,因为不频繁的更新可能使其他线程缺少新上下文。决定何时触发记忆形成也很重要。常见策略包括在设定时间后安排(如果有新事件则重新安排)、使用 cron 计划或允许用户或应用逻辑手动触发。 参见我们的 memory-service 模板作为参考实现。

记忆存储

LangGraph 将长期记忆作为 JSON 文档存储在存储中。每个记忆组织在自定义 namespace(类似于文件夹)和不同的 key(类似于文件名)下。命名空间通常包含用户或组织 ID 或其他标签,使信息更容易组织。这种结构支持记忆的分层组织。然后通过内容过滤器支持跨命名空间搜索。
from langgraph.store.memory import InMemoryStore


def embed(texts: list[str]) -> list[list[float]]:
    # 替换为实际的嵌入函数或 LangChain 嵌入对象
    return [[1.0, 2.0] * len(texts)]


# InMemoryStore 将数据保存到内存字典中。在生产中使用数据库支持的存储。
store = InMemoryStore(index={"embed": embed, "dims": 2})
user_id = "my-user"
application_context = "chitchat"
namespace = (user_id, application_context)
store.put(
    namespace,
    "a-memory",
    {
        "rules": [
            "User likes short, direct language",
            "User only speaks English & python",
        ],
        "my-key": "my-value",
    },
)
# 通过 ID 获取"记忆"
item = store.get(namespace, "a-memory")
# 在此命名空间内搜索"记忆",按内容等价过滤,按向量相似度排序
items = store.search(
    namespace, filter={"my-key": "my-value"}, query="language preferences"
)
有关记忆存储的更多信息,参见持久化指南。

了解更多