LangGraph 状态快照与回滚:Agent 跑飞时的“时光机”恢复方案


核心概念

什么是LangGraph

LangGraph 是由 LangChain 团队在 2024 年推出的基于状态机的多 Agent 协同框架,它是对 LangChain 原有 LCEL(LangChain Expression Language)的重大升级:

  1. 从“线性/树状管道”到“有向循环图(Cyclic Directed Graph, CDG)”:允许 Agent 自主决策循环执行(如自我修正、多轮搜索)、条件分支跳转,彻底打破了 LCEL 无法处理复杂逻辑闭环的限制。
  2. 显式状态管理(State):将多 Agent 协作过程中产生的所有中间数据(如用户输入、搜索结果、中间决策、工具调用日志)统一存储在一个可序列化、可演化、可结构化访问的状态对象中,这是实现“状态快照与回滚”的核心前提。
  3. 消息传递架构(Message Passing):节点(Agent、工具、条件判断)之间通过消息传递进行交互,消息本身会被自动追加到状态对象的历史队列中,无需开发者手动管理会话上下文。

什么是“Agent跑飞”

在 LangGraph 构建的复杂 Agent 系统(如代码助手、企业级客服机器人、多模态内容创作平台、AI 研究助手)中,“Agent跑飞”(Agent Drift/Agent Hallucination Loop/Agent Infinite Loop) 是指系统在运行过程中出现的三类典型异常行为:

  1. 无限循环(Infinite Hallucination Loop):Agent 反复调用相同/相似的工具却无法收敛到预期结果,或陷入自我修正的死胡同(如“我刚才搜索错了关键词→重新搜索→又错了→再试一次”的循环)。
  2. 幻觉输出(Critical Hallucination):Agent 在没有调用外部工具/知识库验证的情况下,生成了虚假、误导性甚至危险的内容(如代码助手生成能删除系统文件的“测试脚本”、医疗咨询助手给出错误的用药建议)。
  3. 状态损坏(State Corruption):由于开发者的状态定义错误、工具返回的数据格式异常、网络中断导致的消息丢失,导致状态对象的结构或内容损坏,后续所有节点都无法正常处理。

什么是“状态快照与回滚”

LangGraph 状态快照与回滚是一套基于显式状态管理的容错机制,它的核心思想类似于游戏存档/读档、虚拟机快照/恢复、Git 的分支/回退:

  1. 状态快照(State Snapshot):在 Agent 系统运行的关键节点(如工具调用前/后、用户确认后、自我修正前),将当前状态对象的完整内容(包括历史消息、私有状态、结构化数据)序列化并持久化存储(支持内存、本地文件、Redis、PostgreSQL、S3 等多种存储后端),生成一个唯一的快照 ID(Snapshot ID)。
  2. 状态回滚(State Rollback):当检测到“Agent跑飞”或其他异常时,根据指定的快照 ID,从持久化存储中加载对应的状态对象,替换当前运行时的状态,并让系统从快照对应的节点重新开始执行(可选是否保留原状态中损坏或错误的部分内容)。

问题背景

1. 多 Agent 协同系统的复杂性飙升

随着 AI 大模型(LLM)能力的不断提升,开发者不再满足于构建“单轮问答”或“线性工具调用”的简单 LLM 应用,而是开始尝试构建由多个专业化 Agent 组成的协同系统

  • 例如,一个企业级研发助手可能包含:
    1. 需求分析 Agent:负责拆解用户的业务需求,生成结构化的任务列表;
    2. 代码生成 Agent:根据任务列表生成不同模块的代码;
    3. 代码审查 Agent:检查生成代码的语法错误、安全漏洞、代码规范;
    4. 测试用例生成 Agent:为每个模块生成单元测试、集成测试用例;
    5. 测试执行 Agent:调用 Docker 容器运行测试用例,返回测试结果;
    6. 自我修正 Agent:根据代码审查或测试执行的结果,要求代码生成 Agent 修改代码。

这种多 Agent 协同系统的执行路径是高度动态、不可预测的:

  • 可能出现“需求分析→代码生成→代码审查→自我修正→代码生成→测试用例生成→测试执行→自我修正→……”的长循环;
  • 可能因某一个 Agent 的幻觉输出(如代码审查 Agent 漏掉了一个严重的 SQL 注入漏洞)导致整个系统的输出不符合预期;
  • 可能因网络中断导致测试执行 Agent 无法返回结果,状态对象中缺少测试结果字段,后续的自我修正 Agent 无法正常工作。

2. LangChain 原有容错机制的局限性

在 LangGraph 推出之前,LangChain 用户主要使用以下几种容错机制,但它们都无法有效解决“Agent跑飞”的问题:

(1)重试机制(Retry Mechanism)

LangChain 提供了 RetryWithBackoffRetryIfException 等装饰器,可以在工具调用失败或 LLM 生成失败时自动重试。

  • 局限性
    • 只能处理“瞬时性异常”(如网络超时、LLM 服务不可用),无法处理“逻辑异常”(如无限循环、幻觉输出、状态损坏);
    • 重试次数有限,超过最大重试次数后系统会直接崩溃,无法恢复;
    • 无法选择性地回滚到某个“安全的中间状态”,只能重试当前节点的操作。
(2)异常捕获与处理(Try-Except)

开发者可以在 LangChain 的节点或工具中手动添加 try-except 块,捕获异常并执行一些简单的处理逻辑(如返回默认值、记录日志、向用户报错)。

  • 局限性
    • 手动添加 try-except 块会增加代码的复杂度,特别是在多 Agent 协同系统中,每个节点都可能抛出异常;
    • 处理逻辑通常是“单点式”的,无法影响整个系统的执行路径;
    • 无法解决“状态损坏后如何恢复到之前的健康状态”的问题。
(3)会话历史截断(Session History Truncation)

对于长会话的应用,开发者通常会使用 RecursiveCharacterTextSplitter 等工具截断会话历史,避免 LLM 的上下文窗口溢出。

  • 局限性
    • 截断会话历史会导致 LLM 丢失重要的上下文信息,可能引发新的“Agent跑飞”问题;
    • 无法选择性地保留或删除某些历史消息;
    • 无法恢复到截断之前的完整状态。

3. 企业级 LLM 应用对“高可用性、高可靠性、可审计性”的要求

随着 LLM 应用从“原型阶段”进入“生产阶段”,企业对系统的要求也从“能用”变成了“好用、耐用、可信”:

  1. 高可用性(High Availability, HA):系统必须能够在出现异常时快速恢复,不能长时间停机;
  2. 高可靠性(High Reliability, HR):系统的输出必须是可预测、可验证的,不能出现“Agent跑飞”导致的严重问题;
  3. 可审计性(Auditability):企业必须能够追溯系统的整个执行过程,包括每一步的状态变化、工具调用、LLM 生成结果,以便在出现问题时进行排查和追责。

LangGraph 状态快照与回滚机制正好满足了这三个要求:

  • 高可用性:快速回滚到健康状态,继续提供服务;
  • 高可靠性:可以在关键节点保存快照,一旦出现问题可以回滚,避免错误的输出;
  • 可审计性:所有快照都包含了完整的执行历史,可以随时查看和分析。

问题描述

1. 功能需求

我们需要为 LangGraph 构建的多 Agent 协同系统实现一套完整的状态快照与回滚功能,具体包括:

(1)快照生成功能
  • 支持自动快照:在用户指定的关键节点(如 STARTEND、工具调用前/后、条件判断后)自动生成快照;
  • 支持手动快照:开发者可以在代码中手动调用快照生成函数,在任意时刻保存状态;
  • 支持快照元数据:每个快照必须包含元数据,如快照 ID、生成时间戳、生成节点、快照描述、当前执行的会话 ID、当前执行的用户 ID、当前状态的哈希值(用于验证快照的完整性);
  • 支持快照增量生成:对于大型状态对象(如包含大量历史消息、多模态数据的状态),支持增量生成快照,只保存与上一个快照相比发生变化的部分,减少存储开销。
(2)快照存储功能
  • 支持多种存储后端
    • 内存存储(适合开发测试环境);
    • 本地文件存储(适合单机部署的生产环境);
    • Redis 存储(适合分布式部署的生产环境,支持高速读写);
    • PostgreSQL 存储(适合需要持久化存储、复杂查询、事务支持的生产环境);
    • S3/GCS/Azure Blob Storage 存储(适合需要海量存储、高可用性、异地备份的生产环境);
  • 支持存储策略配置
    • 快照保留时间(TTL, Time To Live);
    • 最大快照数量(超过数量后自动删除最早的快照);
    • 快照压缩(支持 Gzip、Snappy、Zstandard 等压缩算法);
    • 快照加密(支持 AES-256 等加密算法,保护敏感数据)。
(3)快照查询功能
  • 支持按会话 ID 查询:查询某个会话的所有快照;
  • 支持按用户 ID 查询:查询某个用户的所有会话的所有快照;
  • 支持按时间范围查询:查询某个时间范围内生成的所有快照;
  • 支持按节点名称查询:查询在某个节点生成的所有快照;
  • 支持按快照 ID 查询:查询某个特定快照的详细信息(包括元数据和完整状态);
  • 支持快照完整性验证:根据状态的哈希值验证快照是否被篡改。
(4)状态回滚功能
  • 支持按快照 ID 回滚:指定一个快照 ID,让系统从该快照对应的节点重新开始执行;
  • 支持按时间点回滚:指定一个时间点,让系统回滚到该时间点之前最近的一个健康快照;
  • 支持按执行步骤回滚:指定回滚的步骤数(如回滚 3 步),让系统回滚到当前步骤之前的第 N 个快照;
  • 支持选择性回滚:回滚时可以选择性地保留原状态中的某些部分内容(如保留用户的最新输入,但删除后续所有 Agent 的操作和工具调用);
  • 支持回滚通知:回滚完成后,系统可以向开发者、运维人员或用户发送通知(如邮件、短信、Slack 消息、Webhook)。
(5)异常检测与自动回滚功能
  • 支持无限循环检测:检测系统是否在相同/相似的节点之间循环执行超过指定的次数;
  • 支持幻觉输出检测:可以集成第三方幻觉检测工具(如 LangChain 的 HallucinationDetector、OpenAI 的 Moderation API、自定义的幻觉检测模型),检测 LLM 的输出是否存在幻觉;
  • 支持状态损坏检测:检测当前状态对象的结构或内容是否符合开发者的定义;
  • 支持自动回滚阈值配置:当异常检测的结果超过指定的阈值时,系统自动回滚到最近的一个健康快照;
  • 支持异常日志记录:所有异常检测的结果和自动回滚的操作都必须记录到日志中,以便后续的排查和分析。

2. 非功能需求

  • 性能
    • 快照生成的时间必须小于 100ms(对于小型状态对象),小于 1s(对于大型状态对象,支持增量生成);
    • 快照查询的时间必须小于 10ms(对于按快照 ID 查询),小于 100ms(对于按其他条件查询);
    • 状态回滚的时间必须小于 100ms(对于小型状态对象),小于 1s(对于大型状态对象);
  • 可扩展性
    • 存储后端必须支持水平扩展,以应对海量的快照存储需求;
    • 异常检测和自动回滚功能必须支持异步执行,以避免影响系统的正常执行;
  • 安全性
    • 快照必须支持加密存储,保护敏感数据(如用户的个人信息、企业的商业机密);
    • 快照查询和回滚功能必须支持权限控制,只有授权的用户或系统才能访问和操作;
  • 易用性
    • API 接口必须简单、清晰、易于使用;
    • 配置文件必须简单、直观、易于修改;
    • 文档必须完整、详细、包含大量的代码示例。

问题解决

1. 核心思路

LangGraph 状态快照与回滚的核心思路是利用 LangGraph 显式状态管理和消息传递架构的特性,将系统的执行过程分解为一系列的“状态转换步骤”,在每个关键步骤保存状态快照,当出现异常时可以从任意快照恢复状态并重新执行。

具体来说,我们需要做以下几件事:

  1. 扩展 LangGraph 的 State 类:添加一个 state_hash 属性,用于计算当前状态的哈希值,验证快照的完整性;
  2. 实现 Snapshot 类:用于封装快照的元数据和完整状态;
  3. 实现 SnapshotStore 抽象基类:定义快照存储、查询、删除的通用接口;
  4. 实现多种 SnapshotStore 的具体子类:如 InMemorySnapshotStoreLocalFileSnapshotStoreRedisSnapshotStorePostgreSQLSnapshotStoreS3SnapshotStore
  5. 扩展 LangGraph 的 Graph 类:添加 add_snapshot_node 方法,用于在关键节点自动生成快照;添加 rollback 方法,用于手动或自动回滚到指定的快照;
  6. 实现 ExceptionDetector 抽象基类:定义异常检测的通用接口;
  7. 实现多种 ExceptionDetector 的具体子类:如 InfiniteLoopDetectorHallucinationDetectorStateCorruptionDetector
  8. 实现 AutoRollbackManager 类:用于管理异常检测和自动回滚的逻辑。

2. 技术选型

为了实现上述功能,我们选择了以下技术栈:

功能模块 技术选型
编程语言 Python 3.10+(LangGraph 官方支持的最低版本)
框架核心 LangGraph 0.2.0+(包含显式状态管理、消息传递架构、节点扩展等功能)
状态序列化 pickle(开发测试环境)、msgpack(生产环境,支持跨语言、高性能、小体积)、json(调试环境,人类可读)
快照压缩 zstandard(高性能、高压缩比)、gzip(兼容性好)
快照加密 cryptography(Python 官方推荐的加密库)、AES-256-GCM(认证加密算法)
内存存储 Python 的 dict(简单易用)
本地文件存储 Python 的 os、pathlib 模块(简单易用)
Redis 存储 redis-py(Python 官方推荐的 Redis 客户端)
PostgreSQL 存储 psycopg2-binary(Python 官方推荐的 PostgreSQL 客户端)、SQLAlchemy(ORM 框架,方便数据库操作)
S3 存储 boto3(AWS 官方推荐的 Python SDK)、moto(开发测试环境的 S3 模拟工具)
无限循环检测 LangGraph 的 History 类(用于存储和访问执行历史)
幻觉输出检测 LangChain 的 HallucinationDetector、OpenAI 的 Moderation API
状态损坏检测 Pydantic v2+(用于状态对象的结构化定义和验证)
异步执行 Python 的 asyncio、aiohttp(异步 HTTP 客户端)
日志记录 Python 的 logging 模块(简单易用)、structlog(结构化日志,适合生产环境)
配置管理 Pydantic v2+ 的 Settings 类(用于配置文件的结构化定义和验证)
权限控制 FastAPI(用于构建 API 接口时的权限控制)、OAuth2(常用的认证授权协议)

边界与外延

1. 边界

LangGraph 状态快照与回滚机制虽然强大,但也有一些边界和限制:

(1)只适用于显式状态管理的应用

LangGraph 状态快照与回滚机制完全依赖于 LangGraph 的显式状态管理,如果应用没有使用显式状态管理,而是手动管理会话上下文,那么这套机制就无法使用。

(2)无法处理外部系统的状态变化

LangGraph 状态快照与回滚机制只能恢复 LangGraph 系统内部的状态,无法处理外部系统的状态变化(如数据库的写入、文件的删除、API 的调用)。如果 Agent 在回滚之前调用了外部工具并修改了外部系统的状态,那么回滚之后外部系统的状态仍然是修改后的状态,开发者需要手动处理这种情况(如在工具调用前保存外部系统的状态,回滚时恢复外部系统的状态)。

(3)增量快照的生成和恢复依赖于上一个快照

如果使用增量快照,那么生成增量快照依赖于上一个完整快照或增量快照,恢复增量快照也依赖于上一个完整快照和所有后续的增量快照。如果上一个快照被删除或损坏,那么当前的增量快照就无法使用。

(4)自动回滚可能会导致系统的执行时间延长

如果系统频繁出现异常并自动回滚,那么系统的执行时间可能会延长,甚至无法完成任务。开发者需要合理配置自动回滚的阈值,避免系统陷入“回滚→执行→异常→回滚→……”的死循环。

(5)快照存储需要占用大量的存储空间

如果应用的状态对象很大,并且频繁生成快照,那么快照存储需要占用大量的存储空间。开发者需要合理配置快照的保留时间、最大快照数量、压缩算法,减少存储开销。

2. 外延

LangGraph 状态快照与回滚机制不仅可以用于“Agent跑飞时的恢复”,还可以扩展到以下场景:

(1)多分支探索

在 Agent 系统中,开发者可能需要让 Agent 探索多个不同的执行路径,然后选择最优的路径。例如,一个内容创作 Agent 可能需要探索“先写标题再写内容”和“先写内容再写标题”两个路径,然后选择用户更喜欢的那个路径。LangGraph 状态快照与回滚机制可以帮助开发者实现这种多分支探索:

  1. 在分支点保存状态快照;
  2. 探索第一个路径,记录结果;
  3. 回滚到分支点的快照;
  4. 探索第二个路径,记录结果;
  5. 比较两个路径的结果,选择最优的路径。
(2)交互式调试

在开发 Agent 系统时,开发者可能需要调试系统的执行过程,查看每一步的状态变化、工具调用、LLM 生成结果。LangGraph 状态快照与回滚机制可以帮助开发者实现这种交互式调试:

  1. 在关键节点自动生成快照;
  2. 系统执行到某个节点时暂停,允许开发者查看当前的状态;
  3. 开发者可以手动回滚到之前的某个快照,重新执行;
  4. 开发者可以修改状态对象的内容,然后继续执行。
(3)会话恢复

在长会话的应用中,用户可能会中途退出,然后再次登录继续会话。LangGraph 状态快照与回滚机制可以帮助开发者实现这种会话恢复:

  1. 在用户每次交互后保存状态快照;
  2. 用户再次登录时,查询该用户的最近一个健康快照;
  3. 回滚到该快照,继续会话。
(4)A/B 测试

在优化 Agent 系统时,开发者可能需要对不同的 LLM 模型、提示词、工具配置进行 A/B 测试。LangGraph 状态快照与回滚机制可以帮助开发者实现这种 A/B 测试:

  1. 准备一批测试用例,每个测试用例对应一个初始状态;
  2. 对于每个测试用例,保存初始状态的快照;
  3. 使用 A 组配置执行测试用例,记录结果;
  4. 回滚到初始状态的快照;
  5. 使用 B 组配置执行测试用例,记录结果;
  6. 比较 A 组和 B 组的结果,选择最优的配置。

概念结构与核心要素组成

1. 概念结构

LangGraph 状态快照与回滚机制的概念结构可以分为三层

  1. 状态层(State Layer):负责状态对象的定义、演化、序列化、哈希计算;
  2. 快照层(Snapshot Layer):负责快照的生成、封装、存储、查询、删除;
  3. 控制层(Control Layer):负责异常检测、自动回滚、多分支探索、交互式调试等高级功能。

下面是 LangGraph 状态快照与回滚机制的概念结构示意图:

输入/调用

状态转换

序列化状态

存储快照

查询快照

删除快照

状态哈希

完整性验证结果

执行历史

异常检测

异常检测结果

自动回滚请求

加载快照

替换当前状态

多分支探索请求

交互式调试请求

会话恢复请求

A/B 测试请求

用户/外部系统

LangGraph 系统

状态层

快照层

存储后端

验证模块

控制层

异常检测器

2. 核心要素组成

LangGraph 状态快照与回滚机制的核心要素包括:

(1)State(状态对象)

State 是 LangGraph 系统的核心,它存储了系统在运行过程中产生的所有中间数据。State 可以是一个简单的字典,也可以是一个 Pydantic 模型(推荐使用 Pydantic 模型,因为它支持结构化定义和验证)。

State 通常包含以下几个部分:

  1. messages(历史消息队列):存储系统执行过程中所有的消息(如用户输入、Agent 输出、工具调用请求、工具调用响应);
  2. private_state(私有状态):存储某个特定 Agent 的私有数据(如代码生成 Agent 的当前模块名称、当前代码行数);
  3. structured_data(结构化数据):存储系统执行过程中产生的结构化数据(如需求分析 Agent 生成的任务列表、代码审查 Agent 生成的漏洞列表);
  4. state_hash(状态哈希值):用于计算当前状态的哈希值,验证快照的完整性;
  5. session_id(会话 ID):用于标识当前的会话;
  6. user_id(用户 ID):用于标识当前的用户;
  7. execution_history(执行历史):存储系统执行过程中所有的节点名称、执行时间戳、执行结果。
(2)Snapshot(快照对象)

Snapshot 是对 State 的封装,它包含了 State 的完整内容和元数据。Snapshot 通常包含以下几个部分:

  1. snapshot_id(快照 ID):用于唯一标识当前的快照(通常使用 UUID 生成);
  2. timestamp(生成时间戳):用于记录快照的生成时间;
  3. node_name(生成节点名称):用于记录快照是在哪个节点生成的;
  4. description(快照描述):用于记录快照的生成原因(如“工具调用前”、“用户确认后”、“自动快照”);
  5. session_id(会话 ID):用于标识快照所属的会话;
  6. user_id(用户 ID):用于标识快照所属的用户;
  7. state_hash(状态哈希值):用于验证快照的完整性;
  8. state_data(状态数据):存储序列化后的 State 对象;
  9. is_incremental(是否增量快照):用于标识当前快照是否是增量快照;
  10. parent_snapshot_id(父快照 ID):用于标识当前增量快照的父快照(如果是完整快照,则为 None)。
(3)SnapshotStore(快照存储抽象基类)

SnapshotStore 是一个抽象基类,它定义了快照存储、查询、删除的通用接口。SnapshotStore 通常包含以下几个方法:

  1. save_snapshot(snapshot: Snapshot) -> str:保存快照,返回快照 ID;
  2. get_snapshot(snapshot_id: str) -> Optional[Snapshot]:根据快照 ID 查询快照;
  3. list_snapshots(session_id: Optional[str] = None, user_id: Optional[str] = None, node_name: Optional[str] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None, limit: Optional[int] = None, offset: Optional[int] = None) -> List[Snapshot]:根据条件查询快照列表;
  4. delete_snapshot(snapshot_id: str) -> bool:根据快照 ID 删除快照;
  5. delete_snapshots(session_id: Optional[str] = None, user_id: Optional[str] = None, node_name: Optional[str] = None, start_time: Optional[datetime] = None, end_time: Optional[datetime] = None) -> int:根据条件删除快照列表;
  6. validate_snapshot(snapshot: Snapshot) -> bool:验证快照的完整性;
  7. compress_state(state_data: bytes) -> bytes:压缩状态数据;
  8. decompress_state(compressed_state_data: bytes) -> bytes:解压状态数据;
  9. encrypt_state(state_data: bytes) -> bytes:加密状态数据;
  10. decrypt_state(encrypted_state_data: bytes) -> bytes:解密状态数据。
(4)ExceptionDetector(异常检测抽象基类)

ExceptionDetector 是一个抽象基类,它定义了异常检测的通用接口。ExceptionDetector 通常包含以下几个方法:

  1. detect(state: State) -> Tuple[bool, Optional[str]]:检测当前状态是否存在异常,返回一个元组(是否存在异常,异常描述);
  2. **configure(kwargs) -> None:配置异常检测的参数。
(5)AutoRollbackManager(自动回滚管理器)

AutoRollbackManager 是一个类,它用于管理异常检测和自动回滚的逻辑。AutoRollbackManager 通常包含以下几个方法:

  1. add_detector(detector: ExceptionDetector) -> None:添加一个异常检测器;
  2. remove_detector(detector_name: str) -> None:删除一个异常检测器;
  3. check_and_rollback(state: State, graph: Graph, snapshot_store: SnapshotStore) -> Optional[State]:检查当前状态是否存在异常,如果存在异常则自动回滚到最近的一个健康快照,返回回滚后的状态;
  4. **configure(kwargs) -> None:配置自动回滚的参数。

概念之间的关系

1. 概念核心属性维度对比

为了更好地理解 LangGraph 状态快照与回滚机制的核心要素,我们从目的、数据来源、数据内容、使用场景、生命周期五个维度对它们进行对比:

核心要素 目的 数据来源 数据内容 使用场景 生命周期
State 存储系统运行过程中的所有中间数据 用户输入、Agent 输出、工具调用响应、系统内部计算 messages、private_state、structured_data、state_hash、session_id、user_id、execution_history 系统正常执行、状态快照生成、状态回滚 从会话开始到会话结束
Snapshot 封装 State 的完整内容和元数据,用于持久化存储和恢复 State、系统当前执行的节点、当前时间 snapshot_id、timestamp、node_name、description、session_id、user_id、state_hash、state_data、is_incremental、parent_snapshot_id 状态快照存储、状态快照查询、状态回滚、多分支探索、交互式调试、会话恢复、A/B 测试 从快照生成到快照删除
SnapshotStore 提供快照存储、查询、删除的通用接口 Snapshot、存储后端 无(抽象基类)或存储后端的连接信息(具体子类) 所有与快照相关的操作 从系统启动到系统关闭
ExceptionDetector 检测当前状态是否存在异常 State、执行历史、外部工具(如幻觉检测工具) 无(抽象基类)或检测参数(具体子类) 无限循环检测、幻觉输出检测、状态损坏检测 从检测器添加到检测器删除
AutoRollbackManager 管理异常检测和自动回滚的逻辑 ExceptionDetector、State、Graph、SnapshotStore 异常检测器列表、自动回滚参数 自动回滚、异常日志记录 从系统启动到系统关闭

2. 概念联系的 ER 实体关系图

下面是 LangGraph 状态快照与回滚机制的核心要素之间的 ER 实体关系图:

has

has

has

generates

has_parent

stores

used_by

queries

replaces

USER

SESSION

STATE

SNAPSHOT

SNAPSHOTSTORE

EXCEPTIONDETECTOR

AUTOROLLBACKMANAGER

3. 交互关系图

下面是 LangGraph 状态快照与回滚机制的核心要素之间的交互关系图(以“系统正常执行→自动生成快照→检测到无限循环→自动回滚到最近的健康快照→继续执行”为例):

StorageBackend AutoRollbackManager ExceptionDetector SnapshotStore SnapshotGenerator State LangGraph User StorageBackend AutoRollbackManager ExceptionDetector SnapshotStore SnapshotGenerator State LangGraph User 输入查询 更新状态(添加用户输入) 生成自动快照(START节点) 计算状态哈希值 封装快照对象 保存快照 写入快照 返回快照ID 返回快照ID 快照生成成功 执行节点1(需求分析Agent) 更新状态(添加需求分析Agent的输出) 生成自动快照(节点1后) 计算状态哈希值 封装快照对象 保存快照 写入快照 返回快照ID 返回快照ID 快照生成成功 执行节点2(代码生成Agent) 更新状态(添加代码生成Agent的输出) 生成自动快照(节点2后) 计算状态哈希值 封装快照对象 保存快照 写入快照 返回快照ID 返回快照ID 快照生成成功 执行节点3(代码审查Agent) 更新状态(添加代码审查Agent的输出,要求修改代码) 生成自动快照(节点3后) 计算状态哈希值 封装快照对象 保存快照 写入快照 返回快照ID 返回快照ID 快照生成成功 跳转到节点2(自我修正) 更新状态(添加代码生成Agent的输出,和之前一样) 生成自动快照(节点2后) 计算状态哈希值 封装快照对象 保存快照 写入快照 返回快照ID 返回快照ID 快照生成成功 执行节点3(代码审查Agent) 更新状态(添加代码审查Agent的输出,和之前一样) 生成自动快照(节点3后) 计算状态哈希值 封装快照对象 保存快照 写入快照 返回快照ID 返回快照ID 快照生成成功 检查是否需要自动回滚 检测当前状态是否存在异常 获取执行历史 检测到无限循环(节点2和节点3之间循环超过3次) 返回(True, "检测到无限循环:节点2和节点3之间循环超过3次") 查询当前会话的最近的健康快照(节点1后) 查询快照 返回快照 返回快照 验证快照的完整性 计算快照中状态数据的哈希值 比较计算出的哈希值和快照中的state_hash 返回(True, "快照完整性验证成功") 解压和解密状态数据 解密状态数据 解压状态数据 返回原始状态数据 反序列化状态数据,生成State对象 替换当前状态 返回回滚后的状态,从节点1后继续执行 从节点1后继续执行(可能修改提示词或工具配置) 返回最终结果

数学模型

1. 状态演化模型

LangGraph 系统的执行过程可以看作是一个状态机(State Machine),状态机的状态空间是所有可能的 State 对象的集合,状态机的转换函数是 LangGraph 中每个节点的处理逻辑。

假设:

  • SSS 是状态空间,即所有可能的 State 对象的集合;
  • N={n0,n1,n2,...,nk}N = \{n_0, n_1, n_2, ..., n_k\}N={n0,n1,n2,...,nk} 是 LangGraph 中的所有节点的集合,其中 n0n_0n0 是 START 节点,nkn_knk 是 END 节点;
  • T:N×S→N×ST: N \times S \rightarrow N \times ST:N×SN×S 是状态转换函数,对于任意节点 ni∈Nn_i \in NniN 和状态 sj∈Ss_j \in SsjST(ni,sj)=(ni+1,sj+1)T(n_i, s_j) = (n_{i+1}, s_{j+1})T(ni,sj)=(ni+1,sj+1) 表示系统在节点 nin_ini 处理状态 sjs_jsj 后,跳转到节点 ni+1n_{i+1}ni+1,并生成新的状态 sj+1s_{j+1}sj+1
  • s0∈Ss_0 \in Ss0S 是初始状态,即系统在 START 节点处理之前的状态;
  • sf∈Ss_f \in SsfS 是最终状态,即系统在 END 节点处理之后的状态。

那么,LangGraph 系统的执行过程可以表示为一个状态转换序列
(s0,n0)→T(s1,n1)→T(s2,n2)→T...→T(sf,nk) (s_0, n_0) \xrightarrow{T} (s_1, n_1) \xrightarrow{T} (s_2, n_2) \xrightarrow{T} ... \xrightarrow{T} (s_f, n_k) (s0,n0)T (s1,n1)T (s2,n2)T ...T (sf,nk)

2. 状态快照模型

状态快照是对状态转换序列中某个时刻的状态 sjs_jsj 的持久化存储,我们可以将状态快照模型表示为一个元组
Snapj=(idj,tj,nj,dj,sid,uid,hj,dataj,incj,pidj) Snap_j = (id_j, t_j, n_j, d_j, sid, uid, h_j, data_j, inc_j, pid_j) Snapj=(idj,tj,nj,dj,sid,uid,hj,dataj,incj,pidj)

其中:

  • idjid_jidj 是快照的唯一标识符(通常使用 UUID 生成);
  • tjt_jtj 是快照的生成时间戳;
  • njn_jnj 是快照的生成节点;
  • djd_jdj 是快照的生成描述;
  • sidsidsid 是快照所属的会话 ID;
  • uiduiduid 是快照所属的用户 ID;
  • hjh_jhj 是状态 sjs_jsj 的哈希值,用于验证快照的完整性;
  • datajdata_jdataj 是序列化、压缩、加密后的状态 sjs_jsj 的数据;
  • incjinc_jincj 是一个布尔值,表示当前快照是否是增量快照;
  • pidjpid_jpidj 是当前增量快照的父快照 ID(如果是完整快照,则为 NoneNoneNone)。

3. 状态哈希计算模型

状态哈希值是用于验证快照完整性的关键指标,我们可以使用SHA-256BLAKE3等加密哈希函数来计算状态哈希值。

假设:

  • H:{0,1}∗→{0,1}256H: \{0,1\}^* \rightarrow \{0,1\}^{256}H:{0,1}{0,1}256 是一个加密哈希函数(如 SHA-256);
  • Serialize:S→{0,1}∗Serialize: S \rightarrow \{0,1\}^*Serialize:S{0,1} 是一个序列化函数,将 State 对象转换为二进制数据;
  • sj∈Ss_j \in SsjS 是当前状态。

那么,状态 sjs_jsj 的哈希值 hjh_jhj 可以表示为:
hj=H(Serialize(sj)) h_j = H(Serialize(s_j)) hj=H(Serialize(sj))

4. 增量快照模型

增量快照是只保存与上一个快照相比发生变化的部分内容的快照,它可以减少存储开销。

假设:

  • Snapj−1Snap_{j-1}Snapj1 是上一个快照(可以是完整快照或增量快照);
  • SnapjSnap_jSnapj 是当前增量快照;
  • Delta:{0,1}∗×{0,1}∗→{0,1}∗Delta: \{0,1\}^* \times \{0,1\}^* \rightarrow \{0,1\}^*Delta:{0,1}×{0,1}{0,1} 是一个差分计算函数,计算两个二进制数据之间的差异;
  • ApplyDelta:{0,1}∗×{0,1}∗→{0,1}∗ApplyDelta: \{0,1\}^* \times \{0,1\}^* \rightarrow \{0,1\}^*ApplyDelta:{0,1}×{0,1}{0,1} 是一个差分应用函数,将差异应用到原始二进制数据上,生成新的二进制数据;
  • dataj−1data_{j-1}dataj1 是上一个快照的状态数据(解压、解密后的);
  • datajdata_jdataj 是当前状态的原始数据(序列化后的)。

那么,当前增量快照的状态数据 dataj′data_j'dataj(压缩、加密前的)可以表示为:
dataj′=Delta(dataj−1,dataj) data_j' = Delta(data_{j-1}, data_j) dataj=Delta(dataj1,dataj)

当恢复当前增量快照时,我们需要先恢复上一个完整快照和所有后续的增量快照,然后依次应用差分:
dataj=ApplyDelta(dataj−1,dataj′) data_j = ApplyDelta(data_{j-1}, data_j') dataj=ApplyDelta(dataj1,dataj)

5. 无限循环检测模型

无限循环检测是指检测系统是否在相同/相似的节点之间循环执行超过指定的次数。

假设:

  • E=[(n0,t0,s0),(n1,t1,s1),...,(nj,tj,sj)]E = [(n_0, t_0, s_0), (n_1, t_1, s_1), ..., (n_j, t_j, s_j)]E=[(n0,t0,s0),(n1,t1,s1),...,(nj,tj,sj)] 是系统的执行历史,其中每个元素是一个元组(节点名称,执行时间戳,状态);
  • KKK 是最大允许的循环次数;
  • WWW 是滑动窗口的大小(用于检测相似的节点序列);
  • Similarity:NW×NW→[0,1]Similarity: N^W \times N^W \rightarrow [0,1]Similarity:NW×NW[0,1] 是一个节点序列相似度计算函数(如编辑距离的倒数);
  • ThresholdThresholdThreshold 是相似度阈值(如果两个节点序列的相似度超过 ThresholdThresholdThreshold,则认为它们是相似的)。

那么,无限循环检测的逻辑可以表示为:

  1. 检查执行历史中是否存在连续的 K+1K+1K+1 个相同的节点:
    ∃i∈[0,j−K],ni=ni+1=...=ni+K \exists i \in [0, j-K], n_i = n_{i+1} = ... = n_{i+K} i[0,jK],ni=ni+1=...=ni+K
    如果存在,则认为系统陷入了无限循环;
  2. 如果不存在连续的相同节点,则使用滑动窗口检查执行历史中是否存在相似的节点序列:
    ∃i,l∈[0,j−W+1],i<l,Similarity(E[i:i+W],E[l:l+W])>Threshold \exists i, l \in [0, j-W+1], i < l, Similarity(E[i:i+W], E[l:l+W]) > Threshold i,l[0,jW+1],i<l,Similarity(E[i:i+W],E[l:l+W])>Threshold
    如果存在,并且相似的节点序列出现的次数超过 KKK,则认为系统陷入了无限循环。

算法流程图

1. 状态快照生成算法流程图

下面是状态快照生成算法的流程图:

开始:生成状态快照

获取当前状态、节点名称、快照描述、会话ID、用户ID

判断是否需要生成增量快照?

查询当前会话的最近一个快照

判断最近一个快照是否存在?

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐