LangGraph.js:把长运行 Agent 建模为可恢复状态图
核验日期:2026-07-16。本文依据 LangGraph JavaScript 官方文档当前的 StateGraph/Functional API、Persistence、Interrupts 与 Durable Execution 概念。API 会演进,具体类型以安装版本文档为准。
LangGraph 是低层 Agent 编排框架,重点是长运行、有状态工作流的 durable execution、streaming、human-in-the-loop 和 memory。它不替你选择 Prompt、工具或模型。图的价值是把状态、节点、路由、checkpoint 和恢复显式化;如果任务只是一次模型调用或三行固定流程,普通 TypeScript 更清楚。
从任务状态开始
先定义最小可持久状态,而不是先画大量 Agent 节点。技术目录助手可以有:用户目标、身份引用、检索证据 ID、messages、待执行动作、审批、attempt、budget、最终结果和错误。大文档/secret 不直接复制进每个 checkpoint,保存受权引用与版本。
StateGraph 的节点读取状态并返回部分更新,channel/reducer 决定并发更新如何合并;边决定下一节点。覆盖型字段不应误用追加 reducer,否则状态无限增长;消息列表也需要裁剪/摘要策略。
节点名称、输入输出和副作用 owner 应稳定,便于 trace 和迁移。模型路由结果仍由 Schema 验证,未知分支进入错误节点,不动态 eval。
StateGraph 与 Functional API
StateGraph 适合明确状态、分支、并行和可视化拓扑;Functional API 用普通 if/for/function 配合 entrypoint/task 获得持久化与 interrupt,适合已有代码不想重构成显式图。二者按问题选,不为了截图把每个函数变成节点。
一个节点最好是可测试的业务步骤:输入字段明确、输出有限、外部调用有 timeout/idempotency。把整个 Agent Loop 放进单节点会失去 checkpoint/观测价值;把每个字符串处理拆节点又制造版本和存储开销。
Checkpoint、Thread 与 Store
编译图时配置 checkpointer,运行时用 thread_id 关联一条状态序列。框架在 super-step 边界保存 checkpoint,支持恢复、历史状态、time travel 和错误恢复。checkpoint 是某时刻状态快照;thread 是这些快照的逻辑序列。
Store 用于跨 thread 的长期信息,checkpointer 用于单 thread 运行状态,两者不要混淆。用户偏好写入长期 store 前需要明确同意、来源、TTL 和删除;工作流审批/待办属于 checkpoint/task。生产不能使用进程内 MemorySaver 承诺重启恢复,应选择持久后端并测试事务/并发。
同一 thread_id 并发运行会产生冲突或意外分支,应用应串行、使用版本检查或按业务明确 fork。thread id 是服务端生成/验证的 opaque 标识,不能仅凭用户输入访问他人状态。
Interrupt 与人工审批
interrupt() 在节点中暂停,框架保存状态并把 JSON 可序列化 payload 返回调用方;恢复时用 Command 提供值。关键语义是:恢复会从节点开头重新执行,而不是从源代码那一行继续。因此 interrupt 之前的副作用必须幂等,或把副作用放到 interrupt 之后/独立节点。
不要用 try/catch 吞掉框架的 interrupt 控制异常;同一节点多个动态 interrupt 的顺序需要稳定;payload 不放 secret。审批绑定参数哈希、资源版本、身份和过期时间,resume 后重新授权。
Durable Execution 与副作用
Durable 不等于所有代码 exactly-once。失败恢复可能重放节点,外部 API/数据库写仍需要 idempotency key、事务/outbox 或补偿。把非确定值(当前时间、随机数、外部响应)显式保存,避免 replay 产生不同路线。节点输出先通过 Schema 验证再 checkpoint。
并行 super-step 中部分节点成功、部分失败时,框架可能保存 pending writes 并在恢复时避免重跑已成功部分;具体语义需按 checkpointer/版本测试。无论如何,外部副作用仍由应用保证重复安全。
时间旅行不是生产回滚
历史 checkpoint 可 replay 或 fork,用于调试和比较路线。Replay 会重新执行后续模型/API/interrupt,可能产生新费用和副作用,因此默认使用只读/沙箱工具,或在节点层识别 replay mode。修改状态会产生新 checkpoint,不应篡改审计历史。
业务数据库回滚、发出的邮件和第三方支付不会因图回到旧 checkpoint 自动撤销。生产补偿是显式工作流。
Streaming
LangGraph 可流出 messages、updates 等不同模式。应用把 node progress、模型 token、tool/interrupt 和 final 区分为版本化事件;客户端断开不代表图任务取消。长任务在后端继续时返回 task/thread 引用,客户端可重连;需要取消则写入授权状态并让节点协作检查。
流事件可能重复或在重连时重放,消费者按 checkpoint/task/event id 去重。UI 不把 token delta 当已验证最终答案,高风险动作只在 checkpoint/审批状态确认后展示完成。
子图与多 Agent
子图适合复用一段有独立状态/生命周期的流程,或隔离专业 Agent。父子状态通过明确映射,不默认共享全部 messages/secret。每个子图增加 checkpoint namespace、trace 和升级成本。能用普通函数的纯转换不要做子图。
多 Agent 路由仍需 max turns、budget、权限和终止。图让路线可见,不保证模型选对边;每个边和 handoff 用数据集测试。
错误分类与重试
节点区分 transient、permanent、human-action、policy-denied 和 bug。网络/限流可按幂等与剩余 deadline 重试,validation/授权错误不指数重试。重试策略是工作流配置,不由模型自然语言决定。失败状态保存安全错误、attempt 和 cause reference,敏感 stack 在受控日志。
恢复前检查代码/graph/schema 版本兼容。部署中旧 checkpoint 可能由新代码读取,需要向后兼容迁移、固定 worker 版本或让旧任务 drain;否则一次发布会使所有等待审批任务不可恢复。
测试策略
节点纯逻辑单测;路由表用固定状态全覆盖;模型/工具注入 fake;图测试断言最终状态和实际节点序列;checkpoint 测重启、失败恢复、同 thread 并发、版本冲突;interrupt 测暂停 payload、拒绝、过期、resume 重跑和幂等;stream 测随机断连/重复;migration 测旧 checkpoint。
const fakeModel = async (state: { evidence: string[] }) =>
state.evidence.length
? { route: "final", answer: "基于固定证据的回答" }
: { route: "retrieve" };真实模型只替换节点端口,图的路线/恢复仍可离线验证。不要在 CI 依赖 LangSmith 云或付费模型才能证明核心正确性。
可观测性与数据治理
每个 run/node/checkpoint/interrupt/tool 建关联 span,记录 graph/version、thread(脱敏)、step、state field 大小、route、attempt、duration 和 error class。state 可能含私密上下文,checkpoint 加密、tenant 隔离、备份、TTL、删除与访问审计;调试平台不是无限期数据湖。
监控 active/waiting threads、checkpoint 写延迟/失败、节点 p95/p99、retry、stuck、interrupt age、恢复成功和每任务模型/工具成本。设置扫描器找超过 SLA 的 waiting/running 状态,并提供人工安全终止。
选型边界
采用 LangGraph 的信号:流程长于单请求;需要 checkpoint/restart;有动态/并行分支;人工等待;需查看/修改状态或 time travel;多 Agent 路线值得显式建模。反信号:单次生成、简单固定 pipeline、已有通用工作流引擎足够且模型只是一个 activity、团队不愿运营持久状态。
成熟系统常把 LangGraph 用于语义编排,把 Temporal/队列/数据库服务用于跨天业务事务;也可以只用后者。不要建立两个都认为自己拥有重试/状态的嵌套引擎而不定义边界。
验收需证明图状态 Schema、reducer、thread 隔离、checkpoint 持久、interrupt 重放安全、部署迁移、取消、流重连、错误分类和成本上限,不以图形复杂度衡量能力。