Accessing current step counter in nodes
The current step counter is accessible in config["metadata"]["langgraph_step"] (Python) or config.metadata.langgraph_step (JavaScript) within any node. LangGraph increments this counter as the graph executes. This allows for proactive recursion handling before hitting the recursion limit and enables implementing graceful degradation strategies within graph logic.
RemainingSteps managed value for proactive recursion handling
LangGraph provides a RemainingSteps managed value that automatically tracks how many steps remain before hitting the recursion limit. Add remaining_steps: RemainingSteps to a TypedDict State definition. RemainingSteps is automatically populated by LangGraph and allows checking remaining steps within nodes and conditional edges to implement graceful degradation. This enables returning partial results or routing to fallback nodes when approaching the limit without raising an exception.
Available metadata in config within nodes
Along with langgraph_step, the following metadata is available in config["metadata"] (Python) or config.metadata (JavaScript): langgraph_node (current node name), langgraph_triggers (what triggered the node), langgraph_path (path taken through graph), and langgraph_checkpoint_ns (checkpoint namespace).
Runtime context Python example
Example showing how to use runtime context in Python:
```python
@dataclass
class ContextSchema:
llm_provider: str = "openai"
graph = StateGraph(State, context_schema=ContextSchema)
graph.invoke(inputs, context={"llm_provider": "anthropic"})
from langgraph.runtime import Runtime
def node_a(state: State, runtime: Runtime[ContextSchema]):
llm = get_llm(runtime.context.llm_provider)
```
Runtime context JavaScript example
Example showing how to use runtime context in JavaScript:
```typescript
import { StateGraph, StateSchema } from "@langchain/langgraph";
import * as z from "zod";
const State = new StateSchema({
input: z.string(),
output: z.string(),
});
const ContextSchema = z.object({
llm: z.union([z.literal("openai"), z.literal("anthropic")]),
});
const graph = new StateGraph(State, ContextSchema);
const config = { context: { llm: "anthropic" } };
await graph.invoke(inputs, config);
const nodeA: GraphNode<typeof State> = (state, config) => {
const llm = getLLM(config.context?.llm);
return {};
};
```
Accessing current step counter example
Example showing how to access the current step counter in a Python node:
```python
from langchain_core.runnables import RunnableConfig
from langgraph.graph import StateGraph
def my_node(state: dict, config: RunnableConfig) -> dict:
current_step = config["metadata"]["langgraph_step"]
print(f"Currently on step: {current_step}")
return state
```
Example in JavaScript:
```typescript
import { RunnableConfig } from "@langchain/core/runnables";
import { StateGraph } from "@langchain/langgraph";
const myNode: GraphNode<typeof State> = async (state, config) => {
const currentStep = config.metadata?.langgraph_step;
console.log(`Currently on step: ${currentStep}`);
return state;
}
```
Recursion limit configuration at runtime
The recursion_limit (Python) or recursionLimit (JavaScript) is configured at runtime via the config parameter passed to invoke or stream methods. In Python: graph.invoke(inputs, config={"recursion_limit": 5}, context={"llm": "anthropic"}). In JavaScript: await graph.invoke(inputs, { recursionLimit: 5, context: { llm: "anthropic" } }). The recursion limit key should not be nested inside the configurable key.
Proactive vs reactive recursion handling comparison table
Comparison table for Python approaches to handling recursion limits:
| Approach | Detection | Handling | Control Flow |
|----------|-----------|----------|---------------|
| Proactive (using RemainingSteps) | Before limit reached | Inside graph via conditional routing | Graph continues to completion node |
| Reactive (catching GraphRecursionError) | After limit exceeded | Outside graph in try/catch | Graph execution terminated |
Proactive advantages: Graceful degradation within the graph, Can save intermediate state in checkpoints, Better user experience with partial results, Graph completes normally (no exception). Reactive advantages: Simpler implementation, No need to modify graph logic, Centralized error handling.
Proactive recursion handling complete Python example
Complete Python example of proactive approach using RemainingSteps:
```python
from typing import Annotated, Literal, TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.managed import RemainingSteps
from langgraph.errors import GraphRecursionError
class State(TypedDict):
messages: Annotated[list, lambda x, y: x + y]
remaining_steps: RemainingSteps
def agent_with_monitoring(state: State) -> dict:
remaining = state["remaining_steps"]
if remaining <= 2:
return {"messages": ["Approaching limit, returning partial result"]}
return {"messages": [f"Processing... ({remaining} steps remaining)"]}
def route_decision(state: State) -> Literal["agent", END]:
if state["remaining_steps"] <= 2:
return END
return "agent"
builder = StateGraph(State)
builder.add_node("agent", agent_with_monitoring)
builder.add_edge(START, "agent")
builder.add_conditional_edges("agent", route_decision)
graph = builder.compile()
result = graph.invoke({"messages": []}, {"recursion_limit": 10})
```
Reactive recursion error handling Python example
Python example of reactive approach catching GraphRecursionError:
```python
from langgraph.errors import GraphRecursionError
try:
result = graph.invoke({"messages": []}, {"recursion_limit": 10})
except GraphRecursionError as e:
result = {"messages": ["Fallback: recursion limit exceeded"]}
```
Reactive recursion error handling JavaScript example
JavaScript example of reactive approach catching GraphRecursionError:
```typescript
import { GraphRecursionError } from "@langchain/langgraph";
try {
const result = await app.invoke(
{ messages: [] },
{ recursionLimit: 10 }
);
} catch (error) {
if (error instanceof GraphRecursionError) {
console.log("Recursion limit reached, handling gracefully");
}
}
```
Inspecting metadata in nodes Python example
Python example showing how to inspect all available metadata in a node:
```python
def inspect_metadata(state: dict, config: RunnableConfig) -> dict:
metadata = config["metadata"]
print(f"Step: {metadata['langgraph_step']}")
print(f"Node: {metadata['langgraph_node']}")
print(f"Triggers: {metadata['langgraph_triggers']}")
print(f"Path: {metadata['langgraph_path']}")
print(f"Checkpoint NS: {metadata['langgraph_checkpoint_ns']}")
return state
```
Inspecting metadata in nodes JavaScript example
JavaScript example showing how to inspect all available metadata in a node:
```typescript
const inspectMetadata: GraphNode<typeof State> = async (state, config) => {
const metadata = config.metadata;
console.log(`Step: ${metadata?.langgraph_step}`);
console.log(`Node: ${metadata?.langgraph_node}`);
console.log(`Triggers: ${metadata?.langgraph_triggers}`);
console.log(`Path: ${metadata?.langgraph_path}`);
console.log(`Checkpoint NS: ${metadata?.langgraph_checkpoint_ns}`);
return state;
}
```