- 短期记忆,或称线程作用域内存,通过在会话中维护消息历史来跟踪正在进行的对话。LangGraph 将短期记忆作为代理状态的一部分进行管理。状态通过检查点持久化到数据库中,以便随时恢复线程。当图被调用或某一步骤完成时,短期记忆会更新;在每一步开始时,都会读取状态。
- 长期记忆 跨会话存储特定于用户或应用级别的数据,并在多个对话线程间共享。它可以“随时”、“在任何线程中”被召回。记忆可以作用于任何自定义命名空间,而不仅限于单个线程 ID。LangGraph 提供了存储(参考文档),以便您保存和召回长期记忆。
短期记忆
短期记忆 使您的应用程序能够记住单个线程或对话内的先前交互。线程 将会话中的多次交互组织在一起,类似于电子邮件将消息分组到单一对话中的方式。 LangGraph 通过线程作用域的检查点将短期记忆作为代理状态的一部分进行管理。该状态通常包括对话历史以及其他有状态数据,例如上传的文件、检索到的文档或生成的产物。通过将这些数据存储在图的状态中,机器人可以访问特定对话的完整上下文,同时保持不同线程之间的隔离。管理短期记忆
对话历史是最常见的短期记忆形式,而长对话对当今的 LLM 构成了挑战。完整的历史可能无法放入 LLM 的上下文窗口,从而导致不可恢复的错误。即使您的 LLM 支持完整的上下文长度,大多数 LLM 在长上下文中表现仍然不佳。它们会被陈旧或离题的内容“分散注意力”,同时响应速度变慢、成本增加。 聊天模型使用消息来接受上下文,消息包括开发者提供的指令(系统消息)和用户输入(人类消息)。在聊天应用中,消息在人类输入和模型响应之间交替出现,从而形成一个随时间增长的消息列表。由于上下文窗口有限,且包含大量令牌的消息列表成本高昂,许多应用可以通过手动移除或遗忘陈旧信息的技术受益。 有关管理消息的常用技术的更多信息,请参阅 添加和管理内存 指南。长期记忆
LangGraph 中的长期记忆 允许系统在不同对话或会话之间保留信息。与线程作用域的短期记忆不同,长期记忆保存在自定义的“命名空间”内。 长期记忆是一个复杂挑战,没有一刀切的解决方案。然而,以下问题提供了一个框架,帮助您探索不同的技术:- 记忆的类型是什么? 人类使用记忆来记住事实(语义记忆)、经历(情景记忆)和规则(程序性记忆)。AI 代理也可以以相同的方式使用记忆。例如,AI 代理可以使用记忆来记住关于用户的特定事实,以完成任务。
- 您希望何时更新记忆? 记忆可以在代理的应用逻辑中更新(例如,“在热路径上”)。在这种情况下,代理通常在响应用户之前决定记住事实。或者,记忆可以作为后台任务更新(在后台/异步运行并生成记忆的逻辑)。我们在下文中解释了这些方法之间的权衡。
记忆类型
不同的应用程序需要不同类型的记忆。虽然类比并不完美,但考察人类记忆类型 可以提供深刻的见解。一些研究(例如,CoALA 论文)甚至将这些人类记忆类型映射到 AI 代理使用的记忆类型。语义记忆
在人类和 AI 代理中,语义记忆 涉及特定事实和概念的保留。在人类中,它可以包括在学校学到的信息以及对概念及其关系的理解。对于 AI 代理,语义记忆通常用于通过记住过去交互中的事实或概念来个性化应用程序。语义记忆不同于“语义搜索”,后者是一种使用“含义”(通常作为嵌入)查找相似内容的技术。语义记忆是心理学中的一个术语,指存储事实和知识,而语义搜索是一种基于含义而非精确匹配检索信息的方法。
个人资料
语义记忆可以通过不同方式管理。例如,记忆可以是关于用户、组织或其他实体(包括代理本身)的单一、持续更新的“个人资料”,包含范围明确且具体的信息。个人资料通常只是一个 JSON 文档,其中包含您选择的代表您领域的各种键值对。 在记住个人资料时,您需要确保每次都在更新个人资料。因此,您需要传入之前的个人资料,并要求模型生成新的个人资料(或应用于旧个人资料的某些 JSON 补丁)。随着个人资料变大,这可能会变得容易出错,可能需要将个人资料拆分为多个文档,或在生成文档时进行严格解码,以确保记忆模式保持有效。集合
或者,记忆可以是随着时间推移不断更新和扩展的文档集合。每个单独的记忆可以范围更窄、更易于生成,这意味着您不太可能随着时间推移丢失信息。LLM 生成新信息的_新_对象比将新信息与现有个人资料协调起来更容易。因此,文档集合往往会导致下游更高的召回率。 然而,这将一些复杂性转移到了记忆更新上。模型现在必须_删除_或_更新_列表中的现有项目,这可能很棘手。此外,一些模型可能默认过度插入,而其他模型可能默认过度更新。请参阅 Trustcall 包了解管理此问题的一种方法,并考虑评估(例如,使用 LangSmith 等工具)以帮助您调整行为。 使用文档集合还将复杂性转移到对列表的记忆搜索上。Store 目前支持语义搜索 和按内容过滤。
最后,使用记忆集合可能难以向模型提供全面的上下文。虽然单个记忆可能遵循特定模式,但这种结构可能无法捕捉记忆之间的完整上下文或关系。因此,在使用这些记忆生成响应时,模型可能缺乏在统一个人资料方法中更容易获得的重要上下文信息。
无论采用何种记忆管理方法,核心要点是代理将使用语义记忆来为其响应提供依据,这通常会导致更个性化和相关的交互。
情景记忆
在人类和 AI 代理中,情景记忆 涉及回忆过去的事件或行为。CoALA 论文 对此有很好的阐述:事实可以写入语义记忆,而经历可以写入情景记忆。对于 AI 代理,情景记忆通常用于帮助代理记住如何完成任务。 在实践中,情景记忆通常通过少样本示例提示实现,代理从过去的序列中学习以正确执行任务。有时“展示”比“讲述”更容易,LLM 从示例中学习效果很好。少样本学习允许您通过在提示中更新输入-输出示例来“编程”您的 LLM,以说明预期行为。虽然可以使用各种最佳实践生成少样本示例,但挑战通常在于根据用户输入选择最相关的示例。 请注意,内存存储 只是将数据存储为少样本示例的一种方式。如果您希望有更多开发者参与,或将少样本示例更紧密地与您的评估框架绑定,您还可以使用 LangSmith 数据集 来存储您的数据。然后,动态少样本示例选择器可以开箱即用地实现相同的目标。LangSmith 将为您索引数据集,并根据关键词相似性(使用类似 BM25 的算法 进行基于关键词的相似性)检索与用户输入最相关的少样本示例。 请参阅此视频 了解 LangSmith 中动态少样本示例选择的示例用法。此外,请参阅此博客文章 展示少样本提示以提高工具调用性能,以及此博客文章 使用少样本示例将 LLM 与人类偏好对齐。程序性记忆
在人类和 AI 代理中,程序性记忆 涉及记住用于执行任务的规则。在人类中,程序性记忆就像内化的知识,例如通过基本运动技能和平衡骑自行车。另一方面,情景记忆涉及回忆特定经历,例如第一次成功在没有辅助轮的情况下骑自行车,或一次穿越风景路线的难忘骑行。对于 AI 代理,程序性记忆是模型权重、代理代码和代理提示的组合,共同决定代理的功能。 在实践中,代理修改其模型权重或重写其代码的情况相当罕见。然而,代理修改其自身提示的情况更为常见。 一种有效的方法是通过“反思”或元提示来完善代理的指令。这涉及用当前指令(例如,系统提示)以及最近的对话或明确的用户反馈来提示代理。然后,代理根据此输入完善其自身指令。这种方法特别适用于指令难以事先指定的任务,因为它允许代理从其交互中学习和适应。 例如,我们使用外部反馈和提示重写构建了一个Tweet 生成器,为 Twitter 生成高质量的论文摘要。在这种情况下,特定的摘要提示很难a priori指定,但用户很容易批评生成的 Tweets 并提供有关如何改进摘要过程的反馈。 以下伪代码展示了如何使用 LangGraph 内存存储 实现这一点,使用存储保存提示,update_instructions 节点获取当前提示(以及捕获在 state["messages"] 中的与用户的对话反馈),更新提示,并将新提示保存回存储。然后,call_model 从存储中获取更新后的提示并使用它生成响应。
写入记忆
代理写入记忆有两种主要方法:“在热路径上” 和“在后台”。在热路径上
在运行时创建记忆既有优势也有挑战。积极的一面是,这种方法允许实时更新,使新记忆立即可用于后续交互。它还实现了透明度,因为可以通知用户何时创建和存储记忆。 然而,这种方法也带来了挑战。如果代理需要新工具来决定要提交到内存的内容,可能会增加复杂性。此外,推理要保存到内存的内容的过程可能会影响代理延迟。最后,代理必须在记忆创建和其他职责之间进行多任务处理,可能会影响创建记忆的数量和质量。 例如,ChatGPT 使用 save_memories 工具将记忆作为内容字符串进行插入或更新,并在每个用户消息中决定是否以及如何使用此工具。请参阅我们的 memory-agent 模板作为参考实现。在后台
将记忆创建作为单独的后台任务具有多个优势。它消除了主应用中的延迟,将应用逻辑与内存管理分离,并允许代理更专注于任务完成。这种方法还提供了在时间上灵活创建记忆以避免冗余工作的可能性。 然而,这种方法也有其自身的挑战。确定写入记忆的频率变得至关重要,因为不频繁的更新可能使其他线程缺乏新上下文。决定何时触发记忆形成也很重要。常见策略包括在设定时间段后调度(如果发生新事件则重新调度)、使用 cron 调度,或允许用户或应用逻辑手动触发。 请参阅我们的 memory-service 模板作为参考实现。内存存储
LangGraph 将长期记忆作为 JSON 文档存储在存储中。每个记忆都组织在自定义namespace(类似于文件夹)和唯一的 key(类似于文件名)下。命名空间通常包括用户或组织 ID 或其他标签,以便更轻松地组织信息。这种结构支持记忆的层次化组织。然后通过内容过滤器支持跨命名空间搜索。