Low-level orchestration framework for building stateful, multi-actor applications with LLMs
Core type definitions and error classes for LangGraph. Includes configuration types, runtime policies, and specialized error classes for graph execution failures.
Extended configuration type for LangGraph runnables with graph-specific options.
interface LangGraphRunnableConfig<
Runtime = unknown
> extends RunnableConfig {
configurable?: {
thread_id?: string;
checkpoint_id?: string;
checkpoint_ns?: string;
checkpoint_map?: Record<string, unknown>;
[key: string]: unknown;
};
store?: BaseStore;
writer?: <T>(chunk: T) => void;
runtime?: Runtime;
}Base configuration from LangChain Core.
interface RunnableConfig<ContextType = Record<string, any>> {
tags?: string[];
metadata?: Record<string, unknown>;
callbacks?: Callbacks;
runName?: string;
maxConcurrency?: number;
recursionLimit?: number;
configurable?: Record<string, unknown>;
runId?: string;
signal?: AbortSignal;
}Configuration for automatic retry of failed operations.
interface RetryPolicy {
maxAttempts?: number;
initialInterval?: number;
backoffFactor?: number;
maxInterval?: number;
jitter?: boolean;
retryOn?: (error: Error) => boolean;
}maxAttempts - Maximum number of retry attempts (default: 3)initialInterval - Initial retry delay in milliseconds (default: 500)backoffFactor - Multiplier for delay between retries (default: 2)maxInterval - Maximum retry delay in milliseconds (default: 60000)jitter - Add random jitter to retry delays (default: true)retryOn - Predicate to determine if error should trigger retry// Basic retry
const retryPolicy: RetryPolicy = {
maxAttempts: 3,
initialInterval: 1000,
backoffFactor: 2
};
// With custom retry condition
const retryPolicy: RetryPolicy = {
maxAttempts: 5,
initialInterval: 500,
retryOn: (error) => {
return error.message.includes("timeout") ||
error.message.includes("connection");
}
};
// Exponential backoff with jitter
const retryPolicy: RetryPolicy = {
maxAttempts: 4,
initialInterval: 1000,
backoffFactor: 2,
maxInterval: 10000,
jitter: true
};
// Delays: ~1s, ~2s, ~4s, ~8s (with jitter)
// Use with task
import { task } from "@langchain/langgraph";
const unreliableTask = task({
name: "fetch",
retry: retryPolicy
}, async (url: string) => {
const response = await fetch(url);
return response.json();
});Configuration for caching task results.
interface CachePolicy {
keyFunc?: (input: unknown) => string;
ttl?: number;
}keyFunc - Function to generate cache key from input (default: JSON.stringify)ttl - Time-to-live in milliseconds (optional)// Basic caching
const cachePolicy: CachePolicy = {
keyFunc: (input) => JSON.stringify(input)
};
// With TTL
const cachePolicy: CachePolicy = {
keyFunc: (input: any) => `${input.userId}-${input.query}`,
ttl: 60000 // 1 minute
};
// Use with task
import { task } from "@langchain/langgraph";
const cachedTask = task({
name: "expensive",
cachePolicy: {
keyFunc: (params: any) => `compute-${params.id}`,
ttl: 300000 // 5 minutes
}
}, async (params: any) => {
return expensiveComputation(params);
});
// Use with node
graph.addNode("compute", computeNode, {
cachePolicy: {
keyFunc: (state) => state.userId
}
});Base error class for all LangGraph errors.
class BaseLangGraphError extends Error {
lc_error_code?: string;
constructor(message?: string, fields?: {
lc_error_code?: string;
});
}Thrown when graph exceeds maximum recursion limit.
class GraphRecursionError extends BaseLangGraphError {
constructor(message?: string, fields?: {
lc_error_code?: "GRAPH_RECURSION_LIMIT";
});
static readonly unminifiable_name: "GraphRecursionError";
}try {
await graph.invoke(input, {
recursionLimit: 10
});
} catch (error) {
if (error instanceof GraphRecursionError) {
console.error("Graph exceeded recursion limit");
}
}Thrown for invalid graph values or configurations.
class GraphValueError extends BaseLangGraphError {
constructor(message?: string, fields?: BaseLangGraphErrorFields);
static readonly unminifiable_name: "GraphValueError";
}Thrown when graph execution is interrupted.
class GraphInterrupt extends BaseLangGraphError {
interrupts: Interrupt[];
constructor(
interrupts?: Interrupt[],
fields?: BaseLangGraphErrorFields
);
static readonly unminifiable_name: "GraphInterrupt";
}
function isGraphInterrupt(e?: unknown): e is GraphInterrupt;Raised by a node to interrupt execution for human-in-the-loop.
class NodeInterrupt extends GraphInterrupt {
constructor(
message: any,
fields?: BaseLangGraphErrorFields
);
static readonly unminifiable_name: "NodeInterrupt";
}import { NodeInterrupt, isGraphInterrupt } from "@langchain/langgraph";
const reviewNode = (state: any) => {
if (state.needsReview) {
throw new NodeInterrupt({
question: "Approve this action?",
data: state
});
}
return { approved: true };
};
try {
await graph.invoke(input);
} catch (error) {
if (isGraphInterrupt(error)) {
console.log("Execution interrupted");
console.log(error.interrupts);
}
}Thrown when required input is missing.
class EmptyInputError extends BaseLangGraphError {
constructor(message?: string, fields?: BaseLangGraphErrorFields);
static readonly unminifiable_name: "EmptyInputError";
}Thrown when attempting to read from an empty channel.
class EmptyChannelError extends BaseLangGraphError {
constructor(message?: string, fields?: BaseLangGraphErrorFields);
static readonly unminifiable_name: "EmptyChannelError";
}Thrown when a state update is invalid.
class InvalidUpdateError extends BaseLangGraphError {
constructor(message?: string, fields?: {
lc_error_code?: "INVALID_CONCURRENT_GRAPH_UPDATE" | "INVALID_GRAPH_NODE_RETURN_VALUE";
});
static readonly unminifiable_name: "InvalidUpdateError";
}// Multiple nodes writing to LastValue channel
// Results in InvalidUpdateError with code INVALID_CONCURRENT_GRAPH_UPDATE
// Invalid node return value
// Results in InvalidUpdateError with code INVALID_GRAPH_NODE_RETURN_VALUEThrown when a node is defined but unreachable from the graph's entry point.
class UnreachableNodeError extends BaseLangGraphError {
constructor(message?: string, fields?: {
lc_error_code?: "UNREACHABLE_NODE";
});
static readonly unminifiable_name: "UnreachableNodeError";
}try {
const graph = new StateGraph(State)
.addNode("node1", node1Fn)
.addNode("node2", node2Fn) // Never connected
.addEdge("__start__", "node1")
.addEdge("node1", "__end__")
.compile(); // Will throw UnreachableNodeError
} catch (error) {
if (error instanceof UnreachableNodeError) {
console.error("Node not reachable:", error.message);
}
}Thrown when an error occurs in a remote graph.
class RemoteException extends BaseLangGraphError {
constructor(message?: string, fields?: BaseLangGraphErrorFields);
static readonly unminifiable_name: "RemoteException";
}import { RemoteGraph, RemoteException } from "@langchain/langgraph";
const remoteGraph = new RemoteGraph({
graphId: "my-graph",
url: "https://api.langgraph.cloud",
apiKey: process.env.API_KEY
});
try {
await remoteGraph.invoke(input);
} catch (error) {
if (error instanceof RemoteException) {
console.error("Remote graph error:", error.message);
}
}Deprecated error for multiple subgraphs (no longer thrown).
class MultipleSubgraphsError extends BaseLangGraphError {
constructor(message?: string, fields?: {
lc_error_code?: "MULTIPLE_SUBGRAPHS";
});
static readonly unminifiable_name: "MultipleSubgraphError";
}Base class for errors that bubble up through the graph execution stack.
class GraphBubbleUp extends BaseLangGraphError {
readonly is_bubble_up: true;
}GraphBubbleUp is a base class used internally for errors that should propagate up the graph hierarchy. Both GraphInterrupt and ParentCommand extend this class.
import { isGraphBubbleUp } from "@langchain/langgraph";
try {
await graph.invoke(input);
} catch (error) {
if (isGraphBubbleUp(error)) {
// This error should bubble up to parent graph
console.log("Bubble up error:", error);
}
}Exception used to send a command to a parent graph from a subgraph.
class ParentCommand extends GraphBubbleUp {
command: Command;
constructor(command: Command);
static readonly unminifiable_name: "ParentCommand";
}ParentCommand allows a subgraph to send commands to its parent graph during execution.
import { ParentCommand, Command, isParentCommand } from "@langchain/langgraph";
const subgraphNode = (state: any) => {
if (state.needsParentAction) {
// Send command to parent graph
throw new ParentCommand(
new Command({
goto: "parent_node",
update: { status: "requires_parent_action" }
})
);
}
return state;
};
// In parent graph
try {
await subgraph.invoke(input);
} catch (error) {
if (isParentCommand(error)) {
const command = error.command;
console.log("Received command from subgraph:", command);
// Handle the command
}
}function isGraphBubbleUp(e?: unknown): e is GraphBubbleUp;
function isGraphInterrupt(e?: unknown): e is GraphInterrupt;
function isParentCommand(e?: unknown): e is ParentCommand;import {
isGraphBubbleUp,
isGraphInterrupt,
isParentCommand
} from "@langchain/langgraph";
try {
await graph.invoke(input);
} catch (error) {
if (isGraphInterrupt(error)) {
console.log("Graph interrupted:", error.interrupts);
} else if (isParentCommand(error)) {
console.log("Parent command:", error.command);
} else if (isGraphBubbleUp(error)) {
console.log("Bubble up error");
}
}import {
GraphRecursionError,
GraphInterrupt,
InvalidUpdateError,
UnreachableNodeError,
RemoteException
} from "@langchain/langgraph";
try {
const result = await graph.invoke(input, config);
} catch (error) {
if (error instanceof GraphRecursionError) {
console.error("Recursion limit exceeded");
// Increase recursion limit or check for infinite loops
} else if (error instanceof GraphInterrupt) {
console.log("Execution interrupted");
console.log("Interrupts:", error.interrupts);
// Handle human-in-the-loop
} else if (error instanceof InvalidUpdateError) {
console.error("Invalid state update:", error.message);
// Check channel types and node return values
} else if (error instanceof UnreachableNodeError) {
console.error("Graph has unreachable nodes:", error.message);
// Fix graph topology
} else if (error instanceof RemoteException) {
console.error("Remote graph error:", error.message);
// Handle remote errors, possibly retry
} else {
console.error("Unexpected error:", error);
}
}const retryPolicy: RetryPolicy = {
maxAttempts: 3,
initialInterval: 1000,
backoffFactor: 2,
retryOn: (error) => {
// Retry on network errors but not on validation errors
return !(error instanceof InvalidUpdateError) &&
!(error instanceof GraphValueError);
}
};import { isInterrupted, INTERRUPT } from "@langchain/langgraph";
const result = await graph.invoke(input, config);
if (isInterrupted(result)) {
// Handle interrupt
const interrupts = result[INTERRUPT];
for (const interrupt of interrupts) {
console.log("Interrupt ID:", interrupt.id);
console.log("Interrupt value:", interrupt.value);
}
// Resume with user input
const resumeResult = await graph.invoke(
new Command({ resume: userInput }),
config
);
}import type { StateType, UpdateType, NodeType } from "@langchain/langgraph";
const State = Annotation.Root({
count: Annotation<number>,
messages: Annotation<string[]>
});
type S = StateType<typeof State.spec>; // { count: number, messages: string[] }
type U = UpdateType<typeof State.spec>; // { count?: number, messages?: string[] }
type N = NodeType<typeof State.spec>; // RunnableLike<S, U | Partial<S>>import type { Runtime } from "@langchain/langgraph";
// Access runtime context in nodes
const node = async (state: State, config: LangGraphRunnableConfig<Runtime>) => {
const runtime = config.runtime;
// Access runtime properties
return state;
};type All = "*"; // Represents all nodes in interrupt configuration
type InferWriterType<T>; // Infer writer type from configuration
type InferInterruptInputType<T>; // Infer interrupt input type
interface DrawableGraph {
nodes: DrawableNode[];
edges: DrawableEdge[];
}
interface StreamEvent {
// LangChain stream event structure
event: string;
data: unknown;
name?: string;
tags?: string[];
metadata?: Record<string, unknown>;
}import {
StateGraph,
Annotation,
GraphRecursionError,
GraphInterrupt,
InvalidUpdateError,
isInterrupted,
INTERRUPT
} from "@langchain/langgraph";
import { MemorySaver } from "@langchain/langgraph-checkpoint";
const State = Annotation.Root({
messages: Annotation<string[]>({
reducer: (a, b) => a.concat(b),
default: () => []
}),
retryCount: Annotation<number>
});
const graph = new StateGraph(State)
.addNode("process", processNode, {
retryPolicy: {
maxAttempts: 3,
initialInterval: 1000,
retryOn: (error) => !error.message.includes("fatal")
}
})
.compile({ checkpointer: new MemorySaver() });
async function robustInvoke(input: any, config: any) {
try {
const result = await graph.invoke(input, {
...config,
recursionLimit: 25
});
if (isInterrupted(result)) {
console.log("Waiting for user input");
return {
status: "interrupted",
interrupts: result[INTERRUPT]
};
}
return { status: "success", data: result };
} catch (error) {
if (error instanceof GraphRecursionError) {
console.error("Recursion limit exceeded");
return { status: "error", code: "RECURSION_LIMIT" };
}
if (error instanceof InvalidUpdateError) {
console.error("Invalid update:", error.message);
if (error.lc_error_code === "INVALID_CONCURRENT_GRAPH_UPDATE") {
return { status: "error", code: "CONCURRENT_UPDATE" };
}
}
if (error instanceof GraphInterrupt) {
return {
status: "interrupted",
interrupts: error.interrupts
};
}
// Log unexpected errors
console.error("Unexpected error:", error);
return { status: "error", code: "UNKNOWN", message: error.message };
}
}
// Use it
const result = await robustInvoke(input, {
configurable: { thread_id: "thread-1" }
});