跳转至

LangGraph

LangGraph 的状态图模型与工作流控制

image.png

🧩 1. LangGraph 的核心概念是什么?State、Node、Edge 三者什么关系?

LangGraph 是一个用有向图来编排 Agent 行为的框架。 它把 Agent 的每一次决策、每一个工具调用、每一段思考都抽象为图的 节点,把控制流和数据流抽象为图的 边。整张图共享一份 状态。

一图胜千言:

image.png

三者的职责与关系:

  • State(状态):一份贯穿全图的共享数据字典(TypedDict 或 Pydantic 模型)。每个节点都可以读它、写它。它承载了 Agent 的全部上下文:对话历史、中间结果、控制标志等。

  • Node(节点):一个 Python 函数,接收当前 State,返回更新后的 State(或部分更新)。它可以是一个 LLM 调用、一个工具执行、一段业务逻辑。

  • Edge(边):定义节点之间的流转规则。普通边就是“A 执行完一定去 B”;条件边是“A 执行完后,根据 State 的某个字段值决定去 B 还是去 C”。

关系一句话:State 是大脑的记忆,Node 是会做的事情,Edge 是做完一件事之后接着做什么的决定。

最简单的代码骨架:

from typing import TypedDict
from langgraph.graph import StateGraph, END

class AgentState(TypedDict):
    messages: list
    next_step: str

def node_a(state):
    # 处理逻辑...
    return {"messages": state["messages"] + ["A done"]}

def node_b(state):
    return {"messages": state["messages"] + ["B done"]}

def router(state):
    if "done" in state["messages"][-1]:
        return END
    return "node_b"

graph = StateGraph(AgentState)
graph.add_node("node_a", node_a)
graph.add_node("node_b", node_b)
graph.set_entry_point("node_a")
graph.add_conditional_edges("node_a", router, {"node_b": "node_b", END: END})
graph.add_edge("node_b", "node_a")  # 循环
app = graph.compile()

这就是 LangGraph 的世界观:用图来编程 Agent 的行为,而 State 是这张图的血液。


📦 2. LangGraph 的 State 是什么?如何设计一个合理的 AgentState?

State 是整个图运行时所有节点都能访问的共享内存。 它在 Python 里通常是一个 TypedDictPydantic 模型,每经过一个节点,State 都可能被更新(增量地覆盖部分字段)。

设计一个合理的 AgentState,要遵循四个原则:

  1. 承载全局上下文:所有节点需要用到的信息,都必须存在于 State 中。

  2. 字段尽量扁平,避免深层嵌套:节点更新 State 是浅合并的,嵌套太深容易出错,且不利于调试。

  3. 区分“持久数据”和“控制字段”:messages 这类持续累积的是数据,next_stepshould_continue 这类决定图流向的是控制字段。

  4. 为 Checkpoint 和恢复留好位子:比如可以加一个 user_approval 字段,用于中断时暂存外部输入。

好设计 vs 坏设计:

# ❌ 差:把所有东西丢进一个大字典,没有类型
state = {"everything": [...]}

# ❌ 差:控制字段和数据字段混在一起,名字模糊
class BadState(TypedDict):
    flag: bool
    temp: str
    data: dict

# ✅ 好:职责清晰,字段有类型
from typing import TypedDict, List, Optional
from langgraph.graph.message import add_messages

class GoodAgentState(TypedDict):
    # 数据字段
    messages: List[dict]          # 对话历史(用 add_messages 合并)
    user_query: str               # 原始用户输入
    intermediate_results: dict    # 工具返回的中间结果
    final_answer: Optional[str]   # 最终答案

    # 控制字段
    next_agent: str               # 下一个要激活的节点
    should_continue: bool         # 是否继续循环
    error_count: int              # 连续失败次数

三个典型场景的 State 设计:

① 简单的 Chatbot

class ChatState(TypedDict):
    messages: list  # 只需要对话历史

② ReAct Agent

class ReActState(TypedDict):
    messages: list
    tools_results: dict
    iteration: int
    final_answer: Optional[str]

③ 需要人工审批的 Supervisor

class SupervisorState(TypedDict):
    messages: list
    worker_outputs: dict
    pending_action: Optional[str]   # 待审批的动作
    user_decision: Optional[str]    # 用户在中断时输入的决策

设计时的铁律:

每次你想加一个字段,先问自己:“哪个节点需要它?”如果只有一个节点需要,那它可能不该放在全局 State 里,而是该节点内部的局部变量。State 只放跨节点共享的东西。


💾 3. LangGraph 的 Checkpoint 机制是什么?如何实现 Agent 的中断和恢复?

Checkpoint(检查点)是 LangGraph 在每个步骤后自动保存的 State 快照。 它是图执行持久化的基石,让你可以:

  • 暂停:在某个节点前插入 interrupt(),图运行到这就停止,等待外部输入。

  • 恢复:拿到外部输入后,从断点继续跑下去,State 完好无损。

  • 时间旅行:回溯到之前某个 Checkpoint,重放或分析当时的 State

流程:节点A → [Checkpoint] → 节点B → [Checkpoint] → ...
                     ↑ 可以在这中断
                     外部输入 → 从 Checkpoint 恢复 → 继续执行

Checkpoint 存储什么?

  • 当前的 State(所有字段的值)

  • 图的位置(哪个节点刚执行完,下一个该谁)

  • 配置 ID(thread_id,用于区分不同会话)

内置 Checkpointer:

  • MemorySaver:存内存,适合开发测试。

  • SqliteSaver:存 SQLite 文件,适合单机生产。

  • 也可以自己对接 Redis、Postgres 等。

实现中断和恢复的完整示例:人工审批场景

场景:Agent 生成了一封邮件草稿,必须经人工审批后才能发送。

from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import interrupt  # 中断函数

class EmailState(TypedDict):
    draft: str
    approved: bool

# 节点1:生成邮件草稿
def draft_email(state: EmailState):
    return {"draft": "您好,这是自动生成的合同续约通知..."}

# 节点2:发送邮件(只有审批通过才会执行)
def send_email(state: EmailState):
    # 实际发送逻辑
    return {"draft": state["draft"] + "\n[已发送]"}

# 构建图
graph = StateGraph(EmailState)
graph.add_node("draft", draft_email)
graph.add_node("review", lambda state: state)  # 占位节点,真正的中断在边上
graph.add_node("send", send_email)
graph.set_entry_point("draft")

# 关键:从 draft 到 send 的边上定义中断
def human_review(state: EmailState):
    # interrupt 会暂停整个图,把消息返回给外部
    approval = interrupt(f"请审批以下邮件草稿:\n{state['draft']}\n输入 'yes' 或 'no'")
    if approval == "yes":
        return "send"
    return END

graph.add_conditional_edges("draft", human_review, {"send": "send", END: END})

# 编译,带上 checkpointer
checkpointer = MemorySaver()
app = graph.compile(checkpointer=checkpointer)

# --- 第一次运行:生成草稿,然后中断 ---
config = {"configurable": {"thread_id": "email-001"}}
for event in app.stream({"draft": "", "approved": False}, config):
    print(event)
# 输出到中断处,外部看到提示:“请审批以下邮件草稿...”
# 图在这里暂停,State 被持久化到 MemorySaver

# --- 此时可以关掉进程、过几小时再恢复 ---

# 恢复:向图注入用户的审批决定
from langgraph.types import Command
# 用 Command(resume=...) 传入中断的返回值
resume_value = "yes"  # 假设用户点了“同意”
for event in app.stream(Command(resume=resume_value), config):
    print(event)
# 图从断点继续,human_review 返回 "send",然后执行 send 节点

核心机制解析:

  • interrupt(message) 让图在当前节点后暂停,向外部抛出 GraphInterrupt,并保存 Checkpoint。

  • 外部拿到中断消息,向用户展示,等待用户输入。

  • 外部用 Command(resume=value) 重新调用 app.stream()value 就是 interrupt 的返回值。

  • thread_id 是区分不同对话的关键,恢复时必须用同一个 config

Checkpoint 的更高级用法:时间旅行

# 列出所有历史 Checkpoint
checkpoints = list(checkpointer.list(config))
for cp in checkpoints:
    print(cp.config["configurable"]["checkpoint_id"], cp.metadata)

# 恢复到之前某个 Checkpoint 继续运行
old_config = {"configurable": {"thread_id": "email-001", "checkpoint_id": "xxx"}}
app.stream(Command(resume="yes"), old_config)  # 从历史点重新执行

收束:

LangGraph 的 Checkpoint 机制让 Agent 从“一跑到底”变成“可以随时按暂停、改参数、再继续”的受控流程。这意味着你可以把人工决策、外部审批、长时间的异步任务毫无缝地嵌入 Agent 的执行图中。它不是简单的日志记录,而是把整个运行时虚拟化,为 Agent 赋予了“任意时间点均可操作”的工程能力。


LangGraph 如何实现 Human-in-the-Loop(人机协作)?

难度:⭐⭐⭐(中断机制、人工审核、工作流编排)

1️⃣ Common Answer

Human-in-the-Loop 就是让 Agent 在某些节点停下来,等人工确认后再继续。LangGraph 可以设置 interruptbefore 或 interruptafter 参数,在特定节点前后暂停。人可以修改 State,然后 Agent 继续执行。

2️⃣ Impressive Answer

LangGraph 的 Human-in-the-Loop 基于中断机制 + Checkpoint 持久化,实现了 Agent 与人的无缝协作。核心是在工作流的关键节点插入"人工确认点",让人类可以审核、修正或引导 Agent 的行为。

三种典型模式

模式一:审核确认模式(适合敏感操作)

from langgraph.graph import StateGraph, END

# 在执行支付前中断,等待人工确认
app = workflow.compile(
    checkpointer=PostgresCheckpointSaver.from_conn_string(DB_URL),
    interrupt_before=["execute_payment"]
)

# Agent 执行到 execute_payment 前会暂停
config = {"configurable": {"thread_id": "order_456"}}
result = app.invoke({"messages": [{"role": "user", "content": "支付 100 元"}]}, config)
# 此时 result.status == "interrupted"

# 人工审核后继续执行
approved_state = app.get_state(config)
approved_state.values["approval_status"] = "approved"
app.update_state(config, approved_state.values)
result = app.invoke(None, config)  # 继续执行

模式二:人工修正模式(适合 Agent 出错)

# Agent 生成错误回答,人工修正后继续
state = app.get_state(config)
state.values["messages"].append({
    "role": "human",
    "content": "你的回答有误,正确答案是..."
})
app.update_state(config, state.values)

模式三:人工引导模式(适合复杂决策)

# 在决策节点插入人工选择,决策后暂停让人工确认
app = workflow.compile(
    checkpointer=memory,
    interrupt_after=["decide_strategy"]
)

工程实践要点

  1. 状态可视化:通过 app.get_state(config) 获取当前 State,展示给人工审核

  2. 超时处理:设置人工确认的超时时间,超时后自动降级或拒绝

  3. 审计日志:记录每次人工干预的操作人、时间、修改内容

  4. 权限控制:结合 RBAC,只有授权用户才能执行确认操作

3️⃣ Key Differences

查看内嵌表格


用 LangGraph 实现多工具 ReAct Agent,如何处理工具调用失败和重试?

难度:⭐⭐⭐(错误处理、重试策略、容错设计)

1️⃣ Common Answer

工具调用失败的时候可以捕获异常,然后重试几次。如果还是失败就告诉用户工具不可用。可以用 try-except 包裹工具调用,设置一个重试次数计数器。

2️⃣ Impressive Answer

处理工具调用失败需要分层设计:工具层重试、节点层容错、图层降级。核心是区分临时性错误(网络超时、限流)和永久性错误(参数错误、权限不足),采用不同的处理策略。

三层容错架构

第一层:工具层重试(针对临时性错误)

from tenacity import retry, stop_after_attempt, wait_exponential
from langchain.tools import tool

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=10)
)
@tool
def search_weather(city: str) -> str:
    """查询城市天气"""
    response = requests.get(f"https://api.weather.com/{city}")
    response.raise_for_status()
    return response.json()

第二层:节点层容错(工具节点统一处理)

def tool_node(state: AgentState) -> AgentState:
    """工具调用节点,统一处理错误和重试"""
    tool_calls = state["messages"][-1].get("tool_calls", [])

    for tool_call in tool_calls:
        try:
            result = tools[tool_call["name"]].invoke(tool_call["args"])
            state["messages"].append({
                "role": "tool",
                "tool_call_id": tool_call["id"],
                "content": str(result)
            })
        except Exception as error:
            error_type = classify_error(error)
            if error_type == "temporary":
                state["tool_errors"].append({
                    "tool_call": tool_call,
                    "error": str(error),
                    "error_type": "temporary"
                })
                state["retry_count"] += 1
            else:
                state["messages"].append({
                    "role": "tool",
                    "tool_call_id": tool_call["id"],
                    "content": f"工具调用失败({error_type}):{str(error)}"
                })
    return state

第三层:图层降级(超过重试次数后切换策略)

def should_continue(state: AgentState) -> str:
    temp_errors = [e for e in state["tool_errors"] if e["error_type"] == "temporary"]
    if temp_errors and state["retry_count"] < 3:
        return "retry"
    if state["retry_count"] >= 3:
        return "fallback"
    return "continue"

# 降级节点:优先用缓存,其次询问用户
def fallback_node(state: AgentState) -> AgentState:
    cached_result = get_cached_result(state["tool_errors"][0]["tool_call"])
    if cached_result:
        state["messages"].append({
            "role": "system",
            "content": f"工具调用失败,使用缓存结果:{cached_result}"
        })
    else:
        state["messages"].append({
            "role": "assistant",
            "content": f"工具调用失败,能否直接提供需要的信息?"
        })
    return state

3️⃣ Key Differences

查看内嵌表格


LangGraph 的条件边如何实现动态路由?

⭐⭐⭐ 考察要点:路由函数、状态判断、循环与分支控制

1️⃣ Common Answer

条件边就是根据状态决定走哪条路。写一个函数,接收 State,返回一个字符串,这个字符串就是下一个 Node 的名字。LangGraph 会根据返回值自动跳转。可以用 if-else 判断,实现分支,也可以用循环边实现重复执行。

2️⃣ Impressive Answer

条件边是 LangGraph 实现复杂工作流控制的核心,通过路由函数 + 状态判断 + 图结构定义实现动态路由:

  1. 路由函数的设计
  2. 路由函数签名:(state: State) -> str(state: State) -> Literal["node1", "node2", END]
  3. 函数内部可以访问 State 的所有字段,进行复杂的条件判断
  4. 返回值必须是已定义的 Node 名称或 END 常量(终止)
  5. 支持返回列表,实现并行分支(如 ["node_a", "node_b"]

  6. 动态路由的实现模式

  7. 分支路由:根据 State 中的某个字段值选择不同路径
def route_by_intent(state: State):
    if state["intent"] == "search":
        return "search_node"
    elif state["intent"] == "create":
        return "create_node"
    else:
        return END
  • 循环路由:结合条件边实现 while 循环
  • 设置一个 max_iterations 字段,每次循环递增
  • 当达到阈值或满足终止条件时返回 END

  • 回退路由:当某个 Node 失败时,路由到错误处理 Node

  • 图结构中的条件边定义

  • 使用 graph.add_conditional_edges("source_node", route_function) 定义
  • 可以指定默认边:graph.add_conditional_edges(..., {"default": "fallback_node"})
  • 支持多目标映射:{"A": "node_a", "B": "node_b", END: END}
  • 条件边可以连接到多个目标,实现并行执行

实践要点:条件边让工作流从"固定流程"变成"数据驱动流程",但要注意避免无限循环,建议在 State 中设置 iteration_countmax_iterations 作为安全终止条件。

3️⃣ Key Differences

查看内嵌表格


LangGraph 与 CrewAI 的编排思路有什么本质区别?

⭐⭐⭐ 考察要点:状态图 vs 角色任务、显式控制流 vs 隐式编排、适用场景对比

1️⃣ Common Answer

LangGraph 是用状态图,Node 是函数,Edge 控制流程。CrewAI 是定义多个 Agent,每个 Agent 有角色,自动协作。LangGraph 更灵活,可以自己设计流程。CrewAI 更简单,适合简单的多 Agent 任务。两者都能做多 Agent,但思路不一样。

2️⃣ Impressive Answer

LangGraph 和 CrewAI 代表了两种不同的 Agent 编排范式:显式状态图 vs 隐式角色协作,本质区别在于控制流的管理方式:

  1. 编排模型的根本差异
  2. LangGraph - 显式状态图
    • 工作流是预先定义的有向图,每个 Node 的执行顺序由 Edge 明确规定
    • 状态在图中流动,每个 Node 知道"我从哪里来,要到哪里去"
    • 控制流是确定的、可预测的,适合需要严格流程控制的场景
    • 调试容易,可以画出完整的执行路径图
  3. CrewAI - 隐式角色协作

    • 定义多个 Agent(角色),通过 Task 和 Process 让它们协作
    • Agent 之间通过消息传递进行协作,流程由 Agent 的决策动态生成
    • 控制流是涌现的、不确定的,适合需要灵活协作的场景
    • 流程依赖 Agent 的推理能力,调试较困难
  4. 适用场景的对比

  5. LangGraph 适合的场景
    • 需要严格流程控制的业务流程(如审批流、数据处理流水线)
    • 需要人工介入的场景(Human-in-the-loop,通过条件边实现中断)
    • 需要复杂循环、分支、并行的场景
    • 需要状态持久化和恢复的场景(通过 Checkpointer)
  6. CrewAI 适合的场景

    • 需要多个专业角色协作的创意类任务(如内容创作、营销策划)
    • 流程不确定、需要 Agent 自主决策的探索性任务
    • 快速原型开发,不需要精细控制流
    • 团队成员明确、角色分工清晰的协作场景
  7. 技术实现的差异

  8. LangGraph:State 是中心,Node 是无状态函数,Edge 是控制逻辑,强调"状态驱动"
  9. CrewAI:Agent 是中心,Task 是目标,Process 是协作方式,强调"角色驱动"

总结:LangGraph 是"工程师思维",适合需要确定性、可控性的工程化场景;CrewAI 是"产品思维",适合需要灵活性、创造性的业务场景。在实际项目中,可以结合两者:用 LangGraph 控制整体流程,在某个 Node 内部用 CrewAI 处理需要多 Agent 协作的子任务。

3️⃣ Key Differences

查看内嵌表格


用 LangGraph 实现一个带人工审批节点的 Agent 工作流,如何设计?

⭐⭐⭐⭐ 考察要点:Human-in-the-loop、中断与恢复、状态持久化(Checkpointer)

1️⃣ Common Answer

设计一个图,有 Agent 节点生成方案,然后有人工审批节点。人工审批节点暂停,等用户输入。用户同意就继续,不同意就打回重新生成。可以用条件边判断用户的反馈。需要保存状态,防止用户审批时状态丢失。

2️⃣ Impressive Answer

实现带人工审批的 Agent 工作流需要中断机制、状态持久化、审批流程设计三个核心要素:

  1. 工作流图结构设计
START → Draft_Agent → Review_Node → (approved? → END / rejected? → Draft_Agent)
  • Draft_Agent:生成方案,输出到 state["draft"]

  • Review_Node:人工审批节点,读取 state["draft"],等待用户输入

  • 条件边:根据 state["approval"] 字段决定是否继续或回退

  • 中断与恢复机制

  • 中断实现:在 Review_Node 中使用 interrupt() 函数,暂停执行并等待外部输入
def human_review(state: State):
    draft = state["draft"]
    approval = interrupt({
        "type": "human_review",
        "draft": draft,
        "question": "请审批:同意输入 'approve',拒绝输入 'reject'"
    })
    return {"approval": approval}
  • 恢复执行:通过 graph.update_state() 传入用户的审批结果
graph.update_state(thread_id, {"approval": "approve"})
  • thread_id:每个工作流实例有唯一 ID,用于区分不同会话

  • 状态持久化(Checkpointer)

  • 使用 MemorySaverPostgresSaver 保存状态到数据库
  • 每次节点执行后自动保存 State,即使进程重启也能恢复
  • 支持时间旅行调试:查看历史状态、回滚到某个节点
  • 配置方式:
checkpointer = MemorySaver()
graph = workflow.compile(checkpointer=checkpointer)
  1. 完整执行流程
  2. 步骤 1graph.invoke({"task": "..."}, config={"configurable": {"thread_id": "123"}})
  3. 步骤 2:执行到 Review_Node 时中断,返回 NodeInterrupt 异常
  4. 步骤 3:用户审批,调用 graph.update_state(thread_id="123", {"approval": "approve"})
  5. 步骤 4:从 Review_Node 恢复执行,条件边判断为 approve,流转到 END

  6. 高级特性

  7. 超时机制:设置审批超时时间,超时自动拒绝
  8. 多轮审批:支持多级审批,通过 State 中的 approval_level 字段控制
  9. 审批历史:在 State 中记录每次审批的决策人、时间、意见

实践要点:人工审批节点的关键在于"可中断 + 可恢复 + 可持久化",LangGraph 的 Checkpointer 机制完美解决了这个问题,非常适合需要人工介入的生产场景。

3️⃣ Key Differences

查看内嵌表格


LangGraph 的 Checkpointer 机制是什么?它如何实现状态持久化和时间旅行调试?MemorySaver 和 PostgresSaver 有什么区别?⭐⭐⭐

难度级别:⭐⭐⭐(状态管理、持久化、调试能力)

1️⃣ Common Answer

Checkpointer 就是 LangGraph 用来保存状态的功能。每次节点执行完,它会把当前状态存起来,这样如果程序崩溃了可以从上次的地方继续。时间旅行调试就是可以回到之前的某个状态看看当时发生了什么。MemorySaver 是存在内存里的,PostgresSaver 是存在数据库里的,区别就是一个快一个持久化。

2️⃣ Impressive Answer

  1. Checkpointer 核心机制
  2. Checkpointer 是 LangGraph 的状态快照系统,在每个节点执行前后自动捕获状态
  3. 采用基于 StateSnapshot 的不可变数据结构,每次状态变更生成新的快照
  4. 支持断点续传:通过 config={"thread_id": "xxx"} 恢复特定执行线程的状态

  5. 时间旅行调试实现

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph

checkpointer = MemorySaver()
graph = StateGraph(state_schema, checkpointer=checkpointer)

# 执行并获取所有快照
config = {"thread_id": "debug-123"}
result = graph.invoke(initial_state, config)

# 时间旅行:查看第 3 步的状态
for snapshot in graph.get_state_history(config):
    if snapshot.metadata["step"] == 3:
        print(snapshot.values)  # 查看当时的状态
        # 可以从这一步重新执行
        graph.invoke(None, config, snapshot.config)
  1. MemorySaver vs PostgresSaver 区别
  2. 存储介质:MemorySaver 使用 Python 字典(进程级),PostgresSaver 使用 PostgreSQL(持久化)
  3. 并发支持:MemorySaver 单进程安全,PostgresSaver 支持多进程/分布式
  4. 容量限制:MemorySaver 受限于内存大小,PostgresSaver 可存储历史快照
  5. 性能特性:MemorySaver 低延迟但易丢失,PostgresSaver 有网络开销但可靠
  6. 适用场景:开发调试用 MemorySaver,生产环境用 PostgresSaver

3️⃣ Key Differences

查看内嵌表格

LangGraph 中如何实现子图(Subgraph)?子图和父图之间的状态如何传递和隔离?⭐⭐⭐

难度级别:⭐⭐⭐(模块化设计、状态隔离、嵌套编排)

1️⃣ Common Answer

子图就是把一个图放到另一个图里面用。可以用 add_node 把子图加到父图里。状态传递的话,子图可以访问父图的状态,但子图内部的变量外面看不到。隔离就是子图有自己的作用域,不会影响父图。

2️⃣ Impressive Answer

  1. 子图实现方式
  2. 使用 StateGraph 创建子图,然后通过 add_node 将其作为节点添加到父图
  3. 子图本身是一个完整的 CompiledGraph,可以有自己的边、条件边、checkpointer
  4. 支持嵌套:子图内还可以包含更深层级的子图
from langgraph.graph import StateGraph, END
from typing import TypedDict

# 子图状态定义
class SubgraphState(TypedDict):
    sub_data: str
    sub_result: str

# 创建子图
subgraph = StateGraph(SubgraphState)
subgraph.add_node("sub_step1", lambda state: {"sub_result": state["sub_data"] + " processed"})
subgraph.add_edge("sub_step1", END)
subgraph = subgraph.compile()

# 父图状态定义
class ParentState(TypedDict):
    parent_data: str
    sub_output: str

# 创建父图并添加子图
parent_graph = StateGraph(ParentState)
parent_graph.add_node("call_subgraph", subgraph)
  1. 状态传递机制
  2. 输入映射:父图节点输出 → 子图输入,通过 state_mapping 定义映射规则
  3. 输出映射:子图输出 → 父图后续节点,子图 END 状态自动返回父图
  4. 共享状态:通过 state["shared_key"] 在父子图间传递数据
# 状态映射示例
def map_parent_to_sub(state: ParentState) -> SubgraphState:
    return {"sub_data": state["parent_data"]}

def map_sub_to_parent(state: SubgraphState) -> ParentState:
    return {"sub_output": state["sub_result"]}

parent_graph.add_node("call_subgraph", subgraph,
                     input=map_parent_to_sub,
                     output=map_sub_to_parent)
  1. 状态隔离原则
  2. 命名空间隔离:子图状态键独立于父图,避免命名冲突
  3. 执行隔离:子图内部错误不会直接导致父图崩溃(可通过错误处理机制捕获)
  4. Checkpointer 隔离:子图可使用独立的 checkpointer,实现独立的状态快照
  5. 并发隔离:多个子图实例可并发执行,各自维护独立状态

3️⃣ Key Differences

查看内嵌表格


用 LangGraph 实现一个带重试、降级和超时控制的多步骤 Agent 工作流(如:数据采集→清洗→分析→报告生成),如何设计错误处理和容错机制?⭐⭐⭐⭐

难度级别:⭐⭐⭐⭐(容错设计、工程实践、生产级质量)

1️⃣ Common Answer

可以用 try-catch 来处理错误。重试的话就循环几次,如果还是失败就跳过或者返回默认值。超时可以用 timeout 参数设置。降级就是如果某个步骤失败了,就用一个简单的替代方案。整个流程用 LangGraph 的节点和边连起来就行。

2️⃣ Impressive Answer

  1. 整体架构设计
  2. 采用 StateGraph 构建四步工作流:collect_dataclean_dataanalyze_datagenerate_report
  3. 每个节点配置独立的重试策略、超时时间和降级方案
  4. 使用条件边实现错误分支:成功继续,失败触发降级或终止
from langgraph.graph import StateGraph, END
from typing import TypedDict, Literal
import time

class WorkflowState(TypedDict):
    raw_data: dict
    cleaned_data: dict
    analysis_result: dict
    report: str
    error_step: str
    retry_count: int
    status: Literal["success", "degraded", "failed"]

graph = StateGraph(WorkflowState)
  1. 重试机制实现
def retry_wrapper(node_func, max_retries=3, backoff_factor=2):
    def wrapper(state):
        retry_count = state.get("retry_count", 0)
        try:
            result = node_func(state)
            # 成功后重置重试计数
            result["retry_count"] = 0
            return result
        except Exception as e:
            if retry_count < max_retries:
                # 指数退避重试
                wait_time = backoff_factor ** retry_count
                time.sleep(wait_time)
                return {"retry_count": retry_count + 1, "error_step": node_func.__name__}
            else:
                # 重试耗尽,触发降级
                return {"status": "degraded", "error_step": node_func.__name__}
    return wrapper

# 应用重试包装器
graph.add_node("collect_data", retry_wrapper(collect_data_node, max_retries=3))
  1. 超时控制
from concurrent.futures import TimeoutError

def timeout_wrapper(node_func, timeout_seconds=30):
    def wrapper(state):
        start_time = time.time()
        try:
            result = node_func(state)
            if time.time() - start_time > timeout_seconds:
                raise TimeoutError(f"Node {node_func.__name__} timeout")
            return result
        except TimeoutError:
            return {"status": "degraded", "error_step": node_func.__name__}
    return wrapper

graph.add_node("clean_data", timeout_wrapper(clean_data_node, timeout_seconds=30))
  1. 降级策略设计
def fallback_collect_data(state):
    # 降级:使用缓存数据或默认数据
    return {"raw_data": {"source": "cache", "data": []}, "status": "degraded"}

def fallback_clean_data(state):
    # 降级:跳过清洗,直接返回原始数据
    return {"cleaned_data": state["raw_data"], "status": "degraded"}

# 条件边:根据状态决定下一步
def should_retry_or_fallback(state):
    if state["retry_count"] > 0:
        return "retry"
    elif state["status"] == "degraded":
        return "fallback"
    else:
        return "next"

graph.add_conditional_edges(
    "collect_data",
    should_retry_or_fallback,
    {
        "retry": "collect_data",  # 重试当前节点
        "fallback": "clean_data",  # 降级后继续
        "next": "clean_data"  # 正常流程
    }
)
  1. 全局错误处理与状态监控
def error_handler(state):
    error_step = state.get("error_step", "unknown")
    print(f"Workflow failed at step: {error_step}")
    print(f"Final status: {state['status']}")
    # 发送告警通知
    # 记录错误日志
    return {"status": "failed"}

graph.add_node("error_handler", error_handler)
graph.add_edge("generate_report", END)
graph.add_conditional_edges(
    "generate_report",
    lambda state: "error_handler" if state["status"] == "failed" else END,
    {"error_handler": "error_handler", END: END}
)
  1. Checkpointer 配置(支持断点续传)
from langgraph.checkpoint.postgres import PostgresSaver

checkpointer = PostgresSaver.from_conn_string("postgresql://...")
compiled_graph = graph.compile(checkpointer=checkpointer)

# 执行工作流
result = compiled_graph.invoke(
    initial_state,
    config={"thread_id": "workflow-001"}
)

3️⃣ Key Differences

查看内嵌表格


在 LangGraph 里如何实现"LLM 有工具调用就去执行工具,否则结束"这个 ReAct 循环?

难度级别:⭐⭐(条件边路由函数、START/END 节点、ReAct 循环结构)

1️⃣ Common Answer

在节点里判断 LLM 输出有没有 tool_calls,有的话就调用工具节点,没有就结束。用条件边来配置这个逻辑。

2️⃣ Impressive Answer

标准 ReAct 循环的图结构是两个节点 + 一条条件边:llm 节点调用 LLM,tools 节点执行工具,Supervisor 逻辑放在条件边的路由函数里:

def should_continue(state: AgentState) -> str:
    last_msg = state["messages"][-1]
    if hasattr(last_msg, "tool_calls") and last_msg.tool_calls:
        return "tools"
    return END

graph.add_node("llm", call_llm)
graph.add_node("tools", execute_tools)
graph.add_edge(START, "llm")
graph.add_conditional_edges("llm", should_continue, {"tools": "tools", END: END})
graph.add_edge("tools", "llm")  # 工具执行完回到 LLM,形成循环

关键是 tools → llm 这条边形成了循环,让 Agent 能在"思考-行动"之间反复迭代,直到 LLM 不再发出 tool_calls 才走 END。这是 LangGraph 实现 ReAct 的最小完整结构。

3️⃣ Key Differences

查看内嵌表格


LangGraph Checkpointing 与持久化记忆

LangGraph 的 MemorySaver 和 PostgresSaver 有什么区别?

难度级别:⭐(三种 Checkpointer 的适用场景)

MemorySaver 把状态存在内存里,进程重启就丢失,只适合本地测试和单测。SqliteSaver 存在 SQLite 文件里,适合单机轻量部署,但 SQLite 的写锁限制了并发能力。PostgresSaver 存在 PostgreSQL 里,支持高并发、连接池和高可用,是生产多实例部署的唯一选择——否则不同实例看到的状态会不一致。


LangGraph 的 Checkpointing 机制是如何工作的?thread_id 的作用是什么?生产环境如何选持久化方案?

难度级别:⭐⭐⭐(Checkpoint 存储结构、thread_id 多会话隔离、三种 Checkpointer 选型、Time Travel 调试)

1️⃣ Common Answer

Checkpointing 就是保存图执行过程中的状态,程序重启后可以恢复。有三种实现:MemorySaver 存内存、SqliteSaver 存文件、PostgresSaver 存数据库。thread_id 用来区分不同的会话,不同用户用不同的 thread_id 就不会互相干扰。

2️⃣ Impressive Answer

我会从三个层面来讲:

  1. 工作原理:不只是保存状态,而是完整的执行历史链。每个节点执行完后,框架自动调用 Checkpointer 的 put 方法,把当前 State、checkpoint_id、父节点 ID 一起写入存储,形成有序的历史链。下次用同一个 thread_id 调用时,框架先调用 get 拉取最新 Checkpoint 恢复 State,图从上次停止的地方继续,实现真正的跨请求记忆——不需要客户端手动传对话历史。

  2. thread_id 是多会话隔离的核心键。格式建议用 {user_id}_{session_id},既能隔离不同用户,也支持同一用户的多个会话。通过 config = {"configurable": {"thread_id": "..."}} 传给每次 invoke,框架根据 thread_id 加载对应的 Checkpoint。

  3. 持久化选型标准:并发量 + 部署模式。单测和开发用 MemorySaver,零配置;单机轻量部署用 SqliteSaver;生产多实例必须用 PostgresSaver,否则节点间 State 不一致。另外 Checkpointing 还有一个低调但极有价值的能力——Time Travel:通过 get_state_history 拿到完整执行历史,可以从任意历史节点重放,Agent 任务失败后不用重跑整个流程,直接从失败点恢复,生产排查问题时非常实用。

3️⃣ Key Differences

查看内嵌表格


用户的多轮对话跨了多次 HTTP 请求,LangGraph 如何实现无需客户端传历史的记忆?

难度级别:⭐⭐⭐(Checkpointing 跨请求记忆、thread_id 设计、PostgresSaver 生产选型)

1️⃣ Common Answer

用 Checkpointing 保存状态,每次请求带上 thread_id,框架自动加载上次的历史,不用客户端自己传消息记录。

2️⃣ Impressive Answer

核心思路是让 thread_id 承担会话标识,Checkpointer 承担历史存储,客户端每次只需传当前这条消息:

# 服务端:每次请求只处理新消息
config = {"configurable": {"thread_id": f"{user_id}_{session_id}"}}

result = graph.invoke(
    {"messages": [HumanMessage(current_user_message)]},  # 只传新消息
    config
)

框架在执行前自动从 PostgresSaver 拉取该 thread_id 的最新 Checkpoint,State 里的 messages(带 add_messages reducer)会追加新消息而不是覆盖,所以 LLM 能看到完整的上下文。

生产注意点:thread_id 要绑定到用户的 session,session 过期时要有清理策略(否则 Checkpoint 表会无限增长);多实例部署必须用 PostgresSaver,MemorySaver 在多进程环境下 thread_id 隔离会失效。

3️⃣ Key Differences

查看内嵌表格


4、容易一起考的题

查看内嵌表格


LangGraph Human-in-the-Loop:interrupt 与 resume 机制


1、LangGraph 的 interrupt_before 是什么?

难度级别:⭐(interrupt_before/after 基本配置)

interrupt_before 是图编译时的全局配置,指定在哪些节点执行前自动暂停等待人工确认,不需要修改节点代码,适合统一拦截高风险操作。interrupt_after 是节点执行后暂停,用于人工审查结果。两者都依赖 Checkpointing,暂停状态会持久化,等待人工用 Command(resume=...) 恢复执行。


2、LangGraph 如何实现 Human-in-the-Loop?请解释 interrupt() 的工作原理和 Command(resume=...) 恢复机制?

难度级别:⭐⭐⭐(interrupt 控制流中断原理、GraphInterrupt 异常、Command resume、Checkpointing 底层支撑)

1️⃣ Common Answer

在节点里调用 interrupt() 就会暂停执行,等用户输入后用 Command(resume=用户输入) 恢复。也可以在 compile() 时配置 interrupt_before 指定在哪些节点前暂停。这个功能用于需要人工审核的场景。

2️⃣ Impressive Answer

我会从原理、用法和生产设计三个层面来讲:

  1. interrupt() 的本质:控制流中断,不是 sleep。调用 interrupt() 时,框架做三件事:把当前 State(含 interrupt 的值)写入 Checkpoint → 抛出 GraphInterrupt 特殊异常 → graph.invoke() 捕获该异常并返回,附带 interrupt 的内容。整个图执行就此暂停,状态完整持久化,等待外部恢复。

  2. Command(resume=...) 恢复执行的机制。人工操作完成后,用同一个 thread_id 再次调用 graph.invoke(Command(resume="approve"), config),框架从 Checkpoint 恢复 State,把 resume 的值注入 interrupt() 的返回值,图从暂停点继续执行——不是重头跑,而是接着走。

  3. 生产设计:interrupt_before vs 节点内 interrupt 的选择interrupt_before/after 是编译时的无侵入全局配置,适合统一治理高风险节点;节点内手动调用 interrupt() 更灵活,可以把上下文(比如"要删除的具体数据")一起传给审核人。完整的生产异步审核系统通常是:Agent 触发 interrupt → 写入消息队列 → 推送通知给审核人 → 审核人在 Web 界面操作 → 后端调用 graph.invoke(Command(resume=...), config),同时需要设计超时处理,超时后自动拒绝或升级审核。

3️⃣ Key Differences

查看内嵌表格


3、金融 Agent 要执行一笔大额转账前需要人工确认,如何用 LangGraph 设计这个审核流程?

难度级别:⭐⭐⭐(interrupt 携带上下文、异步审核系统设计、超时处理)

1️⃣ Common Answer

在执行转账的节点前加 interrupt,让人工确认一下,确认后再继续执行,或者用 interrupt_before 配置在转账节点前暂停。

2️⃣ Impressive Answer

这个场景用节点内 interrupt() 而不是 interrupt_before,原因是需要把"转账金额、收款方、风险等级"等上下文信息传给审核人:

def transfer_review_node(state: AgentState) -> dict:
    decision = interrupt({
        "amount": state["transfer_amount"],
        "to_account": state["to_account"],
        "risk_level": state["risk_level"],
        "message": f"即将转账 {state['transfer_amount']} 元到 {state['to_account']},请确认"
    })
    if decision == "approve":
        return {"task_status": "approved"}
    return {"task_status": "rejected", "reject_reason": decision}

服务端收到 interrupt 后,通过消息队列把审核请求推给风控人员。审核人在内部系统点击"通过/拒绝",前端调用:

graph.invoke(Command(resume="approve"), config)  # 或 "reject: 金额超限"

超时设计:如果 30 分钟内无人审核,后台定时任务自动调用 Command(resume="timeout_reject"),节点里对 "timeout_reject" 做特殊处理,记录日志并通知申请人。

3️⃣ Key Differences

查看内嵌表格


5.3 LangGraph 多 Agent 架构

LangGraph Multi-Agent Supervisor 模式


1、Supervisor 模式和普通顺序图有什么区别?

难度级别:⭐(Supervisor 模式基本概念)

普通顺序图的执行路径是固定的,节点按预定顺序运行;Supervisor 模式引入一个中心协调节点,用 LLM 动态决策把任务路由给哪个子 Agent,子 Agent 执行完后结果返回 Supervisor,由 Supervisor 决定下一步——是继续调用其他 Agent 还是结束。这种模式把"决定做什么"和"怎么做"分离,适合任务类型多样、边界清晰的场景。


2、LangGraph Multi-Agent Supervisor 模式的设计思路是什么?Supervisor 如何动态路由任务,子 Agent 如何返回结果?

难度级别:⭐⭐⭐(Supervisor 用结构化输出驱动路由、Command(goto=) 动态路由、子 Agent 固定返回 Supervisor、死循环防护)

1️⃣ Common Answer

Supervisor 是一个 LLM 节点,根据当前情况决定把任务给哪个子 Agent。子 Agent 执行完后通过条件边回到 Supervisor,Supervisor 再决定是否还需要调用其他 Agent 或者结束。这样 Supervisor 就能协调多个子 Agent 完成复杂任务。

2️⃣ Impressive Answer

我会从架构设计、实现细节和生产注意事项三个层面来讲:

  1. 架构本质:用 LLM 作为动态路由器,分离决策和执行。Supervisor 只管协调,子 Agent 只管执行。整体是一个以 Supervisor 为中心的星形结构:所有子 Agent 执行完后都固定返回 Supervisor,由 Supervisor 判断任务完成还是继续调度下一个子 Agent。

  2. Supervisor 节点的实现:结构化输出驱动路由。Supervisor 调用 LLM 并用 with_structured_output 得到结构化的路由决策(下一个 Agent 名称 + 推理原因),然后用 Command(goto=agent_name) 动态跳转:

def supervisor_node(state: AgentState) -> Command:
    response = llm.with_structured_output(RouteDecision).invoke([
        SystemMessage(f"可用 Agent:{', '.join(MEMBERS)},完成时返回 FINISH"),
        *state["messages"]
    ])
    goto = response.next_agent if response.next_agent != "FINISH" else END
    return Command(goto=goto, update={"current_agent": response.next_agent})
  1. 子 Agent 执行完后固定用 Command(goto="supervisor") 返回,把结果写入 messages。

  2. 生产关键:防死循环 + 失败处理。Supervisor 可能在两个子 Agent 之间反复横跳,需要在 State 里记录调用次数,超过阈值强制走 FINISH 或抛错。子 Agent 内部捕获异常,把错误信息写入 messages 让 Supervisor 决策(重试/换方案/报失败)。当任务可拆分时,Supervisor 可以返回 Command(goto=["agent_a", "agent_b"]) 触发并行执行。

3️⃣ Key Differences

查看内嵌表格


3、自动化研究报告生成系统需要搜索、分析、写作三类能力,如何用 Supervisor 模式设计?

难度级别:⭐⭐⭐(多子 Agent 任务分工、Supervisor 路由设计、死循环防护、结果汇总)

1️⃣ Common Answer

建三个子 Agent 分别负责搜索、分析和写作,Supervisor 根据当前阶段决定调哪个,子 Agent 完成后返回 Supervisor,最后汇总出报告。

2️⃣ Impressive Answer

三个子 Agent 职责明确:research_agent 负责网络搜索和资料收集,analysis_agent 负责数据分析和洞察提取,writing_agent 负责报告撰写和格式化。

Supervisor 的系统 Prompt 告知可用的 Agent 和当前任务进展,LLM 根据 messages 历史决策下一步。典型的执行路径是:

关键工程细节:

  • 死循环防护:State 里加 step_count: int,每次 Supervisor 调度 +1,超过 10 次强制 FINISH

  • 子 Agent 消息命名:返回的 AIMessage 带 name="research_agent",Supervisor 在 messages 历史里能清晰看到每个 Agent 的贡献

  • 失败降级:research_agent 搜索失败时,把错误写入 messages,Supervisor 可以决定用缓存数据或跳过该步骤

3️⃣ Key Differences

查看内嵌表格


LangGraph 子图(Subgraph)组合与模块化设计


1、LangGraph 子图是什么?

难度级别:⭐(子图基本概念、作为节点嵌入父图)

子图(Subgraph)是把一个已编译的 CompiledGraph 作为节点嵌入到父图中,让复杂系统可以分解成独立的模块。每个子图有自己的 State,父图和子图通过同名字段自动同步数据,子图的私有字段对父图完全不可见,实现真正的封装隔离。子图可以独立编译和测试,也可以被多个父图复用。


2、LangGraph 子图如何实现父子 State 的隔离与通信?它解决了什么工程问题?

难度级别:⭐⭐⭐(字段名匹配的同步机制、私有字段隔离、独立编译测试、复用场景、Checkpointing 注意事项)

1️⃣ Common Answer

子图就是把一个图嵌到另一个图里,父图和子图通过相同的字段名来传递数据。子图的好处是可以单独测试,也可以复用。父子图各有各的 State,但相同字段名的内容会自动同步。

2️⃣ Impressive Answer

我会从通信机制、工程优势和注意事项三个层面来讲:

  1. State 通信:字段名匹配的自动同步 + 私有字段完全隔离。父图传入子图时,同名字段的值复制进子图 State;子图执行完毕后,同名字段的值写回父图。子图的私有字段(如内部中间状态 retrieved_docsquery_embedding)对父图完全不可见,实现了真正的封装。这意味着子图的内部实现可以随意重构,只要保持同名字段的接口约定不变,父图就不受影响。

  2. 工程优势:模块化开发和独立测试。子图可以独立编译后单独测试,不需要启动整个父图。不同团队可以并行开发不同子图,各自写单测,集成时只验证 State 字段的接口约定。同一个子图还可以用 with_config 绑定不同配置,在父图中多次使用——比如同一套 RAG 子图逻辑,分别绑定金融知识库和法律知识库,并行执行两路检索。

  3. 注意事项:子图的 Checkpointing 有一个坑。子图的内部状态默认不单独保存 Checkpoint,只有父图 State 中同步回来的字段被持久化。如果需要保留子图完整执行历史,必须给子图单独配置 Checkpointer。调试复杂系统时这个细节容易被忽视,Time Travel 回放时只能看到父图视角的历史,看不到子图内部的步骤。

3️⃣ Key Differences

查看内嵌表格


3、一个 Agent 系统需要同时查金融知识库和法律知识库,如何用子图实现并行双路 RAG?

难度级别:⭐⭐⭐(子图复用 + with_config、并行边、结果汇总)

1️⃣ Common Answer

建两个 RAG 子图,一个查金融库,一个查法律库,然后并行执行,最后把结果合并。可以复用同一套 RAG 子图逻辑,用不同配置区分知识库。

2️⃣ Impressive Answer

关键思路是"一套子图逻辑,两份配置实例化":

# 同一个 RAG 子图,用 with_config 绑定不同知识库
rag_subgraph = build_rag_graph().compile()
finance_rag = rag_subgraph.with_config({"configurable": {"kb": "finance"}})
legal_rag   = rag_subgraph.with_config({"configurable": {"kb": "legal"}})

# 父图中作为两个独立节点
parent.add_node("finance_rag", finance_rag)
parent.add_node("legal_rag", legal_rag)

# 并行执行:router 同时触发两个子图
parent.add_edge("router", ["finance_rag", "legal_rag"])
parent.add_edge(["finance_rag", "legal_rag"], "merge_node")  # 等两个都完成后汇总

merge_node 收到来自两个子图的 messages(通过 add_messages reducer 追加),把两路检索结果拼接后交给 LLM 生成最终回答。

子图的私有字段(如 retrieved_docs)不向父图暴露,父图 merge_node 只看 messages,接口干净。如果将来要加第三个知识库,只需再加一个 with_config 实例和一条并行边,不需要修改任何子图内部逻辑。

3️⃣ Key Differences

查看内嵌表格


LangGraph 的错误处理与节点级重试机制


1、LangGraph 节点抛出异常时的默认行为是什么?

难度级别:⭐(考察要点:异常透传、执行中断、Checkpoint 状态丢失)

节点函数抛出未捕获的异常时,LangGraph 立即停止图的执行,异常透传给调用方(.invoke().astream())。如果没有配置 checkpointer,所有已执行节点的状态变更会丢失,图从"进行中"变为"失败"状态。开发阶段这个行为有助于快速发现问题,但生产环境必须设计更精细的错误处理机制。


2、如何在 LangGraph 中设计节点级重试策略和错误状态路由,构建健壮的 Agent 错误处理体系?

难度级别:⭐⭐⭐(考察要点:RetryPolicy 配置与 retry_on 精确指定、错误状态写入 State、条件边路由到 error_handler、Checkpoint 恢复机制)

1️⃣ Common Answer

可以给节点配置 RetryPolicy 来自动重试,还可以专门定义一个错误处理节点,通过条件边路由过去处理错误,这样 Agent 就不会直接失败了。

2️⃣ Impressive Answer

我会从分层防御的视角来回答,LangGraph 的错误处理分三个层次:

  1. 第一层:节点级 RetryPolicy,处理瞬时故障。对于网络超时、限流 429 等瞬时错误,用 RetryPolicy 配置自动重试:max_attempts=3、指数退避 backoff_factor=2.0。关键在于 retry_on 参数要精确指定需要重试的异常类型(如 openai.RateLimitErrorhttpx.TimeoutException),避免对逻辑错误(ValueError 意味着输入有问题)也无意义地重试,重试三次还是会失败。

  2. 第二层:错误状态路由,处理业务级错误。对于重试也无法恢复的错误,核心思路是"不抛异常,写入状态"——在节点内 try-except 捕获异常,把错误信息写入 State 的 error 字段,通过条件边路由到专门的 error_handler 节点。error_handler 可以让 LLM 根据错误信息换一种策略重新规划,或者在超过重试阈值后输出降级回复(比如"请联系人工客服"),实现优雅降级。

  3. 第三层:Checkpoint 持久化,处理系统级恢复。配置 SqliteSaver 等 checkpointer 后,图的状态在每个节点执行后持久化,出错时可从最后一个成功的 checkpoint 恢复,不需要从头重跑。对于长时间运行的 Agent 任务价值极大。三层叠加才能构建真正健壮的生产级 Agent。

3️⃣ Key Differences

查看内嵌表格


3、Agent 在调用外部 API 时频繁遇到限流(429 错误),如何在 LangGraph 层面系统性解决?

难度级别:⭐⭐⭐(考察要点:RetryPolicy 指数退避、retry_on 精确匹配、限流感知的工具设计、Checkpoint 断点续跑)

1️⃣ Common Answer

可以在节点上配置 RetryPolicy,设置 max_attempts 和重试等待时间,遇到 429 就自动重试,等一段时间再调用。

2️⃣ Impressive Answer

限流问题要从两个层面解决,只靠重试是不够的。

第一,RetryPolicy 层:配置 retry_on=(openai.RateLimitError,),只对 429 重试,配合指数退避(initial_interval=1.0backoff_factor=2.0),让重试间隔逐渐拉长,避免密集重试把限流打得更厉害。同时设置合理的 max_attempts(一般 3 次),超过后让错误进入下一层处理。

第二,状态路由层:如果重试全部失败,把限流错误写入 State,路由到 error_handler 节点,判断是否可以降级(比如换一个备用 API endpoint 或模型),而不是直接让用户看到失败。

第三,Checkpoint 层:对于长任务,配置 checkpointer,限流导致的中断可以从断点续跑,不需要重跑已完成的节点,这在 token 成本上也有显著收益。

3️⃣ Key Differences

查看内嵌表格