@langchain/langgraph 0.2.8 → 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 +8 -3
- package/dist/graph/state.d.ts +1 -1
- package/dist/graph/state.js +9 -4
- 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 -110
- package/dist/pregel/index.d.ts +16 -4
- package/dist/pregel/index.js +292 -110
- 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 -1
- 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";
|
|
@@ -163,6 +175,12 @@ export class Pregel extends Runnable {
|
|
|
163
175
|
writable: true,
|
|
164
176
|
value: void 0
|
|
165
177
|
});
|
|
178
|
+
Object.defineProperty(this, "config", {
|
|
179
|
+
enumerable: true,
|
|
180
|
+
configurable: true,
|
|
181
|
+
writable: true,
|
|
182
|
+
value: void 0
|
|
183
|
+
});
|
|
166
184
|
Object.defineProperty(this, "store", {
|
|
167
185
|
enumerable: true,
|
|
168
186
|
configurable: true,
|
|
@@ -186,11 +204,18 @@ export class Pregel extends Runnable {
|
|
|
186
204
|
this.debug = fields.debug ?? this.debug;
|
|
187
205
|
this.checkpointer = fields.checkpointer;
|
|
188
206
|
this.retryPolicy = fields.retryPolicy;
|
|
207
|
+
this.config = fields.config;
|
|
189
208
|
this.store = fields.store;
|
|
190
209
|
if (this.autoValidate) {
|
|
191
210
|
this.validate();
|
|
192
211
|
}
|
|
193
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
|
+
}
|
|
194
219
|
validate() {
|
|
195
220
|
validateGraph({
|
|
196
221
|
nodes: this.nodes,
|
|
@@ -222,50 +247,181 @@ export class Pregel extends Runnable {
|
|
|
222
247
|
return Object.keys(this.channels);
|
|
223
248
|
}
|
|
224
249
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
+
};
|
|
231
304
|
}
|
|
232
|
-
const saved = await this.checkpointer.getTuple(config);
|
|
233
|
-
const checkpoint = saved ? saved.checkpoint : emptyCheckpoint();
|
|
234
|
-
const channels = emptyChannels(this.channels, checkpoint);
|
|
235
305
|
// Pass `skipManaged: true` as managed values should not be returned in get state calls.
|
|
236
306
|
const { managed } = await this.prepareSpecs(config, { skipManaged: true });
|
|
237
|
-
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
|
|
238
347
|
return {
|
|
239
348
|
values: readChannels(channels, this.streamChannelsAsIs),
|
|
240
349
|
next: nextTasks.map((task) => task.name),
|
|
241
|
-
tasks: tasksWithWrites(nextTasks, saved?.pendingWrites ?? []),
|
|
242
|
-
metadata: saved
|
|
243
|
-
config: saved
|
|
244
|
-
createdAt: saved
|
|
245
|
-
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,
|
|
246
355
|
};
|
|
247
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
|
+
}
|
|
248
391
|
/**
|
|
249
392
|
* Get the history of the state of the graph.
|
|
250
393
|
*/
|
|
251
394
|
async *getStateHistory(config, options) {
|
|
252
|
-
|
|
253
|
-
|
|
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.`);
|
|
254
416
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
tasks: tasksWithWrites(nextTasks, saved.pendingWrites ?? []),
|
|
264
|
-
metadata: saved.metadata,
|
|
265
|
-
config: saved.config,
|
|
266
|
-
createdAt: saved.checkpoint.ts,
|
|
267
|
-
parentConfig: saved.parentConfig,
|
|
268
|
-
};
|
|
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
|
+
});
|
|
269
425
|
}
|
|
270
426
|
}
|
|
271
427
|
/**
|
|
@@ -273,34 +429,59 @@ export class Pregel extends Runnable {
|
|
|
273
429
|
* node `as_node`. If `as_node` is not provided, it will be set to the last node
|
|
274
430
|
* that updated the state, if not ambiguous.
|
|
275
431
|
*/
|
|
276
|
-
async updateState(
|
|
277
|
-
|
|
432
|
+
async updateState(inputConfig, values, asNode) {
|
|
433
|
+
const checkpointer = inputConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
|
|
434
|
+
if (!checkpointer) {
|
|
278
435
|
throw new GraphValueError("No checkpointer set");
|
|
279
436
|
}
|
|
280
|
-
//
|
|
281
|
-
const
|
|
282
|
-
|
|
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
|
|
283
463
|
? copyCheckpoint(saved.checkpoint)
|
|
284
464
|
: emptyCheckpoint();
|
|
285
|
-
const checkpointPreviousVersions =
|
|
465
|
+
const checkpointPreviousVersions = {
|
|
466
|
+
...saved?.checkpoint.channel_versions,
|
|
467
|
+
};
|
|
286
468
|
const step = saved?.metadata?.step ?? -1;
|
|
287
469
|
// merge configurable fields with previous checkpoint config
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
...saved?.config.configurable,
|
|
295
|
-
},
|
|
296
|
-
};
|
|
470
|
+
let checkpointConfig = patchConfigurable(config, {
|
|
471
|
+
checkpoint_ns: config.configurable?.checkpoint_ns ?? "",
|
|
472
|
+
});
|
|
473
|
+
if (saved) {
|
|
474
|
+
checkpointConfig = patchConfigurable(config, saved.config.configurable);
|
|
475
|
+
}
|
|
297
476
|
// Find last node that updated the state, if not provided
|
|
298
477
|
if (values == null && asNode === undefined) {
|
|
299
|
-
|
|
478
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, undefined, step), {
|
|
300
479
|
source: "update",
|
|
301
480
|
step,
|
|
302
481
|
writes: {},
|
|
482
|
+
parents: saved?.metadata?.parents ?? {},
|
|
303
483
|
}, {});
|
|
484
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
304
485
|
}
|
|
305
486
|
const nonNullVersion = Object.values(checkpoint.versions_seen)
|
|
306
487
|
.map((seenVersions) => {
|
|
@@ -315,7 +496,6 @@ export class Pregel extends Runnable {
|
|
|
315
496
|
}
|
|
316
497
|
}
|
|
317
498
|
else if (asNode === undefined) {
|
|
318
|
-
// TODO: Double check
|
|
319
499
|
const lastSeenByNode = Object.entries(checkpoint.versions_seen)
|
|
320
500
|
.map(([n, seen]) => {
|
|
321
501
|
return Object.values(seen).map((v) => {
|
|
@@ -372,18 +552,21 @@ export class Pregel extends Runnable {
|
|
|
372
552
|
task, select_, fresh_),
|
|
373
553
|
},
|
|
374
554
|
}));
|
|
555
|
+
// save task writes
|
|
375
556
|
if (saved !== undefined) {
|
|
376
|
-
await
|
|
557
|
+
await checkpointer.putWrites(checkpointConfig, task.writes, task.id);
|
|
377
558
|
}
|
|
378
559
|
// apply to checkpoint
|
|
379
560
|
// TODO: Why does keyof StrRecord allow number and symbol?
|
|
380
|
-
_applyWrites(checkpoint, channels, [task],
|
|
561
|
+
_applyWrites(checkpoint, channels, [task], checkpointer.getNextVersion.bind(this.checkpointer));
|
|
381
562
|
const newVersions = getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions);
|
|
382
|
-
|
|
563
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, channels, step + 1), {
|
|
383
564
|
source: "update",
|
|
384
565
|
step: step + 1,
|
|
385
566
|
writes: { [asNode]: values },
|
|
567
|
+
parents: saved?.metadata?.parents ?? {},
|
|
386
568
|
}, newVersions);
|
|
569
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
387
570
|
}
|
|
388
571
|
_defaults(config) {
|
|
389
572
|
const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
|
|
@@ -411,14 +594,13 @@ export class Pregel extends Runnable {
|
|
|
411
594
|
else {
|
|
412
595
|
defaultStreamMode = this.streamMode;
|
|
413
596
|
}
|
|
414
|
-
|
|
415
|
-
if (config.configurable !== undefined
|
|
416
|
-
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) {
|
|
417
599
|
defaultStreamMode = ["values"];
|
|
418
600
|
}
|
|
601
|
+
let defaultCheckpointer;
|
|
419
602
|
if (config !== undefined &&
|
|
420
|
-
config.configurable?.[CONFIG_KEY_CHECKPOINTER] !== undefined
|
|
421
|
-
(defaultInterruptAfter.length > 0 || defaultInterruptBefore.length > 0)) {
|
|
603
|
+
config.configurable?.[CONFIG_KEY_CHECKPOINTER] !== undefined) {
|
|
422
604
|
defaultCheckpointer = config.configurable[CONFIG_KEY_CHECKPOINTER];
|
|
423
605
|
}
|
|
424
606
|
else {
|
|
@@ -497,7 +679,8 @@ export class Pregel extends Runnable {
|
|
|
497
679
|
};
|
|
498
680
|
}
|
|
499
681
|
async *_streamIterator(input, options) {
|
|
500
|
-
const
|
|
682
|
+
const streamSubgraphs = options?.subgraphs;
|
|
683
|
+
const inputConfig = ensureLangGraphConfig(this.config, options);
|
|
501
684
|
if (inputConfig.recursionLimit === undefined ||
|
|
502
685
|
inputConfig.recursionLimit < 1) {
|
|
503
686
|
throw new Error(`Passed "recursionLimit" must be at least 1.`);
|
|
@@ -513,6 +696,30 @@ export class Pregel extends Runnable {
|
|
|
513
696
|
const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer,] = this._defaults(inputConfig);
|
|
514
697
|
const { channelSpecs, managed } = await this.prepareSpecs(config);
|
|
515
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
|
+
}
|
|
516
723
|
try {
|
|
517
724
|
loop = await PregelLoop.initialize({
|
|
518
725
|
input,
|
|
@@ -524,7 +731,14 @@ export class Pregel extends Runnable {
|
|
|
524
731
|
outputKeys,
|
|
525
732
|
streamKeys: this.streamChannelsAsIs,
|
|
526
733
|
store: this.store,
|
|
734
|
+
stream: new StreamProtocol((chunk) => stream.push(chunk), new Set(streamMode)),
|
|
527
735
|
});
|
|
736
|
+
if (options?.subgraphs) {
|
|
737
|
+
loop.config.configurable = {
|
|
738
|
+
...loop.config.configurable,
|
|
739
|
+
[CONFIG_KEY_STREAM]: loop.stream,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
528
742
|
while (await loop.tick({
|
|
529
743
|
inputKeys: this.inputChannels,
|
|
530
744
|
interruptAfter,
|
|
@@ -534,27 +748,14 @@ export class Pregel extends Runnable {
|
|
|
534
748
|
if (debug) {
|
|
535
749
|
printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
|
|
536
750
|
}
|
|
537
|
-
|
|
538
|
-
const nextItem = loop.stream.shift();
|
|
539
|
-
if (nextItem === undefined) {
|
|
540
|
-
throw new Error("Data structure error.");
|
|
541
|
-
}
|
|
542
|
-
if (streamMode.includes(nextItem[0])) {
|
|
543
|
-
if (streamMode.length === 1) {
|
|
544
|
-
yield nextItem[1];
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
yield nextItem;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
751
|
+
yield* emitCurrentLoopOutputs();
|
|
551
752
|
if (debug) {
|
|
552
|
-
printStepTasks(loop.step, loop.tasks);
|
|
753
|
+
printStepTasks(loop.step, Object.values(loop.tasks));
|
|
553
754
|
}
|
|
554
755
|
// execute tasks, and wait for one to fail or all to finish.
|
|
555
756
|
// each task is independent from all other concurrent tasks
|
|
556
757
|
// yield updates/debug output as each task finishes
|
|
557
|
-
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), {
|
|
558
759
|
stepTimeout: this.stepTimeout,
|
|
559
760
|
signal: config.signal,
|
|
560
761
|
retryPolicy: this.retryPolicy,
|
|
@@ -563,7 +764,12 @@ export class Pregel extends Runnable {
|
|
|
563
764
|
for await (const { task, error } of taskStream) {
|
|
564
765
|
if (error !== undefined) {
|
|
565
766
|
if (isGraphInterrupt(error)) {
|
|
566
|
-
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
|
+
}
|
|
567
773
|
}
|
|
568
774
|
else {
|
|
569
775
|
loop.putWrites(task.id, [
|
|
@@ -574,42 +780,18 @@ export class Pregel extends Runnable {
|
|
|
574
780
|
else {
|
|
575
781
|
loop.putWrites(task.id, task.writes);
|
|
576
782
|
}
|
|
577
|
-
|
|
578
|
-
const nextItem = loop.stream.shift();
|
|
579
|
-
if (nextItem === undefined) {
|
|
580
|
-
throw new Error("Data structure error.");
|
|
581
|
-
}
|
|
582
|
-
if (streamMode.includes(nextItem[0])) {
|
|
583
|
-
if (streamMode.length === 1) {
|
|
584
|
-
yield nextItem[1];
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
yield nextItem;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
783
|
+
yield* emitCurrentLoopOutputs();
|
|
591
784
|
if (error !== undefined && !isGraphInterrupt(error)) {
|
|
592
785
|
throw error;
|
|
593
786
|
}
|
|
594
787
|
}
|
|
595
788
|
if (debug) {
|
|
596
|
-
printStepWrites(loop.step, loop.tasks
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
while (loop.stream.length > 0) {
|
|
600
|
-
const nextItem = loop.stream.shift();
|
|
601
|
-
if (nextItem === undefined) {
|
|
602
|
-
throw new Error("Data structure error.");
|
|
603
|
-
}
|
|
604
|
-
if (streamMode.includes(nextItem[0])) {
|
|
605
|
-
if (streamMode.length === 1) {
|
|
606
|
-
yield nextItem[1];
|
|
607
|
-
}
|
|
608
|
-
else {
|
|
609
|
-
yield nextItem;
|
|
610
|
-
}
|
|
789
|
+
printStepWrites(loop.step, Object.values(loop.tasks)
|
|
790
|
+
.map((task) => task.writes)
|
|
791
|
+
.flat(), this.streamChannelsList);
|
|
611
792
|
}
|
|
612
793
|
}
|
|
794
|
+
yield* emitCurrentLoopOutputs();
|
|
613
795
|
if (loop.status === "out_of_steps") {
|
|
614
796
|
throw new GraphRecursionError([
|
|
615
797
|
`Recursion limit of ${config.recursionLimit} reached`,
|
|
@@ -618,7 +800,7 @@ export class Pregel extends Runnable {
|
|
|
618
800
|
].join(" "));
|
|
619
801
|
}
|
|
620
802
|
await Promise.all(loop?.checkpointerPromises ?? []);
|
|
621
|
-
await runManager?.handleChainEnd(
|
|
803
|
+
await runManager?.handleChainEnd(loop.output);
|
|
622
804
|
}
|
|
623
805
|
catch (e) {
|
|
624
806
|
await runManager?.handleChainError(e);
|
|
@@ -653,7 +835,7 @@ export class Pregel extends Runnable {
|
|
|
653
835
|
async invoke(input, options) {
|
|
654
836
|
const streamMode = options?.streamMode ?? "values";
|
|
655
837
|
const config = {
|
|
656
|
-
...
|
|
838
|
+
...options,
|
|
657
839
|
outputKeys: options?.outputKeys ?? this.outputChannels,
|
|
658
840
|
streamMode,
|
|
659
841
|
};
|
package/dist/pregel/io.cjs
CHANGED
|
@@ -79,12 +79,14 @@ function* mapOutputValues(outputChannels, pendingWrites, channels
|
|
|
79
79
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
80
|
) {
|
|
81
81
|
if (Array.isArray(outputChannels)) {
|
|
82
|
-
if (pendingWrites
|
|
82
|
+
if (pendingWrites === true ||
|
|
83
|
+
pendingWrites.find(([chan, _]) => outputChannels.includes(chan))) {
|
|
83
84
|
yield readChannels(channels, outputChannels);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
else {
|
|
87
|
-
if (pendingWrites
|
|
88
|
+
if (pendingWrites === true ||
|
|
89
|
+
pendingWrites.some(([chan, _]) => chan === outputChannels)) {
|
|
88
90
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
91
|
yield readChannel(channels, outputChannels);
|
|
90
92
|
}
|
|
@@ -94,30 +96,32 @@ exports.mapOutputValues = mapOutputValues;
|
|
|
94
96
|
/**
|
|
95
97
|
* Map pending writes (a sequence of tuples (channel, value)) to output chunk.
|
|
96
98
|
*/
|
|
97
|
-
function* mapOutputUpdates(outputChannels, tasks
|
|
99
|
+
function* mapOutputUpdates(outputChannels, tasks, cached
|
|
98
100
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
101
|
) {
|
|
100
|
-
const outputTasks = tasks.filter((task) =>
|
|
102
|
+
const outputTasks = tasks.filter(([task]) => {
|
|
103
|
+
return task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN);
|
|
104
|
+
});
|
|
101
105
|
if (!outputTasks.length) {
|
|
102
106
|
return;
|
|
103
107
|
}
|
|
104
108
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
105
109
|
let updated;
|
|
106
110
|
if (!Array.isArray(outputChannels)) {
|
|
107
|
-
updated = outputTasks.flatMap((task) => task.writes
|
|
111
|
+
updated = outputTasks.flatMap(([task]) => task.writes
|
|
108
112
|
.filter(([chan, _]) => chan === outputChannels)
|
|
109
113
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
110
114
|
.map(([_, value]) => [task.name, value]));
|
|
111
115
|
}
|
|
112
116
|
else {
|
|
113
117
|
updated = outputTasks
|
|
114
|
-
.filter((task) => task.writes.some(([chan]) => outputChannels.includes(chan)))
|
|
115
|
-
.map((task) => [
|
|
118
|
+
.filter(([task]) => task.writes.some(([chan]) => outputChannels.includes(chan)))
|
|
119
|
+
.map(([task]) => [
|
|
116
120
|
task.name,
|
|
117
121
|
Object.fromEntries(task.writes.filter(([chan]) => outputChannels.includes(chan))),
|
|
118
122
|
]);
|
|
119
123
|
}
|
|
120
|
-
const grouped = Object.fromEntries(outputTasks.map((t) => [t.name, []])
|
|
124
|
+
const grouped = Object.fromEntries(outputTasks.map(([t]) => [t.name, []])
|
|
121
125
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
126
|
);
|
|
123
127
|
for (const [node, value] of updated) {
|
|
@@ -133,6 +137,9 @@ function* mapOutputUpdates(outputChannels, tasks
|
|
|
133
137
|
grouped[node] = value[0];
|
|
134
138
|
}
|
|
135
139
|
}
|
|
140
|
+
if (cached) {
|
|
141
|
+
grouped["__metadata__"] = { cached };
|
|
142
|
+
}
|
|
136
143
|
yield grouped;
|
|
137
144
|
}
|
|
138
145
|
exports.mapOutputUpdates = mapOutputUpdates;
|
package/dist/pregel/io.d.ts
CHANGED
|
@@ -10,9 +10,9 @@ export declare function mapInput<C extends PropertyKey>(inputChannels: C | Array
|
|
|
10
10
|
/**
|
|
11
11
|
* Map pending writes (a sequence of tuples (channel, value)) to output chunk.
|
|
12
12
|
*/
|
|
13
|
-
export declare function mapOutputValues<C extends PropertyKey>(outputChannels: C | Array<C>, pendingWrites: readonly PendingWrite<C>[], channels: Record<C, BaseChannel>): Generator<Record<string, any>, any>;
|
|
13
|
+
export declare function mapOutputValues<C extends PropertyKey>(outputChannels: C | Array<C>, pendingWrites: readonly PendingWrite<C>[] | true, channels: Record<C, BaseChannel>): Generator<Record<string, any>, any>;
|
|
14
14
|
/**
|
|
15
15
|
* Map pending writes (a sequence of tuples (channel, value)) to output chunk.
|
|
16
16
|
*/
|
|
17
|
-
export declare function mapOutputUpdates<N extends PropertyKey, C extends PropertyKey>(outputChannels: C | Array<C>, tasks: readonly PregelExecutableTask<N, C>[]): Generator<Record<N, Record<string, any> |
|
|
17
|
+
export declare function mapOutputUpdates<N extends PropertyKey, C extends PropertyKey>(outputChannels: C | Array<C>, tasks: readonly [PregelExecutableTask<N, C>, PendingWrite<C>[]][], cached?: boolean): Generator<Record<N, Record<string, any> | any>>;
|
|
18
18
|
export declare function single<T>(iter: IterableIterator<T>): T | null;
|