@langchain/langgraph 0.3.4 → 0.3.6
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/channels/base.cjs +2 -2
- package/dist/channels/base.d.ts +3 -1
- package/dist/channels/base.js +2 -2
- package/dist/channels/base.js.map +1 -1
- package/dist/constants.cjs +3 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -1
- package/dist/errors.cjs +1 -6
- package/dist/errors.js +1 -6
- package/dist/errors.js.map +1 -1
- package/dist/graph/state.d.ts +1 -1
- package/dist/graph/zod/zod-registry.d.ts +8 -2
- package/dist/graph/zod/zod-registry.js +1 -1
- package/dist/graph/zod/zod-registry.js.map +1 -1
- package/dist/hash.cjs +333 -0
- package/dist/hash.d.ts +2 -0
- package/dist/hash.js +329 -0
- package/dist/hash.js.map +1 -0
- package/dist/interrupt.cjs +8 -1
- package/dist/interrupt.js +8 -1
- package/dist/interrupt.js.map +1 -1
- package/dist/prebuilt/react_agent_executor.d.ts +2 -2
- package/dist/pregel/algo.cjs +29 -14
- package/dist/pregel/algo.js +30 -15
- package/dist/pregel/algo.js.map +1 -1
- package/dist/pregel/debug.cjs +42 -49
- package/dist/pregel/debug.d.ts +20 -34
- package/dist/pregel/debug.js +43 -50
- package/dist/pregel/debug.js.map +1 -1
- package/dist/pregel/debug.test.cjs +83 -20
- package/dist/pregel/debug.test.js +83 -20
- package/dist/pregel/debug.test.js.map +1 -1
- package/dist/pregel/index.cjs +77 -23
- package/dist/pregel/index.d.ts +7 -2
- package/dist/pregel/index.js +78 -24
- package/dist/pregel/index.js.map +1 -1
- package/dist/pregel/loop.cjs +157 -43
- package/dist/pregel/loop.d.ts +7 -1
- package/dist/pregel/loop.js +158 -44
- package/dist/pregel/loop.js.map +1 -1
- package/dist/pregel/remote.cjs +2 -0
- package/dist/pregel/remote.d.ts +1 -0
- package/dist/pregel/remote.js +2 -0
- package/dist/pregel/remote.js.map +1 -1
- package/dist/pregel/types.d.ts +50 -4
- package/dist/pregel/types.js.map +1 -1
- package/dist/pregel/utils/config.cjs +2 -1
- package/dist/pregel/utils/config.js +2 -1
- package/dist/pregel/utils/config.js.map +1 -1
- package/dist/pregel/validate.cjs +1 -1
- package/dist/pregel/validate.js +1 -1
- package/dist/pregel/validate.js.map +1 -1
- package/dist/pregel/validate.test.cjs +1 -1
- package/dist/pregel/validate.test.js +1 -1
- package/dist/pregel/validate.test.js.map +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +5 -6
package/dist/pregel/index.cjs
CHANGED
|
@@ -679,7 +679,7 @@ class Pregel extends PartialRunnable {
|
|
|
679
679
|
return {
|
|
680
680
|
values: (0, io_js_1.readChannels)(channels, this.streamChannelsAsIs),
|
|
681
681
|
next: nextList,
|
|
682
|
-
tasks: (0, debug_js_1.tasksWithWrites)(nextTasks, saved?.pendingWrites ?? [], taskStates),
|
|
682
|
+
tasks: (0, debug_js_1.tasksWithWrites)(nextTasks, saved?.pendingWrites ?? [], taskStates, this.streamChannelsAsIs),
|
|
683
683
|
metadata,
|
|
684
684
|
config: (0, index_js_1.patchCheckpointMap)(saved.config, saved.metadata),
|
|
685
685
|
createdAt: saved.checkpoint.ts,
|
|
@@ -862,7 +862,7 @@ class Pregel extends PartialRunnable {
|
|
|
862
862
|
// tasks for this checkpoint
|
|
863
863
|
const nextTasks = (0, algo_js_1._prepareNextTasks)(checkpoint, saved.pendingWrites || [], this.nodes, channels, managed, saved.config, true, {
|
|
864
864
|
step: (saved.metadata?.step ?? -1) + 1,
|
|
865
|
-
checkpointer
|
|
865
|
+
checkpointer,
|
|
866
866
|
store: this.store,
|
|
867
867
|
});
|
|
868
868
|
// apply null writes
|
|
@@ -870,13 +870,13 @@ class Pregel extends PartialRunnable {
|
|
|
870
870
|
.filter((w) => w[0] === constants_js_1.NULL_TASK_ID)
|
|
871
871
|
.map((w) => w.slice(1));
|
|
872
872
|
if (nullWrites.length > 0) {
|
|
873
|
-
(0, algo_js_1._applyWrites)(
|
|
873
|
+
(0, algo_js_1._applyWrites)(checkpoint, channels, [
|
|
874
874
|
{
|
|
875
875
|
name: constants_js_1.INPUT,
|
|
876
876
|
writes: nullWrites,
|
|
877
877
|
triggers: [],
|
|
878
878
|
},
|
|
879
|
-
],
|
|
879
|
+
], checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
880
880
|
}
|
|
881
881
|
// apply writes from tasks that already ran
|
|
882
882
|
for (const [taskId, k, v] of saved.pendingWrites || []) {
|
|
@@ -889,29 +889,61 @@ class Pregel extends PartialRunnable {
|
|
|
889
889
|
nextTasks[taskId].writes.push([k, v]);
|
|
890
890
|
}
|
|
891
891
|
// clear all current tasks
|
|
892
|
-
(0, algo_js_1._applyWrites)(checkpoint, channels, Object.values(nextTasks),
|
|
892
|
+
(0, algo_js_1._applyWrites)(checkpoint, channels, Object.values(nextTasks), checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
893
893
|
}
|
|
894
894
|
// save checkpoint
|
|
895
|
-
const nextConfig = await checkpointer.put(checkpointConfig, (0, base_js_1.createCheckpoint)(checkpoint,
|
|
895
|
+
const nextConfig = await checkpointer.put(checkpointConfig, (0, base_js_1.createCheckpoint)(checkpoint, channels, step), {
|
|
896
896
|
...checkpointMetadata,
|
|
897
897
|
source: "update",
|
|
898
898
|
step: step + 1,
|
|
899
899
|
writes: {},
|
|
900
900
|
parents: saved?.metadata?.parents ?? {},
|
|
901
|
-
},
|
|
901
|
+
}, (0, index_js_1.getNewChannelVersions)(checkpointPreviousVersions, checkpoint.channel_versions));
|
|
902
902
|
return (0, index_js_1.patchCheckpointMap)(nextConfig, saved ? saved.metadata : undefined);
|
|
903
903
|
}
|
|
904
|
-
if (
|
|
904
|
+
if (asNode === constants_js_1.COPY) {
|
|
905
905
|
if (updates.length > 1) {
|
|
906
906
|
throw new errors_js_1.InvalidUpdateError(`Cannot copy checkpoint with multiple updates`);
|
|
907
907
|
}
|
|
908
|
-
|
|
908
|
+
if (saved == null) {
|
|
909
|
+
throw new errors_js_1.InvalidUpdateError(`Cannot copy a non-existent checkpoint`);
|
|
910
|
+
}
|
|
911
|
+
const isCopyWithUpdates = (values) => {
|
|
912
|
+
if (!Array.isArray(values))
|
|
913
|
+
return false;
|
|
914
|
+
if (values.length === 0)
|
|
915
|
+
return false;
|
|
916
|
+
return values.every((v) => Array.isArray(v) && v.length === 2);
|
|
917
|
+
};
|
|
918
|
+
const nextCheckpoint = (0, base_js_1.createCheckpoint)(checkpoint, undefined, step);
|
|
919
|
+
const nextConfig = await checkpointer.put(saved.parentConfig ??
|
|
920
|
+
(0, utils_js_1.patchConfigurable)(saved.config, { checkpoint_id: undefined }), nextCheckpoint, {
|
|
909
921
|
source: "fork",
|
|
910
922
|
step: step + 1,
|
|
911
923
|
writes: {},
|
|
912
|
-
parents: saved
|
|
924
|
+
parents: saved.metadata?.parents ?? {},
|
|
913
925
|
}, {});
|
|
914
|
-
|
|
926
|
+
// We want to both clone a checkpoint and update state in one go.
|
|
927
|
+
// Reuse the same task ID if possible.
|
|
928
|
+
if (isCopyWithUpdates(values)) {
|
|
929
|
+
// figure out the task IDs for the next update checkpoint
|
|
930
|
+
const nextTasks = (0, algo_js_1._prepareNextTasks)(nextCheckpoint, saved.pendingWrites, this.nodes, channels, managed, nextConfig, false, { step: step + 2 });
|
|
931
|
+
const tasksGroupBy = Object.values(nextTasks).reduce((acc, { name, id }) => {
|
|
932
|
+
acc[name] ??= [];
|
|
933
|
+
acc[name].push({ id });
|
|
934
|
+
return acc;
|
|
935
|
+
}, {});
|
|
936
|
+
const userGroupBy = values.reduce((acc, item) => {
|
|
937
|
+
const [values, asNode] = item;
|
|
938
|
+
acc[asNode] ??= [];
|
|
939
|
+
const targetIdx = acc[asNode].length;
|
|
940
|
+
const taskId = tasksGroupBy[asNode]?.[targetIdx]?.id;
|
|
941
|
+
acc[asNode].push({ values, asNode, taskId });
|
|
942
|
+
return acc;
|
|
943
|
+
}, {});
|
|
944
|
+
return updateSuperStep((0, index_js_1.patchCheckpointMap)(nextConfig, saved.metadata), Object.values(userGroupBy).flat());
|
|
945
|
+
}
|
|
946
|
+
return (0, index_js_1.patchCheckpointMap)(nextConfig, saved.metadata);
|
|
915
947
|
}
|
|
916
948
|
if (asNode === constants_js_1.INPUT) {
|
|
917
949
|
if (updates.length > 1) {
|
|
@@ -983,7 +1015,7 @@ class Pregel extends PartialRunnable {
|
|
|
983
1015
|
const validUpdates = [];
|
|
984
1016
|
if (updates.length === 1) {
|
|
985
1017
|
// eslint-disable-next-line prefer-const
|
|
986
|
-
let { values, asNode } = updates[0];
|
|
1018
|
+
let { values, asNode, taskId } = updates[0];
|
|
987
1019
|
if (asNode === undefined && Object.keys(this.nodes).length === 1) {
|
|
988
1020
|
// if only one node, use it
|
|
989
1021
|
[asNode] = Object.keys(this.nodes);
|
|
@@ -1019,18 +1051,18 @@ class Pregel extends PartialRunnable {
|
|
|
1019
1051
|
if (asNode === undefined) {
|
|
1020
1052
|
throw new errors_js_1.InvalidUpdateError(`Ambiguous update, specify "asNode"`);
|
|
1021
1053
|
}
|
|
1022
|
-
validUpdates.push({ values, asNode });
|
|
1054
|
+
validUpdates.push({ values, asNode, taskId });
|
|
1023
1055
|
}
|
|
1024
1056
|
else {
|
|
1025
|
-
for (const { asNode, values } of updates) {
|
|
1057
|
+
for (const { asNode, values, taskId } of updates) {
|
|
1026
1058
|
if (asNode == null) {
|
|
1027
1059
|
throw new errors_js_1.InvalidUpdateError(`"asNode" is required when applying multiple updates`);
|
|
1028
1060
|
}
|
|
1029
|
-
validUpdates.push({ values, asNode });
|
|
1061
|
+
validUpdates.push({ values, asNode, taskId });
|
|
1030
1062
|
}
|
|
1031
1063
|
}
|
|
1032
1064
|
const tasks = [];
|
|
1033
|
-
for (const { asNode, values } of validUpdates) {
|
|
1065
|
+
for (const { asNode, values, taskId } of validUpdates) {
|
|
1034
1066
|
if (this.nodes[asNode] === undefined) {
|
|
1035
1067
|
throw new errors_js_1.InvalidUpdateError(`Node "${asNode.toString()}" does not exist`);
|
|
1036
1068
|
}
|
|
@@ -1050,7 +1082,7 @@ class Pregel extends PartialRunnable {
|
|
|
1050
1082
|
: writers[0],
|
|
1051
1083
|
writes: [],
|
|
1052
1084
|
triggers: [constants_js_1.INTERRUPT],
|
|
1053
|
-
id: (0, langgraph_checkpoint_1.uuid5)(constants_js_1.INTERRUPT, checkpoint.id),
|
|
1085
|
+
id: taskId ?? (0, langgraph_checkpoint_1.uuid5)(constants_js_1.INTERRUPT, checkpoint.id),
|
|
1054
1086
|
writers: [],
|
|
1055
1087
|
});
|
|
1056
1088
|
}
|
|
@@ -1139,6 +1171,8 @@ class Pregel extends PartialRunnable {
|
|
|
1139
1171
|
* - checkpointer
|
|
1140
1172
|
* - store
|
|
1141
1173
|
* - whether stream mode is single
|
|
1174
|
+
* - node cache
|
|
1175
|
+
* - whether checkpoint during is enabled
|
|
1142
1176
|
* @internal
|
|
1143
1177
|
*/
|
|
1144
1178
|
_defaults(config) {
|
|
@@ -1167,13 +1201,16 @@ class Pregel extends PartialRunnable {
|
|
|
1167
1201
|
streamModeSingle = typeof streamMode === "string";
|
|
1168
1202
|
}
|
|
1169
1203
|
else {
|
|
1170
|
-
|
|
1204
|
+
// if being called as a node in another graph, default to values mode
|
|
1205
|
+
// but don't overwrite `streamMode`if provided
|
|
1206
|
+
if (config.configurable?.[constants_js_1.CONFIG_KEY_TASK_ID] !== undefined) {
|
|
1207
|
+
defaultStreamMode = ["values"];
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
defaultStreamMode = this.streamMode;
|
|
1211
|
+
}
|
|
1171
1212
|
streamModeSingle = true;
|
|
1172
1213
|
}
|
|
1173
|
-
// if being called as a node in another graph, always use values mode
|
|
1174
|
-
if (config.configurable?.[constants_js_1.CONFIG_KEY_TASK_ID] !== undefined) {
|
|
1175
|
-
defaultStreamMode = ["values"];
|
|
1176
|
-
}
|
|
1177
1214
|
let defaultCheckpointer;
|
|
1178
1215
|
if (this.checkpointer === false) {
|
|
1179
1216
|
defaultCheckpointer = undefined;
|
|
@@ -1182,11 +1219,17 @@ class Pregel extends PartialRunnable {
|
|
|
1182
1219
|
config.configurable?.[constants_js_1.CONFIG_KEY_CHECKPOINTER] !== undefined) {
|
|
1183
1220
|
defaultCheckpointer = config.configurable[constants_js_1.CONFIG_KEY_CHECKPOINTER];
|
|
1184
1221
|
}
|
|
1222
|
+
else if (this.checkpointer === true) {
|
|
1223
|
+
throw new Error("checkpointer: true cannot be used for root graphs.");
|
|
1224
|
+
}
|
|
1185
1225
|
else {
|
|
1186
1226
|
defaultCheckpointer = this.checkpointer;
|
|
1187
1227
|
}
|
|
1188
1228
|
const defaultStore = config.store ?? this.store;
|
|
1189
1229
|
const defaultCache = config.cache ?? this.cache;
|
|
1230
|
+
const defaultCheckpointDuring = config.checkpointDuring ??
|
|
1231
|
+
config?.configurable?.[constants_js_1.CONFIG_KEY_CHECKPOINT_DURING] ??
|
|
1232
|
+
true;
|
|
1190
1233
|
return [
|
|
1191
1234
|
defaultDebug,
|
|
1192
1235
|
defaultStreamMode,
|
|
@@ -1199,6 +1242,7 @@ class Pregel extends PartialRunnable {
|
|
|
1199
1242
|
defaultStore,
|
|
1200
1243
|
streamModeSingle,
|
|
1201
1244
|
defaultCache,
|
|
1245
|
+
defaultCheckpointDuring,
|
|
1202
1246
|
];
|
|
1203
1247
|
}
|
|
1204
1248
|
/**
|
|
@@ -1345,11 +1389,20 @@ class Pregel extends PartialRunnable {
|
|
|
1345
1389
|
const validInput = await this._validateInput(input);
|
|
1346
1390
|
const { runId, ...restConfig } = inputConfig;
|
|
1347
1391
|
// assign defaults
|
|
1348
|
-
const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store, streamModeSingle, cache,] = this._defaults(restConfig);
|
|
1392
|
+
const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store, streamModeSingle, cache, checkpointDuring,] = this._defaults(restConfig);
|
|
1349
1393
|
config.configurable = await this._validateConfigurable(config.configurable);
|
|
1350
1394
|
const stream = new stream_js_1.IterableReadableWritableStream({
|
|
1351
1395
|
modes: new Set(streamMode),
|
|
1352
1396
|
});
|
|
1397
|
+
// set up subgraph checkpointing
|
|
1398
|
+
if (this.checkpointer === true) {
|
|
1399
|
+
config.configurable ??= {};
|
|
1400
|
+
const ns = config.configurable[constants_js_1.CONFIG_KEY_CHECKPOINT_NS] ?? "";
|
|
1401
|
+
config.configurable[constants_js_1.CONFIG_KEY_CHECKPOINT_NS] = ns
|
|
1402
|
+
.split(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR)
|
|
1403
|
+
.map((part) => part.split(constants_js_1.CHECKPOINT_NAMESPACE_END)[0])
|
|
1404
|
+
.join(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR);
|
|
1405
|
+
}
|
|
1353
1406
|
// set up messages stream mode
|
|
1354
1407
|
if (streamMode.includes("messages")) {
|
|
1355
1408
|
const messageStreamer = new messages_js_1.StreamMessagesHandler((chunk) => stream.push(chunk));
|
|
@@ -1409,6 +1462,7 @@ class Pregel extends PartialRunnable {
|
|
|
1409
1462
|
manager: runManager,
|
|
1410
1463
|
debug: this.debug,
|
|
1411
1464
|
triggerToNodes: this.triggerToNodes,
|
|
1465
|
+
checkpointDuring,
|
|
1412
1466
|
});
|
|
1413
1467
|
const runner = new runner_js_1.PregelRunner({
|
|
1414
1468
|
loop,
|
package/dist/pregel/index.d.ts
CHANGED
|
@@ -222,7 +222,7 @@ export declare class Pregel<Nodes extends StrRecord<string, PregelNode>, Channel
|
|
|
222
222
|
* When provided, saves a checkpoint of the graph state at every superstep.
|
|
223
223
|
* When false or undefined, checkpointing is disabled, and the graph will not be able to save or restore state.
|
|
224
224
|
*/
|
|
225
|
-
checkpointer?: BaseCheckpointSaver |
|
|
225
|
+
checkpointer?: BaseCheckpointSaver | boolean;
|
|
226
226
|
/** Optional retry policy for handling failures in node execution */
|
|
227
227
|
retryPolicy?: RetryPolicy;
|
|
228
228
|
/** The default configuration for graph execution, can be overridden on a per-invocation basis */
|
|
@@ -412,6 +412,8 @@ export declare class Pregel<Nodes extends StrRecord<string, PregelNode>, Channel
|
|
|
412
412
|
* - checkpointer
|
|
413
413
|
* - store
|
|
414
414
|
* - whether stream mode is single
|
|
415
|
+
* - node cache
|
|
416
|
+
* - whether checkpoint during is enabled
|
|
415
417
|
* @internal
|
|
416
418
|
*/
|
|
417
419
|
_defaults(config: PregelOptions<Nodes, Channels>): [
|
|
@@ -428,9 +430,12 @@ export declare class Pregel<Nodes extends StrRecord<string, PregelNode>, Channel
|
|
|
428
430
|
All | string[],
|
|
429
431
|
// interrupt after
|
|
430
432
|
BaseCheckpointSaver | undefined,
|
|
433
|
+
// checkpointer
|
|
431
434
|
BaseStore | undefined,
|
|
432
435
|
boolean,
|
|
433
|
-
|
|
436
|
+
// stream mode single
|
|
437
|
+
BaseCache | undefined,
|
|
438
|
+
boolean
|
|
434
439
|
];
|
|
435
440
|
/**
|
|
436
441
|
* Streams the execution of the graph, emitting state updates as they occur.
|
package/dist/pregel/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { _coerceToRunnable, getCallbackManagerForConfig, mergeConfigs, patchConfig, Runnable, RunnableSequence, } from "@langchain/core/runnables";
|
|
3
3
|
import { compareChannelVersions, copyCheckpoint, emptyCheckpoint, SCHEDULED, uuid5, } from "@langchain/langgraph-checkpoint";
|
|
4
4
|
import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
|
|
5
|
-
import { CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_NODE_FINISHED, CONFIG_KEY_READ, CONFIG_KEY_SEND, CONFIG_KEY_STREAM, CONFIG_KEY_TASK_ID, COPY, END, ERROR, INPUT, INTERRUPT, isInterrupted, NULL_TASK_ID, PUSH, } from "../constants.js";
|
|
5
|
+
import { CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_NODE_FINISHED, CONFIG_KEY_READ, CONFIG_KEY_SEND, CONFIG_KEY_STREAM, CONFIG_KEY_TASK_ID, COPY, END, ERROR, INPUT, INTERRUPT, isInterrupted, NULL_TASK_ID, PUSH, CONFIG_KEY_CHECKPOINT_DURING, CONFIG_KEY_CHECKPOINT_NS, } from "../constants.js";
|
|
6
6
|
import { GraphRecursionError, GraphValueError, InvalidUpdateError, } from "../errors.js";
|
|
7
7
|
import { ChannelKeyPlaceholder, isConfiguredManagedValue, ManagedValueMapping, NoopManagedValue, } from "../managed/base.js";
|
|
8
8
|
import { gatherIterator, patchConfigurable } from "../utils.js";
|
|
@@ -675,7 +675,7 @@ export class Pregel extends PartialRunnable {
|
|
|
675
675
|
return {
|
|
676
676
|
values: readChannels(channels, this.streamChannelsAsIs),
|
|
677
677
|
next: nextList,
|
|
678
|
-
tasks: tasksWithWrites(nextTasks, saved?.pendingWrites ?? [], taskStates),
|
|
678
|
+
tasks: tasksWithWrites(nextTasks, saved?.pendingWrites ?? [], taskStates, this.streamChannelsAsIs),
|
|
679
679
|
metadata,
|
|
680
680
|
config: patchCheckpointMap(saved.config, saved.metadata),
|
|
681
681
|
createdAt: saved.checkpoint.ts,
|
|
@@ -858,7 +858,7 @@ export class Pregel extends PartialRunnable {
|
|
|
858
858
|
// tasks for this checkpoint
|
|
859
859
|
const nextTasks = _prepareNextTasks(checkpoint, saved.pendingWrites || [], this.nodes, channels, managed, saved.config, true, {
|
|
860
860
|
step: (saved.metadata?.step ?? -1) + 1,
|
|
861
|
-
checkpointer
|
|
861
|
+
checkpointer,
|
|
862
862
|
store: this.store,
|
|
863
863
|
});
|
|
864
864
|
// apply null writes
|
|
@@ -866,13 +866,13 @@ export class Pregel extends PartialRunnable {
|
|
|
866
866
|
.filter((w) => w[0] === NULL_TASK_ID)
|
|
867
867
|
.map((w) => w.slice(1));
|
|
868
868
|
if (nullWrites.length > 0) {
|
|
869
|
-
_applyWrites(
|
|
869
|
+
_applyWrites(checkpoint, channels, [
|
|
870
870
|
{
|
|
871
871
|
name: INPUT,
|
|
872
872
|
writes: nullWrites,
|
|
873
873
|
triggers: [],
|
|
874
874
|
},
|
|
875
|
-
],
|
|
875
|
+
], checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
876
876
|
}
|
|
877
877
|
// apply writes from tasks that already ran
|
|
878
878
|
for (const [taskId, k, v] of saved.pendingWrites || []) {
|
|
@@ -885,29 +885,61 @@ export class Pregel extends PartialRunnable {
|
|
|
885
885
|
nextTasks[taskId].writes.push([k, v]);
|
|
886
886
|
}
|
|
887
887
|
// clear all current tasks
|
|
888
|
-
_applyWrites(checkpoint, channels, Object.values(nextTasks),
|
|
888
|
+
_applyWrites(checkpoint, channels, Object.values(nextTasks), checkpointer.getNextVersion.bind(checkpointer), this.triggerToNodes);
|
|
889
889
|
}
|
|
890
890
|
// save checkpoint
|
|
891
|
-
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint,
|
|
891
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, channels, step), {
|
|
892
892
|
...checkpointMetadata,
|
|
893
893
|
source: "update",
|
|
894
894
|
step: step + 1,
|
|
895
895
|
writes: {},
|
|
896
896
|
parents: saved?.metadata?.parents ?? {},
|
|
897
|
-
},
|
|
897
|
+
}, getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions));
|
|
898
898
|
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
899
899
|
}
|
|
900
|
-
if (
|
|
900
|
+
if (asNode === COPY) {
|
|
901
901
|
if (updates.length > 1) {
|
|
902
902
|
throw new InvalidUpdateError(`Cannot copy checkpoint with multiple updates`);
|
|
903
903
|
}
|
|
904
|
-
|
|
904
|
+
if (saved == null) {
|
|
905
|
+
throw new InvalidUpdateError(`Cannot copy a non-existent checkpoint`);
|
|
906
|
+
}
|
|
907
|
+
const isCopyWithUpdates = (values) => {
|
|
908
|
+
if (!Array.isArray(values))
|
|
909
|
+
return false;
|
|
910
|
+
if (values.length === 0)
|
|
911
|
+
return false;
|
|
912
|
+
return values.every((v) => Array.isArray(v) && v.length === 2);
|
|
913
|
+
};
|
|
914
|
+
const nextCheckpoint = createCheckpoint(checkpoint, undefined, step);
|
|
915
|
+
const nextConfig = await checkpointer.put(saved.parentConfig ??
|
|
916
|
+
patchConfigurable(saved.config, { checkpoint_id: undefined }), nextCheckpoint, {
|
|
905
917
|
source: "fork",
|
|
906
918
|
step: step + 1,
|
|
907
919
|
writes: {},
|
|
908
|
-
parents: saved
|
|
920
|
+
parents: saved.metadata?.parents ?? {},
|
|
909
921
|
}, {});
|
|
910
|
-
|
|
922
|
+
// We want to both clone a checkpoint and update state in one go.
|
|
923
|
+
// Reuse the same task ID if possible.
|
|
924
|
+
if (isCopyWithUpdates(values)) {
|
|
925
|
+
// figure out the task IDs for the next update checkpoint
|
|
926
|
+
const nextTasks = _prepareNextTasks(nextCheckpoint, saved.pendingWrites, this.nodes, channels, managed, nextConfig, false, { step: step + 2 });
|
|
927
|
+
const tasksGroupBy = Object.values(nextTasks).reduce((acc, { name, id }) => {
|
|
928
|
+
acc[name] ??= [];
|
|
929
|
+
acc[name].push({ id });
|
|
930
|
+
return acc;
|
|
931
|
+
}, {});
|
|
932
|
+
const userGroupBy = values.reduce((acc, item) => {
|
|
933
|
+
const [values, asNode] = item;
|
|
934
|
+
acc[asNode] ??= [];
|
|
935
|
+
const targetIdx = acc[asNode].length;
|
|
936
|
+
const taskId = tasksGroupBy[asNode]?.[targetIdx]?.id;
|
|
937
|
+
acc[asNode].push({ values, asNode, taskId });
|
|
938
|
+
return acc;
|
|
939
|
+
}, {});
|
|
940
|
+
return updateSuperStep(patchCheckpointMap(nextConfig, saved.metadata), Object.values(userGroupBy).flat());
|
|
941
|
+
}
|
|
942
|
+
return patchCheckpointMap(nextConfig, saved.metadata);
|
|
911
943
|
}
|
|
912
944
|
if (asNode === INPUT) {
|
|
913
945
|
if (updates.length > 1) {
|
|
@@ -979,7 +1011,7 @@ export class Pregel extends PartialRunnable {
|
|
|
979
1011
|
const validUpdates = [];
|
|
980
1012
|
if (updates.length === 1) {
|
|
981
1013
|
// eslint-disable-next-line prefer-const
|
|
982
|
-
let { values, asNode } = updates[0];
|
|
1014
|
+
let { values, asNode, taskId } = updates[0];
|
|
983
1015
|
if (asNode === undefined && Object.keys(this.nodes).length === 1) {
|
|
984
1016
|
// if only one node, use it
|
|
985
1017
|
[asNode] = Object.keys(this.nodes);
|
|
@@ -1015,18 +1047,18 @@ export class Pregel extends PartialRunnable {
|
|
|
1015
1047
|
if (asNode === undefined) {
|
|
1016
1048
|
throw new InvalidUpdateError(`Ambiguous update, specify "asNode"`);
|
|
1017
1049
|
}
|
|
1018
|
-
validUpdates.push({ values, asNode });
|
|
1050
|
+
validUpdates.push({ values, asNode, taskId });
|
|
1019
1051
|
}
|
|
1020
1052
|
else {
|
|
1021
|
-
for (const { asNode, values } of updates) {
|
|
1053
|
+
for (const { asNode, values, taskId } of updates) {
|
|
1022
1054
|
if (asNode == null) {
|
|
1023
1055
|
throw new InvalidUpdateError(`"asNode" is required when applying multiple updates`);
|
|
1024
1056
|
}
|
|
1025
|
-
validUpdates.push({ values, asNode });
|
|
1057
|
+
validUpdates.push({ values, asNode, taskId });
|
|
1026
1058
|
}
|
|
1027
1059
|
}
|
|
1028
1060
|
const tasks = [];
|
|
1029
|
-
for (const { asNode, values } of validUpdates) {
|
|
1061
|
+
for (const { asNode, values, taskId } of validUpdates) {
|
|
1030
1062
|
if (this.nodes[asNode] === undefined) {
|
|
1031
1063
|
throw new InvalidUpdateError(`Node "${asNode.toString()}" does not exist`);
|
|
1032
1064
|
}
|
|
@@ -1046,7 +1078,7 @@ export class Pregel extends PartialRunnable {
|
|
|
1046
1078
|
: writers[0],
|
|
1047
1079
|
writes: [],
|
|
1048
1080
|
triggers: [INTERRUPT],
|
|
1049
|
-
id: uuid5(INTERRUPT, checkpoint.id),
|
|
1081
|
+
id: taskId ?? uuid5(INTERRUPT, checkpoint.id),
|
|
1050
1082
|
writers: [],
|
|
1051
1083
|
});
|
|
1052
1084
|
}
|
|
@@ -1135,6 +1167,8 @@ export class Pregel extends PartialRunnable {
|
|
|
1135
1167
|
* - checkpointer
|
|
1136
1168
|
* - store
|
|
1137
1169
|
* - whether stream mode is single
|
|
1170
|
+
* - node cache
|
|
1171
|
+
* - whether checkpoint during is enabled
|
|
1138
1172
|
* @internal
|
|
1139
1173
|
*/
|
|
1140
1174
|
_defaults(config) {
|
|
@@ -1163,13 +1197,16 @@ export class Pregel extends PartialRunnable {
|
|
|
1163
1197
|
streamModeSingle = typeof streamMode === "string";
|
|
1164
1198
|
}
|
|
1165
1199
|
else {
|
|
1166
|
-
|
|
1200
|
+
// if being called as a node in another graph, default to values mode
|
|
1201
|
+
// but don't overwrite `streamMode`if provided
|
|
1202
|
+
if (config.configurable?.[CONFIG_KEY_TASK_ID] !== undefined) {
|
|
1203
|
+
defaultStreamMode = ["values"];
|
|
1204
|
+
}
|
|
1205
|
+
else {
|
|
1206
|
+
defaultStreamMode = this.streamMode;
|
|
1207
|
+
}
|
|
1167
1208
|
streamModeSingle = true;
|
|
1168
1209
|
}
|
|
1169
|
-
// if being called as a node in another graph, always use values mode
|
|
1170
|
-
if (config.configurable?.[CONFIG_KEY_TASK_ID] !== undefined) {
|
|
1171
|
-
defaultStreamMode = ["values"];
|
|
1172
|
-
}
|
|
1173
1210
|
let defaultCheckpointer;
|
|
1174
1211
|
if (this.checkpointer === false) {
|
|
1175
1212
|
defaultCheckpointer = undefined;
|
|
@@ -1178,11 +1215,17 @@ export class Pregel extends PartialRunnable {
|
|
|
1178
1215
|
config.configurable?.[CONFIG_KEY_CHECKPOINTER] !== undefined) {
|
|
1179
1216
|
defaultCheckpointer = config.configurable[CONFIG_KEY_CHECKPOINTER];
|
|
1180
1217
|
}
|
|
1218
|
+
else if (this.checkpointer === true) {
|
|
1219
|
+
throw new Error("checkpointer: true cannot be used for root graphs.");
|
|
1220
|
+
}
|
|
1181
1221
|
else {
|
|
1182
1222
|
defaultCheckpointer = this.checkpointer;
|
|
1183
1223
|
}
|
|
1184
1224
|
const defaultStore = config.store ?? this.store;
|
|
1185
1225
|
const defaultCache = config.cache ?? this.cache;
|
|
1226
|
+
const defaultCheckpointDuring = config.checkpointDuring ??
|
|
1227
|
+
config?.configurable?.[CONFIG_KEY_CHECKPOINT_DURING] ??
|
|
1228
|
+
true;
|
|
1186
1229
|
return [
|
|
1187
1230
|
defaultDebug,
|
|
1188
1231
|
defaultStreamMode,
|
|
@@ -1195,6 +1238,7 @@ export class Pregel extends PartialRunnable {
|
|
|
1195
1238
|
defaultStore,
|
|
1196
1239
|
streamModeSingle,
|
|
1197
1240
|
defaultCache,
|
|
1241
|
+
defaultCheckpointDuring,
|
|
1198
1242
|
];
|
|
1199
1243
|
}
|
|
1200
1244
|
/**
|
|
@@ -1341,11 +1385,20 @@ export class Pregel extends PartialRunnable {
|
|
|
1341
1385
|
const validInput = await this._validateInput(input);
|
|
1342
1386
|
const { runId, ...restConfig } = inputConfig;
|
|
1343
1387
|
// assign defaults
|
|
1344
|
-
const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store, streamModeSingle, cache,] = this._defaults(restConfig);
|
|
1388
|
+
const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store, streamModeSingle, cache, checkpointDuring,] = this._defaults(restConfig);
|
|
1345
1389
|
config.configurable = await this._validateConfigurable(config.configurable);
|
|
1346
1390
|
const stream = new IterableReadableWritableStream({
|
|
1347
1391
|
modes: new Set(streamMode),
|
|
1348
1392
|
});
|
|
1393
|
+
// set up subgraph checkpointing
|
|
1394
|
+
if (this.checkpointer === true) {
|
|
1395
|
+
config.configurable ??= {};
|
|
1396
|
+
const ns = config.configurable[CONFIG_KEY_CHECKPOINT_NS] ?? "";
|
|
1397
|
+
config.configurable[CONFIG_KEY_CHECKPOINT_NS] = ns
|
|
1398
|
+
.split(CHECKPOINT_NAMESPACE_SEPARATOR)
|
|
1399
|
+
.map((part) => part.split(CHECKPOINT_NAMESPACE_END)[0])
|
|
1400
|
+
.join(CHECKPOINT_NAMESPACE_SEPARATOR);
|
|
1401
|
+
}
|
|
1349
1402
|
// set up messages stream mode
|
|
1350
1403
|
if (streamMode.includes("messages")) {
|
|
1351
1404
|
const messageStreamer = new StreamMessagesHandler((chunk) => stream.push(chunk));
|
|
@@ -1405,6 +1458,7 @@ export class Pregel extends PartialRunnable {
|
|
|
1405
1458
|
manager: runManager,
|
|
1406
1459
|
debug: this.debug,
|
|
1407
1460
|
triggerToNodes: this.triggerToNodes,
|
|
1461
|
+
checkpointDuring,
|
|
1408
1462
|
});
|
|
1409
1463
|
const runner = new PregelRunner({
|
|
1410
1464
|
loop,
|