UNPKG

@langchain/langgraph

Version:
1 lines 116 kB
{"version":3,"file":"index.cjs","names":["PregelNode","PASSTHROUGH","Runnable","ChannelWrite","TASKS","Topic","findSubgraphPregel","channelsFromCheckpoint","NULL_TASK_ID","INPUT","_prepareNextTasks","gatherIterator","CONFIG_KEY_CHECKPOINTER","SCHEDULED","readChannels","tasksWithWrites","patchCheckpointMap","GraphValueError","recastCheckpointNamespace","patchConfigurable","InvalidUpdateError","createCheckpoint","getNewChannelVersions","mapInput","INTERRUPT","RunnableSequence","CONFIG_KEY_SEND","CONFIG_KEY_READ","_localRead","PUSH","getConfig","combineAbortSignals","IterableReadableStreamWithAbortSignal","toEventStream","#streamEventsV3","STREAM_EVENTS_V3_MODES","createGraphRunStream","ensureLangGraphConfig","IterableReadableWritableStream","CONFIG_KEY_CHECKPOINT_NS","StreamProtocolMessagesHandler","StreamMessagesHandler","StreamToolsHandler","interrupt","RunControl","CallbackManager","_coerceToDict","getOnlyChannels","PregelLoop","PregelRunner","CONFIG_KEY_NODE_FINISHED","CONFIG_KEY_STREAM","isCheckpointEnvelope","isInterrupted","GraphDrained","GraphRecursionError"],"sources":["../../src/pregel/index.ts"],"sourcesContent":["/* eslint-disable no-param-reassign */\nimport { CallbackManager } from \"@langchain/core/callbacks/manager\";\nimport {\n _coerceToRunnable,\n mergeConfigs,\n patchConfig,\n Runnable,\n RunnableConfig,\n RunnableFunc,\n RunnableLike,\n RunnableSequence,\n} from \"@langchain/core/runnables\";\nimport type { StreamEvent } from \"@langchain/core/tracers/log_stream\";\nimport { IterableReadableStream } from \"@langchain/core/utils/stream\";\nimport {\n All,\n BaseCache,\n BaseCheckpointSaver,\n BaseStore,\n CheckpointListOptions,\n CheckpointMetadata,\n CheckpointTuple,\n compareChannelVersions,\n copyCheckpoint,\n emptyCheckpoint,\n PendingWrite,\n SCHEDULED,\n SendProtocol,\n uuid5,\n} from \"@langchain/langgraph-checkpoint\";\nimport {\n BaseChannel,\n createCheckpoint,\n channelsFromCheckpoint,\n getOnlyChannels,\n} from \"../channels/base.js\";\nimport {\n CHECKPOINT_NAMESPACE_END,\n CHECKPOINT_NAMESPACE_SEPARATOR,\n Command,\n CONFIG_KEY_CHECKPOINTER,\n CONFIG_KEY_NODE_FINISHED,\n CONFIG_KEY_READ,\n CONFIG_KEY_SEND,\n CONFIG_KEY_STREAM,\n CONFIG_KEY_TASK_ID,\n COPY,\n END,\n ERROR,\n INPUT,\n INTERRUPT,\n Interrupt,\n isInterrupted,\n NULL_TASK_ID,\n PUSH,\n CONFIG_KEY_DURABILITY,\n CONFIG_KEY_CHECKPOINT_NS,\n type CommandInstance,\n TASKS,\n} from \"../constants.js\";\nimport {\n GraphDrained,\n GraphRecursionError,\n GraphValueError,\n InvalidUpdateError,\n} from \"../errors.js\";\nimport { RunControl } from \"./runtime.js\";\nimport { gatherIterator, patchConfigurable } from \"../utils.js\";\nimport {\n _applyWrites,\n _localRead,\n _prepareNextTasks,\n StrRecord,\n WritesProtocol,\n} from \"./algo.js\";\nimport {\n printStepCheckpoint,\n printStepTasks,\n printStepWrites,\n tasksWithWrites,\n} from \"./debug.js\";\nimport { mapInput, readChannels } from \"./io.js\";\nimport { PregelLoop } from \"./loop.js\";\nimport { StreamMessagesHandler } from \"./messages.js\";\nimport { StreamProtocolMessagesHandler } from \"./messages-v2.js\";\nimport { PregelNode } from \"./read.js\";\nimport { LangGraphRunnableConfig, type ServerInfo } from \"./runnable_types.js\";\nimport { PregelRunner } from \"./runner.js\";\nimport {\n IterableReadableStreamWithAbortSignal,\n IterableReadableWritableStream,\n StreamToolsHandler,\n toEventStream,\n} from \"./stream.js\";\nimport {\n createGraphRunStream,\n GraphRunStream,\n isCheckpointEnvelope,\n STREAM_EVENTS_V3_MODES,\n} from \"../stream/index.js\";\nimport type {\n InferExtensions,\n ProtocolEvent,\n StreamTransformer,\n} from \"../stream/index.js\";\nimport type {\n Durability,\n GetStateOptions,\n MultipleChannelSubscriptionOptions,\n PregelExecutableTask,\n PregelInputType,\n PregelInterface,\n PregelOptions,\n PregelOutputType,\n PregelParams,\n SingleChannelSubscriptionOptions,\n StateSnapshot,\n StreamMode,\n StreamOutputMap,\n} from \"./types.js\";\nimport {\n ensureLangGraphConfig,\n getConfig,\n recastCheckpointNamespace,\n} from \"./utils/config.js\";\nimport {\n _coerceToDict,\n combineAbortSignals,\n getNewChannelVersions,\n patchCheckpointMap,\n RetryPolicy,\n} from \"./utils/index.js\";\nimport { findSubgraphPregel } from \"./utils/subgraph.js\";\nimport { validateGraph, validateKeys } from \"./validate.js\";\nimport { ChannelWrite, ChannelWriteEntry, PASSTHROUGH } from \"./write.js\";\nimport { Topic } from \"../channels/topic.js\";\nimport { interrupt } from \"../interrupt.js\";\n\ntype WriteValue = Runnable | RunnableFunc<unknown, unknown> | unknown;\ntype StreamEventsOptions = Parameters<Runnable[\"streamEvents\"]>[2];\ntype StreamEventsV3Options<\n Nodes extends StrRecord<string, PregelNode>,\n Channels extends StrRecord<string, BaseChannel>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ContextType extends Record<string, any>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n TTransformers extends ReadonlyArray<() => StreamTransformer<any>>,\n TEncoding extends \"text/event-stream\" | undefined = undefined,\n> = Partial<\n Omit<PregelOptions<Nodes, Channels, ContextType>, \"encoding\" | \"subgraphs\">\n> & {\n version: \"v3\";\n /** User-supplied transformer factories for custom projections. */\n transformers?: TTransformers;\n} & (TEncoding extends \"text/event-stream\"\n ? { encoding: \"text/event-stream\" }\n : { encoding?: undefined });\n\nfunction protocolEventsToEventStream(run: AsyncIterable<ProtocolEvent>) {\n const encoder = new TextEncoder();\n\n return new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const event of run) {\n const namespace = event.params.namespace;\n const eventName = namespace.length\n ? `${event.method}|${namespace.join(\"|\")}`\n : event.method;\n controller.enqueue(\n encoder.encode(\n `event: ${eventName}\\ndata: ${JSON.stringify(event.params.data ?? {})}\\n\\n`\n )\n );\n }\n } catch (error) {\n controller.enqueue(\n encoder.encode(\n `event: error\\ndata: ${JSON.stringify({ message: String(error) })}\\n\\n`\n )\n );\n } finally {\n controller.close();\n }\n },\n });\n}\n\n/**\n * Utility class for working with channels in the Pregel system.\n * Provides static methods for subscribing to channels and writing to them.\n *\n * Channels are the communication pathways between nodes in a Pregel graph.\n * They enable message passing and state updates between different parts of the graph.\n */\nexport class Channel {\n /**\n * Creates a PregelNode that subscribes to a single channel.\n * This is used to define how nodes receive input from channels.\n *\n * @example\n * ```typescript\n * // Subscribe to a single channel\n * const node = Channel.subscribeTo(\"messages\");\n *\n * // Subscribe to multiple channels\n * const node = Channel.subscribeTo([\"messages\", \"state\"]);\n *\n * // Subscribe with a custom key\n * const node = Channel.subscribeTo(\"messages\", { key: \"chat\" });\n * ```\n *\n * @param channel Single channel name to subscribe to\n * @param options Subscription options\n * @returns A PregelNode configured to receive from the specified channels\n * @throws {Error} If a key is specified when subscribing to multiple channels\n */\n static subscribeTo(\n channel: string,\n options?: SingleChannelSubscriptionOptions\n ): PregelNode;\n\n /**\n * Creates a PregelNode that subscribes to multiple channels.\n * This is used to define how nodes receive input from channels.\n *\n * @example\n * ```typescript\n * // Subscribe to a single channel\n * const node = Channel.subscribeTo(\"messages\");\n *\n * // Subscribe to multiple channels\n * const node = Channel.subscribeTo([\"messages\", \"state\"]);\n *\n * // Subscribe with a custom key\n * const node = Channel.subscribeTo(\"messages\", { key: \"chat\" });\n * ```\n *\n * @param channels Single channel name to subscribe to\n * @param options Subscription options\n * @returns A PregelNode configured to receive from the specified channels\n * @throws {Error} If a key is specified when subscribing to multiple channels\n */\n static subscribeTo(\n channels: string[],\n options?: MultipleChannelSubscriptionOptions\n ): PregelNode;\n\n static subscribeTo(\n channels: string | string[],\n options?:\n | SingleChannelSubscriptionOptions\n | MultipleChannelSubscriptionOptions\n ): PregelNode {\n const { key, tags } = {\n key: undefined,\n tags: undefined,\n ...(options ?? {}),\n };\n if (Array.isArray(channels) && key !== undefined) {\n throw new Error(\n \"Can't specify a key when subscribing to multiple channels\"\n );\n }\n\n let channelMappingOrArray: string[] | Record<string, string>;\n\n if (typeof channels === \"string\") {\n if (key) {\n channelMappingOrArray = { [key]: channels };\n } else {\n channelMappingOrArray = [channels];\n }\n } else {\n channelMappingOrArray = Object.fromEntries(\n channels.map((chan) => [chan, chan])\n );\n }\n\n const triggers: string[] = Array.isArray(channels) ? channels : [channels];\n\n return new PregelNode({\n channels: channelMappingOrArray,\n triggers,\n tags,\n });\n }\n\n /**\n * Creates a ChannelWrite that specifies how to write values to channels.\n * This is used to define how nodes send output to channels.\n *\n * @example\n * ```typescript\n * // Write to multiple channels\n * const write = Channel.writeTo([\"output\", \"state\"]);\n *\n * // Write with specific values\n * const write = Channel.writeTo([\"output\"], {\n * state: \"completed\",\n * result: calculateResult()\n * });\n *\n * // Write with a transformation function\n * const write = Channel.writeTo([\"output\"], {\n * result: (x) => processResult(x)\n * });\n * ```\n *\n * @param channels - Array of channel names to write to\n * @param writes - Optional map of channel names to values or transformations\n * @returns A ChannelWrite object that can be used to write to the specified channels\n */\n static writeTo(\n channels: string[],\n writes?: Record<string, WriteValue>\n ): ChannelWrite {\n const channelWriteEntries: Array<ChannelWriteEntry> = [];\n\n for (const channel of channels) {\n channelWriteEntries.push({\n channel,\n value: PASSTHROUGH,\n skipNone: false,\n });\n }\n\n for (const [key, value] of Object.entries(writes ?? {})) {\n if (Runnable.isRunnable(value) || typeof value === \"function\") {\n channelWriteEntries.push({\n channel: key,\n value: PASSTHROUGH,\n skipNone: true,\n mapper: _coerceToRunnable(value as RunnableLike),\n });\n } else {\n channelWriteEntries.push({\n channel: key,\n value,\n skipNone: false,\n });\n }\n }\n\n return new ChannelWrite(channelWriteEntries);\n }\n}\n\nexport type { PregelInputType, PregelOptions, PregelOutputType };\n\n// This is a workaround to allow Pregel to override `invoke` / `stream` and `withConfig`\n// without having to adhere to the types in the `Runnable` class (thanks to `any`).\n// Alternatively we could mark those methods with @ts-ignore / @ts-expect-error,\n// but these do not get carried over when building via `tsc`.\nclass PartialRunnable<\n RunInput,\n RunOutput,\n CallOptions extends RunnableConfig,\n> extends Runnable<RunInput, RunOutput, CallOptions> {\n lc_namespace = [\"langgraph\", \"pregel\"];\n\n override invoke(\n _input: RunInput,\n _options?: Partial<CallOptions>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<any> {\n throw new Error(\"Not implemented\");\n }\n\n // Overriden by `Pregel`\n override withConfig(_config: CallOptions): typeof this {\n return super.withConfig(_config) as typeof this;\n }\n\n // Overriden by `Pregel`\n override stream(\n input: RunInput,\n options?: Partial<CallOptions>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Promise<IterableReadableStream<any>> {\n return super.stream(input, options);\n }\n}\n\n/**\n * The Pregel class is the core runtime engine of LangGraph, implementing a message-passing graph computation model\n * inspired by [Google's Pregel system](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/).\n * It provides the foundation for building reliable, controllable agent workflows that can evolve state over time.\n *\n * Key features:\n * - Message passing between nodes in discrete \"supersteps\"\n * - Built-in persistence layer through checkpointers\n * - First-class streaming support for values, updates, and events\n * - Human-in-the-loop capabilities via interrupts\n * - Support for parallel node execution within supersteps\n *\n * The Pregel class is not intended to be instantiated directly by consumers. Instead, use the following higher-level APIs:\n * - {@link StateGraph}: The main graph class for building agent workflows\n * - Compiling a {@link StateGraph} will return a {@link CompiledGraph} instance, which extends `Pregel`\n * - Functional API: A declarative approach using tasks and entrypoints\n * - A `Pregel` instance is returned by the {@link entrypoint} function\n *\n * @example\n * ```typescript\n * // Using StateGraph API\n * const graph = new StateGraph(annotation)\n * .addNode(\"nodeA\", myNodeFunction)\n * .addEdge(\"nodeA\", \"nodeB\")\n * .compile();\n *\n * // The compiled graph is a Pregel instance\n * const result = await graph.invoke(input);\n * ```\n *\n * @example\n * ```typescript\n * // Using Functional API\n * import { task, entrypoint } from \"@langchain/langgraph\";\n * import { MemorySaver } from \"@langchain/langgraph-checkpoint\";\n *\n * // Define tasks that can be composed\n * const addOne = task(\"add\", async (x: number) => x + 1);\n *\n * // Create a workflow using the entrypoint function\n * const workflow = entrypoint({\n * name: \"workflow\",\n * checkpointer: new MemorySaver()\n * }, async (numbers: number[]) => {\n * // Tasks can be run in parallel\n * const results = await Promise.all(numbers.map(n => addOne(n)));\n * return results;\n * });\n *\n * // The workflow is a Pregel instance\n * const result = await workflow.invoke([1, 2, 3]); // Returns [2, 3, 4]\n * ```\n *\n * @typeParam Nodes - Mapping of node names to their {@link PregelNode} implementations\n * @typeParam Channels - Mapping of channel names to their {@link BaseChannel} or {@link ManagedValueSpec} implementations\n * @typeParam ContextType - Type of context that can be passed to the graph\n * @typeParam InputType - Type of input values accepted by the graph\n * @typeParam OutputType - Type of output values produced by the graph\n */\nexport class Pregel<\n Nodes extends StrRecord<string, PregelNode>,\n Channels extends StrRecord<string, BaseChannel>,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ContextType extends Record<string, any> = StrRecord<string, any>,\n InputType = PregelInputType,\n OutputType = PregelOutputType,\n StreamUpdatesType = InputType,\n StreamValuesType = OutputType,\n NodeReturnType = unknown,\n CommandType = CommandInstance,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n StreamCustom = any,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n TStreamTransformers extends ReadonlyArray<() => StreamTransformer<any>> = [],\n>\n extends PartialRunnable<\n InputType | CommandType | null,\n OutputType,\n PregelOptions<Nodes, Channels, ContextType>\n >\n implements PregelInterface<Nodes, Channels, ContextType>\n{\n /**\n * Name of the class when serialized\n * @internal\n */\n static lc_name() {\n return \"LangGraph\";\n }\n\n /** @internal Used for type inference */\n declare \"~InputType\": InputType;\n\n /** @internal Used for type inference */\n declare \"~OutputType\": OutputType;\n\n /** @internal LangChain namespace for serialization necessary because Pregel extends Runnable */\n lc_namespace = [\"langgraph\", \"pregel\"];\n\n /** @internal Flag indicating this is a Pregel instance - necessary for serialization */\n lg_is_pregel = true;\n\n /** The nodes in the graph, mapping node names to their PregelNode instances */\n nodes: Nodes;\n\n /** The channels in the graph, mapping channel names to their BaseChannel or ManagedValueSpec instances */\n channels: Channels;\n\n /**\n * The input channels for the graph. These channels receive the initial input when the graph is invoked.\n * Can be a single channel key or an array of channel keys.\n */\n inputChannels: keyof Channels | Array<keyof Channels>;\n\n /**\n * The output channels for the graph. These channels contain the final output when the graph completes.\n * Can be a single channel key or an array of channel keys.\n */\n outputChannels: keyof Channels | Array<keyof Channels>;\n\n /** Whether to automatically validate the graph structure when it is compiled. Defaults to true. */\n autoValidate: boolean = true;\n\n /**\n * The streaming modes enabled for this graph. Defaults to [\"values\"].\n * Supported modes:\n * - \"values\": Streams the full state after each step\n * - \"updates\": Streams state updates after each step\n * - \"messages\": Streams messages from within nodes\n * - \"custom\": Streams custom events from within nodes\n * - \"tools\": Streams tool-call lifecycle events (on_tool_start, on_tool_event, on_tool_end, on_tool_error) from LLM tool execution\n * - \"debug\": Streams events related to the execution of the graph - useful for tracing & debugging graph execution\n */\n streamMode: StreamMode[] = [\"values\"];\n\n /**\n * Optional channels to stream. If not specified, all channels will be streamed.\n * Can be a single channel key or an array of channel keys.\n */\n streamChannels?: keyof Channels | Array<keyof Channels>;\n\n /**\n * Optional array of node names or \"all\" to interrupt after executing these nodes.\n * Used for implementing human-in-the-loop workflows.\n */\n interruptAfter?: Array<keyof Nodes> | All;\n\n /**\n * Optional array of node names or \"all\" to interrupt before executing these nodes.\n * Used for implementing human-in-the-loop workflows.\n */\n interruptBefore?: Array<keyof Nodes> | All;\n\n /** Optional timeout in milliseconds for the execution of each superstep */\n stepTimeout?: number;\n\n /** Whether to enable debug logging. Defaults to false. */\n debug: boolean = false;\n\n /**\n * Optional checkpointer for persisting graph state.\n * When provided, saves a checkpoint of the graph state at every superstep.\n * When false or undefined, checkpointing is disabled, and the graph will not be able to save or restore state.\n */\n checkpointer?: BaseCheckpointSaver | boolean;\n\n /** Optional retry policy for handling failures in node execution */\n retryPolicy?: RetryPolicy;\n\n /** The default configuration for graph execution, can be overridden on a per-invocation basis */\n config?: LangGraphRunnableConfig;\n\n /**\n * Optional long-term memory store for the graph, allows for persistence & retrieval of data across threads\n */\n store?: BaseStore;\n\n /**\n * Optional cache for the graph, useful for caching tasks.\n */\n cache?: BaseCache;\n\n /**\n * Optional interrupt helper function.\n * @internal\n */\n private userInterrupt?: unknown;\n\n /**\n * Stream reducer factories registered at compile time. These run\n * automatically for every `streamEvents(..., { version: \"v3\" })` call,\n * before any call-site transformers.\n */\n streamTransformers: TStreamTransformers;\n\n /**\n * The trigger to node mapping for the graph run.\n * @internal\n */\n private triggerToNodes: Record<string, string[]> = {};\n\n /**\n * Constructor for Pregel - meant for internal use only.\n *\n * @internal\n */\n constructor(fields: PregelParams<Nodes, Channels, TStreamTransformers>) {\n super(fields);\n\n let { streamMode } = fields;\n if (streamMode != null && !Array.isArray(streamMode)) {\n streamMode = [streamMode];\n }\n\n this.nodes = fields.nodes;\n this.channels = fields.channels;\n\n if (\n TASKS in this.channels &&\n \"lc_graph_name\" in this.channels[TASKS] &&\n this.channels[TASKS].lc_graph_name !== \"Topic\"\n ) {\n throw new Error(\n `Channel '${TASKS}' is reserved and cannot be used in the graph.`\n );\n } else {\n (this.channels as Record<string, BaseChannel>)[TASKS] =\n new Topic<SendProtocol>({ accumulate: false });\n }\n\n this.autoValidate = fields.autoValidate ?? this.autoValidate;\n this.streamMode = streamMode ?? this.streamMode;\n this.inputChannels = fields.inputChannels;\n this.outputChannels = fields.outputChannels;\n this.streamChannels = fields.streamChannels ?? this.streamChannels;\n this.interruptAfter = fields.interruptAfter;\n this.interruptBefore = fields.interruptBefore;\n this.stepTimeout = fields.stepTimeout ?? this.stepTimeout;\n this.debug = fields.debug ?? this.debug;\n this.checkpointer = fields.checkpointer;\n this.retryPolicy = fields.retryPolicy;\n this.config = fields.config;\n this.store = fields.store;\n this.cache = fields.cache;\n this.name = fields.name;\n this.triggerToNodes = fields.triggerToNodes ?? this.triggerToNodes;\n this.userInterrupt = fields.userInterrupt;\n this.streamTransformers = (fields.streamTransformers ??\n []) as TStreamTransformers;\n\n if (this.autoValidate) {\n this.validate();\n }\n }\n\n /**\n * Creates a new instance of the Pregel graph with updated configuration.\n * This method follows the immutable pattern - instead of modifying the current instance,\n * it returns a new instance with the merged configuration.\n *\n * @example\n * ```typescript\n * // Create a new instance with debug enabled\n * const debugGraph = graph.withConfig({ debug: true });\n *\n * // Create a new instance with a specific thread ID\n * const threadGraph = graph.withConfig({\n * configurable: { thread_id: \"123\" }\n * });\n * ```\n *\n * @param config - The configuration to merge with the current configuration\n * @returns A new Pregel instance with the merged configuration\n */\n override withConfig<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const TTransformers extends ReadonlyArray<() => StreamTransformer<any>> =\n [],\n >(\n config: Omit<LangGraphRunnableConfig, \"store\" | \"writer\" | \"interrupt\"> & {\n streamTransformers: TTransformers;\n }\n ): Pregel<\n Nodes,\n Channels,\n ContextType,\n InputType,\n OutputType,\n StreamUpdatesType,\n StreamValuesType,\n NodeReturnType,\n CommandType,\n StreamCustom,\n readonly [...TStreamTransformers, ...TTransformers]\n >;\n\n override withConfig(\n config: PregelOptions<Nodes, Channels, ContextType>\n ): this;\n\n override withConfig(\n config: Omit<LangGraphRunnableConfig, \"store\" | \"writer\" | \"interrupt\"> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n streamTransformers?: ReadonlyArray<() => StreamTransformer<any>>;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): any {\n const { streamTransformers, ...restConfig } = config;\n const mergedConfig = mergeConfigs(this.config, restConfig);\n const mergedStreamTransformers = [\n ...this.streamTransformers,\n ...(streamTransformers ?? []),\n ];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return new (this.constructor as any)({\n ...this,\n config: mergedConfig,\n streamTransformers: mergedStreamTransformers,\n });\n }\n\n /**\n * Validates the graph structure to ensure it is well-formed.\n * Checks for:\n * - No orphaned nodes\n * - Valid input/output channel configurations\n * - Valid interrupt configurations\n *\n * @returns this - The Pregel instance for method chaining\n * @throws {GraphValidationError} If the graph structure is invalid\n */\n validate(): this {\n validateGraph<Nodes, Channels>({\n nodes: this.nodes,\n channels: this.channels,\n outputChannels: this.outputChannels,\n inputChannels: this.inputChannels,\n streamChannels: this.streamChannels,\n interruptAfterNodes: this.interruptAfter,\n interruptBeforeNodes: this.interruptBefore,\n });\n\n for (const [name, node] of Object.entries(this.nodes)) {\n for (const trigger of node.triggers) {\n this.triggerToNodes[trigger] ??= [];\n this.triggerToNodes[trigger].push(name);\n }\n }\n\n return this;\n }\n\n /**\n * Gets a list of all channels that should be streamed.\n * If streamChannels is specified, returns those channels.\n * Otherwise, returns all channels in the graph.\n *\n * @returns Array of channel keys to stream\n */\n get streamChannelsList(): Array<keyof Channels> {\n if (Array.isArray(this.streamChannels)) {\n return this.streamChannels;\n } else if (this.streamChannels) {\n return [this.streamChannels];\n } else {\n return Object.keys(this.channels);\n }\n }\n\n /**\n * Gets the channels to stream in their original format.\n * If streamChannels is specified, returns it as-is (either single key or array).\n * Otherwise, returns all channels in the graph as an array.\n *\n * @returns Channel keys to stream, either as a single key or array\n */\n get streamChannelsAsIs(): keyof Channels | Array<keyof Channels> {\n if (this.streamChannels) {\n return this.streamChannels;\n } else {\n return Object.keys(this.channels);\n }\n }\n\n /**\n * Gets a drawable representation of the graph structure.\n * This is an async version of getGraph() and is the preferred method to use.\n *\n * @param config - Configuration for generating the graph visualization\n * @returns A representation of the graph that can be visualized\n */\n async getGraphAsync(config: RunnableConfig) {\n return this.getGraph(config);\n }\n\n /**\n * Gets all subgraphs within this graph.\n * A subgraph is a Pregel instance that is nested within a node of this graph.\n *\n * @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release.\n * @param namespace - Optional namespace to filter subgraphs\n * @param recurse - Whether to recursively get subgraphs of subgraphs\n * @returns Generator yielding tuples of [name, subgraph]\n */\n *getSubgraphs(\n namespace?: string,\n recurse?: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): Generator<[string, Pregel<any, any>]> {\n for (const [name, node] of Object.entries(this.nodes)) {\n // filter by prefix\n if (namespace !== undefined) {\n if (!namespace.startsWith(name)) {\n continue;\n }\n }\n // find the subgraph if any\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n type SubgraphPregelType = Pregel<any, any> | undefined;\n\n const candidates = node.subgraphs?.length ? node.subgraphs : [node.bound];\n\n for (const candidate of candidates) {\n const graph = findSubgraphPregel(candidate) as SubgraphPregelType;\n\n if (graph !== undefined) {\n if (name === namespace) {\n yield [name, graph];\n return;\n }\n\n if (namespace === undefined) {\n yield [name, graph];\n }\n\n if (recurse) {\n let newNamespace = namespace;\n if (namespace !== undefined) {\n newNamespace = namespace.slice(name.length + 1);\n }\n for (const [subgraphName, subgraph] of graph.getSubgraphs(\n newNamespace,\n recurse\n )) {\n yield [\n `${name}${CHECKPOINT_NAMESPACE_SEPARATOR}${subgraphName}`,\n subgraph,\n ];\n }\n }\n }\n }\n }\n }\n\n /**\n * Gets all subgraphs within this graph asynchronously.\n * A subgraph is a Pregel instance that is nested within a node of this graph.\n *\n * @param namespace - Optional namespace to filter subgraphs\n * @param recurse - Whether to recursively get subgraphs of subgraphs\n * @returns AsyncGenerator yielding tuples of [name, subgraph]\n */\n async *getSubgraphsAsync(\n namespace?: string,\n recurse?: boolean\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ): AsyncGenerator<[string, Pregel<any, any>]> {\n yield* this.getSubgraphs(namespace, recurse);\n }\n\n /**\n * Prepares a state snapshot from saved checkpoint data.\n * This is an internal method used by getState and getStateHistory.\n *\n * @param config - Configuration for preparing the snapshot\n * @param saved - Optional saved checkpoint data\n * @param subgraphCheckpointer - Optional checkpointer for subgraphs\n * @param applyPendingWrites - Whether to apply pending writes to tasks and then to channels\n * @returns A snapshot of the graph state\n * @internal\n */\n protected async _prepareStateSnapshot({\n config,\n saved,\n subgraphCheckpointer,\n applyPendingWrites = false,\n }: {\n config: RunnableConfig;\n saved?: CheckpointTuple;\n subgraphCheckpointer?: BaseCheckpointSaver;\n applyPendingWrites?: boolean;\n }): Promise<StateSnapshot> {\n if (saved === undefined) {\n return {\n values: {},\n next: [],\n config,\n tasks: [],\n };\n }\n\n // Create all channels, reconstructing any DeltaChannel from ancestor\n // writes via the checkpointer.\n const channels = await channelsFromCheckpoint(\n this.channels as Record<string, BaseChannel>,\n saved.checkpoint,\n {\n saver:\n typeof this.checkpointer === \"object\" ? this.checkpointer : undefined,\n config: saved.config ?? config,\n }\n );\n\n // Apply null writes first (from NULL_TASK_ID)\n if (saved.pendingWrites?.length) {\n const nullWrites = saved.pendingWrites\n .filter(([taskId, _]) => taskId === NULL_TASK_ID)\n .map(\n ([_, channel, value]) => [String(channel), value] as [string, unknown]\n );\n\n if (nullWrites.length > 0) {\n _applyWrites(\n saved.checkpoint,\n channels,\n [\n {\n name: INPUT,\n writes: nullWrites as PendingWrite[],\n triggers: [],\n },\n ],\n undefined,\n this.triggerToNodes\n );\n }\n }\n\n // Prepare next tasks\n const nextTasks = Object.values(\n _prepareNextTasks(\n saved.checkpoint,\n saved.pendingWrites,\n this.nodes,\n channels,\n saved.config,\n true,\n { step: (saved.metadata?.step ?? -1) + 1, store: this.store }\n )\n );\n\n // Find subgraphs\n const subgraphs = await gatherIterator(this.getSubgraphsAsync());\n const parentNamespace = saved.config.configurable?.checkpoint_ns ?? \"\";\n const taskStates: Record<string, RunnableConfig | StateSnapshot> = {};\n\n // Prepare task states for subgraphs\n for (const task of nextTasks) {\n const matchingSubgraph = subgraphs.find(([name]) => name === task.name);\n if (!matchingSubgraph) {\n continue;\n }\n // assemble checkpoint_ns for this task\n let taskNs = `${String(task.name)}${CHECKPOINT_NAMESPACE_END}${task.id}`;\n if (parentNamespace) {\n taskNs = `${parentNamespace}${CHECKPOINT_NAMESPACE_SEPARATOR}${taskNs}`;\n }\n if (subgraphCheckpointer === undefined) {\n // set config as signal that subgraph checkpoints exist\n const config: RunnableConfig = {\n configurable: {\n thread_id: saved.config.configurable?.thread_id,\n checkpoint_ns: taskNs,\n },\n };\n taskStates[task.id] = config;\n } else {\n // get the state of the subgraph\n const subgraphConfig: RunnableConfig = {\n configurable: {\n [CONFIG_KEY_CHECKPOINTER]: subgraphCheckpointer,\n thread_id: saved.config.configurable?.thread_id,\n checkpoint_ns: taskNs,\n },\n };\n const pregel = matchingSubgraph[1];\n taskStates[task.id] = await pregel.getState(subgraphConfig, {\n subgraphs: true,\n });\n }\n }\n\n // Apply pending writes to tasks and then to channels if applyPendingWrites is true\n if (applyPendingWrites && saved.pendingWrites?.length) {\n // Map task IDs to task objects for easy lookup\n const nextTaskById = Object.fromEntries(\n nextTasks.map((task) => [task.id, task])\n );\n\n // Apply pending writes to the appropriate tasks\n for (const [taskId, channel, value] of saved.pendingWrites) {\n // Skip special channels and tasks not in nextTasks\n if ([ERROR, INTERRUPT, SCHEDULED].includes(channel)) {\n continue;\n }\n if (!(taskId in nextTaskById)) {\n continue;\n }\n // Add the write to the task\n nextTaskById[taskId].writes.push([String(channel), value]);\n }\n\n // Apply writes from tasks that have writes\n const tasksWithWrites = nextTasks.filter(\n (task) => task.writes.length > 0\n );\n if (tasksWithWrites.length > 0) {\n _applyWrites(\n saved.checkpoint,\n channels,\n tasksWithWrites as unknown as WritesProtocol[],\n undefined,\n this.triggerToNodes\n );\n }\n }\n\n // Preserve thread_id from the config in metadata\n let metadata = saved?.metadata;\n if (metadata && saved?.config?.configurable?.thread_id) {\n metadata = {\n ...metadata,\n thread_id: saved.config.configurable.thread_id as string,\n } as CheckpointMetadata;\n }\n\n // Filter next tasks - only include tasks without writes\n const nextList = nextTasks\n .filter((task) => task.writes.length === 0)\n .map((task) => task.name as string);\n\n // assemble the state snapshot\n return {\n values: readChannels(\n channels,\n this.streamChannelsAsIs as string | string[]\n ),\n next: nextList,\n tasks: tasksWithWrites(\n nextTasks,\n saved?.pendingWrites ?? [],\n taskStates,\n this.streamChannelsAsIs\n ),\n metadata,\n config: patchCheckpointMap(saved.config, saved.metadata),\n createdAt: saved.checkpoint.ts,\n parentConfig: saved.parentConfig,\n };\n }\n\n /**\n * Gets the current state of the graph.\n * Requires a checkpointer to be configured.\n *\n * @param config - Configuration for retrieving the state\n * @param options - Additional options\n * @returns A snapshot of the current graph state\n * @throws {GraphValueError} If no checkpointer is configured\n */\n async getState(\n config: RunnableConfig,\n options?: GetStateOptions\n ): Promise<StateSnapshot> {\n const checkpointer =\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;\n if (!checkpointer) {\n throw new GraphValueError(\"No checkpointer set\", {\n lc_error_code: \"MISSING_CHECKPOINTER\",\n });\n }\n\n const checkpointNamespace: string =\n config.configurable?.checkpoint_ns ?? \"\";\n if (\n checkpointNamespace !== \"\" &&\n config.configurable?.[CONFIG_KEY_READ] === undefined &&\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined\n ) {\n // remove task_ids from checkpoint_ns\n const recastNamespace = recastCheckpointNamespace(checkpointNamespace);\n for await (const [name, subgraph] of this.getSubgraphsAsync(\n recastNamespace,\n true\n )) {\n if (name === recastNamespace) {\n return await subgraph.getState(\n patchConfigurable(config, {\n [CONFIG_KEY_CHECKPOINTER]: checkpointer,\n }),\n { subgraphs: options?.subgraphs }\n );\n }\n }\n // No static subgraph found for this namespace (e.g. a dynamically-created\n // tool-call subgraph like \"tools:call_abc123\"). Fall back to querying the\n // checkpointer directly with the full checkpoint_ns so callers can still\n // read persisted state (e.g. messages) for these transient subgraphs.\n }\n\n const mergedConfig = mergeConfigs(this.config, config);\n const saved = await checkpointer.getTuple(config);\n const snapshot = await this._prepareStateSnapshot({\n config: mergedConfig,\n saved,\n subgraphCheckpointer: options?.subgraphs ? checkpointer : undefined,\n applyPendingWrites: !config.configurable?.checkpoint_id,\n });\n return snapshot;\n }\n\n /**\n * Gets the history of graph states.\n * Requires a checkpointer to be configured.\n * Useful for:\n * - Debugging execution history\n * - Implementing time travel\n * - Analyzing graph behavior\n *\n * @param config - Configuration for retrieving the history\n * @param options - Options for filtering the history\n * @returns An async iterator of state snapshots\n * @throws {Error} If no checkpointer is configured\n */\n async *getStateHistory(\n config: RunnableConfig,\n options?: CheckpointListOptions\n ): AsyncIterableIterator<StateSnapshot> {\n const checkpointer: BaseCheckpointSaver =\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;\n if (!checkpointer) {\n throw new GraphValueError(\"No checkpointer set\", {\n lc_error_code: \"MISSING_CHECKPOINTER\",\n });\n }\n\n const checkpointNamespace: string =\n config.configurable?.checkpoint_ns ?? \"\";\n if (\n checkpointNamespace !== \"\" &&\n config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined\n ) {\n const recastNamespace = recastCheckpointNamespace(checkpointNamespace);\n\n // find the subgraph with the matching name\n for await (const [name, pregel] of this.getSubgraphsAsync(\n recastNamespace,\n true\n )) {\n if (name === recastNamespace) {\n yield* pregel.getStateHistory(\n patchConfigurable(config, {\n [CONFIG_KEY_CHECKPOINTER]: checkpointer,\n }),\n options\n );\n return;\n }\n }\n // No static subgraph found for this namespace (e.g. a dynamically-created\n // tool-call subgraph like \"tools:call_abc123\"). Fall back to querying the\n // checkpointer directly with the full checkpoint_ns so callers can still\n // read persisted state (e.g. messages) for these transient subgraphs.\n }\n\n const mergedConfig = mergeConfigs(this.config, config, {\n configurable: { checkpoint_ns: checkpointNamespace },\n });\n\n for await (const checkpointTuple of checkpointer.list(\n mergedConfig,\n options\n )) {\n yield this._prepareStateSnapshot({\n config: checkpointTuple.config,\n saved: checkpointTuple,\n });\n }\n }\n\n /**\n * Apply updates to the graph state in bulk.\n * Requires a checkpointer to be configured.\n *\n * This method is useful for recreating a thread\n * from a list of updates, especially if a checkpoint\n * is created as a result of multiple tasks.\n *\n * @internal The API might change in the future.\n *\n * @param startConfig - Configuration for the update\n * @param updates - The list of updates to apply to graph state\n * @returns Updated configuration\n * @throws {GraphValueError} If no checkpointer is configured\n * @throws {InvalidUpdateError} If the update cannot be attributed to a node or an update can be only applied in sequence.\n */\n async bulkUpdateState(\n startConfig: LangGraphRunnableConfig,\n supersteps: Array<{\n updates: Array<{\n values?: Record<string, unknown> | unknown;\n asNode?: keyof Nodes | string;\n }>;\n }>\n ): Promise<RunnableConfig> {\n const checkpointer: BaseCheckpointSaver | undefined =\n startConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;\n if (!checkpointer) {\n throw new GraphValueError(\"No checkpointer set\", {\n lc_error_code: \"MISSING_CHECKPOINTER\",\n });\n }\n if (supersteps.length === 0) {\n throw new Error(\"No supersteps provided\");\n }\n\n if (supersteps.some((s) => s.updates.length === 0)) {\n throw new Error(\"No updates provided\");\n }\n\n // delegate to subgraph\n const checkpointNamespace: string =\n startConfig.configurable?.checkpoint_ns ?? \"\";\n if (\n checkpointNamespace !== \"\" &&\n startConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined\n ) {\n // remove task_ids from checkpoint_ns\n const recastNamespace = recastCheckpointNamespace(checkpointNamespace);\n // find the subgraph with the matching name\n // eslint-disable-next-line no-unreachable-loop\n for await (const [, pregel] of this.getSubgraphsAsync(\n recastNamespace,\n true\n )) {\n return await pregel.bulkUpdateState(\n patchConfigurable(startConfig, {\n [CONFIG_KEY_CHECKPOINTER]: checkpointer,\n }),\n supersteps\n );\n }\n throw new Error(`Subgraph \"${recastNamespace}\" not found`);\n }\n\n const updateSuperStep = async (\n inputConfig: LangGraphRunnableConfig,\n updates: {\n values?: Record<string, unknown> | unknown;\n asNode?: keyof Nodes | string;\n taskId?: string;\n }[]\n ) => {\n // get last checkpoint\n const config = this.config\n ? mergeConfigs(this.config, inputConfig)\n : inputConfig;\n const saved = await checkpointer.getTuple(config);\n const checkpoint =\n saved !== undefined\n ? copyCheckpoint(saved.checkpoint)\n : emptyCheckpoint();\n const checkpointPreviousVersions = {\n ...saved?.checkpoint.channel_versions,\n };\n const step = saved?.metadata?.step ?? -1;\n\n // merge configurable fields with previous checkpoint config\n let checkpointConfig = patchConfigurable(config, {\n checkpoint_ns: config.configurable?.checkpoint_ns ?? \"\",\n });\n let checkpointMetadata = config.metadata ?? {};\n if (saved?.config.configurable) {\n checkpointConfig = patchConfigurable(config, saved.config.configurable);\n checkpointMetadata = {\n ...saved.metadata,\n ...checkpointMetadata,\n };\n }\n\n // Find last node that updated the state, if not provided\n const { values, asNode } = updates[0];\n if (values == null && asNode === undefined) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n `Cannot create empty checkpoint with multiple updates`\n );\n }\n\n const nextConfig = await checkpointer.put(\n checkpointConfig,\n createCheckpoint(checkpoint, undefined, step),\n {\n source: \"update\",\n step: step + 1,\n parents: saved?.metadata?.parents ?? {},\n },\n {}\n );\n return patchCheckpointMap(\n nextConfig,\n saved ? saved.metadata : undefined\n );\n }\n\n // update channels, reconstructing any DeltaChannel from ancestor writes\n const channels = await channelsFromCheckpoint(\n this.channels as Record<string, BaseChannel>,\n checkpoint,\n { saver: checkpointer, config: saved?.config ?? checkpointConfig }\n );\n\n if (values === null && asNode === END) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n `Cannot apply multiple updates when clearing state`\n );\n }\n\n if (saved) {\n // tasks for this checkpoint\n const nextTasks = _prepareNextTasks(\n checkpoint,\n saved.pendingWrites || [],\n this.nodes,\n channels,\n saved.config,\n true,\n {\n step: (saved.metadata?.step ?? -1) + 1,\n checkpointer,\n store: this.store,\n }\n );\n\n // apply null writes\n const nullWrites = (saved.pendingWrites || [])\n .filter((w) => w[0] === NULL_TASK_ID)\n .map((w) => w.slice(1)) as PendingWrite<string>[];\n if (nullWrites.length > 0) {\n _applyWrites(\n checkpoint,\n channels,\n [\n {\n name: INPUT,\n writes: nullWrites,\n triggers: [],\n },\n ],\n checkpointer.getNextVersion.bind(checkpointer),\n this.triggerToNodes\n );\n }\n // apply writes from tasks that already ran\n for (const [taskId, k, v] of saved.pendingWrites || []) {\n if ([ERROR, INTERRUPT, SCHEDULED].includes(k)) {\n continue;\n }\n if (!(taskId in nextTasks)) {\n continue;\n }\n nextTasks[taskId].writes.push([k, v]);\n }\n // clear all current tasks\n _applyWrites(\n checkpoint,\n channels,\n Object.values(nextTasks) as WritesProtocol<string>[],\n checkpointer.getNextVersion.bind(checkpointer),\n this.triggerToNodes\n );\n }\n // save checkpoint\n const nextConfig = await checkpointer.put(\n checkpointConfig,\n createCheckpoint(checkpoint, channels, step),\n {\n ...checkpointMetadata,\n source: \"update\",\n step: step + 1,\n parents: saved?.metadata?.parents ?? {},\n },\n getNewChannelVersions(\n checkpointPreviousVersions,\n checkpoint.channel_versions\n )\n );\n return patchCheckpointMap(\n nextConfig,\n saved ? saved.metadata : undefined\n );\n }\n\n if (asNode === COPY) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n `Cannot copy checkpoint with multiple updates`\n );\n }\n\n if (saved == null) {\n throw new InvalidUpdateError(`Cannot copy a non-existent checkpoint`);\n }\n\n const isCopyWithUpdates = (\n values: unknown\n ): values is [values: unknown, asNode: string][] => {\n if (!Array.isArray(values)) return false;\n if (values.length === 0) return false;\n return values.every((v) => Array.isArray(v) && v.length === 2);\n };\n\n const nextCheckpoint = createCheckpoint(checkpoint, undefined, step);\n const nextConfig = await checkpointer.put(\n saved.parentConfig ??\n patchConfigurable(saved.config, { checkpoint_id: undefined }),\n nextCheckpoint,\n {\n source: \"fork\",\n step: step + 1,\n parents: saved.metadata?.parents ?? {},\n },\n {}\n );\n\n // We want to both clone a checkpoint and update state in one go.\n // Reuse the same task ID if possible.\n if (isCopyWithUpdates(values)) {\n // figure out the task IDs for the next update checkpoint\n const nextTasks = _prepareNextTasks(\n nextCheckpoint,\n saved.pendingWrites,\n this.nodes,\n channels,\n nextConfig,\n false,\n { step: step + 2 }\n );\n\n const tasksGroupBy = Object.values(nextTasks).reduce<\n Record<string, { id: string }[]>\n >((acc, { name, id }) => {\n acc[name] ??= [];\n acc[name].push({ id });\n return acc;\n }, {});\n\n const userGroupBy = values.reduce<\n Record<\n string,\n { values: unknown; asNode: string; taskId?: string }[]\n >\n >((acc, item) => {\n const [values, asNode] = item;\n acc[asNode] ??= [];\n\n const targetIdx = acc[asNode].length;\n const taskId = tasksGroupBy[asNode]?.[targetIdx]?.id;\n acc[asNode].push({ values, asNode, taskId });\n\n return acc;\n }, {});\n\n return updateSuperStep(\n patchCheckpointMap(nextConfig, saved.metadata),\n Object.values(userGroupBy).flat()\n );\n }\n\n return patchCheckpointMap(nextConfig, saved.metadata);\n }\n\n if (asNode === INPUT) {\n if (updates.length > 1) {\n throw new InvalidUpdateError(\n