@langchain/langgraph 0.2.8 → 0.2.10-rc.0
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 +11 -2
- package/dist/constants.d.ts +6 -1
- package/dist/constants.js +10 -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 +2 -3
- package/dist/graph/state.js +9 -4
- package/dist/managed/shared_value.cjs +12 -10
- package/dist/managed/shared_value.d.ts +6 -5
- package/dist/managed/shared_value.js +12 -10
- package/dist/pregel/algo.cjs +124 -54
- package/dist/pregel/algo.d.ts +13 -3
- package/dist/pregel/algo.js +122 -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 +307 -116
- package/dist/pregel/index.d.ts +21 -6
- package/dist/pregel/index.js +305 -117
- 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 +258 -113
- package/dist/pregel/loop.d.ts +24 -9
- package/dist/pregel/loop.js +258 -111
- package/dist/pregel/read.cjs +9 -2
- package/dist/pregel/read.d.ts +3 -2
- package/dist/pregel/read.js +9 -2
- package/dist/pregel/retry.d.ts +1 -1
- package/dist/pregel/runnable_types.cjs +2 -0
- package/dist/pregel/runnable_types.d.ts +5 -0
- package/dist/pregel/runnable_types.js +1 -0
- package/dist/pregel/types.d.ts +8 -4
- package/dist/pregel/utils/config.cjs +73 -0
- package/dist/pregel/utils/config.d.ts +3 -0
- package/dist/pregel/utils/config.js +69 -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.cjs +4 -2
- package/dist/web.d.ts +4 -3
- package/dist/web.js +1 -2
- package/package.json +1 -1
- package/dist/store/base.cjs +0 -12
- package/dist/store/base.d.ts +0 -7
- package/dist/store/base.js +0 -8
- package/dist/store/batch.cjs +0 -126
- package/dist/store/batch.d.ts +0 -55
- package/dist/store/batch.js +0 -122
- package/dist/store/index.cjs +0 -19
- package/dist/store/index.d.ts +0 -3
- package/dist/store/index.js +0 -3
- package/dist/store/memory.cjs +0 -43
- package/dist/store/memory.d.ts +0 -6
- package/dist/store/memory.js +0 -39
package/dist/pregel/loop.cjs
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PregelLoop = void 0;
|
|
7
|
-
const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
|
|
3
|
+
exports.PregelLoop = exports.StreamProtocol = void 0;
|
|
8
4
|
const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
|
|
9
5
|
const base_js_1 = require("../channels/base.cjs");
|
|
10
6
|
const constants_js_1 = require("../constants.cjs");
|
|
@@ -12,12 +8,40 @@ const algo_js_1 = require("./algo.cjs");
|
|
|
12
8
|
const utils_js_1 = require("../utils.cjs");
|
|
13
9
|
const io_js_1 = require("./io.cjs");
|
|
14
10
|
const errors_js_1 = require("../errors.cjs");
|
|
15
|
-
const
|
|
11
|
+
const index_js_1 = require("./utils/index.cjs");
|
|
16
12
|
const debug_js_1 = require("./debug.cjs");
|
|
17
|
-
const batch_js_1 = require("../store/batch.cjs");
|
|
18
13
|
const INPUT_DONE = Symbol.for("INPUT_DONE");
|
|
19
14
|
const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
|
|
20
15
|
const DEFAULT_LOOP_LIMIT = 25;
|
|
16
|
+
const SPECIAL_CHANNELS = [constants_js_1.ERROR, constants_js_1.INTERRUPT];
|
|
17
|
+
class StreamProtocol {
|
|
18
|
+
constructor(pushFn, modes) {
|
|
19
|
+
Object.defineProperty(this, "push", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: void 0
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "modes", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: void 0
|
|
30
|
+
});
|
|
31
|
+
this.push = pushFn;
|
|
32
|
+
this.modes = modes;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.StreamProtocol = StreamProtocol;
|
|
36
|
+
function createDuplexStream(...streams) {
|
|
37
|
+
return new StreamProtocol((value) => {
|
|
38
|
+
for (const stream of streams) {
|
|
39
|
+
if (stream.modes.has(value[1])) {
|
|
40
|
+
stream.push(value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}, new Set(streams.flatMap((s) => Array.from(s.modes))));
|
|
44
|
+
}
|
|
21
45
|
class PregelLoop {
|
|
22
46
|
constructor(params) {
|
|
23
47
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -27,6 +51,13 @@ class PregelLoop {
|
|
|
27
51
|
writable: true,
|
|
28
52
|
value: void 0
|
|
29
53
|
});
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
Object.defineProperty(this, "output", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: void 0
|
|
60
|
+
});
|
|
30
61
|
Object.defineProperty(this, "config", {
|
|
31
62
|
enumerable: true,
|
|
32
63
|
configurable: true,
|
|
@@ -75,6 +106,12 @@ class PregelLoop {
|
|
|
75
106
|
writable: true,
|
|
76
107
|
value: void 0
|
|
77
108
|
});
|
|
109
|
+
Object.defineProperty(this, "checkpointNamespace", {
|
|
110
|
+
enumerable: true,
|
|
111
|
+
configurable: true,
|
|
112
|
+
writable: true,
|
|
113
|
+
value: void 0
|
|
114
|
+
});
|
|
78
115
|
Object.defineProperty(this, "checkpointPendingWrites", {
|
|
79
116
|
enumerable: true,
|
|
80
117
|
configurable: true,
|
|
@@ -123,6 +160,12 @@ class PregelLoop {
|
|
|
123
160
|
writable: true,
|
|
124
161
|
value: void 0
|
|
125
162
|
});
|
|
163
|
+
Object.defineProperty(this, "taskWritesLeft", {
|
|
164
|
+
enumerable: true,
|
|
165
|
+
configurable: true,
|
|
166
|
+
writable: true,
|
|
167
|
+
value: 0
|
|
168
|
+
});
|
|
126
169
|
Object.defineProperty(this, "status", {
|
|
127
170
|
enumerable: true,
|
|
128
171
|
configurable: true,
|
|
@@ -134,14 +177,14 @@ class PregelLoop {
|
|
|
134
177
|
enumerable: true,
|
|
135
178
|
configurable: true,
|
|
136
179
|
writable: true,
|
|
137
|
-
value:
|
|
180
|
+
value: {}
|
|
138
181
|
});
|
|
139
182
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
183
|
Object.defineProperty(this, "stream", {
|
|
141
184
|
enumerable: true,
|
|
142
185
|
configurable: true,
|
|
143
186
|
writable: true,
|
|
144
|
-
value:
|
|
187
|
+
value: void 0
|
|
145
188
|
});
|
|
146
189
|
Object.defineProperty(this, "checkpointerPromises", {
|
|
147
190
|
enumerable: true,
|
|
@@ -168,7 +211,6 @@ class PregelLoop {
|
|
|
168
211
|
value: void 0
|
|
169
212
|
});
|
|
170
213
|
this.input = params.input;
|
|
171
|
-
this.config = params.config;
|
|
172
214
|
this.checkpointer = params.checkpointer;
|
|
173
215
|
// TODO: if managed values no longer needs graph we can replace with
|
|
174
216
|
// managed_specs, channel_specs
|
|
@@ -179,7 +221,6 @@ class PregelLoop {
|
|
|
179
221
|
this.checkpointerGetNextVersion = algo_js_1.increment;
|
|
180
222
|
}
|
|
181
223
|
this.checkpoint = params.checkpoint;
|
|
182
|
-
this.checkpointConfig = params.checkpointConfig;
|
|
183
224
|
this.checkpointMetadata = params.checkpointMetadata;
|
|
184
225
|
this.checkpointPreviousVersions = params.checkpointPreviousVersions;
|
|
185
226
|
this.channels = params.channels;
|
|
@@ -187,29 +228,58 @@ class PregelLoop {
|
|
|
187
228
|
this.checkpointPendingWrites = params.checkpointPendingWrites;
|
|
188
229
|
this.step = params.step;
|
|
189
230
|
this.stop = params.stop;
|
|
190
|
-
this.
|
|
231
|
+
this.config = params.config;
|
|
232
|
+
this.checkpointConfig = params.checkpointConfig;
|
|
233
|
+
this.isNested = params.isNested;
|
|
191
234
|
this.outputKeys = params.outputKeys;
|
|
192
235
|
this.streamKeys = params.streamKeys;
|
|
193
236
|
this.nodes = params.nodes;
|
|
194
|
-
this.skipDoneTasks =
|
|
237
|
+
this.skipDoneTasks = params.skipDoneTasks;
|
|
195
238
|
this.store = params.store;
|
|
239
|
+
this.stream = params.stream;
|
|
240
|
+
this.checkpointNamespace = params.checkpointNamespace;
|
|
196
241
|
}
|
|
197
242
|
static async initialize(params) {
|
|
198
|
-
|
|
199
|
-
|
|
243
|
+
let { config, stream } = params;
|
|
244
|
+
if (stream !== undefined &&
|
|
245
|
+
config.configurable?.[constants_js_1.CONFIG_KEY_STREAM] !== undefined) {
|
|
246
|
+
stream = createDuplexStream(stream, config.configurable[constants_js_1.CONFIG_KEY_STREAM]);
|
|
247
|
+
}
|
|
248
|
+
const skipDoneTasks = config.configurable?.checkpoint_id === undefined;
|
|
249
|
+
const isNested = constants_js_1.CONFIG_KEY_READ in (config.configurable ?? {});
|
|
250
|
+
if (!isNested &&
|
|
251
|
+
config.configurable?.checkpoint_ns !== undefined &&
|
|
252
|
+
config.configurable?.checkpoint_ns !== "") {
|
|
253
|
+
config = (0, index_js_1.patchConfigurable)(config, {
|
|
254
|
+
checkpoint_ns: "",
|
|
255
|
+
checkpoint_id: undefined,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
let checkpointConfig = config;
|
|
259
|
+
if (config.configurable?.[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP] !== undefined &&
|
|
260
|
+
config.configurable?.[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP]?.[config.configurable?.checkpoint_ns]) {
|
|
261
|
+
checkpointConfig = (0, index_js_1.patchConfigurable)(config, {
|
|
262
|
+
checkpoint_id: config.configurable[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP][config.configurable?.checkpoint_ns],
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const checkpointNamespace = config.configurable?.checkpoint_ns?.split(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR) ?? [];
|
|
266
|
+
const saved = (await params.checkpointer?.getTuple(checkpointConfig)) ?? {
|
|
267
|
+
config,
|
|
200
268
|
checkpoint: (0, langgraph_checkpoint_1.emptyCheckpoint)(),
|
|
201
269
|
metadata: {
|
|
202
270
|
source: "input",
|
|
203
271
|
step: -2,
|
|
204
272
|
writes: null,
|
|
273
|
+
parents: {},
|
|
205
274
|
},
|
|
206
275
|
pendingWrites: [],
|
|
207
276
|
};
|
|
208
|
-
|
|
209
|
-
...
|
|
277
|
+
checkpointConfig = {
|
|
278
|
+
...config,
|
|
210
279
|
...saved.config,
|
|
211
280
|
configurable: {
|
|
212
|
-
|
|
281
|
+
checkpoint_ns: "",
|
|
282
|
+
...config.configurable,
|
|
213
283
|
...saved.config.configurable,
|
|
214
284
|
},
|
|
215
285
|
};
|
|
@@ -218,10 +288,10 @@ class PregelLoop {
|
|
|
218
288
|
const checkpointPendingWrites = saved.pendingWrites ?? [];
|
|
219
289
|
const channels = (0, base_js_1.emptyChannels)(params.channelSpecs, checkpoint);
|
|
220
290
|
const step = (checkpointMetadata.step ?? 0) + 1;
|
|
221
|
-
const stop = step + (
|
|
291
|
+
const stop = step + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT) + 1;
|
|
222
292
|
const checkpointPreviousVersions = { ...checkpoint.channel_versions };
|
|
223
293
|
const store = params.store
|
|
224
|
-
? new
|
|
294
|
+
? new langgraph_checkpoint_1.AsyncBatchedStore(params.store)
|
|
225
295
|
: undefined;
|
|
226
296
|
if (store) {
|
|
227
297
|
// Start the store. This is a batch store, so it will run continuously
|
|
@@ -229,13 +299,16 @@ class PregelLoop {
|
|
|
229
299
|
}
|
|
230
300
|
return new PregelLoop({
|
|
231
301
|
input: params.input,
|
|
232
|
-
config
|
|
302
|
+
config,
|
|
233
303
|
checkpointer: params.checkpointer,
|
|
234
304
|
checkpoint,
|
|
235
305
|
checkpointMetadata,
|
|
236
306
|
checkpointConfig,
|
|
307
|
+
checkpointNamespace,
|
|
237
308
|
channels,
|
|
238
309
|
managed: params.managed,
|
|
310
|
+
isNested,
|
|
311
|
+
skipDoneTasks,
|
|
239
312
|
step,
|
|
240
313
|
stop,
|
|
241
314
|
checkpointPreviousVersions,
|
|
@@ -243,6 +316,7 @@ class PregelLoop {
|
|
|
243
316
|
outputKeys: params.outputKeys ?? [],
|
|
244
317
|
streamKeys: params.streamKeys ?? [],
|
|
245
318
|
nodes: params.nodes,
|
|
319
|
+
stream,
|
|
246
320
|
store,
|
|
247
321
|
});
|
|
248
322
|
}
|
|
@@ -265,6 +339,22 @@ class PregelLoop {
|
|
|
265
339
|
* @param writes
|
|
266
340
|
*/
|
|
267
341
|
putWrites(taskId, writes) {
|
|
342
|
+
if (writes.length === 0) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// adjust taskWritesLeft
|
|
346
|
+
const firstChannel = writes[0][0];
|
|
347
|
+
const anyChannelIsSend = writes.find(([channel]) => channel === constants_js_1.TASKS);
|
|
348
|
+
const alwaysSave = anyChannelIsSend || SPECIAL_CHANNELS.includes(firstChannel);
|
|
349
|
+
if (!alwaysSave && !this.taskWritesLeft) {
|
|
350
|
+
return this._outputWrites(taskId, writes);
|
|
351
|
+
}
|
|
352
|
+
else if (firstChannel !== constants_js_1.INTERRUPT) {
|
|
353
|
+
// INTERRUPT makes us want to save the last task's writes
|
|
354
|
+
// so we don't decrement tasksWritesLeft in that case
|
|
355
|
+
this.taskWritesLeft -= 1;
|
|
356
|
+
}
|
|
357
|
+
// save writes
|
|
268
358
|
const pendingWrites = writes.map(([key, value]) => {
|
|
269
359
|
return [taskId, key, value];
|
|
270
360
|
});
|
|
@@ -280,10 +370,23 @@ class PregelLoop {
|
|
|
280
370
|
if (putWritePromise !== undefined) {
|
|
281
371
|
this.checkpointerPromises.push(putWritePromise);
|
|
282
372
|
}
|
|
283
|
-
|
|
373
|
+
this._outputWrites(taskId, writes);
|
|
374
|
+
}
|
|
375
|
+
_outputWrites(taskId, writes, cached = false) {
|
|
376
|
+
const task = this.tasks[taskId];
|
|
284
377
|
if (task !== undefined) {
|
|
285
|
-
|
|
286
|
-
|
|
378
|
+
if (task.config !== undefined &&
|
|
379
|
+
(task.config.tags ?? []).includes(constants_js_1.TAG_HIDDEN)) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (writes.length > 0 &&
|
|
383
|
+
writes[0][0] !== constants_js_1.ERROR &&
|
|
384
|
+
writes[0][0] !== constants_js_1.INTERRUPT) {
|
|
385
|
+
this._emit((0, utils_js_1.gatherIteratorSync)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputUpdates)(this.outputKeys, [[task, writes]], cached), "updates")));
|
|
386
|
+
}
|
|
387
|
+
if (!cached) {
|
|
388
|
+
this._emit((0, utils_js_1.gatherIteratorSync)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugTaskResults)(this.step, [[task, writes]], this.streamKeys), "debug")));
|
|
389
|
+
}
|
|
287
390
|
}
|
|
288
391
|
}
|
|
289
392
|
/**
|
|
@@ -292,35 +395,99 @@ class PregelLoop {
|
|
|
292
395
|
* @param params
|
|
293
396
|
*/
|
|
294
397
|
async tick(params) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (this.status !== "pending") {
|
|
300
|
-
throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
|
|
301
|
-
}
|
|
302
|
-
if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
|
|
303
|
-
await this._first(inputKeys);
|
|
304
|
-
}
|
|
305
|
-
else if (this.tasks.every((task) => task.writes.length > 0)) {
|
|
306
|
-
const writes = this.tasks.flatMap((t) => t.writes);
|
|
307
|
-
// All tasks have finished
|
|
308
|
-
const myWrites = (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, this.tasks, this.checkpointerGetNextVersion);
|
|
309
|
-
for (const [key, values] of Object.entries(myWrites)) {
|
|
310
|
-
await this.updateManagedValues(key, values);
|
|
398
|
+
let tickError;
|
|
399
|
+
try {
|
|
400
|
+
if (this.store && !this.store.isRunning) {
|
|
401
|
+
this.store?.start();
|
|
311
402
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
this.
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
403
|
+
const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
|
|
404
|
+
if (this.status !== "pending") {
|
|
405
|
+
throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
|
|
406
|
+
}
|
|
407
|
+
if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
|
|
408
|
+
await this._first(inputKeys);
|
|
409
|
+
}
|
|
410
|
+
else if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
|
|
411
|
+
const writes = Object.values(this.tasks).flatMap((t) => t.writes);
|
|
412
|
+
// All tasks have finished
|
|
413
|
+
const managedValueWrites = (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
|
|
414
|
+
for (const [key, values] of Object.entries(managedValueWrites)) {
|
|
415
|
+
await this.updateManagedValues(key, values);
|
|
416
|
+
}
|
|
417
|
+
// produce values output
|
|
418
|
+
const valuesOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, writes, this.channels), "values"));
|
|
419
|
+
this._emit(valuesOutput);
|
|
420
|
+
// clear pending writes
|
|
421
|
+
this.checkpointPendingWrites = [];
|
|
422
|
+
await this._putCheckpoint({
|
|
423
|
+
source: "loop",
|
|
424
|
+
writes: (0, io_js_1.mapOutputUpdates)(this.outputKeys, Object.values(this.tasks).map((task) => [task, task.writes])).next().value ?? null,
|
|
425
|
+
});
|
|
426
|
+
// after execution, check if we should interrupt
|
|
427
|
+
if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptAfter, Object.values(this.tasks))) {
|
|
428
|
+
this.status = "interrupt_after";
|
|
429
|
+
if (this.isNested) {
|
|
430
|
+
throw new errors_js_1.GraphInterrupt();
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
if (this.step > this.stop) {
|
|
441
|
+
this.status = "out_of_steps";
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const nextTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.nodes, this.channels, this.managed, this.config, true, {
|
|
445
|
+
step: this.step,
|
|
446
|
+
checkpointer: this.checkpointer,
|
|
447
|
+
isResuming: this.input === INPUT_RESUMING,
|
|
448
|
+
manager,
|
|
449
|
+
store: this.store,
|
|
320
450
|
});
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
451
|
+
this.tasks = nextTasks;
|
|
452
|
+
this.taskWritesLeft = Object.values(this.tasks).length - 1;
|
|
453
|
+
// Produce debug output
|
|
454
|
+
if (this.checkpointer) {
|
|
455
|
+
this._emit(await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugCheckpoint)(this.step - 1, // printing checkpoint for previous step
|
|
456
|
+
this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, Object.values(this.tasks), this.checkpointPendingWrites), "debug")));
|
|
457
|
+
}
|
|
458
|
+
if (Object.values(this.tasks).length === 0) {
|
|
459
|
+
this.status = "done";
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
// if there are pending writes from a previous loop, apply them
|
|
463
|
+
if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
|
|
464
|
+
for (const [tid, k, v] of this.checkpointPendingWrites) {
|
|
465
|
+
if (k === constants_js_1.ERROR || k === constants_js_1.INTERRUPT) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const task = Object.values(this.tasks).find((t) => t.id === tid);
|
|
469
|
+
if (task) {
|
|
470
|
+
task.writes.push([k, v]);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
for (const task of Object.values(this.tasks)) {
|
|
474
|
+
if (task.writes.length > 0) {
|
|
475
|
+
this._outputWrites(task.id, task.writes, true);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// if all tasks have finished, re-tick
|
|
480
|
+
if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
|
|
481
|
+
return this.tick({
|
|
482
|
+
inputKeys,
|
|
483
|
+
interruptAfter,
|
|
484
|
+
interruptBefore,
|
|
485
|
+
manager,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
// Before execution, check if we should interrupt
|
|
489
|
+
if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptBefore, Object.values(this.tasks))) {
|
|
490
|
+
this.status = "interrupt_before";
|
|
324
491
|
if (this.isNested) {
|
|
325
492
|
throw new errors_js_1.GraphInterrupt();
|
|
326
493
|
}
|
|
@@ -328,65 +495,29 @@ class PregelLoop {
|
|
|
328
495
|
return false;
|
|
329
496
|
}
|
|
330
497
|
}
|
|
498
|
+
// Produce debug output
|
|
499
|
+
const debugOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugTasks)(this.step, Object.values(this.tasks)), "debug"));
|
|
500
|
+
this._emit(debugOutput);
|
|
501
|
+
return true;
|
|
331
502
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
this.status = "out_of_steps";
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
339
|
-
const nextTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.nodes, this.channels, this.managed, this.config, true, {
|
|
340
|
-
step: this.step,
|
|
341
|
-
checkpointer: this.checkpointer,
|
|
342
|
-
isResuming: this.input === INPUT_RESUMING,
|
|
343
|
-
manager,
|
|
344
|
-
});
|
|
345
|
-
this.tasks = nextTasks;
|
|
346
|
-
// Produce debug output
|
|
347
|
-
if (this.checkpointer) {
|
|
348
|
-
this.stream.push(...(await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugCheckpoint)(this.step - 1, // printing checkpoint for previous step
|
|
349
|
-
this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, this.tasks, this.checkpointPendingWrites), "debug"))));
|
|
350
|
-
}
|
|
351
|
-
if (this.tasks.length === 0) {
|
|
352
|
-
this.status = "done";
|
|
353
|
-
return false;
|
|
354
|
-
}
|
|
355
|
-
// if there are pending writes from a previous loop, apply them
|
|
356
|
-
if (this.checkpointPendingWrites.length > 0 && this.skipDoneTasks) {
|
|
357
|
-
for (const [tid, k, v] of this.checkpointPendingWrites) {
|
|
358
|
-
if (k === constants_js_1.ERROR || k === constants_js_1.INTERRUPT) {
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
const task = this.tasks.find((t) => t.id === tid);
|
|
362
|
-
if (task) {
|
|
363
|
-
task.writes.push([k, v]);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
// if all tasks have finished, re-tick
|
|
368
|
-
if (this.tasks.every((task) => task.writes.length > 0)) {
|
|
369
|
-
return this.tick({
|
|
370
|
-
inputKeys,
|
|
371
|
-
interruptAfter,
|
|
372
|
-
interruptBefore,
|
|
373
|
-
manager,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
// Before execution, check if we should interrupt
|
|
377
|
-
if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptBefore, this.tasks)) {
|
|
378
|
-
this.status = "interrupt_before";
|
|
379
|
-
if (this.isNested) {
|
|
380
|
-
throw new errors_js_1.GraphInterrupt();
|
|
503
|
+
catch (e) {
|
|
504
|
+
tickError = e;
|
|
505
|
+
if (!this._suppressInterrupt(tickError)) {
|
|
506
|
+
throw tickError;
|
|
381
507
|
}
|
|
382
508
|
else {
|
|
383
|
-
|
|
509
|
+
this.output = (0, io_js_1.readChannels)(this.channels, this.outputKeys);
|
|
510
|
+
}
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
finally {
|
|
514
|
+
if (tickError === undefined) {
|
|
515
|
+
this.output = (0, io_js_1.readChannels)(this.channels, this.outputKeys);
|
|
384
516
|
}
|
|
385
517
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
return true;
|
|
518
|
+
}
|
|
519
|
+
_suppressInterrupt(e) {
|
|
520
|
+
return (0, errors_js_1.isGraphInterrupt)(e) && !this.isNested;
|
|
390
521
|
}
|
|
391
522
|
/**
|
|
392
523
|
* Resuming from previous checkpoint requires
|
|
@@ -394,9 +525,9 @@ class PregelLoop {
|
|
|
394
525
|
* - receiving None input (outer graph) or RESUMING flag (subgraph)
|
|
395
526
|
*/
|
|
396
527
|
async _first(inputKeys) {
|
|
397
|
-
const isResuming =
|
|
398
|
-
this.config.configurable?.[constants_js_1.CONFIG_KEY_RESUMING] !== undefined
|
|
399
|
-
|
|
528
|
+
const isResuming = Object.keys(this.checkpoint.channel_versions).length !== 0 &&
|
|
529
|
+
(this.config.configurable?.[constants_js_1.CONFIG_KEY_RESUMING] !== undefined ||
|
|
530
|
+
this.input === null);
|
|
400
531
|
if (isResuming) {
|
|
401
532
|
for (const channelName of Object.keys(this.channels)) {
|
|
402
533
|
if (this.checkpoint.channel_versions[channelName] !== undefined) {
|
|
@@ -407,6 +538,9 @@ class PregelLoop {
|
|
|
407
538
|
};
|
|
408
539
|
}
|
|
409
540
|
}
|
|
541
|
+
// produce values output
|
|
542
|
+
const valuesOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, true, this.channels), "values"));
|
|
543
|
+
this._emit(valuesOutput);
|
|
410
544
|
// map inputs to channel updates
|
|
411
545
|
}
|
|
412
546
|
else {
|
|
@@ -415,7 +549,7 @@ class PregelLoop {
|
|
|
415
549
|
throw new errors_js_1.EmptyInputError(`Received no input writes for ${JSON.stringify(inputKeys, null, 2)}`);
|
|
416
550
|
}
|
|
417
551
|
const discardTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.nodes, this.channels, this.managed, this.config, true, { step: this.step });
|
|
418
|
-
(0, algo_js_1._applyWrites)(this.checkpoint, this.channels, discardTasks.concat([
|
|
552
|
+
(0, algo_js_1._applyWrites)(this.checkpoint, this.channels, Object.values(discardTasks).concat([
|
|
419
553
|
{
|
|
420
554
|
name: constants_js_1.INPUT,
|
|
421
555
|
writes: inputWrites,
|
|
@@ -430,12 +564,25 @@ class PregelLoop {
|
|
|
430
564
|
}
|
|
431
565
|
// done with input
|
|
432
566
|
this.input = isResuming ? INPUT_RESUMING : INPUT_DONE;
|
|
567
|
+
if (!this.isNested) {
|
|
568
|
+
this.config = (0, index_js_1.patchConfigurable)(this.config, {
|
|
569
|
+
[constants_js_1.CONFIG_KEY_RESUMING]: isResuming,
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
_emit(values) {
|
|
574
|
+
for (const chunk of values) {
|
|
575
|
+
if (this.stream.modes.has(chunk[0])) {
|
|
576
|
+
this.stream.push([this.checkpointNamespace, ...chunk]);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
433
579
|
}
|
|
434
580
|
async _putCheckpoint(inputMetadata) {
|
|
435
581
|
// Assign step
|
|
436
582
|
const metadata = {
|
|
437
583
|
...inputMetadata,
|
|
438
584
|
step: this.step,
|
|
585
|
+
parents: this.config.configurable?.[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP] ?? {},
|
|
439
586
|
};
|
|
440
587
|
// Bail if no checkpointer
|
|
441
588
|
if (this.checkpointer !== undefined) {
|
|
@@ -445,9 +592,7 @@ class PregelLoop {
|
|
|
445
592
|
// this is achieved by writing child checkpoints as progress is made
|
|
446
593
|
// (so that error recovery / resuming from interrupt don't lose work)
|
|
447
594
|
// but doing so always with an id equal to that of the parent checkpoint
|
|
448
|
-
this.checkpoint = (0, base_js_1.createCheckpoint)(this.checkpoint, this.channels, this.step
|
|
449
|
-
// id: this.isNested ? this.config.configurable?.checkpoint_id : undefined,
|
|
450
|
-
);
|
|
595
|
+
this.checkpoint = (0, base_js_1.createCheckpoint)(this.checkpoint, this.channels, this.step);
|
|
451
596
|
this.checkpointConfig = {
|
|
452
597
|
...this.checkpointConfig,
|
|
453
598
|
configurable: {
|
|
@@ -456,7 +601,7 @@ class PregelLoop {
|
|
|
456
601
|
},
|
|
457
602
|
};
|
|
458
603
|
const channelVersions = { ...this.checkpoint.channel_versions };
|
|
459
|
-
const newVersions = (0,
|
|
604
|
+
const newVersions = (0, index_js_1.getNewChannelVersions)(this.checkpointPreviousVersions, channelVersions);
|
|
460
605
|
this.checkpointPreviousVersions = channelVersions;
|
|
461
606
|
// save it, without blocking
|
|
462
607
|
// if there's a previous checkpoint save in progress, wait for it
|
package/dist/pregel/loop.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import Deque from "double-ended-queue";
|
|
2
1
|
import type { RunnableConfig } from "@langchain/core/runnables";
|
|
3
2
|
import type { CallbackManagerForChainRun } from "@langchain/core/callbacks/manager";
|
|
4
|
-
import { BaseCheckpointSaver, Checkpoint, PendingWrite, CheckpointPendingWrite, CheckpointMetadata, All } from "@langchain/langgraph-checkpoint";
|
|
3
|
+
import { BaseCheckpointSaver, Checkpoint, PendingWrite, CheckpointPendingWrite, CheckpointMetadata, All, BaseStore, AsyncBatchedStore } from "@langchain/langgraph-checkpoint";
|
|
5
4
|
import { BaseChannel } from "../channels/base.js";
|
|
6
5
|
import { PregelExecutableTask, StreamMode } from "./types.js";
|
|
7
6
|
import { PregelNode } from "./read.js";
|
|
8
|
-
import { BaseStore } from "../store/base.js";
|
|
9
|
-
import { AsyncBatchedStore } from "../store/batch.js";
|
|
10
7
|
import { ManagedValueMapping } from "../managed/base.js";
|
|
8
|
+
import { LangGraphRunnableConfig } from "./runnable_types.js";
|
|
9
|
+
export type StreamChunk = [string[], StreamMode, unknown];
|
|
11
10
|
export type PregelLoopInitializeParams = {
|
|
12
11
|
input?: any;
|
|
13
12
|
config: RunnableConfig;
|
|
@@ -17,6 +16,7 @@ export type PregelLoopInitializeParams = {
|
|
|
17
16
|
nodes: Record<string, PregelNode>;
|
|
18
17
|
channelSpecs: Record<string, BaseChannel>;
|
|
19
18
|
managed: ManagedValueMapping;
|
|
19
|
+
stream: StreamProtocol;
|
|
20
20
|
store?: BaseStore;
|
|
21
21
|
};
|
|
22
22
|
type PregelLoopParams = {
|
|
@@ -35,11 +35,21 @@ type PregelLoopParams = {
|
|
|
35
35
|
outputKeys: string | string[];
|
|
36
36
|
streamKeys: string | string[];
|
|
37
37
|
nodes: Record<string, PregelNode>;
|
|
38
|
+
checkpointNamespace: string[];
|
|
39
|
+
skipDoneTasks: boolean;
|
|
40
|
+
isNested: boolean;
|
|
41
|
+
stream: StreamProtocol;
|
|
38
42
|
store?: AsyncBatchedStore;
|
|
39
43
|
};
|
|
44
|
+
export declare class StreamProtocol {
|
|
45
|
+
push: (chunk: StreamChunk) => void;
|
|
46
|
+
modes: Set<StreamMode>;
|
|
47
|
+
constructor(pushFn: (chunk: StreamChunk) => void, modes: Set<StreamMode>);
|
|
48
|
+
}
|
|
40
49
|
export declare class PregelLoop {
|
|
41
50
|
protected input?: any;
|
|
42
|
-
|
|
51
|
+
output: any;
|
|
52
|
+
config: LangGraphRunnableConfig;
|
|
43
53
|
protected checkpointer?: BaseCheckpointSaver;
|
|
44
54
|
protected checkpointerGetNextVersion: (current: number | undefined, channel: BaseChannel) => number;
|
|
45
55
|
channels: Record<string, BaseChannel>;
|
|
@@ -47,6 +57,7 @@ export declare class PregelLoop {
|
|
|
47
57
|
protected checkpoint: Checkpoint;
|
|
48
58
|
protected checkpointConfig: RunnableConfig;
|
|
49
59
|
checkpointMetadata: CheckpointMetadata;
|
|
60
|
+
protected checkpointNamespace: string[];
|
|
50
61
|
protected checkpointPendingWrites: CheckpointPendingWrite[];
|
|
51
62
|
protected checkpointPreviousVersions: Record<string, string | number>;
|
|
52
63
|
step: number;
|
|
@@ -55,11 +66,12 @@ export declare class PregelLoop {
|
|
|
55
66
|
protected streamKeys: string | string[];
|
|
56
67
|
protected nodes: Record<string, PregelNode>;
|
|
57
68
|
protected skipDoneTasks: boolean;
|
|
69
|
+
protected taskWritesLeft: number;
|
|
58
70
|
status: "pending" | "done" | "interrupt_before" | "interrupt_after" | "out_of_steps";
|
|
59
|
-
tasks: PregelExecutableTask<any, any
|
|
60
|
-
stream:
|
|
71
|
+
tasks: Record<string, PregelExecutableTask<any, any>>;
|
|
72
|
+
stream: StreamProtocol;
|
|
61
73
|
checkpointerPromises: Promise<unknown>[];
|
|
62
|
-
|
|
74
|
+
isNested: boolean;
|
|
63
75
|
protected _checkpointerChainedPromise: Promise<unknown>;
|
|
64
76
|
store?: AsyncBatchedStore;
|
|
65
77
|
constructor(params: PregelLoopParams);
|
|
@@ -77,6 +89,7 @@ export declare class PregelLoop {
|
|
|
77
89
|
* @param writes
|
|
78
90
|
*/
|
|
79
91
|
putWrites(taskId: string, writes: PendingWrite<string>[]): void;
|
|
92
|
+
_outputWrites(taskId: string, writes: [string, unknown][], cached?: boolean): void;
|
|
80
93
|
/**
|
|
81
94
|
* Execute a single iteration of the Pregel loop.
|
|
82
95
|
* Returns true if more iterations are needed.
|
|
@@ -88,12 +101,14 @@ export declare class PregelLoop {
|
|
|
88
101
|
interruptBefore: string[] | All;
|
|
89
102
|
manager?: CallbackManagerForChainRun;
|
|
90
103
|
}): Promise<boolean>;
|
|
104
|
+
protected _suppressInterrupt(e?: Error): boolean;
|
|
91
105
|
/**
|
|
92
106
|
* Resuming from previous checkpoint requires
|
|
93
107
|
* - finding a previous checkpoint
|
|
94
108
|
* - receiving None input (outer graph) or RESUMING flag (subgraph)
|
|
95
109
|
*/
|
|
96
110
|
protected _first(inputKeys: string | string[]): Promise<void>;
|
|
97
|
-
protected
|
|
111
|
+
protected _emit(values: [StreamMode, unknown][]): void;
|
|
112
|
+
protected _putCheckpoint(inputMetadata: Omit<CheckpointMetadata, "step" | "parents">): Promise<void>;
|
|
98
113
|
}
|
|
99
114
|
export {};
|