checkpoint 状态。这些检查点被保存到 thread 中,图执行后可以访问。因为 threads 允许在执行后访问图的状态,所以包括人机交互、记忆、时间旅行和容错在内的几个强大功能都是可能的。下面,我们将更详细地讨论这些概念。
LangGraph API 自动处理检查点
当使用 LangGraph API 时,您无需手动实现或配置检查点器。API 在幕后为您处理所有持久化基础设施。
线程
线程是检查点器为每个保存的检查点分配的唯一ID或线程标识符。它包含一系列运行的累积状态。当运行执行时,助手底层图的状态将被持久化到线程中。 在调用带有检查点的图时,您必须在配置的configurable 部分指定一个 thread_id。
检查点
线程在特定时间点的状态被称为检查点。检查点是每个超级步骤保存的图状态的快照,并由以下关键属性表示的StateSnapshot 对象表示:
config:与此检查点相关的配置。metadata:与此检查点相关的元数据。values:在此时间点状态通道的值。next:图下一步要执行的节点名称的元组。tasks:包含有关下一步要执行的任务信息的PregelTask对象的元组。如果该步骤之前已尝试执行,它将包含错误信息。如果图在节点内部被中断 动态,任务将包含与中断相关的附加数据。
- 空检查点,以
START作为下一个要执行的节点 - 检查点,用户输入
{'foo': '', 'bar': []}和nodeA作为下一个要执行的节点 - 检查点,以
nodeA{'foo': 'a', 'bar': ['a']}和nodeB的输出作为下一个要执行的节点 - 检查点,以
nodeB{'foo': 'b', 'bar': ['a', 'b']}的输出作为下一个要执行的节点,没有后续节点要执行
bar 通道值包含来自两个节点的输出,因为我们有一个用于 bar 通道的reducer。
获取状态
在与保存的图状态交互时,您必须指定一个线程标识符。您可以通过调用graph.getState(config) 来查看图的_最新_状态。这将返回一个 StateSnapshot 对象,该对象对应于配置中提供的线程ID关联的最新检查点,或者如果提供了检查点ID,则对应于线程的检查点。
getState 的输出将如下所示:
获取状态历史
您可以通过调用graph.getStateHistory(config) 来获取给定线程的图执行完整历史。这将返回与配置中提供的线程 ID 相关的 StateSnapshot 对象列表。重要的是,检查点将按时间顺序排列,最新的检查点 / StateSnapshot 将列表中的第一个。
getStateHistory 的输出将如下所示:
回放
它还可能播放回之前的图执行。如果我们使用invoke 一个具有 thread_id 和 checkpoint_id 的图,那么我们将 重放 在与 checkpoint_id 对应的检查点之前之前已执行的步骤,并且只执行检查点之后的步骤。
thread_id是线程的 ID。checkpoint_id是一个标识符,它指向线程中的特定检查点。
configurable 部分调用图时传递这些参数:
checkpoint_id 之前的步骤。所有在 checkpoint_id 之后的步骤都将执行(即,一个新的分支),即使它们之前已经执行过。有关如何进行时间旅行以了解更多关于重放的信息,请参阅这个 时间旅行指南。
更新状态
除了重新播放特定checkpoints 中的图之外,我们还可以 编辑 图的状态。我们使用 graph.updateState() 来完成这项操作。此方法接受三个不同的参数:
config
配置应包含 thread_id 指定要更新的线程。当仅传递 thread_id 时,我们更新(或分叉)当前状态。可选地,如果我们包含 checkpoint_id 字段,那么我们将分叉所选的检查点。
values
这些值将用于更新状态。请注意,此更新被处理得与节点发出的任何更新完全相同。这意味着如果图状态中的一些通道定义了reducer函数,这些值将被传递给这些函数。这意味着 update_state 不会自动覆盖每个通道的通道值,而只会覆盖没有reducers的通道。让我们通过一个例子来解释一下。
让我们假设您已经使用以下架构定义了您的图状态(参见上面的完整示例):
foo 键(通道)被完全更改(因为未指定该通道的reducer,所以 updateState 会覆盖它)。然而,已指定 bar 键的reducer,因此它将 "b" 追加到 bar 的状态中。
as_node
在调用 updateState 时,您可以可选地指定最后一个 asNode。如果您提供了它,更新将像来自节点 asNode 一样应用。如果没有提供 asNode,它将设置为最后更新状态的节点,如果不存在歧义。这之所以重要,是因为接下来要执行的步骤取决于最后给出更新的节点,因此这可以用来控制哪个节点接下来执行。有关分叉状态的更多了解,请参阅这个 时间旅行指南。
记忆存储
Store接口。作为一个例子,我们可以定义一个InMemoryStore来存储跨线程的用户信息。我们只需像以前一样使用检查点器编译我们的图,并使用我们新的in_memory_store变量。
LangGraph API 自动处理存储
当使用 LangGraph API 时,您无需手动实现或配置存储。API 在幕后为您处理所有存储基础设施。
基本用法
首先,让我们在不使用LangGraph的情况下单独展示这个功能。tuple 进行命名空间划分,在这个特定示例中将是 (<user_id>, "memories")。命名空间可以是任何长度,可以代表任何内容,不一定是用户特定的。
store.put 方法将记忆保存到存储中的命名空间。当我们这样做时,我们会指定命名空间,如上所述,以及一个记忆的键值对:键是记忆的简单唯一标识符 (memory_id),值(一个字典)则是记忆本身。
store.search 方法在我们的命名空间中读取记忆,该方法将返回给定用户的全部记忆作为一个列表。最新的记忆位于列表的末尾。
value:此内存的值key:在此命名空间中此内存的唯一键namespace:字符串列表,此内存类型的命名空间createdAt:此内存创建时的时间戳updatedAt:此内存更新时的时间戳
语义搜索
除了简单的检索之外,该存储还支持语义搜索,允许您根据意义而非精确匹配来查找记忆。要启用此功能,请使用嵌入模型配置存储:fields 参数或在存储记忆时指定 index 参数来控制哪些记忆部分被嵌入:
在 LangGraph 中使用
有了这一切,我们在LangGraph中使用memoryStore。memoryStore与检查点器协同工作:检查点器将状态保存到线程中,如上所述,而memoryStore允许我们在线程之间存储任意信息。我们使用检查点器和memoryStore编译图,如下所示。
thread_id 调用图,与之前一样,还使用 user_id,我们将使用它来命名空间我们的记忆,以便为特定用户存储,正如上面所展示的。
config 和 store 作为节点参数,在 任何节点 中访问 memoryStore 和 user_id。以下是我们在节点中使用语义搜索查找相关记忆的方法:
store.search 方法来获取记忆。回忆一下,记忆是以对象列表的形式返回的,这些对象可以转换为字典。
user_id 相同,我们仍然可以访问相同的记忆。
langgraph.json文件中配置索引设置。例如:
检查点库
在底层,检查点功能由符合 BaseCheckpointSaver 接口的检查点对象提供支持。LangGraph 提供了多个检查点实现,所有实现均通过独立的、可安装的库完成:@langchain/langgraph-checkpoint:检查点保存器的基础接口(BaseCheckpointSaver)和序列化/反序列化接口(SerializerProtocol)。包括内存中的检查点实现(MemorySaver),用于实验。LangGraph自带@langchain/langgraph-checkpoint。@langchain/langgraph-checkpoint-sqlite:LangGraph检查点的实现,使用SQLite数据库(SqliteSaver)。适用于实验和本地工作流程。需要单独安装。@langchain/langgraph-checkpoint-postgres:使用Postgres数据库(PostgresSaver)的高级检查点,用于LangSmith。适用于生产环境。需要单独安装。
检查点接口
每个检查点保存器都遵循BaseCheckpointSaver接口,并实现了以下方法:.put- 存储带有其配置和元数据的检查点。.putWrites- 存储与检查点相关联的中间写入操作(即待写入操作)。.getTuple- 使用给定的配置(thread_id和checkpoint_id)获取检查点元组。这用于在graph.getState()中填充StateSnapshot。.list- 列出与给定配置和筛选标准匹配的检查点。这用于在graph.getStateHistory()中填充状态历史记录。
@langchain/langgraph-checkpoint 定义了实现序列化器的协议,并提供了一个默认实现,该实现可以处理各种类型,包括 LangChain 和 LangGraph 原语、日期时间、枚举等。