学习笔记 · Obsidian
LangGraph 状态、持久化与子图错误排障
六类错误对应六个不变量
| 错误 | 被破坏的不变量 | 修复方向 |
|---|---|---|
GRAPH_RECURSION_LIMIT | 图必须在允许步数内到达停止条件 | 先找无界循环,再评估提高 limit |
INVALID_CHAT_HISTORY | 每个 AI tool call 都有匹配 ToolMessage | 修正历史或从 interrupt 正确恢复 |
INVALID_CONCURRENT_GRAPH_UPDATE | 同一步并行写同一 key 必须有合并规则 | 为 state key 定义 reducer |
INVALID_GRAPH_NODE_RETURN_VALUE | 节点的所有路径都返回 state update dict | 修正返回类型与 key |
MISSING_CHECKPOINTER | 使用内建 persistence 时必须配置 checkpointer | compile/entrypoint 时注入 |
MULTIPLE_SUBGRAPHS | 同节点多次调用持久子图时 namespace 必须不冲突 | 选择正确 checkpointer 模式/唯一节点名 |
循环与步数上限
递归上限表示 StateGraph 在达到 stop condition 前执行了过多步骤。优先检查边条件、计数器、失败重试边和 A↔B 循环;只有确认复杂图确实需要更多合法步骤时,才在 invoke config 中提高 recursion_limit。
上限是防止失控成本的安全阀。提高到很大值必须同时设置模型/工具调用预算、wall-clock timeout、取消与告警,否则只是把无限循环推迟。
Chat history 与 interrupt 恢复
create_agent 的 call_model 收到带未回答 tool calls 的历史会报错。常见来源:手工构造了不完整消息,或图在 tools 前中断/工具异常后,又带一条新 HumanMessage 从 START 调用。
处理有两条路径:
- 为未回答的每个 tool call 补匹配
ToolMessage;直接以这些消息 invoke 会把它们追加并从 START 运行。 - 读取
get_state(config),删除未回答 tool calls 或补齐 ToolMessage,update_state后用invoke(None, config)从 interrupt 恢复。
生产系统应优先使用第二条恢复语义,保留 checkpoint 与审计;不能在不理解状态位置时简单追加新用户消息。
并行更新与节点返回值
同一 superstep 的 fan-out 节点若同时返回同一 state key,而该 key 没有 reducer,LangGraph 无法确定覆盖顺序。将字段声明为带 reducer 的 Annotated 类型,例如 list 使用 append/operator.add;reducer 必须满足确定性与尽量可结合,否则重放或并行顺序变化会产生不同结果。
每个节点必须返回 dict,且 key 属于 state schema。复杂分支、异常处理和提前返回都要覆盖;不要返回裸 list、模型消息或 None。单测应逐分支断言返回 shape,而不只测 happy path。
Checkpointer 与 persistence
直接使用 OSS Graph API 的内建持久化时,在 StateGraph.compile(checkpointer=...) 或 Functional API @entrypoint(checkpointer=...) 注入 checkpointer。InMemorySaver 适合测试,不适合进程重启后的生产恢复;Agent Server 可托管持久化基础设施。
thread ID、checkpoint namespace、序列化版本和数据库迁移必须作为运行契约。配置了 checkpointer 但每次随机 thread ID,与完全没有会话恢复在效果上近似。
多次调用子图的三种模式
同一节点内多次调用子图时,按需求选择:
- 不需要 interrupt:
checkpointer=False,完全关闭子图 checkpoint。 - 需要 interrupt、但不需要跨调用持久:省略参数,继承父图;每次调用获得独立 namespace。
- 需要跨调用持久:
checkpointer=True。系统使用位置后缀区分调用;若需要稳定、基于名称的 namespace,把每个子图包在唯一命名节点中。
不要因为遇到 MULTIPLE_SUBGRAPHS 就一律关闭 checkpoint;这会同时失去 interrupt/恢复能力。应先决定业务所需的持久化范围。
生产验证
- 构造故意循环,验证 recursion limit、成本上限与取消。
- 让并行节点同时写同一 key,验证 reducer 在不同完成顺序下结果一致。
- 在 tool 前 interrupt,分别测试补 ToolMessage、update_state +
invoke(None)。 - 重启进程/实例后恢复同一 thread,验证真实持久 checkpointer,而非内存假象。
- 并发调用同一子图,检查 namespace、checkpoint 隔离和 time travel 结果。