@langchain/langgraph 0.0.34 → 0.1.0-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.
Files changed (82) hide show
  1. package/README.md +11 -15
  2. package/dist/channels/any_value.cjs +3 -1
  3. package/dist/channels/any_value.d.ts +1 -1
  4. package/dist/channels/any_value.js +3 -1
  5. package/dist/channels/base.cjs +8 -0
  6. package/dist/channels/base.d.ts +12 -1
  7. package/dist/channels/base.js +8 -0
  8. package/dist/channels/binop.cjs +2 -1
  9. package/dist/channels/binop.d.ts +1 -1
  10. package/dist/channels/binop.js +2 -1
  11. package/dist/channels/dynamic_barrier_value.cjs +30 -18
  12. package/dist/channels/dynamic_barrier_value.d.ts +2 -1
  13. package/dist/channels/dynamic_barrier_value.js +30 -18
  14. package/dist/channels/ephemeral_value.cjs +3 -1
  15. package/dist/channels/ephemeral_value.d.ts +1 -1
  16. package/dist/channels/ephemeral_value.js +3 -1
  17. package/dist/channels/last_value.cjs +3 -2
  18. package/dist/channels/last_value.d.ts +1 -1
  19. package/dist/channels/last_value.js +3 -2
  20. package/dist/channels/named_barrier_value.cjs +14 -6
  21. package/dist/channels/named_barrier_value.d.ts +2 -1
  22. package/dist/channels/named_barrier_value.js +15 -7
  23. package/dist/channels/topic.cjs +10 -11
  24. package/dist/channels/topic.d.ts +1 -1
  25. package/dist/channels/topic.js +10 -11
  26. package/dist/checkpoint/base.cjs +9 -0
  27. package/dist/checkpoint/base.d.ts +23 -18
  28. package/dist/checkpoint/base.js +9 -0
  29. package/dist/checkpoint/id.cjs +13 -1
  30. package/dist/checkpoint/id.d.ts +1 -0
  31. package/dist/checkpoint/id.js +12 -1
  32. package/dist/checkpoint/index.d.ts +2 -1
  33. package/dist/checkpoint/memory.cjs +152 -39
  34. package/dist/checkpoint/memory.d.ts +6 -3
  35. package/dist/checkpoint/memory.js +152 -39
  36. package/dist/checkpoint/serde/types.cjs +2 -0
  37. package/dist/checkpoint/serde/types.d.ts +40 -0
  38. package/dist/checkpoint/serde/types.js +1 -0
  39. package/dist/checkpoint/sqlite.cjs +127 -98
  40. package/dist/checkpoint/sqlite.d.ts +5 -3
  41. package/dist/checkpoint/sqlite.js +127 -98
  42. package/dist/checkpoint/types.cjs +2 -0
  43. package/dist/checkpoint/types.d.ts +28 -0
  44. package/dist/checkpoint/types.js +1 -0
  45. package/dist/constants.cjs +17 -1
  46. package/dist/constants.d.ts +7 -0
  47. package/dist/constants.js +16 -0
  48. package/dist/errors.cjs +21 -1
  49. package/dist/errors.d.ts +8 -0
  50. package/dist/errors.js +18 -0
  51. package/dist/graph/graph.cjs +6 -3
  52. package/dist/graph/graph.d.ts +6 -2
  53. package/dist/graph/graph.js +7 -4
  54. package/dist/graph/index.d.ts +1 -1
  55. package/dist/graph/state.cjs +7 -7
  56. package/dist/graph/state.js +7 -7
  57. package/dist/pregel/algo.cjs +384 -0
  58. package/dist/pregel/algo.d.ts +32 -0
  59. package/dist/pregel/algo.js +374 -0
  60. package/dist/pregel/debug.cjs +158 -9
  61. package/dist/pregel/debug.d.ts +39 -2
  62. package/dist/pregel/debug.js +151 -7
  63. package/dist/pregel/index.cjs +217 -509
  64. package/dist/pregel/index.d.ts +29 -66
  65. package/dist/pregel/index.js +219 -506
  66. package/dist/pregel/io.cjs +2 -2
  67. package/dist/pregel/io.d.ts +5 -4
  68. package/dist/pregel/io.js +2 -2
  69. package/dist/pregel/loop.cjs +428 -0
  70. package/dist/pregel/loop.d.ts +84 -0
  71. package/dist/pregel/loop.js +421 -0
  72. package/dist/pregel/types.d.ts +53 -4
  73. package/dist/pregel/utils.cjs +33 -0
  74. package/dist/pregel/utils.d.ts +3 -0
  75. package/dist/pregel/utils.js +28 -0
  76. package/dist/pregel/write.cjs +3 -1
  77. package/dist/pregel/write.js +3 -1
  78. package/dist/utils.cjs +21 -1
  79. package/dist/utils.d.ts +4 -0
  80. package/dist/utils.js +18 -0
  81. package/dist/web.d.ts +3 -2
  82. package/package.json +4 -2
@@ -1,18 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._prepareNextTasks = exports._applyWrites = exports._localWrite = exports._localRead = exports._shouldInterrupt = exports.Pregel = exports.Channel = void 0;
3
+ exports.Pregel = exports.Channel = void 0;
4
4
  /* eslint-disable no-param-reassign */
5
5
  const runnables_1 = require("@langchain/core/runnables");
6
- const stream_1 = require("@langchain/core/utils/stream");
7
6
  const base_js_1 = require("../channels/base.cjs");
8
7
  const base_js_2 = require("../checkpoint/base.cjs");
9
8
  const read_js_1 = require("./read.cjs");
10
9
  const validate_js_1 = require("./validate.cjs");
11
10
  const io_js_1 = require("./io.cjs");
11
+ const debug_js_1 = require("./debug.cjs");
12
12
  const write_js_1 = require("./write.cjs");
13
13
  const constants_js_1 = require("../constants.cjs");
14
14
  const errors_js_1 = require("../errors.cjs");
15
- const DEFAULT_LOOP_LIMIT = 25;
15
+ const algo_js_1 = require("./algo.cjs");
16
+ const id_js_1 = require("../checkpoint/id.cjs");
17
+ const utils_js_1 = require("../utils.cjs");
18
+ const utils_js_2 = require("./utils.cjs");
19
+ const loop_js_1 = require("./loop.cjs");
16
20
  function isString(value) {
17
21
  return typeof value === "string";
18
22
  }
@@ -96,13 +100,13 @@ class Pregel extends runnables_1.Runnable {
96
100
  writable: true,
97
101
  value: void 0
98
102
  });
99
- Object.defineProperty(this, "inputs", {
103
+ Object.defineProperty(this, "inputChannels", {
100
104
  enumerable: true,
101
105
  configurable: true,
102
106
  writable: true,
103
107
  value: void 0
104
108
  });
105
- Object.defineProperty(this, "outputs", {
109
+ Object.defineProperty(this, "outputChannels", {
106
110
  enumerable: true,
107
111
  configurable: true,
108
112
  writable: true,
@@ -118,7 +122,7 @@ class Pregel extends runnables_1.Runnable {
118
122
  enumerable: true,
119
123
  configurable: true,
120
124
  writable: true,
121
- value: "values"
125
+ value: ["values"]
122
126
  });
123
127
  Object.defineProperty(this, "streamChannels", {
124
128
  enumerable: true,
@@ -156,20 +160,22 @@ class Pregel extends runnables_1.Runnable {
156
160
  writable: true,
157
161
  value: void 0
158
162
  });
163
+ let { streamMode } = fields;
164
+ if (streamMode != null && !Array.isArray(streamMode)) {
165
+ streamMode = [streamMode];
166
+ }
159
167
  this.nodes = fields.nodes;
160
168
  this.channels = fields.channels;
161
169
  this.autoValidate = fields.autoValidate ?? this.autoValidate;
162
- this.streamMode = fields.streamMode ?? this.streamMode;
163
- this.outputs = fields.outputs;
170
+ this.streamMode = streamMode ?? this.streamMode;
171
+ this.inputChannels = fields.inputChannels;
172
+ this.outputChannels = fields.outputChannels;
164
173
  this.streamChannels = fields.streamChannels ?? this.streamChannels;
165
174
  this.interruptAfter = fields.interruptAfter;
166
175
  this.interruptBefore = fields.interruptBefore;
167
- this.inputs = fields.inputs;
168
176
  this.stepTimeout = fields.stepTimeout ?? this.stepTimeout;
169
177
  this.debug = fields.debug ?? this.debug;
170
178
  this.checkpointer = fields.checkpointer;
171
- // Bind the method to the instance
172
- this._transform = this._transform.bind(this);
173
179
  if (this.autoValidate) {
174
180
  this.validate();
175
181
  }
@@ -178,8 +184,8 @@ class Pregel extends runnables_1.Runnable {
178
184
  (0, validate_js_1.validateGraph)({
179
185
  nodes: this.nodes,
180
186
  channels: this.channels,
181
- outputChannels: this.outputs,
182
- inputChannels: this.inputs,
187
+ outputChannels: this.outputChannels,
188
+ inputChannels: this.inputChannels,
183
189
  streamChannels: this.streamChannels,
184
190
  interruptAfterNodes: this.interruptAfter,
185
191
  interruptBeforeNodes: this.interruptBefore,
@@ -212,8 +218,7 @@ class Pregel extends runnables_1.Runnable {
212
218
  const saved = await this.checkpointer.getTuple(config);
213
219
  const checkpoint = saved ? saved.checkpoint : (0, base_js_2.emptyCheckpoint)();
214
220
  const channels = (0, base_js_1.emptyChannels)(this.channels, checkpoint);
215
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
216
- const [_, nextTasks] = _prepareNextTasks(checkpoint, this.nodes, channels, false, { step: -1 });
221
+ const [, nextTasks] = (0, algo_js_1._prepareNextTasks)(checkpoint, this.nodes, channels, saved !== undefined ? saved.config : config, false, { step: -1 });
217
222
  return {
218
223
  values: (0, io_js_1.readChannels)(channels, this.streamChannelsAsIs),
219
224
  next: nextTasks.map((task) => task.name),
@@ -223,14 +228,13 @@ class Pregel extends runnables_1.Runnable {
223
228
  parentConfig: saved?.parentConfig,
224
229
  };
225
230
  }
226
- async *getStateHistory(config, limit, before) {
231
+ async *getStateHistory(config, options) {
227
232
  if (!this.checkpointer) {
228
233
  throw new errors_js_1.GraphValueError("No checkpointer set");
229
234
  }
230
- for await (const saved of this.checkpointer.list(config, limit, before)) {
235
+ for await (const saved of this.checkpointer.list(config, options)) {
231
236
  const channels = (0, base_js_1.emptyChannels)(this.channels, saved.checkpoint);
232
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
233
- const [_, nextTasks] = _prepareNextTasks(saved.checkpoint, this.nodes, channels, false, { step: -1 });
237
+ const [, nextTasks] = (0, algo_js_1._prepareNextTasks)(saved.checkpoint, this.nodes, channels, saved.config, false, { step: -1 });
234
238
  yield {
235
239
  values: (0, io_js_1.readChannels)(channels, this.streamChannelsAsIs),
236
240
  next: nextTasks.map((task) => task.name),
@@ -250,38 +254,75 @@ class Pregel extends runnables_1.Runnable {
250
254
  const checkpoint = saved
251
255
  ? (0, base_js_2.copyCheckpoint)(saved.checkpoint)
252
256
  : (0, base_js_2.emptyCheckpoint)();
253
- // Find last that updated the state, if not provided
254
- const maxSeens = Object.entries(checkpoint.versions_seen).reduce((acc, [node, versions]) => {
255
- const maxSeen = Math.max(...Object.values(versions));
256
- if (maxSeen) {
257
- if (!acc[maxSeen]) {
258
- acc[maxSeen] = [];
259
- }
260
- acc[maxSeen].push(node);
261
- }
262
- return acc;
263
- }, {});
264
- if (!asNode && !Object.keys(maxSeens).length) {
265
- if (!Array.isArray(this.inputs) && this.inputs in this.nodes) {
266
- asNode = this.inputs;
257
+ const checkpointPreviousVersions = saved?.checkpoint.channel_versions ?? {};
258
+ const step = saved?.metadata?.step ?? -1;
259
+ // merge configurable fields with previous checkpoint config
260
+ const checkpointConfig = {
261
+ ...config,
262
+ configurable: {
263
+ ...config.configurable,
264
+ // TODO: add proper support for updating nested subgraph state
265
+ checkpoint_ns: "",
266
+ ...saved?.config.configurable,
267
+ },
268
+ };
269
+ // Find last node that updated the state, if not provided
270
+ if (values === undefined && asNode === undefined) {
271
+ return await this.checkpointer.put(checkpointConfig, (0, base_js_1.createCheckpoint)(checkpoint, {}, step), {
272
+ source: "update",
273
+ step,
274
+ writes: {},
275
+ }, {});
276
+ }
277
+ const nonNullVersion = Object.values(checkpoint.versions_seen)
278
+ .map((seenVersions) => {
279
+ return Object.values(seenVersions);
280
+ })
281
+ .flat()
282
+ .find((v) => !!v);
283
+ if (asNode === undefined && !nonNullVersion) {
284
+ if (typeof this.inputChannels === "string" &&
285
+ this.nodes[this.inputChannels] !== undefined) {
286
+ asNode = this.inputChannels;
267
287
  }
268
288
  }
269
- else if (!asNode) {
270
- const maxSeen = Math.max(...Object.keys(maxSeens).map(Number));
271
- const nodes = maxSeens[maxSeen];
272
- if (nodes.length === 1) {
273
- asNode = nodes[0];
289
+ else if (asNode === undefined) {
290
+ // TODO: Double check
291
+ const lastSeenByNode = Object.entries(checkpoint.versions_seen)
292
+ .map(([n, seen]) => {
293
+ return Object.values(seen).map((v) => {
294
+ return [v, n];
295
+ });
296
+ })
297
+ .flat()
298
+ .sort(([aNumber], [bNumber]) => {
299
+ return aNumber - bNumber;
300
+ });
301
+ // if two nodes updated the state at the same time, it's ambiguous
302
+ if (lastSeenByNode) {
303
+ if (lastSeenByNode.length === 1) {
304
+ // eslint-disable-next-line prefer-destructuring
305
+ asNode = lastSeenByNode[0][1];
306
+ }
307
+ else if (lastSeenByNode[lastSeenByNode.length - 1][0] !==
308
+ lastSeenByNode[lastSeenByNode.length - 2][0]) {
309
+ // eslint-disable-next-line prefer-destructuring
310
+ asNode = lastSeenByNode[lastSeenByNode.length - 1][1];
311
+ }
274
312
  }
275
313
  }
276
- if (!asNode) {
277
- throw new errors_js_1.InvalidUpdateError("Ambiguous update, specify as_node");
314
+ if (asNode === undefined) {
315
+ throw new errors_js_1.InvalidUpdateError(`Ambiguous update, specify "asNode"`);
316
+ }
317
+ if (this.nodes[asNode] === undefined) {
318
+ throw new errors_js_1.InvalidUpdateError(`Node "${asNode.toString()}" does not exist`);
278
319
  }
279
320
  // update channels
280
321
  const channels = (0, base_js_1.emptyChannels)(this.channels, checkpoint);
281
- // create task to run all writers of the chosen node
322
+ // run all writers of the chosen node
282
323
  const writers = this.nodes[asNode].getWriters();
283
324
  if (!writers.length) {
284
- throw new errors_js_1.InvalidUpdateError(`No writers found for node ${asNode}`);
325
+ throw new errors_js_1.InvalidUpdateError(`No writers found for node "${asNode.toString()}"`);
285
326
  }
286
327
  const task = {
287
328
  name: asNode,
@@ -290,24 +331,29 @@ class Pregel extends runnables_1.Runnable {
290
331
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
291
332
  writers.length > 1 ? runnables_1.RunnableSequence.from(writers) : writers[0],
292
333
  writes: [],
334
+ triggers: [constants_js_1.INTERRUPT],
293
335
  config: undefined,
336
+ id: (0, id_js_1.uuid5)(constants_js_1.INTERRUPT, checkpoint.id),
294
337
  };
295
338
  // execute task
296
339
  await task.proc.invoke(task.input, (0, runnables_1.patchConfig)(config, {
297
340
  runName: `${this.name}UpdateState`,
298
341
  configurable: {
299
342
  [constants_js_1.CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
300
- [constants_js_1.CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, task.writes),
343
+ [constants_js_1.CONFIG_KEY_READ]: algo_js_1._localRead.bind(undefined, checkpoint, channels,
344
+ // TODO: Why does keyof StrRecord allow number and symbol?
345
+ task),
301
346
  },
302
347
  }));
303
348
  // apply to checkpoint and save
304
- _applyWrites(checkpoint, channels, task.writes);
305
- const step = (saved?.metadata?.step ?? -2) + 1;
306
- return await this.checkpointer.put(saved?.config ?? config, (0, base_js_1.createCheckpoint)(checkpoint, channels, step), {
349
+ // TODO: Why does keyof StrRecord allow number and symbol?
350
+ (0, algo_js_1._applyWrites)(checkpoint, channels, [task], this.checkpointer.getNextVersion.bind(this.checkpointer));
351
+ const newVersions = (0, utils_js_2.getNewChannelVersions)(checkpointPreviousVersions, checkpoint.channel_versions);
352
+ return await this.checkpointer.put(checkpointConfig, (0, base_js_1.createCheckpoint)(checkpoint, channels, step + 1), {
307
353
  source: "update",
308
- step,
354
+ step: step + 1,
309
355
  writes: { [asNode]: values },
310
- });
356
+ }, newVersions);
311
357
  }
312
358
  _defaults(config) {
313
359
  const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
@@ -321,7 +367,7 @@ class Pregel extends runnables_1.Runnable {
321
367
  }
322
368
  let defaultInputKeys = inputKeys;
323
369
  if (defaultInputKeys === undefined) {
324
- defaultInputKeys = this.inputs;
370
+ defaultInputKeys = this.inputChannels;
325
371
  }
326
372
  else {
327
373
  (0, validate_js_1.validateKeys)(defaultInputKeys, this.channels);
@@ -330,14 +376,23 @@ class Pregel extends runnables_1.Runnable {
330
376
  const defaultInterruptAfter = interruptAfter ?? this.interruptAfter ?? [];
331
377
  let defaultStreamMode;
332
378
  if (streamMode !== undefined) {
333
- defaultStreamMode = streamMode;
379
+ defaultStreamMode = Array.isArray(streamMode) ? streamMode : [streamMode];
334
380
  }
335
381
  else {
336
382
  defaultStreamMode = this.streamMode;
337
383
  }
384
+ let defaultCheckpointer;
338
385
  if (config.configurable !== undefined &&
339
386
  config.configurable[constants_js_1.CONFIG_KEY_READ] !== undefined) {
340
- defaultStreamMode = "values";
387
+ defaultStreamMode = ["values"];
388
+ }
389
+ if (config !== undefined &&
390
+ config.configurable?.[constants_js_1.CONFIG_KEY_CHECKPOINTER] !== undefined &&
391
+ (defaultInterruptAfter.length > 0 || defaultInterruptBefore.length > 0)) {
392
+ defaultCheckpointer = config.configurable[constants_js_1.CONFIG_KEY_CHECKPOINTER];
393
+ }
394
+ else {
395
+ defaultCheckpointer = this.checkpointer;
341
396
  }
342
397
  return [
343
398
  defaultDebug,
@@ -347,489 +402,142 @@ class Pregel extends runnables_1.Runnable {
347
402
  rest,
348
403
  defaultInterruptBefore,
349
404
  defaultInterruptAfter,
405
+ defaultCheckpointer,
350
406
  ];
351
407
  }
352
- async *_transform(input, runManager, config = {}) {
353
- const bg = [];
408
+ async *_streamIterator(input, options) {
409
+ const inputConfig = (0, runnables_1.ensureConfig)(options);
410
+ if (inputConfig.recursionLimit === undefined ||
411
+ inputConfig.recursionLimit < 1) {
412
+ throw new Error(`Passed "recursionLimit" must be at least 1.`);
413
+ }
414
+ if (this.checkpointer !== undefined &&
415
+ inputConfig.configurable === undefined) {
416
+ throw new Error(`Checkpointer requires one or more of the following "configurable" keys: "thread_id", "checkpoint_ns", "checkpoint_id"`);
417
+ }
418
+ const callbackManager = await (0, runnables_1.getCallbackManagerForConfig)(inputConfig);
419
+ const runManager = await callbackManager?.handleChainStart(this.toJSON(), (0, utils_js_2._coerceToDict)(input, "input"), inputConfig.runId, undefined, undefined, undefined, inputConfig?.runName ?? this.getName());
420
+ delete inputConfig.runId;
421
+ // assign defaults
422
+ const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer,] = this._defaults(inputConfig);
423
+ let loop;
424
+ let backgroundError;
425
+ const onBackgroundError = (e) => {
426
+ backgroundError = e;
427
+ };
354
428
  try {
355
- if (config.recursionLimit && config.recursionLimit < 1) {
356
- throw new errors_js_1.GraphValueError(`Recursion limit must be greater than 0, got ${config.recursionLimit}`);
357
- }
358
- if (this.checkpointer && !config.configurable) {
359
- throw new errors_js_1.GraphValueError(`Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_id`);
360
- }
361
- // assign defaults
362
- const [debug, streamMode, inputKeys, outputKeys, restConfig, interruptBefore, interruptAfter,] = this._defaults(config);
363
- // copy nodes to ignore mutations during execution
364
- const processes = { ...this.nodes };
365
- // get checkpoint, or create an empty one
366
- const saved = this.checkpointer
367
- ? await this.checkpointer.getTuple(config)
368
- : null;
369
- let checkpoint = saved ? saved.checkpoint : (0, base_js_2.emptyCheckpoint)();
370
- let checkpointConfig = saved ? saved.config : config;
371
- let start = (saved?.metadata?.step ?? -2) + 1;
372
- // create channels from checkpoint
373
- const channels = (0, base_js_1.emptyChannels)(this.channels, checkpoint);
374
- // map inputs to channel updates
375
- const inputPendingWrites = [];
376
- for await (const c of input) {
377
- for (const value of (0, io_js_1.mapInput)(inputKeys, c)) {
378
- inputPendingWrites.push(value);
379
- }
380
- }
381
- if (inputPendingWrites.length) {
382
- // discard any unfinished tasks from previous checkpoint
383
- const discarded = _prepareNextTasks(checkpoint, processes, channels, true, { step: -1 });
384
- checkpoint = discarded[0]; // eslint-disable-line prefer-destructuring
385
- // apply input writes
386
- _applyWrites(checkpoint, channels, inputPendingWrites);
387
- // save input checkpoint
388
- if (this.checkpointer) {
389
- checkpoint = (0, base_js_1.createCheckpoint)(checkpoint, channels, start);
390
- bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
391
- source: "input",
392
- step: start,
393
- writes: Object.fromEntries(inputPendingWrites),
394
- }));
395
- checkpointConfig = {
396
- configurable: {
397
- ...checkpointConfig.configurable,
398
- checkpoint_id: checkpoint.id,
399
- },
400
- };
429
+ loop = await loop_js_1.PregelLoop.initialize({
430
+ input,
431
+ config,
432
+ checkpointer,
433
+ graph: this,
434
+ onBackgroundError,
435
+ });
436
+ while (backgroundError === undefined &&
437
+ (await loop.tick({
438
+ outputKeys,
439
+ interruptAfter,
440
+ interruptBefore,
441
+ manager: runManager,
442
+ }))) {
443
+ if (debug) {
444
+ (0, debug_js_1.printStepCheckpoint)(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
401
445
  }
402
- // increment start to 0
403
- start += 1;
404
- }
405
- else {
406
- checkpoint = (0, base_js_2.copyCheckpoint)(checkpoint);
407
- for (const k of this.streamChannelsList) {
408
- const version = checkpoint.channel_versions[k] ?? 0;
409
- if (!checkpoint.versions_seen[constants_js_1.INTERRUPT]) {
410
- checkpoint.versions_seen[constants_js_1.INTERRUPT] = {};
446
+ while (loop.stream.length > 0) {
447
+ const nextItem = loop.stream.shift();
448
+ if (nextItem === undefined) {
449
+ throw new Error("Data structure error.");
450
+ }
451
+ if (streamMode.includes(nextItem[0])) {
452
+ if (streamMode.length === 1) {
453
+ yield nextItem[1];
454
+ }
455
+ else {
456
+ yield nextItem;
457
+ }
411
458
  }
412
- checkpoint.versions_seen[constants_js_1.INTERRUPT][k] = version;
413
- }
414
- }
415
- // Similarly to Bulk Synchronous Parallel / Pregel model
416
- // computation proceeds in steps, while there are channel updates
417
- // channel updates from step N are only visible in step N+1
418
- // channels are guaranteed to be immutable for the duration of the step,
419
- // with channel updates applied only at the transition between steps
420
- const stop = start + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT);
421
- for (let step = start; step < stop + 1; step += 1) {
422
- const [nextCheckpoint, nextTasks] = _prepareNextTasks(checkpoint, processes, channels, true, { step });
423
- // if no more tasks, we're done
424
- if (nextTasks.length === 0 && step === start) {
425
- throw new errors_js_1.GraphValueError(`No tasks to run in graph.`);
426
- }
427
- else if (nextTasks.length === 0) {
428
- break;
429
- }
430
- else if (step === stop) {
431
- throw new errors_js_1.GraphRecursionError(`Recursion limit of ${config.recursionLimit} reached without hitting a stop condition. You can increase the limit by setting the "recursionLimit" config key.`);
432
- }
433
- // before execution, check if we should interrupt
434
- if (_shouldInterrupt(checkpoint, interruptBefore, this.streamChannelsList, nextTasks)) {
435
- break;
436
- }
437
- else {
438
- checkpoint = nextCheckpoint;
439
459
  }
440
460
  if (debug) {
441
- console.log(nextTasks);
461
+ (0, debug_js_1.printStepTasks)(loop.step, loop.tasks);
442
462
  }
443
- const tasksWithConfig = nextTasks.map(
444
- // eslint-disable-next-line no-loop-func
445
- (task, i) => [
446
- task.proc,
447
- task.input,
448
- (0, runnables_1.patchConfig)((0, runnables_1.mergeConfigs)(restConfig, processes[task.name].config, {
449
- metadata: {
450
- langgraph_step: step,
451
- langgraph_node: task.name,
452
- langgraph_triggers: [constants_js_1.TASKS],
453
- langgraph_task_idx: i,
454
- },
455
- }), {
456
- callbacks: runManager?.getChild(`graph:step:${step}`),
457
- runName: task.name,
458
- configurable: {
459
- ...config.configurable,
460
- [constants_js_1.CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
461
- [constants_js_1.CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, task.writes),
462
- },
463
- }),
464
- ]);
465
463
  // execute tasks, and wait for one to fail or all to finish.
466
464
  // each task is independent from all other concurrent tasks
467
- const tasks = tasksWithConfig.map(([proc, input, updatedConfig]) => () => proc.invoke(input, updatedConfig));
468
- await executeTasks(tasks, this.stepTimeout, config.signal);
469
- // combine pending writes from all tasks
470
- const pendingWrites = [];
471
- for (const task of nextTasks) {
472
- pendingWrites.push(...task.writes);
473
- }
474
- // apply writes to channels
475
- _applyWrites(checkpoint, channels, pendingWrites);
476
- // yield current value and checkpoint view
477
- if (streamMode === "values") {
478
- yield* (0, io_js_1.mapOutputValues)(outputKeys, pendingWrites, channels);
465
+ // yield updates/debug output as each task finishes
466
+ const tasks = loop.tasks.map((pregelTask) => () => {
467
+ return pregelTask.proc.invoke(pregelTask.input, pregelTask.config);
468
+ });
469
+ await (0, algo_js_1.executeTasks)(tasks, this.stepTimeout, config.signal);
470
+ for (const task of loop.tasks) {
471
+ loop.putWrites(task.id, task.writes);
479
472
  }
480
- else if (streamMode === "updates") {
473
+ if (streamMode.includes("updates")) {
481
474
  // TODO: Refactor
482
- for await (const task of nextTasks) {
483
- yield* (0, io_js_1.mapOutputUpdates)(outputKeys, [task]);
475
+ for await (const task of loop.tasks) {
476
+ yield* (0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputUpdates)(outputKeys, [task]), streamMode.length > 1 ? "updates" : undefined);
484
477
  }
485
478
  }
486
- // save end of step checkpoint
487
- if (this.checkpointer) {
488
- checkpoint = (0, base_js_1.createCheckpoint)(checkpoint, channels, step);
489
- bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
490
- source: "loop",
491
- step,
492
- writes: (0, io_js_1.single)(this.streamMode === "values"
493
- ? (0, io_js_1.mapOutputValues)(outputKeys, pendingWrites, channels)
494
- : (0, io_js_1.mapOutputUpdates)(outputKeys, nextTasks)),
495
- }));
496
- checkpointConfig = {
497
- configurable: {
498
- ...checkpointConfig.configurable,
499
- checkpoint_id: checkpoint.id,
500
- },
501
- };
502
- }
503
- if (_shouldInterrupt(checkpoint, interruptAfter, this.streamChannelsList, nextTasks)) {
504
- break;
479
+ if (streamMode.includes("debug")) {
480
+ yield* (0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugTaskResults)(loop.step, loop.tasks, this.streamChannelsList), streamMode.length > 1 ? "debug" : undefined);
505
481
  }
506
- }
507
- }
508
- finally {
509
- await Promise.all(bg);
510
- }
511
- }
512
- async invoke(input, options) {
513
- const config = (0, runnables_1.ensureConfig)(options);
514
- if (!config?.outputKeys) {
515
- config.outputKeys = this.outputs;
516
- }
517
- if (!config?.streamMode) {
518
- config.streamMode = "values";
519
- }
520
- let latest;
521
- for await (const chunk of await this.stream(input, config)) {
522
- latest = chunk;
523
- }
524
- if (!latest) {
525
- return undefined;
526
- }
527
- return latest;
528
- }
529
- async stream(input, config) {
530
- const inputIterator = (async function* () {
531
- yield input;
532
- })();
533
- return stream_1.IterableReadableStream.fromAsyncGenerator(this.transform(inputIterator, config));
534
- }
535
- async *transform(generator, config) {
536
- for await (const chunk of this._transformStreamWithConfig(generator, this._transform, config)) {
537
- yield chunk;
538
- }
539
- }
540
- }
541
- exports.Pregel = Pregel;
542
- async function executeTasks(tasks, stepTimeout, signal) {
543
- if (stepTimeout && signal) {
544
- if ("any" in AbortSignal) {
545
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
546
- signal = AbortSignal.any([
547
- signal,
548
- AbortSignal.timeout(stepTimeout),
549
- ]);
550
- }
551
- }
552
- else if (stepTimeout) {
553
- signal = AbortSignal.timeout(stepTimeout);
554
- }
555
- // Abort if signal is aborted
556
- signal?.throwIfAborted();
557
- // Start all tasks
558
- const started = tasks.map((task) => task());
559
- // Wait for all tasks to settle
560
- // If any tasks fail, or signal is aborted, the promise will reject
561
- await Promise.all(signal
562
- ? [
563
- ...started,
564
- new Promise((_resolve, reject) => {
565
- signal?.addEventListener("abort", () => reject(new Error("Abort")));
566
- }),
567
- ]
568
- : started);
569
- }
570
- function _shouldInterrupt(checkpoint, interruptNodes, snapshotChannels, tasks) {
571
- const anySnapshotChannelUpdated = snapshotChannels.some((chan) => (0, base_js_2.getChannelVersion)(checkpoint, chan) >
572
- (0, base_js_2.getVersionSeen)(checkpoint, constants_js_1.INTERRUPT, chan));
573
- const anyTaskNodeInInterruptNodes = tasks.some((task) => interruptNodes === "*"
574
- ? !task.config?.tags?.includes(constants_js_1.TAG_HIDDEN)
575
- : interruptNodes.includes(task.name));
576
- return anySnapshotChannelUpdated && anyTaskNodeInInterruptNodes;
577
- }
578
- exports._shouldInterrupt = _shouldInterrupt;
579
- function _localRead(checkpoint, channels, writes, select, fresh = false) {
580
- if (fresh) {
581
- const newCheckpoint = (0, base_js_1.createCheckpoint)(checkpoint, channels, -1);
582
- // create a new copy of channels
583
- const newChannels = (0, base_js_1.emptyChannels)(channels, newCheckpoint);
584
- // Note: _applyWrites contains side effects
585
- _applyWrites((0, base_js_2.copyCheckpoint)(newCheckpoint), newChannels, writes);
586
- return (0, io_js_1.readChannels)(newChannels, select);
587
- }
588
- else {
589
- return (0, io_js_1.readChannels)(channels, select);
590
- }
591
- }
592
- exports._localRead = _localRead;
593
- function _localWrite(
594
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
595
- commit, processes, channels,
596
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
597
- writes) {
598
- for (const [chan, value] of writes) {
599
- if (chan === constants_js_1.TASKS) {
600
- if (!(0, constants_js_1._isSend)(value)) {
601
- throw new errors_js_1.InvalidUpdateError(`Invalid packet type, expected SendProtocol, got ${JSON.stringify(value)}`);
602
- }
603
- if (!(value.node in processes)) {
604
- throw new errors_js_1.InvalidUpdateError(`Invalid node name ${value.node} in packet`);
605
- }
606
- }
607
- else if (!(chan in channels)) {
608
- console.warn(`Skipping write for channel '${chan}' which has no readers`);
609
- }
610
- }
611
- commit(writes);
612
- }
613
- exports._localWrite = _localWrite;
614
- function _applyWrites(checkpoint, channels, pendingWrites) {
615
- if (checkpoint.pending_sends) {
616
- checkpoint.pending_sends = [];
617
- }
618
- const pendingWritesByChannel = {};
619
- // Group writes by channel
620
- for (const [chan, val] of pendingWrites) {
621
- if (chan === constants_js_1.TASKS) {
622
- checkpoint.pending_sends.push({
623
- node: val.node,
624
- args: val.args,
625
- });
626
- }
627
- else {
628
- if (chan in pendingWritesByChannel) {
629
- pendingWritesByChannel[chan].push(val);
630
- }
631
- else {
632
- pendingWritesByChannel[chan] = [val];
633
- }
634
- }
635
- }
636
- // find the highest version of all channels
637
- let maxVersion = 0;
638
- if (Object.keys(checkpoint.channel_versions).length > 0) {
639
- maxVersion = Math.max(...Object.values(checkpoint.channel_versions));
640
- }
641
- const updatedChannels = new Set();
642
- // Apply writes to channels
643
- for (const [chan, vals] of Object.entries(pendingWritesByChannel)) {
644
- if (chan in channels) {
645
- // side effect: update channels
646
- try {
647
- channels[chan].update(vals);
648
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
649
- }
650
- catch (e) {
651
- if (e.name === errors_js_1.InvalidUpdateError.unminifiable_name) {
652
- throw new errors_js_1.InvalidUpdateError(`Invalid update for channel ${chan}. Values: ${vals}\n\nError: ${e.message}`);
482
+ if (debug) {
483
+ (0, debug_js_1.printStepWrites)(loop.step, loop.tasks.map((task) => task.writes).flat(), this.streamChannelsList);
653
484
  }
654
485
  }
655
- // side effect: update checkpoint channel versions
656
- checkpoint.channel_versions[chan] = maxVersion + 1;
657
- updatedChannels.add(chan);
658
- }
659
- else {
660
- console.warn(`Skipping write for channel ${chan} which has no readers`);
661
- }
662
- }
663
- // Channels that weren't updated in this step are notified of a new step
664
- for (const chan in channels) {
665
- if (!updatedChannels.has(chan)) {
666
- // side effect: update channels
667
- channels[chan].update([]);
668
- }
669
- }
670
- }
671
- exports._applyWrites = _applyWrites;
672
- function _prepareNextTasks(checkpoint, processes, channels, forExecution, extra) {
673
- const newCheckpoint = (0, base_js_2.copyCheckpoint)(checkpoint);
674
- const tasks = [];
675
- const taskDescriptions = [];
676
- for (const packet of checkpoint.pending_sends) {
677
- if (!(0, constants_js_1._isSendInterface)(packet)) {
678
- console.warn(`Ignoring invalid packet ${JSON.stringify(packet)} in pending sends.`);
679
- continue;
680
- }
681
- if (!(packet.node in processes)) {
682
- console.warn(`Ignoring unknown node name ${packet.node} in pending sends.`);
683
- continue;
684
- }
685
- if (forExecution) {
686
- const proc = processes[packet.node];
687
- const node = proc.getNode();
688
- if (node !== undefined) {
689
- const triggers = [constants_js_1.TASKS];
690
- const metadata = {
691
- langgraph_step: extra.step,
692
- langgraph_node: packet.node,
693
- langgraph_triggers: triggers,
694
- langgraph_task_idx: tasks.length,
695
- };
696
- const writes = [];
697
- tasks.push({
698
- name: packet.node,
699
- input: packet.args,
700
- proc: node,
701
- writes,
702
- config: (0, runnables_1.patchConfig)((0, runnables_1.mergeConfigs)(proc.config, processes[packet.node].config, {
703
- metadata,
704
- }), {
705
- runName: packet.node,
706
- // callbacks:
707
- configurable: {
708
- [constants_js_1.CONFIG_KEY_SEND]: _localWrite.bind(undefined, (items) => writes.push(...items), processes, channels),
709
- [constants_js_1.CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, writes),
710
- },
711
- }),
712
- });
713
- }
714
- }
715
- else {
716
- taskDescriptions.push({
717
- name: packet.node,
718
- input: packet.args,
719
- });
720
- }
721
- }
722
- // Check if any processes should be run in next step
723
- // If so, prepare the values to be passed to them
724
- for (const [name, proc] of Object.entries(processes)) {
725
- const hasUpdatedChannels = proc.triggers
726
- .filter((chan) => {
727
- try {
728
- (0, io_js_1.readChannel)(channels, chan, false);
729
- return true;
486
+ if (backgroundError !== undefined) {
487
+ throw backgroundError;
730
488
  }
731
- catch (e) {
732
- return false;
733
- }
734
- })
735
- .some((chan) => (0, base_js_2.getChannelVersion)(newCheckpoint, chan) >
736
- (0, base_js_2.getVersionSeen)(newCheckpoint, name, chan));
737
- // If any of the channels read by this process were updated
738
- if (hasUpdatedChannels) {
739
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
740
- let val;
741
- // If all trigger channels subscribed by this process are not empty
742
- // then invoke the process with the values of all non-empty channels
743
- if (Array.isArray(proc.channels)) {
744
- let emptyChannels = 0;
745
- for (const chan of proc.channels) {
746
- try {
747
- val = (0, io_js_1.readChannel)(channels, chan, false);
748
- break;
749
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
750
- }
751
- catch (e) {
752
- if (e.name === errors_js_1.EmptyChannelError.unminifiable_name) {
753
- emptyChannels += 1;
754
- continue;
755
- }
756
- else {
757
- throw e;
758
- }
759
- }
489
+ while (loop.stream.length > 0) {
490
+ const nextItem = loop.stream.shift();
491
+ if (nextItem === undefined) {
492
+ throw new Error("Data structure error.");
760
493
  }
761
- if (emptyChannels === proc.channels.length) {
762
- continue;
763
- }
764
- }
765
- else if (typeof proc.channels === "object") {
766
- val = {};
767
- try {
768
- for (const [k, chan] of Object.entries(proc.channels)) {
769
- val[k] = (0, io_js_1.readChannel)(channels, chan, !proc.triggers.includes(chan));
770
- }
771
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
772
- }
773
- catch (e) {
774
- if (e.name === errors_js_1.EmptyChannelError.unminifiable_name) {
775
- continue;
494
+ if (streamMode.includes(nextItem[0])) {
495
+ if (streamMode.length === 1) {
496
+ yield nextItem[1];
776
497
  }
777
498
  else {
778
- throw e;
499
+ yield nextItem;
779
500
  }
780
501
  }
781
502
  }
782
- else {
783
- throw new Error(`Invalid channels type, expected list or dict, got ${proc.channels}`);
784
- }
785
- // If the process has a mapper, apply it to the value
786
- if (proc.mapper !== undefined) {
787
- val = proc.mapper(val);
788
- }
789
- if (forExecution) {
790
- // Update seen versions
791
- if (!newCheckpoint.versions_seen[name]) {
792
- newCheckpoint.versions_seen[name] = {};
793
- }
794
- proc.triggers.forEach((chan) => {
795
- const version = newCheckpoint.channel_versions[chan];
796
- if (version !== undefined) {
797
- // side effect: updates newCheckpoint
798
- newCheckpoint.versions_seen[name][chan] = version;
799
- }
800
- });
801
- const node = proc.getNode();
802
- if (node !== undefined) {
803
- const metadata = {
804
- langgraph_step: extra.step,
805
- langgraph_node: name,
806
- langgraph_triggers: proc.triggers,
807
- langgraph_task_idx: tasks.length,
808
- };
809
- const writes = [];
810
- tasks.push({
811
- name,
812
- input: val,
813
- proc: node,
814
- writes,
815
- config: (0, runnables_1.patchConfig)((0, runnables_1.mergeConfigs)(proc.config, { metadata }), {
816
- runName: name,
817
- configurable: {
818
- [constants_js_1.CONFIG_KEY_SEND]: _localWrite.bind(undefined, (items) => writes.push(...items), processes, channels),
819
- [constants_js_1.CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, writes),
820
- },
821
- }),
822
- });
823
- }
824
- }
825
- else {
826
- taskDescriptions.push({
827
- name,
828
- input: val,
829
- });
503
+ if (loop.status === "out_of_steps") {
504
+ throw new errors_js_1.GraphRecursionError([
505
+ `Recursion limit of ${config.recursionLimit} reached`,
506
+ "without hitting a stop condition. You can increase the",
507
+ `limit by setting the "recursionLimit" config key.`,
508
+ ].join(" "));
830
509
  }
510
+ await runManager?.handleChainEnd((0, io_js_1.readChannels)(loop.channels, outputKeys));
511
+ }
512
+ catch (e) {
513
+ await runManager?.handleChainError(e);
514
+ throw e;
515
+ }
516
+ finally {
517
+ await loop?.backgroundTasksPromise;
831
518
  }
832
519
  }
833
- return [newCheckpoint, forExecution ? tasks : taskDescriptions];
520
+ /**
521
+ * Run the graph with a single input and config.
522
+ * @param input
523
+ * @param options
524
+ */
525
+ async invoke(input, options) {
526
+ const streamMode = options?.streamMode ?? "values";
527
+ const config = {
528
+ ...(0, runnables_1.ensureConfig)(options),
529
+ outputKeys: options?.outputKeys ?? this.outputChannels,
530
+ streamMode,
531
+ };
532
+ const chunks = [];
533
+ const stream = await this.stream(input, config);
534
+ for await (const chunk of stream) {
535
+ chunks.push(chunk);
536
+ }
537
+ if (streamMode === "values") {
538
+ return chunks[chunks.length - 1];
539
+ }
540
+ return chunks;
541
+ }
834
542
  }
835
- exports._prepareNextTasks = _prepareNextTasks;
543
+ exports.Pregel = Pregel;