Skip to content

第 14 章 · 综合实战三:容错、审批与恢复 ​

把第 6、10 章的机制合成一条生产形态的工单流水线:历史检索节点带重试策略,起草节点产出草稿,升级节点在批准/拒绝前暂停,用检查点跨两次调用恢复。全程离线。

机制与设计选择 ​

fetch 节点首次调用抛 TimeoutError,RetryPolicy(max_attempts=3, retry_on=TimeoutError) 自动重试到成功;escalate 用 interrupt() 暂停,两次调用必须同一 thread_id。审批只改变状态字段,不代替人工做决定;节点恢复时会从头重放,所以幂等责任仍在写操作的实现方。注意 LangGraph 1.2 的默认 retry_on 并不覆盖 TimeoutError,必须显式指定。

示例 ​

python
from typing import TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import RetryPolicy, interrupt, Command

class State(TypedDict):
    subject: str
    stage: str
    approved: bool

fetch_attempts = {"n": 0}

def fetch_customer_history(state: State):
    fetch_attempts["n"] += 1
    if fetch_attempts["n"] == 1:
        raise TimeoutError("simulated transient failure")
    return {"stage": "history_loaded"}

def draft_reply(state: State):
    return {"stage": "drafted"}

def escalate(state: State):
    decision = interrupt({"stage": "awaiting_approval", "reason": "high-severity ticket"})
    return {"approved": bool(decision), "stage": "approved" if decision else "rejected"}

builder = StateGraph(State)
builder.add_node("fetch", fetch_customer_history,
                 retry_policy=RetryPolicy(max_attempts=3, initial_interval=0.01, retry_on=TimeoutError))
builder.add_node("draft", draft_reply)
builder.add_node("escalate", escalate)
builder.add_edge(START, "fetch")
builder.add_edge("fetch", "draft")
builder.add_edge("draft", "escalate")
builder.add_edge("escalate", END)
graph = builder.compile(checkpointer=InMemorySaver())

for thread, decision in [("T-1", True), ("T-2", False)]:
    cfg = {"configurable": {"thread_id": thread}}
    paused = graph.invoke({"subject": "severe crash", "stage": "new", "approved": False}, cfg)
    assert paused["__interrupt__"], thread
    assert paused["__interrupt__"][0].value["stage"] == "awaiting_approval", thread
    result = graph.invoke(Command(resume=decision), config=cfg)
    assert result["approved"] is decision, thread
    print(thread, "->", result["stage"], "| fetch attempts:", fetch_attempts["n"])
assert fetch_attempts["n"] >= 2
print("retry + approval + resume ok")

运行与验收 ​

直接运行:两次线程都应完成,T-1 为 approved、T-2 为 rejected,fetch attempts 至少 2,说明重试确实发生。断言同时检查暂停点 stage 与恢复后的 approved 值,不只看最终打印。

容易踩的坑 ​

RetryPolicy 只能覆盖你声明过的瞬时故障:1.2 的默认 retry_on 不重试 TimeoutError,漏写 retry_on 等于不重试;把认证错误也放进重试只会放大失败。interrupt 恢复必须沿用原线程,新建 thread_id 会从头开始而不是从暂停点继续。内存检查点不适合跨进程,迁数据库时保留同一套 thread_id 语义。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. 首次 fetch 失败后发生什么?

2. 恢复用哪个线程?

3. interrupt 后节点从哪开始?

4. 内存检查点适合跨进程吗?

动手实践 ​

  1. 为 T-3 线程演示同一线程的第二次工单处理。
  2. 把重试耗尽后的错误处理器接上,落一条失败记录。
  3. 把 MemorySaver 换成 PostgreSQL 检查点,说明连接与租户隔离。

官方参考 ​

核验日期:2026-09-17,示例已在本机离线运行验证。