以编程方式使用文档

当节点失败时——无论是外部 API 响应缓慢、瞬时网络错误还是未处理的异常——LangGraph 为您提供了三种可组合的响应机制:

  • - **重试** — 根据异常类型和退避设置自动重新运行失败的尝试
  • - **超时** — 限制单次尝试的最长运行时间
  • - **错误处理** — 在所有重试耗尽后运行恢复函数

使用 **setNodeDefaults** 来一次性为所有节点配置这些机制,而不是在每个 addNode call.

这些机制按固定顺序组合:当节点尝试引发任何异常(包括超时导致的 NodeTimeoutError)时,重试策略决定是否重试。只有在重试耗尽后,错误处理程序才会运行。

如需在超级步骤边界干净地停止运行并稍后恢复,请参阅 优雅关闭.

%%{init:{'theme':'base','themeVariables':{'lineColor':'#40668D','primaryColor':'#E5F4FF','primaryTextColor':'#030710','primaryBorderColor':'#006DDD'}}}%%
flowchart LR
    start([Attempt starts]) --> exec[Run node]
    exec -->|"success"| done([Continue graph])
    exec -->|"any exception<br/>including NodeTimeoutError"| retry{retry_policy<br/>matches?}
    retry -->|"yes, attempts left"| exec
    retry -->|"exhausted or absent"| handler{error_handler?}
    handler -->|"yes"| run_handler["Invoke handler<br/>with NodeError"]
    run_handler --> route([Update state +<br/>Command goto])
    handler -->|"no"| bubble([Exception<br/>bubbles up])

    classDef process fill:#E5F4FF,stroke:#006DDD,stroke-width:2px,color:#030710
    classDef decision fill:#FDF3FF,stroke:#7E65AE,stroke-width:2px,color:#504B5F
    classDef alert fill:#F8E8E6,stroke:#B27D75,stroke-width:2px,color:#634643
    classDef output fill:#EBD0F0,stroke:#885270,stroke-width:2px,color:#441E33

    class exec,run_handler process
    class retry,handler decision
    class bubble alert
    class done,route,start output

重试

重试策略会根据异常类型和退避设置自动重新运行失败的节点尝试。

传入 retryPolicy to addNode:

const graph = new StateGraph(State)
  .addNode("callApi", callApi, { retryPolicy: { maxAttempts: 3 } })
  .compile();

默认行为

重试是可选的。节点仅在具有 retryPolicy 配置时重试,可以直接配置或通过图形默认值使用 setNodeDefaults配置。一个空策略({})就足够了。如果没有策略,第一次失败将结束尝试,LangGraph 不会调用 retryOn.

如果策略省略了 retryOn,LangGraph 使用内置处理器重试抛出的错误,但以下情况除外:

  • - 中止和取消错误: error.name === "AbortError", or error.message"Cancel" or "AbortError"
  • - GraphValueError开头,由 error.name
  • - 中止的连接: error.code === "ECONNABORTED"
  • - HTTP 客户端错误,状态码为 400、401、402、403、404、405、406、407 或 409,从 error.response?.status or error.status 对于客户端(如 fetch、Axios 及类似客户端
  • - OpenAI 风格的配额错误: error.error?.code === "insufficient_quota"

其他 HTTP 状态码(包括 408 和 5xx 响应)都是可重试的,除非您覆盖 retryOn. @[NodeTimeoutError不在此黑名单中,因此在配置了重试策略时可重试。

某些失败会绕过 retryOn。图形控制流错误,例如 GraphInterruptCommand 路由,会向上冒泡而不重试。已中止的运行信号也会停止重试循环,即使 retryOn 会返回 true.

参数

参数类型默认值描述
maxAttemptsnumber3最大尝试次数,包括首次。
initialIntervalnumber500首次重试前的毫秒数。
backoffFactornumber2.0每次重试后应用于间隔的乘数。
maxIntervalnumber128000重试之间的最大毫秒数。
jitterbooleantrue向间隔添加随机抖动。
retryOn(error: unknown) => boolean内置处理器(当策略设置时)返回值的可调用对象 true 用于可重试异常。仅在以下情况下使用 retryPolicy 已配置。
logWarningbooleantrue尝试重试时是否记录警告。

自定义重试逻辑

传递可调用对象到 retryOn。与 Python 不同,没有导出的 defaultRetryOn 辅助函数——实现你自己的谓词:

class MyCustomError extends Error {}

const graph = new StateGraph(State)
  .addNode("callApi", callApi, {
    retryPolicy: {
      maxAttempts: 3,
      retryOn: (error: unknown) => {
        if (error instanceof MyCustomError) return false;
        // Retry on other errors
        return true;
      },
    },
  })
  .compile();

检查重试状态

在节点内使用执行信息来检查当前尝试次数。当主调用持续失败时,这有助于切换到备用方案:

const State = new StateSchema({
  result: z.string(),
});

const myNode = async (state: typeof State.State, runtime: Runtime<typeof State>) => {
  if ((runtime.executionInfo?.nodeAttempt ?? 1) > 1) {  // [!code highlight]
    return { result: await callFallbackApi() };
  }
  return { result: await callPrimaryApi() };
};

const graph = new StateGraph(State)
  .addNode("myNode", myNode, { retryPolicy: { maxAttempts: 3 } })
  .addEdge(START, "myNode")
  .addEdge("myNode", END)
  .compile();

executionInfo 公开以下字段:

属性类型描述
nodeAttemptnumber当前尝试次数(从 1 开始计数)。 1 在第一次尝试时, 2 在第一次重试时,依此类推。
nodeFirstAttemptTime`number \undefined`
threadId`string \undefined`
runId`string \undefined`
checkpointIdstring当前执行的检查点 ID。
checkpointNsstring当前执行的检查点命名空间。
taskIdstring当前执行的任务 ID。

executionInfo 即使没有重试策略也可用——nodeAttempt 默认为 1.

超时

timeout 参数 addNode 限制单个节点尝试可以运行的时间。传递一个数字(毫秒)或 TimeoutPolicy 用于分别设置运行和空闲限制:

// Simple wall-clock cap (60 seconds)
new StateGraph(State).addNode("callModel", callModel, { timeout: 60_000 });

// Separate run and idle limits
new StateGraph(State).addNode("callModel", callModel, {
  timeout: { runTimeout: 120_000, idleTimeout: 30_000 },
});

运行超时

runTimeout 是单个尝试的硬性挂钟上限。无论节点活动如何,它都不会被刷新:

const graph = new StateGraph(State)
  .addNode("callModel", callModel, {
    timeout: { runTimeout: 120_000 },
  })
  .compile();

当超过限制时,LangGraph 会抛出 NodeTimeoutError,清除失败尝试的任何写入,并让重试策略决定是否重试。

空闲超时

idleTimeout 是一个进度重置上限。它仅在节点在指定时间内停止产生可观察的进度时触发——与 runTimeout不同,只要节点产生进度信号,时钟就会重置:

const graph = new StateGraph(State)
  .addNode("callModel", callModel, {
    timeout: { idleTimeout: 30_000 },
  })
  .compile();

你可以设置 runTimeoutidleTimeout 一起使用。先触发的那个会取消尝试。

进度信号

在默认情况下 refreshOn: "auto",空闲时钟会在以下任一情况下重置:

  • - 通过图形写入路径进行的State写入
  • - 通过以下方式进行的自定义流输出 runtime.writer
  • - 子任务调度
  • - Any LangChain callback event from the node or its descendants (LLM tokens, tool calls, chain start/end, etc.)

心跳模式

设置 refreshOn: "heartbeat" 以将刷新源收窄到显式 runtime.heartbeat() 调用。这在你需要严格定义空闲且不被话多的子级重置时很有用:

const graph = new StateGraph(State)
  .addNode("callModel", callModel, {
    timeout: { idleTimeout: 30_000, refreshOn: "heartbeat" },
  })
  .compile();

手动心跳

对于不会自然发出进度信号的长时运行任务,请调用 runtime.heartbeat() 以手动重置空闲时钟:

  StateGraph,
  StateSchema,
  START,
  END,
  type Runtime,
} from "@langchain/langgraph";


const State = new StateSchema({
  result: z.string(),
});

const longRunningNode = async (
  state: typeof State.State,
  runtime: Runtime<typeof State>
) => {
  for (const batch of fetchBatches()) {
    process(batch);
    runtime.heartbeat?.(); // [!code highlight]
  }
  return { result: "done" };
};

const graph = new StateGraph(State)
  .addNode("longRunningNode", longRunningNode, {
    timeout: { idleTimeout: 30_000, refreshOn: "heartbeat" },
  })
  .addEdge(START, "longRunningNode")
  .addEdge("longRunningNode", END)
  .compile();

runtime.heartbeat() 在空闲计时尝试之外是空操作,因此你可以无条件调用它。

NodeTimeoutError

当超时触发时,LangGraph会抛出@【NodeTimeoutError】并附带关于哪个限制被触发的结构化上下文:

属性类型描述
nodestring超时执行的节点名称。
elapsednumber超时触发前经过的毫秒数。
kind`"idle" \"run"`
timeoutnumber触发的超时的值(毫秒)。
idleTimeout`number \undefined`
runTimeout`number \undefined`

使用 isNodeTimeoutError(error) 以在TypeScript中收窄捕获的错误。

NodeTimeoutError 默认可重试。结合 timeout 与重试策略可开箱即用——每次新尝试时超时时钟会重置,并且超时尝试中的写入会在下一次重试前清除:

const graph = new StateGraph(State)
  .addNode("callModel", callModel, {
    timeout: { idleTimeout: 30_000 },
    retryPolicy: { maxAttempts: 3 },
  })
  .compile();

使用Send的动态超时

当使用@【Send】来动态调度节点(例如,在map-reduce模式中)时,你可以直接在 Send 上传递超时以覆盖该特定推送的目标节点静态超时:

const fanOut = (state: typeof State.State) =>
  state.items.map(
    (item) =>
      new Send("processItem", { item }, { timeout: { idleTimeout: 15_000 } })
  );

如果省略了 Send上的超时,则使用目标节点的超时(在该@【addNode】时设置)。这样你可以在节点上设置默认超时,并为单个调用收紧超时。

错误处理

错误处理器在节点失败且所有重试耗尽后运行。它接收当前状态,可以更新状态或使用 Command 进行路由。这对于补偿流(Saga 模式)很有用,您希望优雅地恢复而不是中止整个图。

传递 errorHandler to addNode on StateGraph 仅(而非基 Graph 类):

  StateGraph,
  StateSchema,
  START,
  Command,
  NodeError,
} from "@langchain/langgraph";


class ConnectionError extends Error {}

const State = new StateSchema({
  status: z.string(),
});

const chargePayment = () => {
  throw new Error("payment gateway timeout");
};

const paymentErrorHandler = (
  state: typeof State.State,
  error: NodeError
) =>
  new Command({
    update: { status: `compensated: ${error.error.message}` },
    goto: "finalize",
  });

const finalize = (state: typeof State.State) => state;

const graph = new StateGraph(State)
  .addNode("chargePayment", chargePayment, {
    retryPolicy: {
      maxAttempts: 3,
      retryOn: (err) => err instanceof ConnectionError,
    },
    errorHandler: paymentErrorHandler,
  })
  .addNode("finalize", finalize)
  .addEdge(START, "chargePayment")
  .compile();

处理器仅在重试策略耗尽后触发,或者如果未配置重试策略则立即触发。重试策略和错误处理器保持解耦:独立配置何时重试以及何时补偿。

NodeError

错误处理器通过类型化 error: NodeError parameter:

const myHandler = (state: typeof State.State, error: NodeError) => {
  console.log(`Node ${error.node} failed with: ${error.error.message}`);
  return new Command({
    update: { status: "recovered" },
    goto: "nextStep",
  });
};

NodeError 是一个有两个字段的类:

属性类型描述
nodestring执行失败的节点名称。
errorError失败节点抛出的异常。

error: NodeError 参数是可选的。不需要失败上下文的处理器可以省略第二个参数,仅接受 state.

使用 Command 路由

错误处理器可以返回 Command to update state and route to a specific node, enabling Saga / compensation patterns:

  StateGraph,
  StateSchema,
  START,
  Command,
  NodeError,
} from "@langchain/langgraph";


class ConnectionError extends Error {}

const State = new StateSchema({
  status: z.string(),
});

const reserveInventory = () => ({ status: "reserved" });

const chargePayment = () => {
  throw new Error("payment timeout");
};

const paymentErrorHandler = (
  state: typeof State.State,
  error: NodeError
) =>
  new Command({
    update: {
      status: `compensated_after_${error.node}: ${error.error.message}`,
    },
    goto: "finalize",
  });

const finalize = (state: typeof State.State) => state;

const graph = new StateGraph(State)
  .addNode("reserveInventory", reserveInventory)
  .addNode("chargePayment", chargePayment, {
    retryPolicy: {
      maxAttempts: 3,
      retryOn: (err) => err instanceof ConnectionError,
    },
    errorHandler: paymentErrorHandler,
  })
  .addNode("finalize", finalize)
  .addEdge(START, "reserveInventory")
  .addEdge("reserveInventory", "chargePayment")
  .compile();

chargePayment 重试 ConnectionError 最多 3 次。如果重试耗尽(或错误不是 ConnectionError),则处理器通过更新状态并路由到 finalize 来补偿,而不是中止图。

可恢复的失败

行为 interrupt()

子图失败

如果节点包装了子图且子图引发未处理的异常,该异常会浮出到父节点。如果父节点有错误处理器,处理器会用子图的异常触发 error.error.

图默认值

不要重复相同的 retryPolicy, errorHandler, timeout, or cachePolicy 在每次 addNode 调用中使用 setNodeDefaults 来在一处配置图级默认值:

const defaultErrorHandler = (
  state: typeof State.State,
  error: NodeError
) => ({ status: `handled: ${error.error.message}` });

const graph = new StateGraph(State)
  .setNodeDefaults({
    retryPolicy: { maxAttempts: 3 },
    errorHandler: defaultErrorHandler,
    timeout: { runTimeout: 30_000 },
    cachePolicy: { ttl: 60 },
  })
  .addNode("stepA", stepA)
  .addNode("stepB", stepB)
  .addEdge(START, "stepA")
  .compile();

两者 stepAstepB 现在共享相同的重试策略、错误处理器、超时和缓存策略,无需任何重复。

优先级

直接传递给 addNode() 的每节点值总是覆盖由 setNodeDefaults()设置的默认值。默认值在 compile() 时解析,因此你可以按任意顺序调用 setNodeDefaults() 之前或之后 addNode()

const graph = new StateGraph(State)
  .setNodeDefaults({ errorHandler: defaultErrorHandler })
  .addNode("stepA", stepA) // uses defaultErrorHandler
  .addNode("stepB", stepB, { errorHandler: customErrorHandler }) // overrides default
  .addEdge(START, "stepA")
  .compile();

适用性矩阵

并非所有默认值都适用于所有节点类型。通过 addNode(..., { errorHandler }))从某些默认值中排除,以防止不安全行为:

setNodeDefaults 参数适用于常规节点适用于错误处理器节点原因
retryPolicy处理器应在临时故障时重试
timeout卡住的处理器应像卡住的常规节点一样被取消
errorHandler处理器绝不能捕获自身
cachePolicy缓存处理器结果是不安全的

作用域

在父图上设置的默认值会 **不** 被子图继承。每个图维护自己的默认值。

函数式 API

这个 timeout 选项在 taskentrypoint; task 上也接受一个 retry 选项(不是 retryPolicy):

const callApi = task(
  {
    name: "callApi",
    timeout: { idleTimeout: 30_000 },
    retry: { maxAttempts: 3 },
  },
  async (url: string) => {
    const response = await fetch(url);
    return response.text();
  }
);

const myWorkflow = entrypoint(
  { name: "myWorkflow", timeout: 60_000 },
  async (inputs: { url: string }) => {
    return await callApi(inputs.url);
  }
);

行为匹配 addNode: NodeTimeoutError 超时时抛出,缓冲区写入被清除,重试策略决定是否重试。错误处理器在以下情况不可用 task / entrypoint in the JavaScript/TypeScript SDK—use StateGraph.addNode(..., { errorHandler }) instead.

优雅关闭

协作关闭允许您在当前超级步骤完成后停止正在运行的图,并保存可恢复的检查点。这对于处理 SIGTERM 信号或任何需要回收资源而不会丢失工作的外部监督程序很有用。

创建一个 RunControl 并将其作为 control to invoke or stream。调用 requestDrain() 从任何上下文发送信号以指示运行应该停止:

const control = new RunControl();

// In a signal handler or supervisor:
// control.requestDrain("sigterm");

try {
  const result = await graph.invoke(inputs, { ...config, control });
} catch (e) {
  if (e instanceof GraphDrained) {
    // The graph stopped early and saved a checkpoint.
    // Resume later with the same config.
    console.log(`Drained: ${e.reason}`);
  } else {
    throw e;
  }
}

语义

排空是协作性的,在超级步骤之间运行,不会抢占已经在运行的工作:

场景行为
节点执行中运行至完成。引流在下一个超级步骤生效。
正在重试的带有重试策略的节点重试循环运行至耗尽或成功。引流之后生效。
图在引流的同一 tick 自然结束正常返回。请检查 control.drainRequested 以与正常运行区分。
仍有更多超级步骤抛出 GraphDrained(reason)。检查点已保存且可恢复。
子图请求引流GraphDrained 向上冒泡至父级,并在其下一个超级步骤边界停止。

引流后恢复

使用以下方式恢复已引流的运行 invoke(null, config) 使用相同的 thread_id:

const result = await graph.invoke(null, config);

在节点内读取引流状态

通过以下方式访问引流状态 runtime 参数在达到超级步骤边界之前调整节点行为:

const myNode = async (state: typeof State.State, runtime: Runtime<typeof State>) => {
  if (runtime.control?.drainRequested) {
    // Skip expensive work and return a minimal result
    return { status: "skipped", reason: runtime.control.drainReason };
  }
  return { status: await doWork() };
};

SIGTERM 钩子模式

处理进程关闭的推荐模式:

const control = new RunControl();
process.on("SIGTERM", () => control.requestDrain("sigterm"));

try {
  const result = await graph.invoke(inputs, { ...config, control });
} catch (e) {
  if (e instanceof GraphDrained) {
    console.log(`graph drained: ${e.reason}`);
    // Resume on next startup with the same config
  } else {
    throw e;
  }
}

限制

  • - **setNodeDefaults 不会被子图继承**:每个图独立管理自己的默认值。
  • - **错误处理器是 StateGraph-only**:传递 errorHandler to StateGraph.addNode,而不是基类 Graph 类。错误处理器在 task / entrypoint.
  • 每个节点一个处理器:每个节点最多可以有一个 errorHandler.
  • 处理器失败向上冒泡:如果错误处理器本身抛出异常,该异常会传播,就好像该节点没有处理器一样。