UNPKG

@langchain/langgraph

Version:

LangGraph

883 lines 35.7 kB
import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, WRITES_IDX_MAP, BaseCache, } from "@langchain/langgraph-checkpoint"; import { createCheckpoint, emptyChannels, } from "../channels/base.js"; import { isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, RESUME, TAG_HIDDEN, PUSH, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_CHECKPOINT_NS, } from "../constants.js"; import { _applyWrites, _prepareNextTasks, _prepareSingleTask, increment, shouldInterrupt, } from "./algo.js"; import { gatherIterator, gatherIteratorSync, prefixGenerator, } from "../utils.js"; import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels, } from "./io.js"; import { EmptyInputError, GraphInterrupt, isGraphInterrupt, } from "../errors.js"; import { getNewChannelVersions, patchConfigurable } from "./utils/index.js"; import { mapDebugTasks, mapDebugCheckpoint, mapDebugTaskResults, printStepTasks, } from "./debug.js"; import { IterableReadableWritableStream } from "./stream.js"; const INPUT_DONE = Symbol.for("INPUT_DONE"); const INPUT_RESUMING = Symbol.for("INPUT_RESUMING"); const DEFAULT_LOOP_LIMIT = 25; function createDuplexStream(...streams) { return new IterableReadableWritableStream({ passthroughFn: (value) => { for (const stream of streams) { if (stream.modes.has(value[1])) { stream.push(value); } } }, modes: new Set(streams.flatMap((s) => Array.from(s.modes))), }); } class AsyncBatchedCache extends BaseCache { constructor(cache) { super(); Object.defineProperty(this, "cache", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "queue", { enumerable: true, configurable: true, writable: true, value: Promise.resolve() }); this.cache = cache; } async get(keys) { return this.enqueueOperation("get", keys); } async set(pairs) { return this.enqueueOperation("set", pairs); } async clear(namespaces) { return this.enqueueOperation("clear", namespaces); } async stop() { await this.queue; } enqueueOperation(type, ...args) { const newPromise = this.queue.then(() => { // @ts-expect-error Tuple type warning return this.cache[type](...args); }); this.queue = newPromise.then(() => void 0, () => void 0); return newPromise; } } export class PregelLoop { get isResuming() { const hasChannelVersions = Object.keys(this.checkpoint.channel_versions).length !== 0; const configHasResumingFlag = this.config.configurable?.[CONFIG_KEY_RESUMING] !== undefined; const configIsResuming = configHasResumingFlag && this.config.configurable?.[CONFIG_KEY_RESUMING]; const inputIsNullOrUndefined = this.input === null || this.input === undefined; const inputIsCommandResuming = isCommand(this.input) && this.input.resume != null; const inputIsResuming = this.input === INPUT_RESUMING; return (hasChannelVersions && (configIsResuming || inputIsNullOrUndefined || inputIsCommandResuming || inputIsResuming)); } constructor(params) { // eslint-disable-next-line @typescript-eslint/no-explicit-any Object.defineProperty(this, "input", { enumerable: true, configurable: true, writable: true, value: void 0 }); // eslint-disable-next-line @typescript-eslint/no-explicit-any Object.defineProperty(this, "output", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpointer", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpointerGetNextVersion", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "channels", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "managed", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpoint", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpointConfig", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpointMetadata", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpointNamespace", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpointPendingWrites", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "checkpointPreviousVersions", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "step", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "stop", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "outputKeys", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "streamKeys", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "nodes", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "skipDoneTasks", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "prevCheckpointConfig", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "status", { enumerable: true, configurable: true, writable: true, value: "pending" }); // eslint-disable-next-line @typescript-eslint/no-explicit-any Object.defineProperty(this, "tasks", { enumerable: true, configurable: true, writable: true, value: {} }); // eslint-disable-next-line @typescript-eslint/no-explicit-any Object.defineProperty(this, "stream", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "checkpointerPromises", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "isNested", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "_checkpointerChainedPromise", { enumerable: true, configurable: true, writable: true, value: Promise.resolve() }); Object.defineProperty(this, "store", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "cache", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "manager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "interruptAfter", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "interruptBefore", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "toInterrupt", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(this, "debug", { enumerable: true, configurable: true, writable: true, value: false }); Object.defineProperty(this, "triggerToNodes", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.input = params.input; this.checkpointer = params.checkpointer; // TODO: if managed values no longer needs graph we can replace with // managed_specs, channel_specs if (this.checkpointer !== undefined) { this.checkpointerGetNextVersion = this.checkpointer.getNextVersion.bind(this.checkpointer); } else { this.checkpointerGetNextVersion = increment; } this.checkpoint = params.checkpoint; this.checkpointMetadata = params.checkpointMetadata; this.checkpointPreviousVersions = params.checkpointPreviousVersions; this.channels = params.channels; this.managed = params.managed; this.checkpointPendingWrites = params.checkpointPendingWrites; this.step = params.step; this.stop = params.stop; this.config = params.config; this.checkpointConfig = params.checkpointConfig; this.isNested = params.isNested; this.manager = params.manager; this.outputKeys = params.outputKeys; this.streamKeys = params.streamKeys; this.nodes = params.nodes; this.skipDoneTasks = params.skipDoneTasks; this.store = params.store; this.cache = params.cache ? new AsyncBatchedCache(params.cache) : undefined; this.stream = params.stream; this.checkpointNamespace = params.checkpointNamespace; this.prevCheckpointConfig = params.prevCheckpointConfig; this.interruptAfter = params.interruptAfter; this.interruptBefore = params.interruptBefore; this.debug = params.debug; this.triggerToNodes = params.triggerToNodes; } static async initialize(params) { let { config, stream } = params; if (stream !== undefined && config.configurable?.[CONFIG_KEY_STREAM] !== undefined) { stream = createDuplexStream(stream, config.configurable[CONFIG_KEY_STREAM]); } const skipDoneTasks = config.configurable ? !("checkpoint_id" in config.configurable) : true; const scratchpad = config.configurable?.[CONFIG_KEY_SCRATCHPAD]; if (config.configurable && scratchpad) { if (scratchpad.subgraphCounter > 0) { config = patchConfigurable(config, { [CONFIG_KEY_CHECKPOINT_NS]: [ config.configurable[CONFIG_KEY_CHECKPOINT_NS], scratchpad.subgraphCounter.toString(), ].join(CHECKPOINT_NAMESPACE_SEPARATOR), }); } scratchpad.subgraphCounter += 1; } const isNested = CONFIG_KEY_READ in (config.configurable ?? {}); if (!isNested && config.configurable?.checkpoint_ns !== undefined && config.configurable?.checkpoint_ns !== "") { config = patchConfigurable(config, { checkpoint_ns: "", checkpoint_id: undefined, }); } let checkpointConfig = config; if (config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP] !== undefined && config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP]?.[config.configurable?.checkpoint_ns]) { checkpointConfig = patchConfigurable(config, { checkpoint_id: config.configurable[CONFIG_KEY_CHECKPOINT_MAP][config.configurable?.checkpoint_ns], }); } const checkpointNamespace = config.configurable?.checkpoint_ns?.split(CHECKPOINT_NAMESPACE_SEPARATOR) ?? []; const saved = (await params.checkpointer?.getTuple(checkpointConfig)) ?? { config, checkpoint: emptyCheckpoint(), metadata: { source: "input", step: -2, writes: null, parents: {}, }, pendingWrites: [], }; checkpointConfig = { ...config, ...saved.config, configurable: { checkpoint_ns: "", ...config.configurable, ...saved.config.configurable, }, }; const prevCheckpointConfig = saved.parentConfig; const checkpoint = copyCheckpoint(saved.checkpoint); const checkpointMetadata = { ...saved.metadata }; const checkpointPendingWrites = saved.pendingWrites ?? []; const channels = emptyChannels(params.channelSpecs, checkpoint); const step = (checkpointMetadata.step ?? 0) + 1; const stop = step + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT) + 1; const checkpointPreviousVersions = { ...checkpoint.channel_versions }; const store = params.store ? new AsyncBatchedStore(params.store) : undefined; if (store) { // Start the store. This is a batch store, so it will run continuously store.start(); } return new PregelLoop({ input: params.input, config, checkpointer: params.checkpointer, checkpoint, checkpointMetadata, checkpointConfig, prevCheckpointConfig, checkpointNamespace, channels, managed: params.managed, isNested, manager: params.manager, skipDoneTasks, step, stop, checkpointPreviousVersions, checkpointPendingWrites, outputKeys: params.outputKeys ?? [], streamKeys: params.streamKeys ?? [], nodes: params.nodes, stream, store, cache: params.cache, interruptAfter: params.interruptAfter, interruptBefore: params.interruptBefore, debug: params.debug, triggerToNodes: params.triggerToNodes, }); } _checkpointerPutAfterPrevious(input) { this._checkpointerChainedPromise = this._checkpointerChainedPromise.then(() => { return this.checkpointer?.put(input.config, input.checkpoint, input.metadata, input.newVersions); }); this.checkpointerPromises.push(this._checkpointerChainedPromise); } // eslint-disable-next-line @typescript-eslint/no-explicit-any async updateManagedValues(key, values) { const mv = this.managed.get(key); if (mv && "update" in mv && typeof mv.update === "function") { await mv.update(values); } } /** * Put writes for a task, to be read by the next tick. * @param taskId * @param writes */ putWrites(taskId, writes) { let writesCopy = writes; if (writesCopy.length === 0) { return; } // deduplicate writes to special channels, last write wins if (writesCopy.every(([key]) => key in WRITES_IDX_MAP)) { writesCopy = Array.from(new Map(writesCopy.map((w) => [w[0], w])).values()); } // save writes for (const [c, v] of writesCopy) { const idx = this.checkpointPendingWrites.findIndex((w) => w[0] === taskId && w[1] === c); if (c in WRITES_IDX_MAP && idx !== -1) { this.checkpointPendingWrites[idx] = [taskId, c, v]; } else { this.checkpointPendingWrites.push([taskId, c, v]); } } const putWritePromise = this.checkpointer?.putWrites({ ...this.checkpointConfig, configurable: { ...this.checkpointConfig.configurable, checkpoint_ns: this.config.configurable?.checkpoint_ns ?? "", checkpoint_id: this.checkpoint.id, }, }, writesCopy, taskId); if (putWritePromise !== undefined) { this.checkpointerPromises.push(putWritePromise); } if (this.tasks) { this._outputWrites(taskId, writesCopy); } if (!writes.length || !this.cache || !this.tasks) { return; } // only cache tasks with a cache key const task = this.tasks[taskId]; if (task == null || task.cache_key == null) { return; } // only cache successful tasks if (writes[0][0] === ERROR || writes[0][0] === INTERRUPT) { return; } void this.cache.set([ { key: [task.cache_key.ns, task.cache_key.key], value: task.writes, ttl: task.cache_key.ttl, }, ]); } _outputWrites(taskId, writes, cached = false) { const task = this.tasks[taskId]; if (task !== undefined) { if (task.config !== undefined && (task.config.tags ?? []).includes(TAG_HIDDEN)) { return; } if (writes.length > 0 && writes[0][0] !== ERROR && writes[0][0] !== INTERRUPT) { this._emit(gatherIteratorSync(prefixGenerator(mapOutputUpdates(this.outputKeys, [[task, writes]], cached), "updates"))); } if (!cached) { this._emit(gatherIteratorSync(prefixGenerator(mapDebugTaskResults(this.step, [[task, writes]], this.streamKeys), "debug"))); } } } async _matchCachedWrites() { if (!this.cache) return []; const matched = []; const serializeKey = ([ns, key]) => { return `ns:${ns.join(",")}|key:${key}`; }; const keys = []; const keyMap = {}; for (const task of Object.values(this.tasks)) { if (task.cache_key != null && !task.writes.length) { keys.push([task.cache_key.ns, task.cache_key.key]); keyMap[serializeKey([task.cache_key.ns, task.cache_key.key])] = task; } } if (keys.length === 0) return []; const cache = await this.cache.get(keys); for (const { key, value } of cache) { const task = keyMap[serializeKey(key)]; if (task != null) { // update the task with the cached writes task.writes.push(...value); matched.push({ task, result: value }); } } return matched; } /** * Execute a single iteration of the Pregel loop. * Returns true if more iterations are needed. * @param params */ async tick(params) { if (this.store && !this.store.isRunning) { this.store?.start(); } const { inputKeys = [] } = params; if (this.status !== "pending") { throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`); } if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) { await this._first(inputKeys); } else if (this.toInterrupt.length > 0) { this.status = "interrupt_before"; throw new GraphInterrupt(); } else if (Object.values(this.tasks).every((task) => task.writes.length > 0)) { // finish superstep const writes = Object.values(this.tasks).flatMap((t) => t.writes); // All tasks have finished const managedValueWrites = _applyWrites(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion, this.triggerToNodes); for (const [key, values] of Object.entries(managedValueWrites)) { await this.updateManagedValues(key, values); } // produce values output const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, writes, this.channels), "values")); this._emit(valuesOutput); // clear pending writes this.checkpointPendingWrites = []; await this._putCheckpoint({ source: "loop", writes: mapOutputUpdates(this.outputKeys, Object.values(this.tasks).map((task) => [task, task.writes])).next().value ?? null, }); // after execution, check if we should interrupt if (shouldInterrupt(this.checkpoint, this.interruptAfter, Object.values(this.tasks))) { this.status = "interrupt_after"; throw new GraphInterrupt(); } // unset resuming flag if (this.config.configurable?.[CONFIG_KEY_RESUMING] !== undefined) { delete this.config.configurable?.[CONFIG_KEY_RESUMING]; } } else { return false; } if (this.step > this.stop) { this.status = "out_of_steps"; return false; } const nextTasks = _prepareNextTasks(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, this.config, true, { step: this.step, checkpointer: this.checkpointer, isResuming: this.isResuming, manager: this.manager, store: this.store, stream: this.stream, }); this.tasks = nextTasks; // Produce debug output if (this.checkpointer) { this._emit(await gatherIterator(prefixGenerator(mapDebugCheckpoint(this.step - 1, // printing checkpoint for previous step this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, Object.values(this.tasks), this.checkpointPendingWrites, this.prevCheckpointConfig), "debug"))); } if (Object.values(this.tasks).length === 0) { this.status = "done"; return false; } // if there are pending writes from a previous loop, apply them if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) { for (const [tid, k, v] of this.checkpointPendingWrites) { if (k === ERROR || k === INTERRUPT || k === RESUME) { continue; } const task = Object.values(this.tasks).find((t) => t.id === tid); if (task) { task.writes.push([k, v]); } } for (const task of Object.values(this.tasks)) { if (task.writes.length > 0) { this._outputWrites(task.id, task.writes, true); } } } // if all tasks have finished, re-tick if (Object.values(this.tasks).every((task) => task.writes.length > 0)) { return this.tick({ inputKeys }); } // Before execution, check if we should interrupt if (shouldInterrupt(this.checkpoint, this.interruptBefore, Object.values(this.tasks))) { this.status = "interrupt_before"; throw new GraphInterrupt(); } // Produce debug output const debugOutput = await gatherIterator(prefixGenerator(mapDebugTasks(this.step, Object.values(this.tasks)), "debug")); this._emit(debugOutput); return true; } async finishAndHandleError(error) { const suppress = this._suppressInterrupt(error); if (suppress || error === undefined) { this.output = readChannels(this.channels, this.outputKeys); } if (suppress) { // emit one last "values" event, with pending writes applied if (this.tasks !== undefined && this.checkpointPendingWrites.length > 0 && Object.values(this.tasks).some((task) => task.writes.length > 0)) { const managedValueWrites = _applyWrites(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion, this.triggerToNodes); for (const [key, values] of Object.entries(managedValueWrites)) { await this.updateManagedValues(key, values); } this._emit(gatherIteratorSync(prefixGenerator(mapOutputValues(this.outputKeys, Object.values(this.tasks).flatMap((t) => t.writes), this.channels), "values"))); } // Emit INTERRUPT event const interrupts = { [INTERRUPT]: error.interrupts }; this._emit([ ["updates", interrupts], ["values", interrupts], ]); } return suppress; } async acceptPush(task, writeIdx, call) { if (this.interruptAfter?.length > 0 && shouldInterrupt(this.checkpoint, this.interruptAfter, [task])) { this.toInterrupt.push(task); return; } const pushed = _prepareSingleTask([PUSH, task.path ?? [], writeIdx, task.id, call], this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, task.config ?? {}, true, { step: this.step, checkpointer: this.checkpointer, manager: this.manager, store: this.store, stream: this.stream, }); if (!pushed) return; if (this.interruptBefore?.length > 0 && shouldInterrupt(this.checkpoint, this.interruptBefore, [pushed])) { this.toInterrupt.push(pushed); return; } this._emit(gatherIteratorSync(prefixGenerator(mapDebugTasks(this.step, [pushed]), "debug"))); if (this.debug) printStepTasks(this.step, [pushed]); this.tasks[pushed.id] = pushed; if (this.skipDoneTasks) this._matchWrites({ [pushed.id]: pushed }); const tasks = await this._matchCachedWrites(); for (const { task } of tasks) { this._outputWrites(task.id, task.writes, true); } return pushed; } _suppressInterrupt(e) { return isGraphInterrupt(e) && !this.isNested; } async _first(inputKeys) { /* * Resuming from previous checkpoint requires * - finding a previous checkpoint * - receiving null input (outer graph) or RESUMING flag (subgraph) */ const { configurable } = this.config; // take resume value from parent const scratchpad = configurable?.[CONFIG_KEY_SCRATCHPAD]; if (scratchpad && scratchpad.nullResume !== undefined) { this.putWrites(NULL_TASK_ID, [[RESUME, scratchpad.nullResume]]); } if (isCommand(this.input)) { const hasResume = this.input.resume != null; if (hasResume && this.checkpointer == null) { throw new Error("Cannot use Command(resume=...) without checkpointer"); } const writes = {}; // group writes by task id for (const [tid, key, value] of mapCommand(this.input, this.checkpointPendingWrites)) { if (writes[tid] === undefined) { writes[tid] = []; } writes[tid].push([key, value]); } if (Object.keys(writes).length === 0) { throw new EmptyInputError("Received empty Command input"); } // save writes for (const [tid, ws] of Object.entries(writes)) { this.putWrites(tid, ws); } } // apply null writes const nullWrites = (this.checkpointPendingWrites ?? []) .filter((w) => w[0] === NULL_TASK_ID) .map((w) => w.slice(1)); if (nullWrites.length > 0) { _applyWrites(this.checkpoint, this.channels, [ { name: INPUT, writes: nullWrites, triggers: [], }, ], this.checkpointerGetNextVersion, this.triggerToNodes); } const isCommandUpdateOrGoto = isCommand(this.input) && nullWrites.length > 0; if (this.isResuming || isCommandUpdateOrGoto) { for (const channelName of Object.keys(this.channels)) { if (this.checkpoint.channel_versions[channelName] !== undefined) { const version = this.checkpoint.channel_versions[channelName]; this.checkpoint.versions_seen[INTERRUPT] = { ...this.checkpoint.versions_seen[INTERRUPT], [channelName]: version, }; } } // produce values output const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, true, this.channels), "values")); this._emit(valuesOutput); } if (this.isResuming) { this.input = INPUT_RESUMING; } else if (isCommandUpdateOrGoto) { // we need to create a new checkpoint for Command(update=...) or Command(goto=...) // in case the result of Command(goto=...) is an interrupt. // If not done, the checkpoint containing the interrupt will be lost. await this._putCheckpoint({ source: "input", writes: {} }); this.input = INPUT_DONE; } else { // map inputs to channel updates const inputWrites = await gatherIterator(mapInput(inputKeys, this.input)); if (inputWrites.length > 0) { const discardTasks = _prepareNextTasks(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, this.config, true, { step: this.step }); _applyWrites(this.checkpoint, this.channels, Object.values(discardTasks).concat([ { name: INPUT, writes: inputWrites, triggers: [], }, ]), this.checkpointerGetNextVersion, this.triggerToNodes); // save input checkpoint await this._putCheckpoint({ source: "input", writes: Object.fromEntries(inputWrites), }); this.input = INPUT_DONE; } else if (!(CONFIG_KEY_RESUMING in (this.config.configurable ?? {}))) { throw new EmptyInputError(`Received no input writes for ${JSON.stringify(inputKeys, null, 2)}`); } else { // done with input this.input = INPUT_DONE; } } if (!this.isNested) { this.config = patchConfigurable(this.config, { [CONFIG_KEY_RESUMING]: this.isResuming, }); } } _emit(values) { for (const chunk of values) { if (this.stream.modes.has(chunk[0])) { this.stream.push([this.checkpointNamespace, ...chunk]); } } } async _putCheckpoint(inputMetadata) { // Assign step const metadata = { ...inputMetadata, step: this.step, parents: this.config.configurable?.[CONFIG_KEY_CHECKPOINT_MAP] ?? {}, }; // Bail if no checkpointer if (this.checkpointer !== undefined) { // store the previous checkpoint config for debug events this.prevCheckpointConfig = this.checkpointConfig?.configurable ?.checkpoint_id ? this.checkpointConfig : undefined; // create new checkpoint this.checkpointMetadata = metadata; // child graphs keep at most one checkpoint per parent checkpoint // this is achieved by writing child checkpoints as progress is made // (so that error recovery / resuming from interrupt don't lose work) // but doing so always with an id equal to that of the parent checkpoint this.checkpoint = createCheckpoint(this.checkpoint, this.channels, this.step); this.checkpointConfig = { ...this.checkpointConfig, configurable: { ...this.checkpointConfig.configurable, checkpoint_ns: this.config.configurable?.checkpoint_ns ?? "", }, }; const channelVersions = { ...this.checkpoint.channel_versions }; const newVersions = getNewChannelVersions(this.checkpointPreviousVersions, channelVersions); this.checkpointPreviousVersions = channelVersions; // save it, without blocking // if there's a previous checkpoint save in progress, wait for it // ensuring checkpointers receive checkpoints in order void this._checkpointerPutAfterPrevious({ config: { ...this.checkpointConfig }, checkpoint: copyCheckpoint(this.checkpoint), metadata: { ...this.checkpointMetadata }, newVersions, }); this.checkpointConfig = { ...this.checkpointConfig, configurable: { ...this.checkpointConfig.configurable, checkpoint_id: this.checkpoint.id, }, }; } this.step += 1; } _matchWrites(tasks) { for (const [tid, k, v] of this.checkpointPendingWrites) { if (k === ERROR || k === INTERRUPT || k === RESUME) { continue; } const task = Object.values(tasks).find((t) => t.id === tid); if (task) { task.writes.push([k, v]); } } for (const task of Object.values(tasks)) { if (task.writes.length > 0) { this._outputWrites(task.id, task.writes, true); } } } } //# sourceMappingURL=loop.js.map