跳到主要内容
约 10 分钟4 张卡

Agent 状态与持久化执行

Agent 状态记录任务执行到某一时刻的消息、进度和动作结果。持久化执行把这些信息保存到可靠存储,使任务在进程崩溃、超时或等待人工输入后继续推进。

本文先说明要保存哪些状态,以及怎样通过检查点恢复任务。随后讨论工具重试时的副作用,最后介绍人工介入的暂停与恢复。检查点负责恢复进度,幂等机制则防止恢复时重复产生业务效果。

1. 面试核心​

先记住这几条,再读正文
  • 检查点恢复。 检查点把消息、进度、动作结果和已用预算写入持久存储,供任务中断后恢复。卡 · 状态内容
  • 副作用去重。 写操作使用稳定幂等键和下游原子去重,防止执行成功但结果未记录时重复产生副作用。卡 · 副作用与幂等恢复
  • 错误处理。 瞬时错误由程序有界重试,需要修改参数或方案的错误交给模型,持续失败转入兜底。
  • 人工介入。 人工介入把任务保存为等待状态,释放工作进程,收到有效输入后再恢复执行。卡 · 人工介入
  • 中断重放。 LangGraph 恢复中断节点时会从节点开头重跑,因此 interrupt() 之前的代码会再次执行。卡 · LangGraph 中断节点重跑

2. 状态内容​

恢复任务需要知道已经完成什么,以及接下来允许做什么。仅保存对话历史还不够,还要记录动作结果、任务状态和剩余预算。

内容用途
任务、租户和用户标识定位任务与隔离访问
消息与工具结果恢复模型上下文
计划和进度定位剩余工作
动作、参数与幂等键核对执行结果
生命周期状态控制运行、等待、完成和取消
已用预算与截止时间防止恢复后重新获得完整预算

具体保存时,可以把信息组织为身份、进度、动作和生命周期记录:

  • 身份与会话记录任务 ID、会话 ID、用户和租户,便于找到任务并检查访问权限。
  • 执行进度记录模型输入输出、工具返回结果,以及待办、当前步骤和已完成步骤。
  • 动作记录保存工具名、参数、幂等键、执行状态和结果,用于核对外部操作是否完成。
  • 生命周期与预算记录任务是运行、等待、失败、完成还是取消,并累计已用步数、Token 和时间。

有限状态机用一组明确的状态与转换规则描述任务生命周期。例如,running 表示正在执行,waiting_approval 表示等待审批。执行失败用 failed 表示,正常完成用 done 表示。应用可以规定等待状态在批准后转为 running,拒绝或超时后转为 cancelled。明确这些转换后,程序就能判断每个回调能做什么。

转换时使用条件更新或版本检查,让并发恢复请求只有一次能成功推进任务。大文件等产物放到外部存储,状态中保留引用与版本,避免每次检查点都复制完整文件。各步输入、输出和状态也能用于排错、审计及构建评测集。

3. 检查点与恢复​

检查点是任务状态在某个执行位置的持久记录。任务崩溃后,程序加载最近一次可靠保存的记录,再核对最后一个动作,决定接下来执行哪一步。

步骤 1 → 检查点 1 → 步骤 2 → 检查点 2 → 步骤 3 崩溃
↑
恢复后核对动作,再推进步骤 3

设计检查点时,需要确定写入时机、存储位置和恢复流程:

  • 写入时机:可在每个节点或每轮循环结束后保存,耗时步骤内部再增加中间检查点。保存越频繁,故障时重做的工作越少,但写入开销也越多。异步保存时,还要评估最后几次更新丢失的窗口。
  • 存储位置:可以使用关系数据库、启用持久化的键值存储或对象存储。只存内存时,进程或节点故障会丢失状态。Redis 的复制和落盘也有故障窗口,需要按容忍的数据损失选择配置。
  • 恢复流程:加载最近检查点,核对最后一个动作的执行结果,再按幂等规则处理尚未确认的动作。完成核对后继续循环,并沿用原来的预算与截止时间。

LangGraph 用 checkpointer 保存图状态,checkpointer 就是负责读写检查点的组件。它在每个超步后保存状态,一个超步是图执行中同一轮可运行节点的推进阶段。应用通过 thread_id 标识这条执行线程,恢复时使用同一标识和对应的检查点存储。

InMemorySaver 只把记录保存在进程内存中,因此重启后无法取回。需要跨进程恢复时,要配置可靠的持久化检查点存储。

Temporal 则保存事件历史,通过重放历史重建工作流状态。重放要求工作流代码满足确定性约束,也就是相同历史应导出相同的执行决策。外部副作用放在 Activity 中执行;Activity 是工作流调用外部操作的执行单元,还需要考虑重试与幂等。

3.1 副作用与幂等恢复​

工具执行成功到本地记录结果之间,存在一个崩溃窗口。比如下单已经成功,但进程还没写入检查点就退出。这时,检查点只能显示结果尚未记录,恢复程序需要向下游核对。

幂等指同一个业务动作重复提交时,仍只产生一次业务效果。为保护这个崩溃窗口,可以按下面的顺序执行:

  1. 调用前持久保存动作意图、参数和幂等键,明确准备做什么。
  2. 下游按幂等键原子去重,执行动作并保存结果,重复请求复用已记录的结果。
  3. 恢复后按幂等键或业务 ID 查询结果,已完成时直接采用;需要重试时继续使用相同键。
  4. 结果无法确认时保留未知状态,再通过对账或人工处理核对,避免贸然重复写入。

幂等键绑定具体业务动作和参数,去重记录的保留时间要覆盖重试窗口。模型调用 ID 负责把工具结果关联到消息,业务幂等键负责去重,两者的用途不同。

先查询再执行仍有并发间隙:查询没有结果时,原请求也许正在执行。因此,下游还需要原子去重,确保并发请求只能产生一次业务效果。只读动作可以重试,不过第二次读取的数据版本和额外费用仍会变化。

多步业务已经产生部分效果时,可以准备补偿动作,例如取消订单或退款。补偿是用新的业务操作修复结果,具体机制见 Saga。补偿本身也会失败,需要记录状态并重试;已发送的邮件等外部影响则无法完全撤回。

4. 失败处理​

失败后是否重试,取决于错误能否在参数不变时恢复。网络短暂中断可以等待后重试,参数或业务条件错误则需要改变请求。

类型处理
瞬时故障有界退避,写入先满足幂等条件
参数错误返回具体字段问题,供模型修正
业务拒绝返回拒绝原因,调整方案
权限拒绝保持受限状态,按授权流程处理
持续失败熔断、稍后恢复或人工接管
模型不可用保存状态,使用经过验证的备用方案

超时、429 和连接断开可以由程序指数退避,也就是失败后逐渐拉长等待间隔,并限制重试次数。参数缺失时返回具体字段,订单不存在等业务错误则返回拒绝原因,让模型调整方案。权限拒绝需要走授权流程,保持已有约束。

同一错误持续出现时,可以熔断,暂时停止调用故障下游。应用再选择经过验证的备用工具或模型,或保存任务等待人工接管。

向用户返回结果时,要明确标出已完成、未完成和状态未知的动作。这样用户才能判断是否需要核对订单或重试任务。恢复后继续累计原预算,并沿用任务截止时间,避免每次重启都获得一份新额度。

5. 人工介入​

人工介入(Human-in-the-loop,HITL)让任务暂停,等待人补充信息或作出决定。常见的介入位置有以下三类:

  • 高风险动作之前:删除、转账、对外邮件和生产发布前,展示动作、参数与影响,让用户批准、修改或拒绝。
  • 信息不足时:保存当前问题,向用户澄清影响后续执行的信息。
  • 反复失败后:把已执行步骤和错误一并交给人工,便于接手处理。

到达介入点后,程序保存检查点和待处理事项,把任务转为 waiting_approval,再通知审批人。等待状态已经持久保存,所以工作进程可以释放资源,处理其他任务。

审批记录绑定动作、参数、审批人和有效期限,并记录决定时间。回调或消息到达后,程序校验身份、任务版本和授权,再把决定写回状态。批准后继续执行,参数修改后重新校验,拒绝则记录原因并终止或调整方案。

超时与重复回调也要有明确的状态转换,避免旧审批重新启动已经取消的任务。授权细节见工具授权与执行沙箱。

5.1 LangGraph 中断节点重跑​

使用 LangGraph 暂停任务时,要理解恢复会从哪里重新执行。在配置 checkpointer 后,interrupt() 可以暂停节点并保存状态。应用使用同一 thread_id 传入 Command(resume=...),其中的值会成为对应 interrupt() 的返回值。

恢复会从中断节点开头重跑,所以 interrupt() 前面的代码会再次执行。比如审批前先发送一封邮件,恢复时就会再次触发发送。可以把副作用移到审批之后,或拆进已经完成的独立节点。

网络调用结果和随机值也需要保存为可复用状态,否则节点重跑会得到不同结果。即使副作用移到审批之后,执行过程仍会遇到失败与重试,因此要继续使用同一业务幂等键。

6 · 面试题卡片​

本页题目与参考回答

6.1 Agent 执行中断后如何恢复?检查点和快照怎么持久化?​

考察重点

  • 能说明恢复需要哪些状态,何时写检查点,以及存储故障会丢失什么。
  • 能区分进度恢复与业务动作核对,处理结果尚未记录的崩溃窗口。

相关内容:状态内容、检查点与恢复。

参考回答

任务通过持久检查点恢复进度,再核对未确认的动作结果,决定从哪里继续。

检查点保存消息、计划、工具参数与结果,以及生命周期状态和已用预算。可以在每个节点或每轮结束后写入数据库等可靠存储,耗时步骤再增加中间检查点。保存的执行记录还可用于排错、审计与评测。

LangGraph 通过 thread_id 定位检查点,内存 checkpointer 在进程重启后会丢失记录。Redis 也要根据复制与落盘方式,评估故障时会丢失多少更新。

恢复时先加载最近记录,再核对最后一个动作。只读动作可以重新读取,写操作使用调用前保存的幂等键查询或重试,由下游原子去重。无法确认的结果保留未知状态,恢复后继续累计原预算。

6.2 工具调用失败时如何处理?重试时怎样保证不会重复执行?​

考察重点

  • 能根据错误类型选择重试、修正参数、降级或人工处理。
  • 能说明幂等键和原子去重如何配合,并解释查询为空为何仍需谨慎重试。

相关内容:副作用与幂等恢复、失败处理。

参考回答

工具失败后按错误类型处理,写操作通过稳定幂等键和下游原子去重防止重复执行。

超时与限流由程序指数退避,并限制次数。参数错误或业务拒绝作为工具结果交给模型,供它修改参数或方案。权限拒绝保持原有约束,持续失败则熔断、降级或转人工,并告知用户哪些动作已经完成。

调用前记录意图与参数,重试沿用同一个幂等键。恢复时可以查询下游已有结果,但查询为空也可能是原请求尚未完成。因此,查询之后的重试仍需下游原子去重。

无法确认的写入保留未知状态,已产生的部分业务效果按预定方案补偿。任务状态转换用版本号或条件更新保护,避免并发恢复请求重复推进。

6.3 Human-in-the-loop 如何实现?人工介入后任务怎样继续?​

考察重点

  • 能判断哪些动作、缺失信息或失败需要人工介入。
  • 能解释等待期间如何释放资源,以及收到审批后如何校验并恢复。

相关内容:人工介入、检查点与恢复。

参考回答

人工介入通过持久化等待状态暂停任务,收到有效决定后再恢复。

介入点可以放在高风险动作前、信息不足时,或多次失败后。暂停时保存检查点、动作参数和任务版本,并向审批人展示待处理内容及原因。随后释放工作进程,等待回调或消息。

收到决定后,程序校验身份、时效和状态版本。批准后执行,修改参数后重新校验,拒绝则记录原因并终止或调整方案。审批记录保留审批人与时间,超时和重复回调按状态转换规则处理。

LangGraph 可以用 interrupt() 配合 checkpointer 实现暂停。恢复时会重跑中断节点,因此副作用要放在合适的执行位置,并使用幂等键保护。

6.4 LangGraph 恢复 interrupt 后,会从暂停的代码行继续吗?​

考察重点:能说明恢复的执行起点,以及节点重跑为什么会重复触发副作用。

相关内容:LangGraph 中断节点重跑。

参考回答

恢复会从中断节点开头重新执行,而不是从暂停的代码行直接继续。

Command(resume=...) 的值会成为对应 interrupt() 的返回值,但此前的代码已经再次运行。因此,审批前发送邮件会触发重复发送。

副作用可以移到审批后,或拆进已经完成的独立节点。节点中需要复用的网络结果与随机值应保存到状态,写操作重试时继续使用稳定的幂等键。

7. 参考资料​