@langchain/langgraph
Version:
790 lines (789 loc) • 29 kB
JavaScript
import { CACHE_NS_WRITES, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_NODE_ERROR, CONFIG_KEY_PREVIOUS_STATE, CONFIG_KEY_READ, CONFIG_KEY_RESUME_MAP, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_SEND, CONFIG_KEY_TASK_ID, ERROR, ERROR_SOURCE_NODE, INTERRUPT, NO_WRITES, PREVIOUS, PULL, PUSH, RESERVED, RESUME, RETURN, Send, TAG_HIDDEN, TASKS, _isSend, _isSendInterface } from "../constants.js";
import { EmptyChannelError, InvalidUpdateError, NodeError } from "../errors.js";
import { createCheckpoint, emptyChannels, getOnlyChannels, isDeltaChannel } from "../channels/base.js";
import { XXH3 } from "../hash.js";
import { readChannel, readChannels } from "./io.js";
import { isCall } from "./types.js";
import { getNullChannelVersion } from "./utils/index.js";
import { getRunnableForFunc } from "./call.js";
import { copyCheckpoint, maxChannelVersion, uuid5 } from "@langchain/langgraph-checkpoint";
import { mergeConfigs, patchConfig } from "@langchain/core/runnables";
//#region src/pregel/algo.ts
const increment = (current) => {
return current !== void 0 ? current + 1 : 1;
};
function triggersNextStep(updatedChannels, triggerToNodes) {
if (triggerToNodes == null) return false;
for (const chan of updatedChannels) if (triggerToNodes[chan]) return true;
return false;
}
function maxChannelMapVersion(channelVersions) {
let maxVersion;
for (const chan in channelVersions) {
if (!Object.prototype.hasOwnProperty.call(channelVersions, chan)) continue;
if (maxVersion == null) maxVersion = channelVersions[chan];
else maxVersion = maxChannelVersion(maxVersion, channelVersions[chan]);
}
return maxVersion;
}
function shouldInterrupt(checkpoint, interruptNodes, tasks) {
const nullVersion = getNullChannelVersion(checkpoint.channel_versions);
const seen = checkpoint.versions_seen["__interrupt__"] ?? {};
let anyChannelUpdated = false;
if ((checkpoint.channel_versions["__start__"] ?? nullVersion) > (seen["__start__"] ?? nullVersion)) anyChannelUpdated = true;
else for (const chan in checkpoint.channel_versions) {
if (!Object.prototype.hasOwnProperty.call(checkpoint.channel_versions, chan)) continue;
if (checkpoint.channel_versions[chan] > (seen[chan] ?? nullVersion)) {
anyChannelUpdated = true;
break;
}
}
const anyTriggeredNodeInInterruptNodes = tasks.some((task) => interruptNodes === "*" ? !task.config?.tags?.includes(TAG_HIDDEN) : interruptNodes.includes(task.name));
return anyChannelUpdated && anyTriggeredNodeInInterruptNodes;
}
function _localRead(checkpoint, channels, task, select, fresh = false) {
let updated = /* @__PURE__ */ new Set();
if (!Array.isArray(select)) {
for (const [c] of task.writes) if (c === select) {
updated = new Set([c]);
break;
}
updated = updated || /* @__PURE__ */ new Set();
} else updated = new Set(select.filter((c) => task.writes.some(([key, _]) => key === c)));
let values;
if (fresh && updated.size > 0) {
const localChannels = Object.fromEntries(Object.entries(channels).filter(([k, _]) => updated.has(k)));
const channelsToSnapshot = /* @__PURE__ */ new Set();
for (const k in localChannels) {
if (!Object.prototype.hasOwnProperty.call(localChannels, k)) continue;
const ch = localChannels[k];
if (isDeltaChannel(ch) && ch.isAvailable()) channelsToSnapshot.add(k);
}
const newCheckpoint = createCheckpoint(checkpoint, localChannels, -1, { channelsToSnapshot });
const newChannels = emptyChannels(localChannels, newCheckpoint);
_applyWrites(copyCheckpoint(newCheckpoint), newChannels, [task], void 0, void 0);
values = readChannels({
...channels,
...newChannels
}, select);
} else values = readChannels(channels, select);
return values;
}
function _localWrite(commit, processes, writes) {
for (const [chan, value] of writes) if (["__pregel_push", "__pregel_tasks"].includes(chan) && value != null) {
if (!_isSend(value)) throw new InvalidUpdateError(`Invalid packet type, expected SendProtocol, got ${JSON.stringify(value)}`);
if (!(value.node in processes)) throw new InvalidUpdateError(`Invalid node name "${value.node}" in Send packet`);
}
commit(writes);
}
const IGNORE = new Set([
NO_WRITES,
PUSH,
RESUME,
INTERRUPT,
RETURN,
ERROR,
ERROR_SOURCE_NODE
]);
const RESERVED_SET = new Set(RESERVED);
function _applyWrites(checkpoint, channels, tasks, getNextVersion, triggerToNodes) {
const pathCache = /* @__PURE__ */ new Map();
for (const task of tasks) pathCache.set(task, task.path?.slice(0, 3) || []);
tasks.sort((a, b) => {
const aPath = pathCache.get(a);
const bPath = pathCache.get(b);
for (let i = 0; i < Math.min(aPath.length, bPath.length); i += 1) {
if (aPath[i] < bPath[i]) return -1;
if (aPath[i] > bPath[i]) return 1;
}
return aPath.length - bPath.length;
});
const onlyChannels = getOnlyChannels(channels);
let bumpStep = false;
const channelsToConsume = /* @__PURE__ */ new Set();
for (const task of tasks) {
if (task.triggers.length > 0) bumpStep = true;
checkpoint.versions_seen[task.name] ??= {};
for (const chan of task.triggers) {
if (chan in checkpoint.channel_versions) checkpoint.versions_seen[task.name][chan] = checkpoint.channel_versions[chan];
if (!RESERVED_SET.has(chan)) channelsToConsume.add(chan);
}
}
let maxVersion = maxChannelMapVersion(checkpoint.channel_versions);
let usedNewVersion = false;
for (const chan of channelsToConsume) if (chan in onlyChannels && onlyChannels[chan].consume()) {
if (getNextVersion !== void 0) {
checkpoint.channel_versions[chan] = getNextVersion(maxVersion);
usedNewVersion = true;
}
}
const pendingWritesByChannel = {};
const pendingWriteTaskIdsByChannel = {};
for (const task of tasks) {
const taskId = task.id ?? "";
for (const [chan, val] of task.writes) if (IGNORE.has(chan)) {} else if (chan in onlyChannels) {
pendingWritesByChannel[chan] ??= [];
pendingWritesByChannel[chan].push(val);
pendingWriteTaskIdsByChannel[chan] ??= [];
pendingWriteTaskIdsByChannel[chan].push(taskId);
}
}
for (const [chan, vals] of Object.entries(pendingWritesByChannel)) {
if (vals.length < 2) continue;
if (onlyChannels[chan]?.lc_graph_name !== "DeltaChannel") continue;
const taskIds = pendingWriteTaskIdsByChannel[chan];
const paired = vals.map((val, i) => ({
val,
taskId: taskIds[i]
}));
paired.sort((a, b) => a.taskId < b.taskId ? -1 : a.taskId > b.taskId ? 1 : 0);
pendingWritesByChannel[chan] = paired.map((p) => p.val);
}
if (maxVersion != null && getNextVersion != null) maxVersion = usedNewVersion ? getNextVersion(maxVersion) : maxVersion;
const updatedChannels = /* @__PURE__ */ new Set();
for (const [chan, vals] of Object.entries(pendingWritesByChannel)) if (chan in onlyChannels) {
const channel = onlyChannels[chan];
let updated;
try {
updated = channel.update(vals);
} catch (e) {
if (e.name === InvalidUpdateError.unminifiable_name) {
const wrappedError = new InvalidUpdateError(`Invalid update for channel "${chan}" with values ${JSON.stringify(vals)}: ${e.message}`);
wrappedError.lc_error_code = e.lc_error_code;
throw wrappedError;
} else throw e;
}
if (updated && getNextVersion !== void 0) {
checkpoint.channel_versions[chan] = getNextVersion(maxVersion);
if (channel.isAvailable()) updatedChannels.add(chan);
}
}
if (bumpStep) for (const chan in onlyChannels) {
if (!Object.prototype.hasOwnProperty.call(onlyChannels, chan)) continue;
const channel = onlyChannels[chan];
if (channel.isAvailable() && !updatedChannels.has(chan)) {
if (channel.update([]) && getNextVersion !== void 0) {
checkpoint.channel_versions[chan] = getNextVersion(maxVersion);
if (channel.isAvailable()) updatedChannels.add(chan);
}
}
}
if (bumpStep && !triggersNextStep(updatedChannels, triggerToNodes)) for (const chan in onlyChannels) {
if (!Object.prototype.hasOwnProperty.call(onlyChannels, chan)) continue;
const channel = onlyChannels[chan];
if (channel.finish() && getNextVersion !== void 0) {
checkpoint.channel_versions[chan] = getNextVersion(maxVersion);
if (channel.isAvailable()) updatedChannels.add(chan);
}
}
return updatedChannels;
}
function* candidateNodes(checkpoint, processes, extra) {
if (extra.updatedChannels != null && extra.triggerToNodes != null) {
const triggeredNodes = /* @__PURE__ */ new Set();
for (const channel of extra.updatedChannels) {
const nodeIds = extra.triggerToNodes[channel];
for (const id of nodeIds ?? []) triggeredNodes.add(id);
}
yield* [...triggeredNodes].sort();
return;
}
if ((() => {
for (const chan in checkpoint.channel_versions) if (checkpoint.channel_versions[chan] !== null) return false;
return true;
})()) return;
for (const name in processes) {
if (!Object.prototype.hasOwnProperty.call(processes, name)) continue;
yield name;
}
}
/**
* Build an index over pendingWrites for O(1) lookups.
*
* @internal Exported for benchmarks and regression tests only.
*/
function _indexPendingWrites(pendingWrites) {
let nullResume;
const resumeByTaskId = /* @__PURE__ */ new Map();
const successfulWriteTaskIds = /* @__PURE__ */ new Set();
if (pendingWrites) for (const [tid, chan, val] of pendingWrites) {
if (tid === "00000000-0000-0000-0000-000000000000" && chan === "__resume__" && nullResume === void 0) nullResume = val;
if (chan === "__resume__" && tid !== "00000000-0000-0000-0000-000000000000") {
let arr = resumeByTaskId.get(tid);
if (!arr) {
arr = [];
resumeByTaskId.set(tid, arr);
}
arr.push(val);
}
if (chan !== "__error__") successfulWriteTaskIds.add(tid);
}
return {
nullResume,
resumeByTaskId,
successfulWriteTaskIds
};
}
/**
* Prepare the set of tasks that will make up the next Pregel step.
* This is the union of all PUSH tasks (Sends) and PULL tasks (nodes triggered
* by edges).
*/
function _prepareNextTasks(checkpoint, pendingWrites, processes, channels, config, forExecution, extra) {
const tasks = {};
const indexedExtra = extra.pendingWritesIndex ? extra : {
...extra,
pendingWritesIndex: _indexPendingWrites(pendingWrites)
};
const tasksChannel = channels[TASKS];
if (tasksChannel?.isAvailable()) {
const len = tasksChannel.get().length;
for (let i = 0; i < len; i += 1) {
const task = _prepareSingleTask([PUSH, i], checkpoint, pendingWrites, processes, channels, config, forExecution, indexedExtra);
if (task !== void 0) tasks[task.id] = task;
}
}
for (const name of candidateNodes(checkpoint, processes, indexedExtra)) {
const task = _prepareSingleTask([PULL, name], checkpoint, pendingWrites, processes, channels, config, forExecution, indexedExtra);
if (task !== void 0) tasks[task.id] = task;
}
return tasks;
}
/**
* Prepares a single task for the next Pregel step, given a task path, which
* uniquely identifies a PUSH or PULL task within the graph.
*/
function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, channels, config, forExecution, extra) {
const { step, checkpointer, manager } = extra;
const configurable = config.configurable ?? {};
const parentNamespace = configurable.checkpoint_ns ?? "";
if (taskPath[0] === "__pregel_push" && isCall(taskPath[taskPath.length - 1])) {
const call = taskPath[taskPath.length - 1];
const proc = getRunnableForFunc(call.name, call.func);
const triggers = [PUSH];
const checkpointNamespace = parentNamespace === "" ? call.name : `${parentNamespace}|${call.name}`;
const id = uuid5(JSON.stringify([
checkpointNamespace,
step.toString(),
call.name,
PUSH,
taskPath[1],
taskPath[2]
]), checkpoint.id);
const taskCheckpointNamespace = `${checkpointNamespace}:${id}`;
const outputTaskPath = [...taskPath.slice(0, 3), true];
const metadata = {
langgraph_step: step,
langgraph_node: call.name,
langgraph_triggers: triggers,
langgraph_path: outputTaskPath,
langgraph_checkpoint_ns: taskCheckpointNamespace,
checkpoint_ns: taskCheckpointNamespace
};
if (forExecution) {
const writes = [];
const executionInfo = {
checkpointId: checkpoint.id,
checkpointNs: taskCheckpointNamespace,
taskId: id,
threadId: configurable.thread_id,
runId: config.runId != null ? String(config.runId) : void 0,
nodeAttempt: 1
};
return {
name: call.name,
input: call.input,
proc,
writes,
config: {
...patchConfig(mergeConfigs(config, {
metadata,
store: extra.store ?? config.store
}), {
runName: call.name,
callbacks: manager?.getChild(`graph:step:${step}`),
configurable: {
[CONFIG_KEY_TASK_ID]: id,
[CONFIG_KEY_SEND]: (writes_) => _localWrite((items) => writes.push(...items), processes, writes_),
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(checkpoint, channels, {
name: call.name,
writes,
triggers,
path: outputTaskPath
}, select_, fresh_),
[CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable["__pregel_checkpointer"],
[CONFIG_KEY_CHECKPOINT_MAP]: {
...configurable[CONFIG_KEY_CHECKPOINT_MAP],
[parentNamespace]: checkpoint.id
},
[CONFIG_KEY_SCRATCHPAD]: _scratchpad({
pendingWrites: pendingWrites ?? [],
taskId: id,
currentTaskInput: call.input,
resumeMap: config.configurable?.[CONFIG_KEY_RESUME_MAP],
namespaceHash: XXH3(taskCheckpointNamespace),
pendingWritesIndex: extra.pendingWritesIndex
}),
[CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[PREVIOUS],
checkpoint_id: void 0,
checkpoint_ns: taskCheckpointNamespace
}
}),
executionInfo
},
triggers,
retry_policy: call.retry,
cache_key: call.cache ? {
key: XXH3((call.cache.keyFunc ?? JSON.stringify)([call.input])),
ns: [CACHE_NS_WRITES, call.name ?? "__dynamic__"],
ttl: call.cache.ttl
} : void 0,
id,
path: outputTaskPath,
writers: [],
timeout: call.timeout
};
} else return {
id,
name: call.name,
interrupts: [],
path: outputTaskPath
};
} else if (taskPath[0] === "__pregel_push") {
const index = typeof taskPath[1] === "number" ? taskPath[1] : parseInt(taskPath[1], 10);
if (!channels["__pregel_tasks"]?.isAvailable()) return;
const sends = channels[TASKS].get();
if (index < 0 || index >= sends.length) return;
const packet = _isSendInterface(sends[index]) && !_isSend(sends[index]) ? new Send(sends[index].node, sends[index].args, sends[index].timeout !== void 0 ? { timeout: sends[index].timeout } : void 0) : sends[index];
if (!_isSendInterface(packet)) {
console.warn(`Ignoring invalid packet ${JSON.stringify(packet)} in pending sends.`);
return;
}
if (!(packet.node in processes)) {
console.warn(`Ignoring unknown node name ${packet.node} in pending sends.`);
return;
}
const triggers = [PUSH];
const checkpointNamespace = parentNamespace === "" ? packet.node : `${parentNamespace}|${packet.node}`;
const taskId = uuid5(JSON.stringify([
checkpointNamespace,
step.toString(),
packet.node,
PUSH,
index.toString()
]), checkpoint.id);
const taskCheckpointNamespace = `${checkpointNamespace}:${taskId}`;
let metadata = {
langgraph_step: step,
langgraph_node: packet.node,
langgraph_triggers: triggers,
langgraph_path: taskPath.slice(0, 3),
langgraph_checkpoint_ns: taskCheckpointNamespace,
checkpoint_ns: taskCheckpointNamespace
};
if (forExecution) {
const proc = processes[packet.node];
const node = proc.getNode();
if (node !== void 0) {
if (proc.metadata !== void 0) metadata = {
...metadata,
...proc.metadata
};
const writes = [];
const executionInfo = {
checkpointId: checkpoint.id,
checkpointNs: taskCheckpointNamespace,
taskId,
threadId: configurable.thread_id,
runId: config.runId != null ? String(config.runId) : void 0,
nodeAttempt: 1
};
return {
name: packet.node,
input: packet.args,
proc: node,
subgraphs: proc.subgraphs,
writes,
config: {
...patchConfig(mergeConfigs(config, {
metadata,
tags: proc.tags,
store: extra.store ?? config.store
}), {
runName: packet.node,
callbacks: manager?.getChild(`graph:step:${step}`),
configurable: {
[CONFIG_KEY_TASK_ID]: taskId,
[CONFIG_KEY_SEND]: (writes_) => _localWrite((items) => writes.push(...items), processes, writes_),
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(checkpoint, channels, {
name: packet.node,
writes,
triggers,
path: taskPath
}, select_, fresh_),
[CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable["__pregel_checkpointer"],
[CONFIG_KEY_CHECKPOINT_MAP]: {
...configurable[CONFIG_KEY_CHECKPOINT_MAP],
[parentNamespace]: checkpoint.id
},
[CONFIG_KEY_SCRATCHPAD]: _scratchpad({
pendingWrites: pendingWrites ?? [],
taskId,
currentTaskInput: packet.args,
resumeMap: config.configurable?.[CONFIG_KEY_RESUME_MAP],
namespaceHash: XXH3(taskCheckpointNamespace),
pendingWritesIndex: extra.pendingWritesIndex
}),
[CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[PREVIOUS],
checkpoint_id: void 0,
checkpoint_ns: taskCheckpointNamespace
}
}),
executionInfo
},
triggers,
retry_policy: proc.retryPolicy,
cache_key: proc.cachePolicy ? {
key: XXH3((proc.cachePolicy.keyFunc ?? JSON.stringify)([packet.args])),
ns: [
CACHE_NS_WRITES,
proc.name ?? "__dynamic__",
packet.node
],
ttl: proc.cachePolicy.ttl
} : void 0,
id: taskId,
path: taskPath,
writers: proc.getWriters(),
timeout: packet.timeout ?? proc.timeout
};
}
} else return {
id: taskId,
name: packet.node,
interrupts: [],
path: taskPath
};
} else if (taskPath[0] === "__pregel_pull") {
const name = taskPath[1].toString();
const proc = processes[name];
if (proc === void 0) return;
if (pendingWrites?.length) {
const checkpointNamespace = parentNamespace === "" ? name : `${parentNamespace}|${name}`;
const taskId = uuid5(JSON.stringify([
checkpointNamespace,
step.toString(),
name,
PULL,
name
]), checkpoint.id);
if (extra.pendingWritesIndex ? extra.pendingWritesIndex.successfulWriteTaskIds.has(taskId) : pendingWrites.some((w) => w[0] === taskId && w[1] !== "__error__")) return;
}
const nullVersion = getNullChannelVersion(checkpoint.channel_versions);
if (nullVersion === void 0) return;
const seen = checkpoint.versions_seen[name] ?? {};
const trigger = proc.triggers.find((chan) => {
if (!channels[chan].isAvailable()) return false;
return (checkpoint.channel_versions[chan] ?? nullVersion) > (seen[chan] ?? nullVersion);
});
if (trigger !== void 0) {
const val = _procInput(proc, channels, forExecution);
if (val === void 0) return;
const checkpointNamespace = parentNamespace === "" ? name : `${parentNamespace}|${name}`;
const taskId = uuid5(JSON.stringify([
checkpointNamespace,
step.toString(),
name,
PULL,
[trigger]
]), checkpoint.id);
const taskCheckpointNamespace = `${checkpointNamespace}:${taskId}`;
let metadata = {
langgraph_step: step,
langgraph_node: name,
langgraph_triggers: [trigger],
langgraph_path: taskPath,
langgraph_checkpoint_ns: taskCheckpointNamespace,
checkpoint_ns: taskCheckpointNamespace
};
if (forExecution) {
const node = proc.getNode();
if (node !== void 0) {
if (proc.metadata !== void 0) metadata = {
...metadata,
...proc.metadata
};
const writes = [];
const executionInfo = {
checkpointId: checkpoint.id,
checkpointNs: taskCheckpointNamespace,
taskId,
threadId: configurable.thread_id,
runId: config.runId != null ? String(config.runId) : void 0,
nodeAttempt: 1
};
return {
name,
input: val,
proc: node,
subgraphs: proc.subgraphs,
writes,
config: {
...patchConfig(mergeConfigs(config, {
metadata,
tags: proc.tags,
store: extra.store ?? config.store
}), {
runName: name,
callbacks: manager?.getChild(`graph:step:${step}`),
configurable: {
[CONFIG_KEY_TASK_ID]: taskId,
[CONFIG_KEY_SEND]: (writes_) => _localWrite((items) => {
writes.push(...items);
}, processes, writes_),
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(checkpoint, channels, {
name,
writes,
triggers: [trigger],
path: taskPath
}, select_, fresh_),
[CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable["__pregel_checkpointer"],
[CONFIG_KEY_CHECKPOINT_MAP]: {
...configurable[CONFIG_KEY_CHECKPOINT_MAP],
[parentNamespace]: checkpoint.id
},
[CONFIG_KEY_SCRATCHPAD]: _scratchpad({
pendingWrites: pendingWrites ?? [],
taskId,
currentTaskInput: val,
resumeMap: config.configurable?.[CONFIG_KEY_RESUME_MAP],
namespaceHash: XXH3(taskCheckpointNamespace),
pendingWritesIndex: extra.pendingWritesIndex
}),
[CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[PREVIOUS],
checkpoint_id: void 0,
checkpoint_ns: taskCheckpointNamespace
}
}),
executionInfo
},
triggers: [trigger],
retry_policy: proc.retryPolicy,
cache_key: proc.cachePolicy ? {
key: XXH3((proc.cachePolicy.keyFunc ?? JSON.stringify)([val])),
ns: [
CACHE_NS_WRITES,
proc.name ?? "__dynamic__",
name
],
ttl: proc.cachePolicy.ttl
} : void 0,
id: taskId,
path: taskPath,
writers: proc.getWriters(),
timeout: proc.timeout
};
}
} else return {
id: taskId,
name,
interrupts: [],
path: taskPath
};
}
}
}
/**
* Prepare an immediate node-level error handler task for a failed task.
*
* The handler runs only after the failed node's retry policy is exhausted (the
* runner schedules it once a non-bubble-up error settles). It is prepared like
* a PUSH task targeting the auto-generated handler node, receives the failed
* node's input, and is injected with a {@link NodeError} under
* {@link CONFIG_KEY_NODE_ERROR} so the handler can inspect the failure
* provenance (and route via `Command({ goto })`).
*
* @internal
*/
function _prepareNodeErrorHandlerTask(failedTask, handlerNodeName, error, checkpoint, pendingWrites, processes, channels, config, extra) {
const { step, checkpointer, manager } = extra;
const proc = processes[handlerNodeName];
if (proc === void 0) return;
const node = proc.getNode();
if (node === void 0) return;
const configurable = config.configurable ?? {};
const parentNamespace = configurable.checkpoint_ns ?? "";
const triggers = [PUSH];
const checkpointNamespace = parentNamespace === "" ? handlerNodeName : `${parentNamespace}|${handlerNodeName}`;
const taskId = uuid5(JSON.stringify([
checkpointNamespace,
step.toString(),
handlerNodeName,
PUSH,
"node_error_handler",
failedTask.id
]), checkpoint.id);
const taskCheckpointNamespace = `${checkpointNamespace}:${taskId}`;
const taskPath = [
PUSH,
String(failedTask.name),
handlerNodeName,
false
];
let metadata = {
langgraph_step: step,
langgraph_node: handlerNodeName,
langgraph_triggers: triggers,
langgraph_path: taskPath,
langgraph_checkpoint_ns: taskCheckpointNamespace,
checkpoint_ns: taskCheckpointNamespace
};
if (proc.metadata !== void 0) metadata = {
...metadata,
...proc.metadata
};
const writes = [];
const executionInfo = {
checkpointId: checkpoint.id,
checkpointNs: taskCheckpointNamespace,
taskId,
threadId: configurable.thread_id,
runId: config.runId != null ? String(config.runId) : void 0,
nodeAttempt: 1
};
return {
name: handlerNodeName,
input: failedTask.input,
proc: node,
subgraphs: proc.subgraphs,
writes,
config: {
...patchConfig(mergeConfigs(config, {
metadata,
tags: proc.tags,
store: extra.store ?? config.store
}), {
runName: handlerNodeName,
callbacks: manager?.getChild(`graph:step:${step}`),
configurable: {
[CONFIG_KEY_TASK_ID]: taskId,
[CONFIG_KEY_SEND]: (writes_) => _localWrite((items) => writes.push(...items), processes, writes_),
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(checkpoint, channels, {
name: handlerNodeName,
writes,
triggers,
path: taskPath
}, select_, fresh_),
[CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable["__pregel_checkpointer"],
[CONFIG_KEY_CHECKPOINT_MAP]: {
...configurable[CONFIG_KEY_CHECKPOINT_MAP],
[parentNamespace]: checkpoint.id
},
[CONFIG_KEY_SCRATCHPAD]: _scratchpad({
pendingWrites: pendingWrites ?? [],
taskId,
currentTaskInput: failedTask.input,
resumeMap: config.configurable?.[CONFIG_KEY_RESUME_MAP],
namespaceHash: XXH3(taskCheckpointNamespace)
}),
[CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[PREVIOUS],
[CONFIG_KEY_NODE_ERROR]: new NodeError(String(failedTask.name), error),
checkpoint_id: void 0,
checkpoint_ns: taskCheckpointNamespace
}
}),
executionInfo
},
triggers,
retry_policy: proc.retryPolicy,
cache_key: void 0,
id: taskId,
path: taskPath,
writers: proc.getWriters()
};
}
/**
* Function injected under CONFIG_KEY_READ in task config, to read current state.
* Used by conditional edges to read a copy of the state with reflecting the writes
* from that node only.
*
* @internal
*/
function _procInput(proc, channels, forExecution) {
let val;
if (typeof proc.channels === "object" && !Array.isArray(proc.channels)) {
val = {};
for (const [k, chan] of Object.entries(proc.channels)) if (proc.triggers.includes(chan)) try {
val[k] = readChannel(channels, chan, false);
} catch (e) {
if (e.name === EmptyChannelError.unminifiable_name) return;
else throw e;
}
else if (chan in channels) try {
val[k] = readChannel(channels, chan, false);
} catch (e) {
if (e.name === EmptyChannelError.unminifiable_name) continue;
else throw e;
}
} else if (Array.isArray(proc.channels)) {
let successfulRead = false;
for (const chan of proc.channels) try {
val = readChannel(channels, chan, false);
successfulRead = true;
break;
} catch (e) {
if (e.name === EmptyChannelError.unminifiable_name) continue;
else throw e;
}
if (!successfulRead) return;
} else throw new Error(`Invalid channels type, expected list or dict, got ${proc.channels}`);
if (forExecution && proc.mapper !== void 0) val = proc.mapper(val);
return val;
}
/**
* Remove any values belonging to UntrackedValue channels from a Send packet
* before checkpointing.
*
* Send is often called with state to be passed to the destination node,
* which may contain UntrackedValues at the top level.
*
* @internal
*/
function sanitizeUntrackedValuesInSend(packet, channels) {
if (typeof packet.args !== "object" || packet.args === null) return packet;
const sanitizedArg = {};
for (const [key, value] of Object.entries(packet.args)) {
const channel = channels[key];
if (!channel || channel.lc_graph_name !== "UntrackedValue") sanitizedArg[key] = value;
}
return new Send(packet.node, sanitizedArg);
}
function _scratchpad({ pendingWrites, taskId, currentTaskInput, resumeMap, namespaceHash, pendingWritesIndex }) {
const nullResume = pendingWritesIndex ? pendingWritesIndex.nullResume : pendingWrites.find(([writeTaskId, chan]) => writeTaskId === "00000000-0000-0000-0000-000000000000" && chan === "__resume__")?.[2];
const scratchpad = {
callCounter: 0,
interruptCounter: -1,
resume: (() => {
const result = pendingWritesIndex ? (pendingWritesIndex.resumeByTaskId.get(taskId) ?? []).flat() : pendingWrites.filter(([writeTaskId, chan]) => writeTaskId === taskId && chan === "__resume__").flatMap(([_writeTaskId, _chan, resume]) => resume);
if (resumeMap != null && namespaceHash in resumeMap) {
const mappedResume = resumeMap[namespaceHash];
result.push(mappedResume);
}
return result;
})(),
nullResume,
subgraphCounter: 0,
currentTaskInput,
consumeNullResume: () => {
if (scratchpad.nullResume) {
delete scratchpad.nullResume;
pendingWrites.splice(pendingWrites.findIndex(([writeTaskId, chan]) => writeTaskId === "00000000-0000-0000-0000-000000000000" && chan === "__resume__"), 1);
return nullResume;
}
}
};
return scratchpad;
}
//#endregion
export { _applyWrites, _localRead, _prepareNextTasks, _prepareNodeErrorHandlerTask, _prepareSingleTask, increment, sanitizeUntrackedValuesInSend, shouldInterrupt };
//# sourceMappingURL=algo.js.map