From 188f8590d1bf62ef8be60b3c0560dac1c6326b71 Mon Sep 17 00:00:00 2001 From: Docs Bot Date: Tue, 19 May 2026 21:00:12 +0000 Subject: [PATCH] Add set_node_defaults section to LangGraph fault tolerance page Document the new set_node_defaults() method on StateGraph that allows configuring graph-wide defaults for retry_policy, error_handler, timeout, and cache_policy. Particularly highlights the error_handler default as the most valuable new capability from PR langchain-ai/langgraph#7747. Includes: - Overview section with full example - Precedence rules (per-node overrides defaults) - Default error handler with RunnableConfig example - Applicability matrix showing which defaults apply to handler nodes - Scope note (not inherited by subgraphs) - Updated intro bullets and Limitations section --- src/oss/langgraph/fault-tolerance.mdx | 110 ++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/src/oss/langgraph/fault-tolerance.mdx b/src/oss/langgraph/fault-tolerance.mdx index 2e5c434ae6..96e9ce6132 100644 --- a/src/oss/langgraph/fault-tolerance.mdx +++ b/src/oss/langgraph/fault-tolerance.mdx @@ -11,6 +11,8 @@ When a node fails—from a slow external API, a transient network error, or an u - [**Timeouts**](#timeouts) — cap how long a single attempt may run - [**Error handling**](#error-handling) — run a recovery function after all retries are exhausted +Use [**`set_node_defaults`**](#graph-wide-defaults-with-set_node_defaults) to configure these mechanisms once for all nodes instead of repeating them on every `add_node` call. + These compose in a fixed order: when a node attempt raises any exception (including @[`NodeTimeoutError`] from a timeout), the retry policy decides whether to retry. Only after retries are exhausted does the error handler run. For stopping a run cleanly at a superstep boundary and resuming later, see [Graceful shutdown](/oss/langgraph/durable-execution#graceful-shutdown). @@ -424,6 +426,113 @@ Failure provenance is checkpointed. If the graph is interrupted or the process c If a node wraps a subgraph and the subgraph raises an unhandled exception, that exception surfaces to the parent node. If the parent node has an `error_handler`, the handler fires with the subgraph's exception in `error.error`. +## Graph-wide defaults with `set_node_defaults` + + +Requires `langgraph>=1.2`. + + +Instead of repeating the same `retry_policy=`, `error_handler=`, `timeout=`, or `cache_policy=` on every `add_node` call, use `set_node_defaults()` to configure graph-wide defaults in one place: + +```python +from langgraph.errors import NodeError +from langgraph.types import RetryPolicy, TimeoutPolicy +from langgraph.graph import StateGraph, START +from typing_extensions import TypedDict + +class State(TypedDict): + status: str + +def default_error_handler(state: State, error: NodeError) -> State: + return {"status": f"handled: {error.error}"} + +graph = ( + StateGraph(State) + .set_node_defaults( + retry_policy=RetryPolicy(max_attempts=3), + error_handler=default_error_handler, + timeout=TimeoutPolicy(run_timeout=30), + ) + .add_node("step_a", step_a) + .add_node("step_b", step_b) + .add_edge(START, "step_a") + .compile() +) +``` + +Both `step_a` and `step_b` now share the same retry policy, error handler, and timeout without any duplication. + +### Precedence + +Per-node values passed directly to `add_node()` always override the defaults set by `set_node_defaults()`. Defaults are resolved at `compile()` time, so you can call `set_node_defaults()` before or after `add_node()` in any order: + +```python +graph = ( + StateGraph(State) + .set_node_defaults(error_handler=default_error_handler) + .add_node("step_a", step_a) # uses default_error_handler + .add_node("step_b", step_b, error_handler=custom_error_handler) # uses custom_error_handler + .add_edge(START, "step_a") + .compile() +) +``` + +### Default error handler + +The `error_handler` default is particularly valuable when you want a single catch-all recovery function for any node that fails without its own handler. The handler accepts the same `(state, error: NodeError)` signature described in [Error handling](#error-handling): + +```python +from langgraph.errors import NodeError +from langgraph.graph import StateGraph, START +from langgraph.types import RetryPolicy +from typing_extensions import TypedDict + +class State(TypedDict): + status: str + +def always_failing(state: State) -> State: + raise ValueError("something went wrong") + +def default_handler(state: State, error: NodeError) -> State: + return {"status": f"recovered from {error.node}: {error.error}"} + +graph = ( + StateGraph(State) + .set_node_defaults( + retry_policy=RetryPolicy(max_attempts=2), + error_handler=default_handler, + ) + .add_node("always_failing", always_failing) + .add_edge(START, "always_failing") + .compile() +) +``` + +The node is retried twice, then `default_handler` runs. The default handler also accepts `RunnableConfig` as an optional third argument if you need access to config values such as `thread_id`: + +```python +from langchain_core.runnables import RunnableConfig + +def default_handler(state: State, error: NodeError, config: RunnableConfig) -> State: + thread_id = config["configurable"].get("thread_id") + return {"status": f"handled on thread {thread_id}"} +``` + +### Applicability matrix + +Not all defaults apply to all node types. Error-handler nodes (those registered via `add_node(error_handler=...)`) are excluded from certain defaults to prevent unsafe behavior: + +| `set_node_defaults` parameter | Applies to regular nodes | Applies to error-handler nodes | Reason | +| ----------------------------- | ------------------------ | ------------------------------ | ------ | +| `retry_policy` | ✅ | ✅ | Handlers should be retried on transient failures | +| `timeout` | ✅ | ✅ | Stuck handlers should be cancelled like stuck regular nodes | +| `error_handler` | ✅ | ❌ | Handlers must never catch themselves | +| `cache_policy` | ✅ | ❌ | Caching handler results is unsafe | + +### Scope + +Defaults set on a parent graph are **not** inherited by subgraphs. Each graph maintains its own defaults. + ## Functional API The same `timeout=` and `retry_policy=` parameters are available on `@task` and `@entrypoint` in the functional API: @@ -454,5 +563,6 @@ The behavior is identical to `add_node`: `NodeTimeoutError` is raised on timeout - **Timeouts are async-only**: sync nodes with a `timeout` are rejected at compile time. - **One handler per node**: each node can have at most one `error_handler`. - **Handler failures bubble up**: if the error handler itself raises, that exception propagates as if the node had no handler. +- **`set_node_defaults` is not inherited by subgraphs**: each graph manages its own defaults independently. :::