@langchain/langgraph 0.2.7 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.cjs +12 -2
- package/dist/constants.d.ts +7 -1
- package/dist/constants.js +11 -1
- package/dist/errors.cjs +5 -4
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +5 -4
- package/dist/graph/graph.cjs +7 -2
- package/dist/graph/graph.js +8 -3
- package/dist/graph/message.cjs +2 -0
- package/dist/graph/message.js +2 -0
- package/dist/graph/state.cjs +9 -7
- package/dist/graph/state.d.ts +2 -2
- package/dist/graph/state.js +10 -8
- package/dist/managed/shared_value.cjs +1 -1
- package/dist/managed/shared_value.js +1 -1
- package/dist/pregel/algo.cjs +119 -54
- package/dist/pregel/algo.d.ts +5 -2
- package/dist/pregel/algo.js +117 -53
- package/dist/pregel/debug.cjs +15 -18
- package/dist/pregel/debug.d.ts +3 -2
- package/dist/pregel/debug.js +16 -19
- package/dist/pregel/index.cjs +295 -123
- package/dist/pregel/index.d.ts +16 -5
- package/dist/pregel/index.js +292 -123
- package/dist/pregel/io.cjs +15 -8
- package/dist/pregel/io.d.ts +2 -2
- package/dist/pregel/io.js +15 -8
- package/dist/pregel/loop.cjs +256 -111
- package/dist/pregel/loop.d.ts +21 -5
- package/dist/pregel/loop.js +256 -109
- package/dist/pregel/read.cjs +9 -2
- package/dist/pregel/read.d.ts +2 -1
- package/dist/pregel/read.js +9 -2
- package/dist/pregel/retry.d.ts +1 -1
- package/dist/pregel/types.d.ts +5 -2
- package/dist/pregel/utils/config.cjs +72 -0
- package/dist/pregel/utils/config.d.ts +2 -0
- package/dist/pregel/utils/config.js +68 -0
- package/dist/pregel/{utils.cjs → utils/index.cjs} +33 -10
- package/dist/pregel/{utils.d.ts → utils/index.d.ts} +4 -7
- package/dist/pregel/{utils.js → utils/index.js} +30 -8
- package/dist/utils.cjs +5 -3
- package/dist/utils.js +5 -3
- package/dist/web.d.ts +2 -1
- package/package.json +1 -1
package/dist/pregel/index.js
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
|
-
import { Runnable, RunnableSequence, _coerceToRunnable,
|
|
2
|
+
import { Runnable, RunnableSequence, _coerceToRunnable, getCallbackManagerForConfig, mergeConfigs, patchConfig, } from "@langchain/core/runnables";
|
|
3
3
|
import { compareChannelVersions, copyCheckpoint, emptyCheckpoint, uuid5, } from "@langchain/langgraph-checkpoint";
|
|
4
|
+
import Deque from "double-ended-queue";
|
|
4
5
|
import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
|
|
5
6
|
import { PregelNode } from "./read.js";
|
|
6
7
|
import { validateGraph, validateKeys } from "./validate.js";
|
|
7
8
|
import { readChannels } from "./io.js";
|
|
8
9
|
import { printStepCheckpoint, printStepTasks, printStepWrites, tasksWithWrites, } from "./debug.js";
|
|
9
10
|
import { ChannelWrite, PASSTHROUGH } from "./write.js";
|
|
10
|
-
import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, CONFIG_KEY_STORE, ERROR, INTERRUPT, } from "../constants.js";
|
|
11
|
+
import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, CONFIG_KEY_STORE, ERROR, INTERRUPT, CHECKPOINT_NAMESPACE_SEPARATOR, CHECKPOINT_NAMESPACE_END, CONFIG_KEY_STREAM, CONFIG_KEY_TASK_ID, } from "../constants.js";
|
|
11
12
|
import { GraphRecursionError, GraphValueError, InvalidUpdateError, isGraphInterrupt, } from "../errors.js";
|
|
12
13
|
import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
|
|
13
|
-
import { _coerceToDict, getNewChannelVersions } from "./utils.js";
|
|
14
|
-
import { PregelLoop } from "./loop.js";
|
|
14
|
+
import { _coerceToDict, getNewChannelVersions, patchCheckpointMap, } from "./utils/index.js";
|
|
15
|
+
import { PregelLoop, StreamProtocol } from "./loop.js";
|
|
15
16
|
import { executeTasksWithRetry } from "./retry.js";
|
|
16
17
|
import { ChannelKeyPlaceholder, isConfiguredManagedValue, ManagedValueMapping, NoopManagedValue, } from "../managed/base.js";
|
|
17
|
-
import { patchConfigurable } from "../utils.js";
|
|
18
|
+
import { gatherIterator, patchConfigurable } from "../utils.js";
|
|
19
|
+
import { ensureLangGraphConfig } from "./utils/config.js";
|
|
18
20
|
function isString(value) {
|
|
19
21
|
return typeof value === "string";
|
|
20
22
|
}
|
|
@@ -72,6 +74,16 @@ export class Channel {
|
|
|
72
74
|
return new ChannelWrite(channelWriteEntries);
|
|
73
75
|
}
|
|
74
76
|
}
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
+
function isPregel(x) {
|
|
79
|
+
return ("inputChannels" in x &&
|
|
80
|
+
x.inputChannels !== undefined &&
|
|
81
|
+
"outputChannels" &&
|
|
82
|
+
x.outputChannels !== undefined);
|
|
83
|
+
}
|
|
84
|
+
function isRunnableSequence(x) {
|
|
85
|
+
return "steps" in x && Array.isArray(x.steps);
|
|
86
|
+
}
|
|
75
87
|
export class Pregel extends Runnable {
|
|
76
88
|
static lc_name() {
|
|
77
89
|
return "LangGraph";
|
|
@@ -109,12 +121,6 @@ export class Pregel extends Runnable {
|
|
|
109
121
|
writable: true,
|
|
110
122
|
value: void 0
|
|
111
123
|
});
|
|
112
|
-
Object.defineProperty(this, "configKeys", {
|
|
113
|
-
enumerable: true,
|
|
114
|
-
configurable: true,
|
|
115
|
-
writable: true,
|
|
116
|
-
value: void 0
|
|
117
|
-
});
|
|
118
124
|
Object.defineProperty(this, "autoValidate", {
|
|
119
125
|
enumerable: true,
|
|
120
126
|
configurable: true,
|
|
@@ -169,6 +175,12 @@ export class Pregel extends Runnable {
|
|
|
169
175
|
writable: true,
|
|
170
176
|
value: void 0
|
|
171
177
|
});
|
|
178
|
+
Object.defineProperty(this, "config", {
|
|
179
|
+
enumerable: true,
|
|
180
|
+
configurable: true,
|
|
181
|
+
writable: true,
|
|
182
|
+
value: void 0
|
|
183
|
+
});
|
|
172
184
|
Object.defineProperty(this, "store", {
|
|
173
185
|
enumerable: true,
|
|
174
186
|
configurable: true,
|
|
@@ -185,7 +197,6 @@ export class Pregel extends Runnable {
|
|
|
185
197
|
this.streamMode = streamMode ?? this.streamMode;
|
|
186
198
|
this.inputChannels = fields.inputChannels;
|
|
187
199
|
this.outputChannels = fields.outputChannels;
|
|
188
|
-
this.configKeys = fields.configKeys;
|
|
189
200
|
this.streamChannels = fields.streamChannels ?? this.streamChannels;
|
|
190
201
|
this.interruptAfter = fields.interruptAfter;
|
|
191
202
|
this.interruptBefore = fields.interruptBefore;
|
|
@@ -193,11 +204,18 @@ export class Pregel extends Runnable {
|
|
|
193
204
|
this.debug = fields.debug ?? this.debug;
|
|
194
205
|
this.checkpointer = fields.checkpointer;
|
|
195
206
|
this.retryPolicy = fields.retryPolicy;
|
|
207
|
+
this.config = fields.config;
|
|
196
208
|
this.store = fields.store;
|
|
197
209
|
if (this.autoValidate) {
|
|
198
210
|
this.validate();
|
|
199
211
|
}
|
|
200
212
|
}
|
|
213
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
214
|
+
// @ts-ignore Remove ignore when we remove support for 0.2 versions of core
|
|
215
|
+
withConfig(config) {
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
+
return new this.constructor({ ...this, config });
|
|
218
|
+
}
|
|
201
219
|
validate() {
|
|
202
220
|
validateGraph({
|
|
203
221
|
nodes: this.nodes,
|
|
@@ -229,50 +247,181 @@ export class Pregel extends Runnable {
|
|
|
229
247
|
return Object.keys(this.channels);
|
|
230
248
|
}
|
|
231
249
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
*getSubgraphs(namespace, recurse
|
|
251
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
|
+
) {
|
|
253
|
+
for (const [name, node] of Object.entries(this.nodes)) {
|
|
254
|
+
// filter by prefix
|
|
255
|
+
if (namespace !== undefined) {
|
|
256
|
+
if (!namespace.startsWith(name)) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// find the subgraph if any
|
|
261
|
+
let graph;
|
|
262
|
+
const candidates = [node.bound];
|
|
263
|
+
for (const candidate of candidates) {
|
|
264
|
+
if (isPregel(candidate)) {
|
|
265
|
+
graph = candidate;
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
else if (isRunnableSequence(candidate)) {
|
|
269
|
+
candidates.push(...candidate.steps);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// if found, yield recursively
|
|
273
|
+
if (graph !== undefined) {
|
|
274
|
+
if (name === namespace) {
|
|
275
|
+
yield [name, graph];
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (namespace === undefined) {
|
|
279
|
+
yield [name, graph];
|
|
280
|
+
}
|
|
281
|
+
if (recurse) {
|
|
282
|
+
let newNamespace = namespace;
|
|
283
|
+
if (namespace !== undefined) {
|
|
284
|
+
newNamespace = namespace.slice(name.length + 1);
|
|
285
|
+
}
|
|
286
|
+
for (const [subgraphName, subgraph] of graph.getSubgraphs(newNamespace, recurse)) {
|
|
287
|
+
yield [
|
|
288
|
+
`${name}${CHECKPOINT_NAMESPACE_SEPARATOR}${subgraphName}`,
|
|
289
|
+
subgraph,
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async _prepareStateSnapshot({ config, saved, subgraphCheckpointer, }) {
|
|
297
|
+
if (saved === undefined) {
|
|
298
|
+
return {
|
|
299
|
+
values: {},
|
|
300
|
+
next: [],
|
|
301
|
+
config,
|
|
302
|
+
tasks: [],
|
|
303
|
+
};
|
|
238
304
|
}
|
|
239
|
-
const saved = await this.checkpointer.getTuple(config);
|
|
240
|
-
const checkpoint = saved ? saved.checkpoint : emptyCheckpoint();
|
|
241
|
-
const channels = emptyChannels(this.channels, checkpoint);
|
|
242
305
|
// Pass `skipManaged: true` as managed values should not be returned in get state calls.
|
|
243
306
|
const { managed } = await this.prepareSpecs(config, { skipManaged: true });
|
|
244
|
-
const
|
|
307
|
+
const channels = emptyChannels(this.channels, saved.checkpoint);
|
|
308
|
+
const nextTasks = Object.values(_prepareNextTasks(saved.checkpoint, this.nodes, channels, managed, saved.config, false, { step: (saved.metadata?.step ?? -1) + 1 }));
|
|
309
|
+
const subgraphs = await gatherIterator(this.getSubgraphs());
|
|
310
|
+
const parentNamespace = saved.config.configurable?.checkpoint_ns ?? "";
|
|
311
|
+
const taskStates = {};
|
|
312
|
+
for (const task of nextTasks) {
|
|
313
|
+
const matchingSubgraph = subgraphs.find(([name]) => name === task.name);
|
|
314
|
+
if (!matchingSubgraph) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
// assemble checkpoint_ns for this task
|
|
318
|
+
let taskNs = `${task.name}${CHECKPOINT_NAMESPACE_END}${task.id}`;
|
|
319
|
+
if (parentNamespace) {
|
|
320
|
+
taskNs = `${parentNamespace}${CHECKPOINT_NAMESPACE_SEPARATOR}${taskNs}`;
|
|
321
|
+
}
|
|
322
|
+
if (subgraphCheckpointer === undefined) {
|
|
323
|
+
// set config as signal that subgraph checkpoints exist
|
|
324
|
+
const config = {
|
|
325
|
+
configurable: {
|
|
326
|
+
thread_id: saved.config.configurable?.thread_id,
|
|
327
|
+
checkpoint_ns: taskNs,
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
taskStates[task.id] = config;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
// get the state of the subgraph
|
|
334
|
+
const config = {
|
|
335
|
+
configurable: {
|
|
336
|
+
[CONFIG_KEY_CHECKPOINTER]: subgraphCheckpointer,
|
|
337
|
+
thread_id: saved.config.configurable?.thread_id,
|
|
338
|
+
checkpoint_ns: taskNs,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
taskStates[task.id] = await matchingSubgraph[1].getState(config, {
|
|
342
|
+
subgraphs: true,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// assemble the state snapshot
|
|
245
347
|
return {
|
|
246
348
|
values: readChannels(channels, this.streamChannelsAsIs),
|
|
247
349
|
next: nextTasks.map((task) => task.name),
|
|
248
|
-
tasks: tasksWithWrites(nextTasks, saved?.pendingWrites ?? []),
|
|
249
|
-
metadata: saved
|
|
250
|
-
config: saved
|
|
251
|
-
createdAt: saved
|
|
252
|
-
parentConfig: saved
|
|
350
|
+
tasks: tasksWithWrites(nextTasks, saved?.pendingWrites ?? [], taskStates),
|
|
351
|
+
metadata: saved.metadata,
|
|
352
|
+
config: patchCheckpointMap(saved.config, saved.metadata),
|
|
353
|
+
createdAt: saved.checkpoint.ts,
|
|
354
|
+
parentConfig: saved.parentConfig,
|
|
253
355
|
};
|
|
254
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Get the current state of the graph.
|
|
359
|
+
*/
|
|
360
|
+
async getState(config, options) {
|
|
361
|
+
const checkpointer = config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
|
|
362
|
+
if (!checkpointer) {
|
|
363
|
+
throw new GraphValueError("No checkpointer set");
|
|
364
|
+
}
|
|
365
|
+
const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
|
|
366
|
+
if (checkpointNamespace !== "" &&
|
|
367
|
+
config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined) {
|
|
368
|
+
// remove task_ids from checkpoint_ns
|
|
369
|
+
const recastCheckpointNamespace = checkpointNamespace
|
|
370
|
+
.split(CHECKPOINT_NAMESPACE_SEPARATOR)
|
|
371
|
+
.map((part) => part.split(CHECKPOINT_NAMESPACE_END)[0])
|
|
372
|
+
.join(CHECKPOINT_NAMESPACE_SEPARATOR);
|
|
373
|
+
for (const [name, subgraph] of this.getSubgraphs(recastCheckpointNamespace, true)) {
|
|
374
|
+
if (name === recastCheckpointNamespace) {
|
|
375
|
+
return await subgraph.getState(patchConfigurable(config, {
|
|
376
|
+
[CONFIG_KEY_CHECKPOINTER]: checkpointer,
|
|
377
|
+
}), { subgraphs: options?.subgraphs });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
throw new Error(`Subgraph with namespace "${recastCheckpointNamespace}" not found.`);
|
|
381
|
+
}
|
|
382
|
+
const mergedConfig = mergeConfigs(this.config, config);
|
|
383
|
+
const saved = await checkpointer.getTuple(config);
|
|
384
|
+
const snapshot = await this._prepareStateSnapshot({
|
|
385
|
+
config: mergedConfig,
|
|
386
|
+
saved,
|
|
387
|
+
subgraphCheckpointer: options?.subgraphs ? checkpointer : undefined,
|
|
388
|
+
});
|
|
389
|
+
return snapshot;
|
|
390
|
+
}
|
|
255
391
|
/**
|
|
256
392
|
* Get the history of the state of the graph.
|
|
257
393
|
*/
|
|
258
394
|
async *getStateHistory(config, options) {
|
|
259
|
-
|
|
260
|
-
|
|
395
|
+
const checkpointer = config.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
|
|
396
|
+
if (!checkpointer) {
|
|
397
|
+
throw new Error("No checkpointer set");
|
|
398
|
+
}
|
|
399
|
+
const checkpointNamespace = config.configurable?.checkpoint_ns ?? "";
|
|
400
|
+
if (checkpointNamespace !== "" &&
|
|
401
|
+
config.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined) {
|
|
402
|
+
const recastCheckpointNamespace = checkpointNamespace
|
|
403
|
+
.split(CHECKPOINT_NAMESPACE_SEPARATOR)
|
|
404
|
+
.map((part) => part.split(CHECKPOINT_NAMESPACE_END)[0])
|
|
405
|
+
.join(CHECKPOINT_NAMESPACE_SEPARATOR);
|
|
406
|
+
// find the subgraph with the matching name
|
|
407
|
+
for (const [name, pregel] of this.getSubgraphs(recastCheckpointNamespace, true)) {
|
|
408
|
+
if (name === recastCheckpointNamespace) {
|
|
409
|
+
yield* pregel.getStateHistory(patchConfigurable(config, {
|
|
410
|
+
[CONFIG_KEY_CHECKPOINTER]: checkpointer,
|
|
411
|
+
}), options);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
throw new Error(`Subgraph with namespace "${recastCheckpointNamespace}" not found.`);
|
|
261
416
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
tasks: tasksWithWrites(nextTasks, saved.pendingWrites ?? []),
|
|
271
|
-
metadata: saved.metadata,
|
|
272
|
-
config: saved.config,
|
|
273
|
-
createdAt: saved.checkpoint.ts,
|
|
274
|
-
parentConfig: saved.parentConfig,
|
|
275
|
-
};
|
|
417
|
+
const mergedConfig = mergeConfigs(this.config, config, {
|
|
418
|
+
configurable: { checkpoint_ns: checkpointNamespace },
|
|
419
|
+
});
|
|
420
|
+
for await (const checkpointTuple of checkpointer.list(mergedConfig, options)) {
|
|
421
|
+
yield this._prepareStateSnapshot({
|
|
422
|
+
config: checkpointTuple.config,
|
|
423
|
+
saved: checkpointTuple,
|
|
424
|
+
});
|
|
276
425
|
}
|
|
277
426
|
}
|
|
278
427
|
/**
|
|
@@ -280,34 +429,59 @@ export class Pregel extends Runnable {
|
|
|
280
429
|
* node `as_node`. If `as_node` is not provided, it will be set to the last node
|
|
281
430
|
* that updated the state, if not ambiguous.
|
|
282
431
|
*/
|
|
283
|
-
async updateState(
|
|
284
|
-
|
|
432
|
+
async updateState(inputConfig, values, asNode) {
|
|
433
|
+
const checkpointer = inputConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
|
|
434
|
+
if (!checkpointer) {
|
|
285
435
|
throw new GraphValueError("No checkpointer set");
|
|
286
436
|
}
|
|
287
|
-
//
|
|
288
|
-
const
|
|
289
|
-
|
|
437
|
+
// delegate to subgraph
|
|
438
|
+
const checkpointNamespace = inputConfig.configurable?.checkpoint_ns ?? "";
|
|
439
|
+
if (checkpointNamespace !== "" &&
|
|
440
|
+
inputConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined) {
|
|
441
|
+
// remove task_ids from checkpoint_ns
|
|
442
|
+
const recastCheckpointNamespace = checkpointNamespace
|
|
443
|
+
.split(CHECKPOINT_NAMESPACE_SEPARATOR)
|
|
444
|
+
.map((part) => {
|
|
445
|
+
return part.split(CHECKPOINT_NAMESPACE_END)[0];
|
|
446
|
+
})
|
|
447
|
+
.join(CHECKPOINT_NAMESPACE_SEPARATOR);
|
|
448
|
+
// find the subgraph with the matching name
|
|
449
|
+
// eslint-disable-next-line no-unreachable-loop
|
|
450
|
+
for (const [, pregel] of this.getSubgraphs(recastCheckpointNamespace, true)) {
|
|
451
|
+
return await pregel.updateState(patchConfigurable(inputConfig, {
|
|
452
|
+
[CONFIG_KEY_CHECKPOINTER]: checkpointer,
|
|
453
|
+
}), values, asNode);
|
|
454
|
+
}
|
|
455
|
+
throw new Error(`Subgraph "${recastCheckpointNamespace}" not found`);
|
|
456
|
+
}
|
|
457
|
+
// get last checkpoint
|
|
458
|
+
const config = this.config
|
|
459
|
+
? mergeConfigs(this.config, inputConfig)
|
|
460
|
+
: inputConfig;
|
|
461
|
+
const saved = await checkpointer.getTuple(config);
|
|
462
|
+
const checkpoint = saved !== undefined
|
|
290
463
|
? copyCheckpoint(saved.checkpoint)
|
|
291
464
|
: emptyCheckpoint();
|
|
292
|
-
const checkpointPreviousVersions =
|
|
465
|
+
const checkpointPreviousVersions = {
|
|
466
|
+
...saved?.checkpoint.channel_versions,
|
|
467
|
+
};
|
|
293
468
|
const step = saved?.metadata?.step ?? -1;
|
|
294
469
|
// merge configurable fields with previous checkpoint config
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
...saved?.config.configurable,
|
|
302
|
-
},
|
|
303
|
-
};
|
|
470
|
+
let checkpointConfig = patchConfigurable(config, {
|
|
471
|
+
checkpoint_ns: config.configurable?.checkpoint_ns ?? "",
|
|
472
|
+
});
|
|
473
|
+
if (saved) {
|
|
474
|
+
checkpointConfig = patchConfigurable(config, saved.config.configurable);
|
|
475
|
+
}
|
|
304
476
|
// Find last node that updated the state, if not provided
|
|
305
477
|
if (values == null && asNode === undefined) {
|
|
306
|
-
|
|
478
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, undefined, step), {
|
|
307
479
|
source: "update",
|
|
308
480
|
step,
|
|
309
481
|
writes: {},
|
|
482
|
+
parents: saved?.metadata?.parents ?? {},
|
|
310
483
|
}, {});
|
|
484
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
311
485
|
}
|
|
312
486
|
const nonNullVersion = Object.values(checkpoint.versions_seen)
|
|
313
487
|
.map((seenVersions) => {
|
|
@@ -322,7 +496,6 @@ export class Pregel extends Runnable {
|
|
|
322
496
|
}
|
|
323
497
|
}
|
|
324
498
|
else if (asNode === undefined) {
|
|
325
|
-
// TODO: Double check
|
|
326
499
|
const lastSeenByNode = Object.entries(checkpoint.versions_seen)
|
|
327
500
|
.map(([n, seen]) => {
|
|
328
501
|
return Object.values(seen).map((v) => {
|
|
@@ -379,18 +552,21 @@ export class Pregel extends Runnable {
|
|
|
379
552
|
task, select_, fresh_),
|
|
380
553
|
},
|
|
381
554
|
}));
|
|
555
|
+
// save task writes
|
|
382
556
|
if (saved !== undefined) {
|
|
383
|
-
await
|
|
557
|
+
await checkpointer.putWrites(checkpointConfig, task.writes, task.id);
|
|
384
558
|
}
|
|
385
559
|
// apply to checkpoint
|
|
386
560
|
// TODO: Why does keyof StrRecord allow number and symbol?
|
|
387
|
-
_applyWrites(checkpoint, channels, [task],
|
|
561
|
+
_applyWrites(checkpoint, channels, [task], checkpointer.getNextVersion.bind(this.checkpointer));
|
|
388
562
|
const newVersions = getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions);
|
|
389
|
-
|
|
563
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, channels, step + 1), {
|
|
390
564
|
source: "update",
|
|
391
565
|
step: step + 1,
|
|
392
566
|
writes: { [asNode]: values },
|
|
567
|
+
parents: saved?.metadata?.parents ?? {},
|
|
393
568
|
}, newVersions);
|
|
569
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
394
570
|
}
|
|
395
571
|
_defaults(config) {
|
|
396
572
|
const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
|
|
@@ -418,25 +594,18 @@ export class Pregel extends Runnable {
|
|
|
418
594
|
else {
|
|
419
595
|
defaultStreamMode = this.streamMode;
|
|
420
596
|
}
|
|
421
|
-
|
|
422
|
-
if (config.configurable !== undefined
|
|
423
|
-
config.configurable[CONFIG_KEY_READ] !== undefined) {
|
|
597
|
+
// if being called as a node in another graph, always use values mode
|
|
598
|
+
if (config.configurable?.[CONFIG_KEY_TASK_ID] !== undefined) {
|
|
424
599
|
defaultStreamMode = ["values"];
|
|
425
600
|
}
|
|
601
|
+
let defaultCheckpointer;
|
|
426
602
|
if (config !== undefined &&
|
|
427
|
-
config.configurable?.[CONFIG_KEY_CHECKPOINTER] !== undefined
|
|
428
|
-
(defaultInterruptAfter.length > 0 || defaultInterruptBefore.length > 0)) {
|
|
603
|
+
config.configurable?.[CONFIG_KEY_CHECKPOINTER] !== undefined) {
|
|
429
604
|
defaultCheckpointer = config.configurable[CONFIG_KEY_CHECKPOINTER];
|
|
430
605
|
}
|
|
431
606
|
else {
|
|
432
607
|
defaultCheckpointer = this.checkpointer;
|
|
433
608
|
}
|
|
434
|
-
if (this.configKeys !== undefined) {
|
|
435
|
-
const newConfigurable = Object.fromEntries(Object.entries(rest.configurable ?? {}).filter(([key]) => {
|
|
436
|
-
return this.configKeys?.includes(key);
|
|
437
|
-
}));
|
|
438
|
-
rest.configurable = newConfigurable;
|
|
439
|
-
}
|
|
440
609
|
return [
|
|
441
610
|
defaultDebug,
|
|
442
611
|
defaultStreamMode,
|
|
@@ -510,7 +679,8 @@ export class Pregel extends Runnable {
|
|
|
510
679
|
};
|
|
511
680
|
}
|
|
512
681
|
async *_streamIterator(input, options) {
|
|
513
|
-
const
|
|
682
|
+
const streamSubgraphs = options?.subgraphs;
|
|
683
|
+
const inputConfig = ensureLangGraphConfig(this.config, options);
|
|
514
684
|
if (inputConfig.recursionLimit === undefined ||
|
|
515
685
|
inputConfig.recursionLimit < 1) {
|
|
516
686
|
throw new Error(`Passed "recursionLimit" must be at least 1.`);
|
|
@@ -526,6 +696,30 @@ export class Pregel extends Runnable {
|
|
|
526
696
|
const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer,] = this._defaults(inputConfig);
|
|
527
697
|
const { channelSpecs, managed } = await this.prepareSpecs(config);
|
|
528
698
|
let loop;
|
|
699
|
+
const stream = new Deque();
|
|
700
|
+
function* emitCurrentLoopOutputs() {
|
|
701
|
+
while (loop !== undefined && stream.length > 0) {
|
|
702
|
+
const nextItem = stream.shift();
|
|
703
|
+
if (nextItem === undefined) {
|
|
704
|
+
throw new Error("Data structure error.");
|
|
705
|
+
}
|
|
706
|
+
const [namespace, mode, payload] = nextItem;
|
|
707
|
+
if (streamMode.includes(mode)) {
|
|
708
|
+
if (streamSubgraphs && streamMode.length > 1) {
|
|
709
|
+
yield [namespace, mode, payload];
|
|
710
|
+
}
|
|
711
|
+
else if (streamMode.length > 1) {
|
|
712
|
+
yield [mode, payload];
|
|
713
|
+
}
|
|
714
|
+
else if (streamSubgraphs) {
|
|
715
|
+
yield [namespace, payload];
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
yield payload;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
529
723
|
try {
|
|
530
724
|
loop = await PregelLoop.initialize({
|
|
531
725
|
input,
|
|
@@ -537,7 +731,14 @@ export class Pregel extends Runnable {
|
|
|
537
731
|
outputKeys,
|
|
538
732
|
streamKeys: this.streamChannelsAsIs,
|
|
539
733
|
store: this.store,
|
|
734
|
+
stream: new StreamProtocol((chunk) => stream.push(chunk), new Set(streamMode)),
|
|
540
735
|
});
|
|
736
|
+
if (options?.subgraphs) {
|
|
737
|
+
loop.config.configurable = {
|
|
738
|
+
...loop.config.configurable,
|
|
739
|
+
[CONFIG_KEY_STREAM]: loop.stream,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
541
742
|
while (await loop.tick({
|
|
542
743
|
inputKeys: this.inputChannels,
|
|
543
744
|
interruptAfter,
|
|
@@ -547,27 +748,14 @@ export class Pregel extends Runnable {
|
|
|
547
748
|
if (debug) {
|
|
548
749
|
printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
|
|
549
750
|
}
|
|
550
|
-
|
|
551
|
-
const nextItem = loop.stream.shift();
|
|
552
|
-
if (nextItem === undefined) {
|
|
553
|
-
throw new Error("Data structure error.");
|
|
554
|
-
}
|
|
555
|
-
if (streamMode.includes(nextItem[0])) {
|
|
556
|
-
if (streamMode.length === 1) {
|
|
557
|
-
yield nextItem[1];
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
yield nextItem;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
751
|
+
yield* emitCurrentLoopOutputs();
|
|
564
752
|
if (debug) {
|
|
565
|
-
printStepTasks(loop.step, loop.tasks);
|
|
753
|
+
printStepTasks(loop.step, Object.values(loop.tasks));
|
|
566
754
|
}
|
|
567
755
|
// execute tasks, and wait for one to fail or all to finish.
|
|
568
756
|
// each task is independent from all other concurrent tasks
|
|
569
757
|
// yield updates/debug output as each task finishes
|
|
570
|
-
const taskStream = executeTasksWithRetry(loop.tasks.filter((task) => task.writes.length === 0), {
|
|
758
|
+
const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
|
|
571
759
|
stepTimeout: this.stepTimeout,
|
|
572
760
|
signal: config.signal,
|
|
573
761
|
retryPolicy: this.retryPolicy,
|
|
@@ -576,7 +764,12 @@ export class Pregel extends Runnable {
|
|
|
576
764
|
for await (const { task, error } of taskStream) {
|
|
577
765
|
if (error !== undefined) {
|
|
578
766
|
if (isGraphInterrupt(error)) {
|
|
579
|
-
loop.
|
|
767
|
+
if (loop.isNested) {
|
|
768
|
+
throw error;
|
|
769
|
+
}
|
|
770
|
+
if (error.interrupts.length) {
|
|
771
|
+
loop.putWrites(task.id, error.interrupts.map((interrupt) => [INTERRUPT, interrupt]));
|
|
772
|
+
}
|
|
580
773
|
}
|
|
581
774
|
else {
|
|
582
775
|
loop.putWrites(task.id, [
|
|
@@ -587,42 +780,18 @@ export class Pregel extends Runnable {
|
|
|
587
780
|
else {
|
|
588
781
|
loop.putWrites(task.id, task.writes);
|
|
589
782
|
}
|
|
590
|
-
|
|
591
|
-
const nextItem = loop.stream.shift();
|
|
592
|
-
if (nextItem === undefined) {
|
|
593
|
-
throw new Error("Data structure error.");
|
|
594
|
-
}
|
|
595
|
-
if (streamMode.includes(nextItem[0])) {
|
|
596
|
-
if (streamMode.length === 1) {
|
|
597
|
-
yield nextItem[1];
|
|
598
|
-
}
|
|
599
|
-
else {
|
|
600
|
-
yield nextItem;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
783
|
+
yield* emitCurrentLoopOutputs();
|
|
604
784
|
if (error !== undefined && !isGraphInterrupt(error)) {
|
|
605
785
|
throw error;
|
|
606
786
|
}
|
|
607
787
|
}
|
|
608
788
|
if (debug) {
|
|
609
|
-
printStepWrites(loop.step, loop.tasks
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
while (loop.stream.length > 0) {
|
|
613
|
-
const nextItem = loop.stream.shift();
|
|
614
|
-
if (nextItem === undefined) {
|
|
615
|
-
throw new Error("Data structure error.");
|
|
616
|
-
}
|
|
617
|
-
if (streamMode.includes(nextItem[0])) {
|
|
618
|
-
if (streamMode.length === 1) {
|
|
619
|
-
yield nextItem[1];
|
|
620
|
-
}
|
|
621
|
-
else {
|
|
622
|
-
yield nextItem;
|
|
623
|
-
}
|
|
789
|
+
printStepWrites(loop.step, Object.values(loop.tasks)
|
|
790
|
+
.map((task) => task.writes)
|
|
791
|
+
.flat(), this.streamChannelsList);
|
|
624
792
|
}
|
|
625
793
|
}
|
|
794
|
+
yield* emitCurrentLoopOutputs();
|
|
626
795
|
if (loop.status === "out_of_steps") {
|
|
627
796
|
throw new GraphRecursionError([
|
|
628
797
|
`Recursion limit of ${config.recursionLimit} reached`,
|
|
@@ -631,7 +800,7 @@ export class Pregel extends Runnable {
|
|
|
631
800
|
].join(" "));
|
|
632
801
|
}
|
|
633
802
|
await Promise.all(loop?.checkpointerPromises ?? []);
|
|
634
|
-
await runManager?.handleChainEnd(
|
|
803
|
+
await runManager?.handleChainEnd(loop.output);
|
|
635
804
|
}
|
|
636
805
|
catch (e) {
|
|
637
806
|
await runManager?.handleChainError(e);
|
|
@@ -666,7 +835,7 @@ export class Pregel extends Runnable {
|
|
|
666
835
|
async invoke(input, options) {
|
|
667
836
|
const streamMode = options?.streamMode ?? "values";
|
|
668
837
|
const config = {
|
|
669
|
-
...
|
|
838
|
+
...options,
|
|
670
839
|
outputKeys: options?.outputKeys ?? this.outputChannels,
|
|
671
840
|
streamMode,
|
|
672
841
|
};
|