[拆解LangChain执行引擎]非常规Pending Write的持久化
PendingWrite三元组的第二部分表示写入的Channel但是对于一些特殊的场景比如出错、无写入、中断和恢复它们的值不再是一个普通的Channel名称而是使用如下的值__error__执行Node对应的任务出现异常__no_writes__Node任务成功执行但是没有执行针对Channel的输出__interrupt__任务中断__resume__表示恢复执行提供的数据接下来我们两个例子来产生上述这几种特殊的Pending Write。我们先来模拟出错的场景如下面的代码片段所示我们执行的Pregel对象具有一个唯一的Node它的处理函数直接抛出一个异常。/* by 01130.hk - online tools website : 01130.hk/zh/keyboardcode.html */ from langgraph.pregel import Pregel, NodeBuilder from langgraph.channels import LastValue from langgraph.checkpoint.memory import InMemorySaver from typing import Any def handle(args: dict[str, Any])-None: raise Exception(manllually raised exception) node NodeBuilder().subscribe_to(start).do(handle) app Pregel( nodes{body: node}, channels{start: LastValue(str)}, checkpointerInMemorySaver(), input_channels[start], output_channels[], ) config {configurable: {thread_id: 123}} try: result app.invoke({start: begin}, configconfig) except Exception as ex: print(fCaught exception:{ex} ) (_, _, _, _, pending_writes) app.checkpointer.get_tuple(config) print(pending_writes)我们在try/except块中完成针对Pregel的调用并捕捉和输出得到的异常信息。接下来我们调用Checkpointer一个InMemorySaver对象的get_tuple方法得到对应的CheckpointTuple元组然后将pending_writes部分输出出来。从如下所示的输出结果可以看出这个Pending Write三元组的Channel名称被设置为__error__整个Exception对象成为了写入的内容。Caught exception:manllually raised exception [(f9ff1e88-4d82-f417-ad11-8fd870bfe647, __error__, Exception(manllually raised exception))]由于并不是所有的Node都有向Channel写入执行结果的需求所以只要处理函数成功执行即使没有Channel输出的行为该任务的状态也会被视为成功Checkpointer只是采用不同的形式来记录这种不需要写入的Pending Write。如下的这个程序不仅仅演示了这种无输出写入的场景还同时模拟了中断和恢复。/* by 01130.hk - online tools website : 01130.hk/zh/keyboardcode.html */ from langgraph.pregel import Pregel, NodeBuilder from langgraph.channels import LastValue from langgraph.checkpoint.memory import InMemorySaver from typing import Any from langgraph.types import Command, interrupt def foo(args: dict[str, Any]) - list[str]: resume1 interrupt(1st interrupt) assert resume1 1st resume resume2 interrupt(2nd interrupt) assert resume2 2nd resume resume3 interrupt(3rd interrupt) assert resume3 3rd resume return [resume1, resume2, resume3] def bar(args: dict[str, Any]) - None: pass app Pregel( nodes{ foo: NodeBuilder().subscribe_only(start).do(foo).write_to(output), bar: NodeBuilder().subscribe_only(start).do(bar), }, channels{ start: LastValue(str), output: LastValue(list[str]), }, input_channels[start], output_channels[output], checkpointerInMemorySaver(), ) config {configurable: {thread_id: 123}} result app.invoke(input{start: begin}, configconfig, stream_modetasks) (_, _, _, _, pending_writes) app.checkpointer.get_tuple(config) print(fAfter invoke:\n{pending_writes}) app.invoke(inputCommand(resume1st resume), configconfig) (_, _, _, _, pending_writes) app.checkpointer.get_tuple(config) print(f\nAfter resume 1:\n{pending_writes}) app.invoke(inputCommand(resume2nd resume), configconfig) (_, _, _, _, pending_writes) app.checkpointer.get_tuple(config) print(f\nAfter resume 2:\n{pending_writes}) result app.invoke(inputCommand(resume3rd resume), configconfig) assert result {output: [1st resume, 2nd resume, 3rd resume]} (_, _, _, _, pending_writes) app.checkpointer.get_tuple(config) print(f\nAfter resume 3:\n{pending_writes})如上面的代码片段所示我们为Pregel提供了两个并行执行的节点foo和bar其中bar对应的函数并未执行任何有效操作也没有任何的输出。我们为节点foo对应的处理函数制造了三次人为中断所以需要至少四次调用才能结束。我们在创建的RunnableConfig对象中提供了统一的Thread ID并将它作为后续方法调用的参数。针对Pregel的三次调用第一次是为常规调用后面两次分别是针对两次中断的恢复调用。我们在每次调用后得到并输出Checkpointer记录下来的Pending Write。从如下的输出结果可以看出第一次常规调用后 节点foo停在第一个中断处节点bar成功执行但没有输出所以Checkpointer将它们作为Pending Write记录下来Channel名称分别是__interrupt__和__no_writes__前者的写入内容是一个Interrupt对象它具有我们指定的值“1st interrupt”。我们也看到了Interrupt对象具有一个唯一标识在恢复调用时我们可以利用此标识为其指定针对性的恢复数据Command(resume{id:resume value)。After invoke: [(8d407c25-02f6-9101-d1b8-5a99c247edde, __interrupt__, [Interrupt(value1st interrupt, id5603cdf275d8b8ba0633d272fa176fd3)]), (22507855-e257-1b5b-eb1a-3c3fb0a071e9, __no_writes__, None)] After resume 1: [(8d407c25-02f6-9101-d1b8-5a99c247edde, __interrupt__, [Interrupt(value2nd interrupt, id5603cdf275d8b8ba0633d272fa176fd3)]), (22507855-e257-1b5b-eb1a-3c3fb0a071e9, __no_writes__, None), (00000000-0000-0000-0000-000000000000, __resume__, 1st resume), (8d407c25-02f6-9101-d1b8-5a99c247edde, __resume__, [1st resume])] After resume 2: [(8d407c25-02f6-9101-d1b8-5a99c247edde, __interrupt__, [Interrupt(value3rd interrupt, id5603cdf275d8b8ba0633d272fa176fd3)]), (22507855-e257-1b5b-eb1a-3c3fb0a071e9, __no_writes__, None), (00000000-0000-0000-0000-000000000000, __resume__, 2nd resume), (8d407c25-02f6-9101-d1b8-5a99c247edde, __resume__, [1st resume, 2nd resume])] After resume 3: []针对第一个中断的恢复调用后节点foo停在第二个中断处此时Checkpointer会创建两个新的Pending Write持久化我们提供的Resume Value“1st resume”它的Channel名称就是__resume__但为什么是两个呢这实际上反映了 Pregel 处理外部指令注入与Node内部消费的同步机制。第一个被称为全局Resume ValueGlobal Resume Value, 它代表从外部通过Command(resume...)注入到图中的原始指令。由于它不是由图内Node产生的因此 Task ID 为空它是唤醒整个暂停状态的“总开关”。第二个节点foo对全局Resume Value的消费记录所以具有一个明确的Taks ID。当节点foo被唤醒并执行到interrupt行时它会从全局Resume Value读取数据。为了保证幂等性和可回溯性系统会将拿走了哪个Resume Value记录在它的任务路径下。针对Resume的冗余设置是为了解决重入与回溯问题。全局记录证明了用户确实提供了这个值。Node记录证明了这个值确实被这个特定的interrupt函数调用消费了。一个Node内部可以连续调用多次interrupt函数系统需要按顺序记录该Node消费过的所有Resume Value以便在“时间旅行”或重试时能够精确对齐。当我们调用interrupt函数实施人为中断时底层实际上会抛出一个GraphInterrupt异常Pregel通过捕获这个异常进而生成针对性的PendingWrite所以针对同一个任务有可能有一个唯一的中断类型的PendingWrite。由于恢复执行总是会从头执行Node函数所以基于中断的PendingWrite并不会恢复执行造成任何影响。所以当我们完成第二次恢复调用后持久化的中断PendingWrite反映的是针对第二次interrupt函数的调用对应Interrupt对象的值为2nd interrupt。Resume Value必须按照顺序提供因为每遇到一个interrpt函数的调用都会利用前面介绍过的计算器提供的索引从Resume Value列表中读取Resume Value作为该调用的返回值所以持久化的第二个基于恢复的PendingWrite对应的值变成了包含两个Resume Value的列表[1st resume, 2nd resume]。在针对第三个中断的恢复执行结束后fooNode完成了它的执行任务而bar对应的任务本身就是成功状态所以整个Superstep顺利结束自然也就不存在Pending Write了。

相关新闻

AI知识检索新体验:GTE+SeqGPT镜像效果实测

AI知识检索新体验:GTE+SeqGPT镜像效果实测

AI知识检索新体验:GTESeqGPT镜像效果实测 1. 为什么你需要一个“能听懂意思”的知识库? 你有没有遇到过这样的情况:在公司内部知识库搜索“怎么让服务器不卡”,结果返回的全是“Linux内存优化”“CPU负载排查”这类技术文档&…

2026/7/5 8:08:39 阅读更多 →
yz-bijini-cosplay与YOLOv8结合:二次元角色自动识别与生成系统部署指南

yz-bijini-cosplay与YOLOv8结合:二次元角色自动识别与生成系统部署指南

yz-bijini-cosplay与YOLOv8结合:二次元角色自动识别与生成系统部署指南 1. 引言 在动漫内容创作领域,每天都有大量二次元角色需要识别和处理。传统的人工识别方式不仅效率低下,而且容易出错。想象一下,一个动漫平台每天需要处理…

2026/5/17 5:45:05 阅读更多 →
SiameseUIE中文信息抽取:属性情感分析实战案例

SiameseUIE中文信息抽取:属性情感分析实战案例

SiameseUIE中文信息抽取:属性情感分析实战案例 1. 引言 在当今电商和社交媒体时代,用户评论中蕴含着大量有价值的信息。如何从海量文本中自动提取产品属性和用户情感,成为了企业洞察用户需求、改进产品的重要技术手段。传统的情感分析方法往…

2026/7/4 6:10:27 阅读更多 →

最新新闻

10分钟学会OpenEuler bridge-utils:新手必备网络桥接配置技巧

10分钟学会OpenEuler bridge-utils:新手必备网络桥接配置技巧

10分钟学会OpenEuler bridge-utils:新手必备网络桥接配置技巧 【免费下载链接】bridge-utils Utilities for configuring the linux ethernet bridge 项目地址: https://gitcode.com/openeuler/bridge-utils 前往项目官网免费下载:https://ar.ope…

2026/7/5 8:08:17 阅读更多 →
超实用!内网/交换机/路由器/无线运维排障干货大全

超实用!内网/交换机/路由器/无线运维排障干货大全

🌟 一、网络排障黄金流程(核心必记)所有网络故障排查遵循由近到远原则,适配80%办公网络问题,一步快速定位故障点!排查顺序:本地网卡 → 网线/墙面网口面板 → 交换机端口 → 网关 → 外网万能排…

2026/7/5 8:08:17 阅读更多 →
NVIDIA Profile Inspector深度探索:解锁显卡隐藏性能的7个实战技巧

NVIDIA Profile Inspector深度探索:解锁显卡隐藏性能的7个实战技巧

NVIDIA Profile Inspector深度探索:解锁显卡隐藏性能的7个实战技巧 【免费下载链接】nvidiaProfileInspector 项目地址: https://gitcode.com/gh_mirrors/nv/nvidiaProfileInspector NVIDIA Profile Inspector是一款能够深入访问NVIDIA驱动内部数据库的工具…

2026/7/5 8:08:17 阅读更多 →
openEuler安全设施实战指南:从日志分析到入侵检测的10个最佳实践 [特殊字符]

openEuler安全设施实战指南:从日志分析到入侵检测的10个最佳实践 [特殊字符]

openEuler安全设施实战指南:从日志分析到入侵检测的10个最佳实践 🔒 【免费下载链接】security-facility The repository for security facility SIG 项目地址: https://gitcode.com/openeuler/security-facility 前往项目官网免费下载&#xff1…

2026/7/5 8:06:17 阅读更多 →
NestOS-Config核心架构解析:深入理解rpm-ostree与ignition配置

NestOS-Config核心架构解析:深入理解rpm-ostree与ignition配置

NestOS-Config核心架构解析:深入理解rpm-ostree与ignition配置 【免费下载链接】nestos-config nestos-config provides base manifest configuration for building NestOS. 项目地址: https://gitcode.com/openeuler/nestos-config 前往项目官网免费下载&am…

2026/7/5 8:04:16 阅读更多 →
ExtFUSE社区贡献指南:如何参与这个开源文件系统革命

ExtFUSE社区贡献指南:如何参与这个开源文件系统革命

ExtFUSE社区贡献指南:如何参与这个开源文件系统革命 【免费下载链接】extfuse Extension Framework for FUSE 项目地址: https://gitcode.com/openeuler/extfuse 前往项目官网免费下载:https://ar.openeuler.org/ar/ ExtFUSE(Extensi…

2026/7/5 8:00:16 阅读更多 →

日新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/5 0:03:34 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/5 0:03:34 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/5 0:07:38 阅读更多 →

月新闻