@langchain/langgraph
Version:
1,136 lines • 54.9 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const require_constants = require("../constants.cjs");
const require_errors = require("../errors.cjs");
const require_base = require("../channels/base.cjs");
const require_config = require("./utils/config.cjs");
const require_utils = require("../utils.cjs");
const require_write = require("./write.cjs");
const require_read = require("./read.cjs");
const require_runtime = require("./runtime.cjs");
const require_io = require("./io.cjs");
const require_index = require("./utils/index.cjs");
const require_algo = require("./algo.cjs");
const require_subgraph = require("./utils/subgraph.cjs");
const require_debug = require("./debug.cjs");
const require_convert = require("../stream/convert.cjs");
const require_stream = require("./stream.cjs");
const require_loop = require("./loop.cjs");
const require_messages = require("./messages.cjs");
const require_messages_v2 = require("./messages-v2.cjs");
const require_runner = require("./runner.cjs");
const require_run_stream = require("../stream/run-stream.cjs");
require("../stream/index.cjs");
const require_validate = require("./validate.cjs");
const require_topic = require("../channels/topic.cjs");
const require_interrupt = require("../interrupt.cjs");
let _langchain_langgraph_checkpoint = require("@langchain/langgraph-checkpoint");
let _langchain_core_runnables = require("@langchain/core/runnables");
let _langchain_core_callbacks_manager = require("@langchain/core/callbacks/manager");
//#region src/pregel/index.ts
function protocolEventsToEventStream(run) {
const encoder = new TextEncoder();
return new ReadableStream({ async start(controller) {
try {
for await (const event of run) {
const namespace = event.params.namespace;
const eventName = namespace.length ? `${event.method}|${namespace.join("|")}` : event.method;
controller.enqueue(encoder.encode(`event: ${eventName}\ndata: ${JSON.stringify(event.params.data ?? {})}\n\n`));
}
} catch (error) {
controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({ message: String(error) })}\n\n`));
} finally {
controller.close();
}
} });
}
/**
* Utility class for working with channels in the Pregel system.
* Provides static methods for subscribing to channels and writing to them.
*
* Channels are the communication pathways between nodes in a Pregel graph.
* They enable message passing and state updates between different parts of the graph.
*/
var Channel = class {
static subscribeTo(channels, options) {
const { key, tags } = {
key: void 0,
tags: void 0,
...options ?? {}
};
if (Array.isArray(channels) && key !== void 0) throw new Error("Can't specify a key when subscribing to multiple channels");
let channelMappingOrArray;
if (typeof channels === "string") if (key) channelMappingOrArray = { [key]: channels };
else channelMappingOrArray = [channels];
else channelMappingOrArray = Object.fromEntries(channels.map((chan) => [chan, chan]));
return new require_read.PregelNode({
channels: channelMappingOrArray,
triggers: Array.isArray(channels) ? channels : [channels],
tags
});
}
/**
* Creates a ChannelWrite that specifies how to write values to channels.
* This is used to define how nodes send output to channels.
*
* @example
* ```typescript
* // Write to multiple channels
* const write = Channel.writeTo(["output", "state"]);
*
* // Write with specific values
* const write = Channel.writeTo(["output"], {
* state: "completed",
* result: calculateResult()
* });
*
* // Write with a transformation function
* const write = Channel.writeTo(["output"], {
* result: (x) => processResult(x)
* });
* ```
*
* @param channels - Array of channel names to write to
* @param writes - Optional map of channel names to values or transformations
* @returns A ChannelWrite object that can be used to write to the specified channels
*/
static writeTo(channels, writes) {
const channelWriteEntries = [];
for (const channel of channels) channelWriteEntries.push({
channel,
value: require_write.PASSTHROUGH,
skipNone: false
});
for (const [key, value] of Object.entries(writes ?? {})) if (_langchain_core_runnables.Runnable.isRunnable(value) || typeof value === "function") channelWriteEntries.push({
channel: key,
value: require_write.PASSTHROUGH,
skipNone: true,
mapper: (0, _langchain_core_runnables._coerceToRunnable)(value)
});
else channelWriteEntries.push({
channel: key,
value,
skipNone: false
});
return new require_write.ChannelWrite(channelWriteEntries);
}
};
var PartialRunnable = class extends _langchain_core_runnables.Runnable {
lc_namespace = ["langgraph", "pregel"];
invoke(_input, _options) {
throw new Error("Not implemented");
}
withConfig(_config) {
return super.withConfig(_config);
}
stream(input, options) {
return super.stream(input, options);
}
};
/**
* The Pregel class is the core runtime engine of LangGraph, implementing a message-passing graph computation model
* inspired by [Google's Pregel system](https://research.google/pubs/pregel-a-system-for-large-scale-graph-processing/).
* It provides the foundation for building reliable, controllable agent workflows that can evolve state over time.
*
* Key features:
* - Message passing between nodes in discrete "supersteps"
* - Built-in persistence layer through checkpointers
* - First-class streaming support for values, updates, and events
* - Human-in-the-loop capabilities via interrupts
* - Support for parallel node execution within supersteps
*
* The Pregel class is not intended to be instantiated directly by consumers. Instead, use the following higher-level APIs:
* - {@link StateGraph}: The main graph class for building agent workflows
* - Compiling a {@link StateGraph} will return a {@link CompiledGraph} instance, which extends `Pregel`
* - Functional API: A declarative approach using tasks and entrypoints
* - A `Pregel` instance is returned by the {@link entrypoint} function
*
* @example
* ```typescript
* // Using StateGraph API
* const graph = new StateGraph(annotation)
* .addNode("nodeA", myNodeFunction)
* .addEdge("nodeA", "nodeB")
* .compile();
*
* // The compiled graph is a Pregel instance
* const result = await graph.invoke(input);
* ```
*
* @example
* ```typescript
* // Using Functional API
* import { task, entrypoint } from "@langchain/langgraph";
* import { MemorySaver } from "@langchain/langgraph-checkpoint";
*
* // Define tasks that can be composed
* const addOne = task("add", async (x: number) => x + 1);
*
* // Create a workflow using the entrypoint function
* const workflow = entrypoint({
* name: "workflow",
* checkpointer: new MemorySaver()
* }, async (numbers: number[]) => {
* // Tasks can be run in parallel
* const results = await Promise.all(numbers.map(n => addOne(n)));
* return results;
* });
*
* // The workflow is a Pregel instance
* const result = await workflow.invoke([1, 2, 3]); // Returns [2, 3, 4]
* ```
*
* @typeParam Nodes - Mapping of node names to their {@link PregelNode} implementations
* @typeParam Channels - Mapping of channel names to their {@link BaseChannel} or {@link ManagedValueSpec} implementations
* @typeParam ContextType - Type of context that can be passed to the graph
* @typeParam InputType - Type of input values accepted by the graph
* @typeParam OutputType - Type of output values produced by the graph
*/
var Pregel = class extends PartialRunnable {
/**
* Name of the class when serialized
* @internal
*/
static lc_name() {
return "LangGraph";
}
/** @internal LangChain namespace for serialization necessary because Pregel extends Runnable */
lc_namespace = ["langgraph", "pregel"];
/** @internal Flag indicating this is a Pregel instance - necessary for serialization */
lg_is_pregel = true;
/** The nodes in the graph, mapping node names to their PregelNode instances */
nodes;
/** The channels in the graph, mapping channel names to their BaseChannel or ManagedValueSpec instances */
channels;
/**
* The input channels for the graph. These channels receive the initial input when the graph is invoked.
* Can be a single channel key or an array of channel keys.
*/
inputChannels;
/**
* The output channels for the graph. These channels contain the final output when the graph completes.
* Can be a single channel key or an array of channel keys.
*/
outputChannels;
/** Whether to automatically validate the graph structure when it is compiled. Defaults to true. */
autoValidate = true;
/**
* The streaming modes enabled for this graph. Defaults to ["values"].
* Supported modes:
* - "values": Streams the full state after each step
* - "updates": Streams state updates after each step
* - "messages": Streams messages from within nodes
* - "custom": Streams custom events from within nodes
* - "tools": Streams tool-call lifecycle events (on_tool_start, on_tool_event, on_tool_end, on_tool_error) from LLM tool execution
* - "debug": Streams events related to the execution of the graph - useful for tracing & debugging graph execution
*/
streamMode = ["values"];
/**
* Optional channels to stream. If not specified, all channels will be streamed.
* Can be a single channel key or an array of channel keys.
*/
streamChannels;
/**
* Optional array of node names or "all" to interrupt after executing these nodes.
* Used for implementing human-in-the-loop workflows.
*/
interruptAfter;
/**
* Optional array of node names or "all" to interrupt before executing these nodes.
* Used for implementing human-in-the-loop workflows.
*/
interruptBefore;
/** Optional timeout in milliseconds for the execution of each superstep */
stepTimeout;
/** Whether to enable debug logging. Defaults to false. */
debug = false;
/**
* Optional checkpointer for persisting graph state.
* When provided, saves a checkpoint of the graph state at every superstep.
* When false or undefined, checkpointing is disabled, and the graph will not be able to save or restore state.
*/
checkpointer;
/** Optional retry policy for handling failures in node execution */
retryPolicy;
/** The default configuration for graph execution, can be overridden on a per-invocation basis */
config;
/**
* Optional long-term memory store for the graph, allows for persistence & retrieval of data across threads
*/
store;
/**
* Optional cache for the graph, useful for caching tasks.
*/
cache;
/**
* Optional interrupt helper function.
* @internal
*/
userInterrupt;
/**
* Stream reducer factories registered at compile time. These run
* automatically for every `streamEvents(..., { version: "v3" })` call,
* before any call-site transformers.
*/
streamTransformers;
/**
* The trigger to node mapping for the graph run.
* @internal
*/
triggerToNodes = {};
/**
* Constructor for Pregel - meant for internal use only.
*
* @internal
*/
constructor(fields) {
super(fields);
let { streamMode } = fields;
if (streamMode != null && !Array.isArray(streamMode)) streamMode = [streamMode];
this.nodes = fields.nodes;
this.channels = fields.channels;
if ("__pregel_tasks" in this.channels && "lc_graph_name" in this.channels["__pregel_tasks"] && this.channels["__pregel_tasks"].lc_graph_name !== "Topic") throw new Error(`Channel '${require_constants.TASKS}' is reserved and cannot be used in the graph.`);
else this.channels[require_constants.TASKS] = new require_topic.Topic({ accumulate: false });
this.autoValidate = fields.autoValidate ?? this.autoValidate;
this.streamMode = streamMode ?? this.streamMode;
this.inputChannels = fields.inputChannels;
this.outputChannels = fields.outputChannels;
this.streamChannels = fields.streamChannels ?? this.streamChannels;
this.interruptAfter = fields.interruptAfter;
this.interruptBefore = fields.interruptBefore;
this.stepTimeout = fields.stepTimeout ?? this.stepTimeout;
this.debug = fields.debug ?? this.debug;
this.checkpointer = fields.checkpointer;
this.retryPolicy = fields.retryPolicy;
this.config = fields.config;
this.store = fields.store;
this.cache = fields.cache;
this.name = fields.name;
this.triggerToNodes = fields.triggerToNodes ?? this.triggerToNodes;
this.userInterrupt = fields.userInterrupt;
this.streamTransformers = fields.streamTransformers ?? [];
if (this.autoValidate) this.validate();
}
withConfig(config) {
const { streamTransformers, ...restConfig } = config;
const mergedConfig = (0, _langchain_core_runnables.mergeConfigs)(this.config, restConfig);
const mergedStreamTransformers = [...this.streamTransformers, ...streamTransformers ?? []];
return new this.constructor({
...this,
config: mergedConfig,
streamTransformers: mergedStreamTransformers
});
}
/**
* Validates the graph structure to ensure it is well-formed.
* Checks for:
* - No orphaned nodes
* - Valid input/output channel configurations
* - Valid interrupt configurations
*
* @returns this - The Pregel instance for method chaining
* @throws {GraphValidationError} If the graph structure is invalid
*/
validate() {
require_validate.validateGraph({
nodes: this.nodes,
channels: this.channels,
outputChannels: this.outputChannels,
inputChannels: this.inputChannels,
streamChannels: this.streamChannels,
interruptAfterNodes: this.interruptAfter,
interruptBeforeNodes: this.interruptBefore
});
for (const [name, node] of Object.entries(this.nodes)) for (const trigger of node.triggers) {
this.triggerToNodes[trigger] ??= [];
this.triggerToNodes[trigger].push(name);
}
return this;
}
/**
* Gets a list of all channels that should be streamed.
* If streamChannels is specified, returns those channels.
* Otherwise, returns all channels in the graph.
*
* @returns Array of channel keys to stream
*/
get streamChannelsList() {
if (Array.isArray(this.streamChannels)) return this.streamChannels;
else if (this.streamChannels) return [this.streamChannels];
else return Object.keys(this.channels);
}
/**
* Gets the channels to stream in their original format.
* If streamChannels is specified, returns it as-is (either single key or array).
* Otherwise, returns all channels in the graph as an array.
*
* @returns Channel keys to stream, either as a single key or array
*/
get streamChannelsAsIs() {
if (this.streamChannels) return this.streamChannels;
else return Object.keys(this.channels);
}
/**
* Gets a drawable representation of the graph structure.
* This is an async version of getGraph() and is the preferred method to use.
*
* @param config - Configuration for generating the graph visualization
* @returns A representation of the graph that can be visualized
*/
async getGraphAsync(config) {
return this.getGraph(config);
}
/**
* Gets all subgraphs within this graph.
* A subgraph is a Pregel instance that is nested within a node of this graph.
*
* @deprecated Use getSubgraphsAsync instead. The async method will become the default in the next minor release.
* @param namespace - Optional namespace to filter subgraphs
* @param recurse - Whether to recursively get subgraphs of subgraphs
* @returns Generator yielding tuples of [name, subgraph]
*/
*getSubgraphs(namespace, recurse) {
for (const [name, node] of Object.entries(this.nodes)) {
if (namespace !== void 0) {
if (!namespace.startsWith(name)) continue;
}
const candidates = node.subgraphs?.length ? node.subgraphs : [node.bound];
for (const candidate of candidates) {
const graph = require_subgraph.findSubgraphPregel(candidate);
if (graph !== void 0) {
if (name === namespace) {
yield [name, graph];
return;
}
if (namespace === void 0) yield [name, graph];
if (recurse) {
let newNamespace = namespace;
if (namespace !== void 0) newNamespace = namespace.slice(name.length + 1);
for (const [subgraphName, subgraph] of graph.getSubgraphs(newNamespace, recurse)) yield [`${name}|${subgraphName}`, subgraph];
}
}
}
}
}
/**
* Gets all subgraphs within this graph asynchronously.
* A subgraph is a Pregel instance that is nested within a node of this graph.
*
* @param namespace - Optional namespace to filter subgraphs
* @param recurse - Whether to recursively get subgraphs of subgraphs
* @returns AsyncGenerator yielding tuples of [name, subgraph]
*/
async *getSubgraphsAsync(namespace, recurse) {
yield* this.getSubgraphs(namespace, recurse);
}
/**
* Prepares a state snapshot from saved checkpoint data.
* This is an internal method used by getState and getStateHistory.
*
* @param config - Configuration for preparing the snapshot
* @param saved - Optional saved checkpoint data
* @param subgraphCheckpointer - Optional checkpointer for subgraphs
* @param applyPendingWrites - Whether to apply pending writes to tasks and then to channels
* @returns A snapshot of the graph state
* @internal
*/
async _prepareStateSnapshot({ config, saved, subgraphCheckpointer, applyPendingWrites = false }) {
if (saved === void 0) return {
values: {},
next: [],
config,
tasks: []
};
const channels = await require_base.channelsFromCheckpoint(this.channels, saved.checkpoint, {
saver: typeof this.checkpointer === "object" ? this.checkpointer : void 0,
config: saved.config ?? config
});
if (saved.pendingWrites?.length) {
const nullWrites = saved.pendingWrites.filter(([taskId, _]) => taskId === require_constants.NULL_TASK_ID).map(([_, channel, value]) => [String(channel), value]);
if (nullWrites.length > 0) require_algo._applyWrites(saved.checkpoint, channels, [{
name: require_constants.INPUT,
writes: nullWrites,
triggers: []
}], void 0, this.triggerToNodes);
}
const nextTasks = Object.values(require_algo._prepareNextTasks(saved.checkpoint, saved.pendingWrites, this.nodes, channels, saved.config, true, {
step: (saved.metadata?.step ?? -1) + 1,
store: this.store
}));
const subgraphs = await require_utils.gatherIterator(this.getSubgraphsAsync());
const parentNamespace = saved.config.configurable?.checkpoint_ns ?? "";
const taskStates = {};
for (const task of nextTasks) {
const matchingSubgraph = subgraphs.find(([name]) => name === task.name);
if (!matchingSubgraph) continue;
let taskNs = `${String(task.name)}:${task.id}`;
if (parentNamespace) taskNs = `${parentNamespace}|${taskNs}`;
if (subgraphCheckpointer === void 0) {
const config = { configurable: {
thread_id: saved.config.configurable?.thread_id,
checkpoint_ns: taskNs
} };
taskStates[task.id] = config;
} else {
const subgraphConfig = { configurable: {
[require_constants.CONFIG_KEY_CHECKPOINTER]: subgraphCheckpointer,
thread_id: saved.config.configurable?.thread_id,
checkpoint_ns: taskNs
} };
const pregel = matchingSubgraph[1];
taskStates[task.id] = await pregel.getState(subgraphConfig, { subgraphs: true });
}
}
if (applyPendingWrites && saved.pendingWrites?.length) {
const nextTaskById = Object.fromEntries(nextTasks.map((task) => [task.id, task]));
for (const [taskId, channel, value] of saved.pendingWrites) {
if ([
"__error__",
"__interrupt__",
_langchain_langgraph_checkpoint.SCHEDULED
].includes(channel)) continue;
if (!(taskId in nextTaskById)) continue;
nextTaskById[taskId].writes.push([String(channel), value]);
}
const tasksWithWrites = nextTasks.filter((task) => task.writes.length > 0);
if (tasksWithWrites.length > 0) require_algo._applyWrites(saved.checkpoint, channels, tasksWithWrites, void 0, this.triggerToNodes);
}
let metadata = saved?.metadata;
if (metadata && saved?.config?.configurable?.thread_id) metadata = {
...metadata,
thread_id: saved.config.configurable.thread_id
};
const nextList = nextTasks.filter((task) => task.writes.length === 0).map((task) => task.name);
return {
values: require_io.readChannels(channels, this.streamChannelsAsIs),
next: nextList,
tasks: require_debug.tasksWithWrites(nextTasks, saved?.pendingWrites ?? [], taskStates, this.streamChannelsAsIs),
metadata,
config: require_index.patchCheckpointMap(saved.config, saved.metadata),
createdAt: saved.checkpoint.ts,
parentConfig: saved.parentConfig
};
}
/**
* Gets the current state of the graph.
* Requires a checkpointer to be configured.
*
* @param config - Configuration for retrieving the state
* @param options - Additional options
* @returns A snapshot of the current graph state
* @throws {GraphValueError} If no checkpointer is configured
*/
async getState(config, options) {
const checkpointer = config.configurable?.["__pregel_checkpointer"] ?? this.checkpointer;
if (!checkpointer) throw new require_errors.GraphValueError("No checkpointer set", { lc_error_code: "MISSING_CHECKPOINTER" });
const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
if (checkpointNamespace !== "" && config.configurable?.["__pregel_read"] === void 0 && config.configurable?.["__pregel_checkpointer"] === void 0) {
const recastNamespace = require_config.recastCheckpointNamespace(checkpointNamespace);
for await (const [name, subgraph] of this.getSubgraphsAsync(recastNamespace, true)) if (name === recastNamespace) return await subgraph.getState(require_utils.patchConfigurable(config, { [require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer }), { subgraphs: options?.subgraphs });
}
const mergedConfig = (0, _langchain_core_runnables.mergeConfigs)(this.config, config);
const saved = await checkpointer.getTuple(config);
return await this._prepareStateSnapshot({
config: mergedConfig,
saved,
subgraphCheckpointer: options?.subgraphs ? checkpointer : void 0,
applyPendingWrites: !config.configurable?.checkpoint_id
});
}
/**
* Gets the history of graph states.
* Requires a checkpointer to be configured.
* Useful for:
* - Debugging execution history
* - Implementing time travel
* - Analyzing graph behavior
*
* @param config - Configuration for retrieving the history
* @param options - Options for filtering the history
* @returns An async iterator of state snapshots
* @throws {Error} If no checkpointer is configured
*/
async *getStateHistory(config, options) {
const checkpointer = config.configurable?.["__pregel_checkpointer"] ?? this.checkpointer;
if (!checkpointer) throw new require_errors.GraphValueError("No checkpointer set", { lc_error_code: "MISSING_CHECKPOINTER" });
const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
if (checkpointNamespace !== "" && config.configurable?.["__pregel_checkpointer"] === void 0) {
const recastNamespace = require_config.recastCheckpointNamespace(checkpointNamespace);
for await (const [name, pregel] of this.getSubgraphsAsync(recastNamespace, true)) if (name === recastNamespace) {
yield* pregel.getStateHistory(require_utils.patchConfigurable(config, { [require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer }), options);
return;
}
}
const mergedConfig = (0, _langchain_core_runnables.mergeConfigs)(this.config, config, { configurable: { checkpoint_ns: checkpointNamespace } });
for await (const checkpointTuple of checkpointer.list(mergedConfig, options)) yield this._prepareStateSnapshot({
config: checkpointTuple.config,
saved: checkpointTuple
});
}
/**
* Apply updates to the graph state in bulk.
* Requires a checkpointer to be configured.
*
* This method is useful for recreating a thread
* from a list of updates, especially if a checkpoint
* is created as a result of multiple tasks.
*
* @internal The API might change in the future.
*
* @param startConfig - Configuration for the update
* @param updates - The list of updates to apply to graph state
* @returns Updated configuration
* @throws {GraphValueError} If no checkpointer is configured
* @throws {InvalidUpdateError} If the update cannot be attributed to a node or an update can be only applied in sequence.
*/
async bulkUpdateState(startConfig, supersteps) {
const checkpointer = startConfig.configurable?.["__pregel_checkpointer"] ?? this.checkpointer;
if (!checkpointer) throw new require_errors.GraphValueError("No checkpointer set", { lc_error_code: "MISSING_CHECKPOINTER" });
if (supersteps.length === 0) throw new Error("No supersteps provided");
if (supersteps.some((s) => s.updates.length === 0)) throw new Error("No updates provided");
const checkpointNamespace = startConfig.configurable?.checkpoint_ns ?? "";
if (checkpointNamespace !== "" && startConfig.configurable?.["__pregel_checkpointer"] === void 0) {
const recastNamespace = require_config.recastCheckpointNamespace(checkpointNamespace);
for await (const [, pregel] of this.getSubgraphsAsync(recastNamespace, true)) return await pregel.bulkUpdateState(require_utils.patchConfigurable(startConfig, { [require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer }), supersteps);
throw new Error(`Subgraph "${recastNamespace}" not found`);
}
const updateSuperStep = async (inputConfig, updates) => {
const config = this.config ? (0, _langchain_core_runnables.mergeConfigs)(this.config, inputConfig) : inputConfig;
const saved = await checkpointer.getTuple(config);
const checkpoint = saved !== void 0 ? (0, _langchain_langgraph_checkpoint.copyCheckpoint)(saved.checkpoint) : (0, _langchain_langgraph_checkpoint.emptyCheckpoint)();
const checkpointPreviousVersions = { ...saved?.checkpoint.channel_versions };
const step = saved?.metadata?.step ?? -1;
let checkpointConfig = require_utils.patchConfigurable(config, { checkpoint_ns: config.configurable?.checkpoint_ns ?? "" });
let checkpointMetadata = config.metadata ?? {};
if (saved?.config.configurable) {
checkpointConfig = require_utils.patchConfigurable(config, saved.config.configurable);
checkpointMetadata = {
...saved.metadata,
...checkpointMetadata
};
}
const { values, asNode } = updates[0];
if (values == null && asNode === void 0) {
if (updates.length > 1) throw new require_errors.InvalidUpdateError(`Cannot create empty checkpoint with multiple updates`);
return require_index.patchCheckpointMap(await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, void 0, step), {
source: "update",
step: step + 1,
parents: saved?.metadata?.parents ?? {}
}, {}), saved ? saved.metadata : void 0);
}
const channels = await require_base.channelsFromCheckpoint(this.channels, checkpoint, {
saver: checkpointer,
config: saved?.config ?? checkpointConfig
});
if (values === null && asNode === "__end__") {
if (updates.length > 1) throw new require_errors.InvalidUpdateError(`Cannot apply multiple updates when clearing state`);
if (saved) {
const nextTasks = require_algo._prepareNextTasks(checkpoint, saved.pendingWrites || [], this.nodes, channels, saved.config, true, {
step: (saved.metadata?.step ?? -1) + 1,
checkpointer,
store: this.store
});
const nullWrites = (saved.pendingWrites || []).filter((w) => w[0] === require_constants.NULL_TASK_ID).map((w) => w.slice(1));
if (nullWrites.length > 0) require_algo._applyWrites(checkpoint, channels, [{
name: require_constants.INPUT,
writes: nullWrites,
triggers: []
}], checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
for (const [taskId, k, v] of saved.pendingWrites || []) {
if ([
"__error__",
"__interrupt__",
_langchain_langgraph_checkpoint.SCHEDULED
].includes(k)) continue;
if (!(taskId in nextTasks)) continue;
nextTasks[taskId].writes.push([k, v]);
}
require_algo._applyWrites(checkpoint, channels, Object.values(nextTasks), checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
}
return require_index.patchCheckpointMap(await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, step), {
...checkpointMetadata,
source: "update",
step: step + 1,
parents: saved?.metadata?.parents ?? {}
}, require_index.getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions)), saved ? saved.metadata : void 0);
}
if (asNode === "__copy__") {
if (updates.length > 1) throw new require_errors.InvalidUpdateError(`Cannot copy checkpoint with multiple updates`);
if (saved == null) throw new require_errors.InvalidUpdateError(`Cannot copy a non-existent checkpoint`);
const isCopyWithUpdates = (values) => {
if (!Array.isArray(values)) return false;
if (values.length === 0) return false;
return values.every((v) => Array.isArray(v) && v.length === 2);
};
const nextCheckpoint = require_base.createCheckpoint(checkpoint, void 0, step);
const nextConfig = await checkpointer.put(saved.parentConfig ?? require_utils.patchConfigurable(saved.config, { checkpoint_id: void 0 }), nextCheckpoint, {
source: "fork",
step: step + 1,
parents: saved.metadata?.parents ?? {}
}, {});
if (isCopyWithUpdates(values)) {
const nextTasks = require_algo._prepareNextTasks(nextCheckpoint, saved.pendingWrites, this.nodes, channels, nextConfig, false, { step: step + 2 });
const tasksGroupBy = Object.values(nextTasks).reduce((acc, { name, id }) => {
acc[name] ??= [];
acc[name].push({ id });
return acc;
}, {});
const userGroupBy = values.reduce((acc, item) => {
const [values, asNode] = item;
acc[asNode] ??= [];
const targetIdx = acc[asNode].length;
const taskId = tasksGroupBy[asNode]?.[targetIdx]?.id;
acc[asNode].push({
values,
asNode,
taskId
});
return acc;
}, {});
return updateSuperStep(require_index.patchCheckpointMap(nextConfig, saved.metadata), Object.values(userGroupBy).flat());
}
return require_index.patchCheckpointMap(nextConfig, saved.metadata);
}
if (asNode === "__input__") {
if (updates.length > 1) throw new require_errors.InvalidUpdateError(`Cannot apply multiple updates when updating as input`);
const inputWrites = await require_utils.gatherIterator(require_io.mapInput(this.inputChannels, values));
if (inputWrites.length === 0) throw new require_errors.InvalidUpdateError(`Received no input writes for ${JSON.stringify(this.inputChannels, null, 2)}`);
require_algo._applyWrites(checkpoint, channels, [{
name: require_constants.INPUT,
writes: inputWrites,
triggers: []
}], checkpointer.getNextVersion.bind(this.checkpointer), this.triggerToNodes);
const nextStep = saved?.metadata?.step != null ? saved.metadata.step + 1 : -1;
const nextConfig = await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, nextStep), {
source: "input",
step: nextStep,
parents: saved?.metadata?.parents ?? {}
}, require_index.getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions));
await checkpointer.putWrites(nextConfig, inputWrites, (0, _langchain_langgraph_checkpoint.uuid5)(require_constants.INPUT, checkpoint.id));
return require_index.patchCheckpointMap(nextConfig, saved ? saved.metadata : void 0);
}
if (config.configurable?.checkpoint_id === void 0 && saved?.pendingWrites !== void 0 && saved.pendingWrites.length > 0) {
const nextTasks = require_algo._prepareNextTasks(checkpoint, saved.pendingWrites, this.nodes, channels, saved.config, true, {
store: this.store,
checkpointer: this.checkpointer,
step: (saved.metadata?.step ?? -1) + 1
});
const nullWrites = (saved.pendingWrites ?? []).filter((w) => w[0] === require_constants.NULL_TASK_ID).map((w) => w.slice(1));
if (nullWrites.length > 0) require_algo._applyWrites(saved.checkpoint, channels, [{
name: require_constants.INPUT,
writes: nullWrites,
triggers: []
}], void 0, this.triggerToNodes);
for (const [tid, k, v] of saved.pendingWrites) {
if ([
"__error__",
"__interrupt__",
_langchain_langgraph_checkpoint.SCHEDULED
].includes(k) || nextTasks[tid] === void 0) continue;
nextTasks[tid].writes.push([k, v]);
}
const tasks = Object.values(nextTasks).filter((task) => {
return task.writes.length > 0;
});
if (tasks.length > 0) require_algo._applyWrites(checkpoint, channels, tasks, void 0, this.triggerToNodes);
}
const nonNullVersion = Object.values(checkpoint.versions_seen).map((seenVersions) => {
return Object.values(seenVersions);
}).flat().find((v) => !!v);
const validUpdates = [];
if (updates.length === 1) {
let { values, asNode, taskId } = updates[0];
if (asNode === void 0 && Object.keys(this.nodes).length === 1) [asNode] = Object.keys(this.nodes);
else if (asNode === void 0 && nonNullVersion === void 0) {
if (typeof this.inputChannels === "string" && this.nodes[this.inputChannels] !== void 0) asNode = this.inputChannels;
} else if (asNode === void 0) {
const lastSeenByNode = Object.entries(checkpoint.versions_seen).map(([n, seen]) => {
return Object.values(seen).map((v) => {
return [v, n];
});
}).flat().filter(([_, v]) => v !== require_constants.INTERRUPT).sort(([aNumber], [bNumber]) => (0, _langchain_langgraph_checkpoint.compareChannelVersions)(aNumber, bNumber));
if (lastSeenByNode) {
if (lastSeenByNode.length === 1) asNode = lastSeenByNode[0][1];
else if (lastSeenByNode[lastSeenByNode.length - 1][0] !== lastSeenByNode[lastSeenByNode.length - 2][0]) asNode = lastSeenByNode[lastSeenByNode.length - 1][1];
}
}
if (asNode === void 0) throw new require_errors.InvalidUpdateError(`Ambiguous update, specify "asNode"`);
validUpdates.push({
values,
asNode,
taskId
});
} else for (const { asNode, values, taskId } of updates) {
if (asNode == null) throw new require_errors.InvalidUpdateError(`"asNode" is required when applying multiple updates`);
validUpdates.push({
values,
asNode,
taskId
});
}
const tasks = [];
for (const { asNode, values, taskId } of validUpdates) {
if (this.nodes[asNode] === void 0) throw new require_errors.InvalidUpdateError(`Node "${asNode.toString()}" does not exist`);
const writers = this.nodes[asNode].getWriters();
if (!writers.length) throw new require_errors.InvalidUpdateError(`No writers found for node "${asNode.toString()}"`);
tasks.push({
name: asNode,
input: values,
proc: writers.length > 1 ? _langchain_core_runnables.RunnableSequence.from(writers, { omitSequenceTags: true }) : writers[0],
writes: [],
triggers: [require_constants.INTERRUPT],
id: taskId ?? (0, _langchain_langgraph_checkpoint.uuid5)("__interrupt__", checkpoint.id),
writers: []
});
}
for (const task of tasks) await task.proc.invoke(task.input, (0, _langchain_core_runnables.patchConfig)({
...config,
store: config?.store ?? this.store
}, {
runName: config.runName ?? `${this.getName()}UpdateState`,
configurable: {
[require_constants.CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
[require_constants.CONFIG_KEY_READ]: (select_, fresh_ = false) => require_algo._localRead(checkpoint, channels, task, select_, fresh_)
}
}));
for (const task of tasks) {
const channelWrites = task.writes.filter((w) => w[0] !== require_constants.PUSH);
if (saved !== void 0 && channelWrites.length > 0) await checkpointer.putWrites(checkpointConfig, channelWrites, task.id);
}
require_algo._applyWrites(checkpoint, channels, tasks, checkpointer.getNextVersion.bind(this.checkpointer), this.triggerToNodes);
const newVersions = require_index.getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions);
const nextConfig = await checkpointer.put(checkpointConfig, require_base.createCheckpoint(checkpoint, channels, step + 1), {
source: "update",
step: step + 1,
parents: saved?.metadata?.parents ?? {}
}, newVersions);
for (const task of tasks) {
const pushWrites = task.writes.filter((w) => w[0] === require_constants.PUSH);
if (pushWrites.length > 0) await checkpointer.putWrites(nextConfig, pushWrites, task.id);
}
return require_index.patchCheckpointMap(nextConfig, saved ? saved.metadata : void 0);
};
let currentConfig = startConfig;
for (const { updates } of supersteps) currentConfig = await updateSuperStep(currentConfig, updates);
return currentConfig;
}
/**
* Updates the state of the graph with new values.
* Requires a checkpointer to be configured.
*
* This method can be used for:
* - Implementing human-in-the-loop workflows
* - Modifying graph state during breakpoints
* - Integrating external inputs into the graph
*
* @param inputConfig - Configuration for the update
* @param values - The values to update the state with
* @param asNode - Optional node name to attribute the update to
* @returns Updated configuration
* @throws {GraphValueError} If no checkpointer is configured
* @throws {InvalidUpdateError} If the update cannot be attributed to a node
*/
async updateState(inputConfig, values, asNode) {
return this.bulkUpdateState(inputConfig, [{ updates: [{
values,
asNode
}] }]);
}
/**
* Gets the default values for various graph configuration options.
* This is an internal method used to process and normalize configuration options.
*
* @param config - The input configuration options
* @returns A tuple containing normalized values for:
* - debug mode
* - stream modes
* - input keys
* - output keys
* - remaining config
* - interrupt before nodes
* - interrupt after nodes
* - checkpointer
* - store
* - whether stream mode is single
* - node cache
* - whether checkpoint during is enabled
* @internal
*/
_defaults(config) {
const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
let streamModeSingle = true;
const defaultDebug = debug !== void 0 ? debug : this.debug;
let defaultOutputKeys = outputKeys;
if (defaultOutputKeys === void 0) defaultOutputKeys = this.streamChannelsAsIs;
else require_validate.validateKeys(defaultOutputKeys, this.channels);
let defaultInputKeys = inputKeys;
if (defaultInputKeys === void 0) defaultInputKeys = this.inputChannels;
else require_validate.validateKeys(defaultInputKeys, this.channels);
const defaultInterruptBefore = interruptBefore ?? this.interruptBefore ?? [];
const defaultInterruptAfter = interruptAfter ?? this.interruptAfter ?? [];
let defaultStreamMode;
if (streamMode !== void 0) {
defaultStreamMode = Array.isArray(streamMode) ? streamMode : [streamMode];
streamModeSingle = typeof streamMode === "string";
} else {
if (config.configurable?.["__pregel_task_id"] !== void 0) defaultStreamMode = ["values"];
else defaultStreamMode = this.streamMode;
streamModeSingle = true;
}
let defaultCheckpointer;
if (this.checkpointer === false) defaultCheckpointer = void 0;
else if (config !== void 0 && config.configurable?.["__pregel_checkpointer"] !== void 0) defaultCheckpointer = config.configurable[require_constants.CONFIG_KEY_CHECKPOINTER];
else if (this.checkpointer === true) throw new Error("checkpointer: true cannot be used for root graphs.");
else defaultCheckpointer = this.checkpointer;
const defaultStore = config.store ?? this.store;
const defaultCache = config.cache ?? this.cache;
if (config.durability != null && config.checkpointDuring != null) throw new Error("Cannot use both `durability` and `checkpointDuring` at the same time.");
const checkpointDuringDurability = (() => {
if (config.checkpointDuring == null) return void 0;
if (config.checkpointDuring === false) return "exit";
return "async";
})();
const defaultDurability = config.durability ?? checkpointDuringDurability ?? config?.configurable?.["__pregel_durability"] ?? "async";
return [
defaultDebug,
defaultStreamMode,
defaultInputKeys,
defaultOutputKeys,
rest,
defaultInterruptBefore,
defaultInterruptAfter,
defaultCheckpointer,
defaultStore,
streamModeSingle,
defaultCache,
defaultDurability
];
}
/**
* Streams the execution of the graph, emitting state updates as they occur.
* This is the primary method for observing graph execution in real-time.
*
* Stream modes:
* - "values": Emits complete state after each step
* - "updates": Emits only state changes after each step
* - "debug": Emits detailed debug information
* - "messages": Emits messages from within nodes
* - "custom": Emits custom events from within nodes
* - "checkpoints": Emits checkpoints from within nodes
* - "tasks": Emits tasks from within nodes
*
* @param input - The input to start graph execution with
* @param options - Configuration options for streaming
* @returns An async iterable stream of graph state updates
*/
async stream(input, options) {
const abortController = new AbortController();
const ambientConfigurable = require_config.getConfig()?.configurable;
if (ambientConfigurable?.["__pregel_read"] !== void 0 && options?.configurable?.["__pregel_read"] === void 0) options = {
...options,
configurable: {
...ambientConfigurable,
...options?.configurable
}
};
const config = {
recursionLimit: this.config?.recursionLimit,
...options,
signal: require_index.combineAbortSignals(options?.signal, abortController.signal).signal
};
const stream = await super.stream(input, config);
return new require_stream.IterableReadableStreamWithAbortSignal(options?.encoding === "text/event-stream" ? require_stream.toEventStream(stream) : stream, abortController);
}
async #streamEventsV3(input, options) {
const { version, encoding, transformers: userTransformers, ...restOptions } = options;
const streamOptions = {
recursionLimit: this.config?.recursionLimit,
...restOptions,
configurable: {
...this.config?.configurable,
...restOptions?.configurable
},
version,
streamMode: require_convert.STREAM_EVENTS_V3_MODES,
subgraphs: true,
encoding: void 0
};
const sourcePromise = this.stream(input, streamOptions);
const graphRun = require_run_stream.createGraphRunStream({ [Symbol.asyncIterator]: async function* () {
const src = await sourcePromise;
for await (const chunk of src) yield chunk;
} }, [...this.streamTransformers ?? [], ...userTransformers ?? []]);
if (encoding === "text/event-stream") {
const abortController = new AbortController();
abortController.signal.addEventListener("abort", () => graphRun.abort(abortController.signal.reason), { once: true });
return new require_stream.IterableReadableStreamWithAbortSignal(protocolEventsToEventStream(graphRun), abortController);
}
return graphRun;
}
streamEvents(input, options, streamOptions) {
if (options.version === "v3") return this.#streamEventsV3(input, options);
const abortController = new AbortController();
const config = {
recursionLimit: this.config?.recursionLimit,
...options,
signal: require_index.combineAbortSignals(options?.signal, abortController.signal).signal
};
return new require_stream.IterableReadableStreamWithAbortSignal(super.streamEvents(input, config, streamOptions), abortController);
}
/**
* Validates the input for the graph.
* @param input - The input to validate
* @returns The validated input
* @internal
*/
async _validateInput(input) {
return input;
}
/**
* Validates the context options for the graph.
* @param context - The context options to validate
* @returns The validated context options
* @internal
*/
async _validateContext(context) {
return context;
}
/**
* Internal iterator used by stream() to generate state updates.
* This method handles the core logic of graph execution and streaming.
*
* @param input - The input to start graph execution with
* @param options - Configuration options for streaming
* @returns AsyncGenerator yielding state updates
* @internal
*/
async *_streamIterator(input, options) {
const streamEncoding = "version" in (options ?? {}) ? void 0 : options?.encoding ?? void 0;
const streamSubgraphs = options?.subgraphs;
const isV3 = options?.version === "v3";
const inputConfig = require_config.ensureLangGraphConfig(this.config, options);
if (inputConfig.recursionLimit === void 0 || inputConfig.recursionLimit < 1) throw new Error(`Passed "recursionLimit" must be at least 1.`);
if (this.checkpointer !== void 0 && this.checkpointer !== false && inputConfig.configurable === void 0) throw new Error(`Checkpointer requires one or more of the following "configurable" keys: "thread_id", "checkpoint_ns", "checkpoint_id"`);
const validInput = await this._validateInput(input);
const { runId, ...restConfig } = inputConfig;
const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store, streamModeSingle, cache, durability] = this._defaults(restConfig);
config.metadata = {
ls_integration: "langgraph",
...config.metadata
};
if (typeof config.context !== "undefined") config.context = await this._validateContext(config.context);
else config.configurable = await this._validateContext(config.configurable);
const stream = new require_stream.IterableReadableWritableStream({ modes: new Set(streamMode) });
if (this.checkpointer === true) {
config.configurable ??= {};
const ns = config.configurable["checkpoint_ns"] ?? "";
config.configurable[require_constants.CONFIG_KEY_CHECKPOINT_NS] = ns.split("|").map((part) => part.split(":")[0]).join("|");
}
if (streamMode.includes("messages")) {
const messageStreamer = isV3 ? new require_messages_v2.StreamProtocolMessagesHandler((chunk) => stream.push(chunk)) : new require_messages.StreamMessagesHandler((chunk) => stream.push(chunk));
const { callbacks } = config;
if (callbacks === void 0) config.callbacks = [messageStreamer];
else if (Array.isArray(callbacks)) config.callbacks = callbacks.concat(messageStreamer);
else {
const copiedCallbacks = callbacks.copy();
copiedCallbacks.addHandler(messageStreamer, true);
config.callbacks = copiedCallbacks;
}
}
if (streamMode.includes("tools")) {
const toolStreamer = new require_stream.StreamToolsHandler((chunk) => stream.push(chunk));
const { callbacks } = config;
if (callbacks === void 0) config.callbacks = [toolStreamer];
else if (Array.isArray(callbacks)) config.callbacks = callbacks.concat(toolStreamer);
else {
const copiedCallbacks = callbacks.copy();
copiedCallbacks.addHandler(toolStreamer, true);
config.callbacks = copiedCallbacks;
}
}
config.writer ??= (chunk) => {
if (!streamMode.includes("custom")) return;
const ns = (require_config.getConfig()?.configurable?.[require_constants.CONFIG_KEY_CHECKPOINT_NS])?.split("|").slice(0, -1);
stream.push([
ns ?? [],
"custom",
chunk
]);
};
config.interrupt ??= this.userInterrupt ?? require_interrupt.interrupt;
if (config.serverInfo == null) config.serverInfo = _buildServerInfo(config);
config.control ??= new require_runtime.RunControl();
const callbackManagerOptions = { tracerInheritableMetadata: _getTracingMetadataDefaults(config) };
const runManager = await (await _langchain_core_callbacks_manager.CallbackManager._configureSync(config?.callbacks, void 0, config?.tags, void 0, config?.metadata, void 0, callbackManagerOptions))?.handleChainStart(this.toJSON(), require_index._coerceToDict(input, "input"), runId, void 0, void 0, void 0, config?.runName ?? this.getName());
const channelSpecs = require_base.getOnlyChannels(this.channels);
let loop;
let loopError;
/**
* The PregelLoop will yield events from concurrent tasks as soon as they are
* generated. Each task can push multiple events onto the stream in any order.
*
* We use a separate background method and stream here in order to yield events
* from the loop to the main stream and therefore back to the user as soon as
* they are available.
*/
const createAndRunLoop = async () => {
try {
loop = await require_loop.PregelLoop.initialize({
input: validInput,
config,
checkpointer,
nodes: this.nodes,
channelSpecs,
outputKeys,
streamKeys: this.streamChannelsAsIs,
store,
cache,
stream,
interruptAfter,
interruptBefore,
manager: runManager,
debug: this.debug,
triggerToNodes: this.triggerToNodes,
durability
});
const runner = new require_runner.PregelRunner({
loop,
nodeFinished: config.configurable?.[require_constants.CONFIG_KEY_NODE_FINISHED]
});
if (options?.subgraphs) loop.config.configurable = {
...loop.config.configurable,
[require_constants.CONFIG_KEY_STREAM]: loop.stream
};
await this._runLoop({
loop,
runner,
debug,
config
});
if (durability === "sync") await Promise.all(loop?.checkpointerPromises ?? []);
} catch (e) {
loopError = e;
} finally {
try {
if (loop) {
await loop.store?.stop();
await loop.cache?.stop();
}
await Promise.all(loop?.checkpointerPromises ?? []);
} catch (e) {
loopError = loopError ?? e;
}
if (loopError) {
await new Promise((resolve) => {
queueMicrotask(resolve);
});
stream.error(loopError);
} else stream.close();
}
};
const runLoopPromise = createAndRunLoop();
try {
for await (const chunk of stream) {
if (chunk === void 0) throw new Error("Data structure error.");
const [namespace, mode, payload] = chunk;
const isStreamEvents = "version" in (options ?? {});
if (streamMode.includes(mode) || mode === "checkpoints" && require_convert.isCheckpointEnvelope(payload) && (isV3 || isStreamEvents && streamSubgraphs && stre