@langchain/langgraph 0.2.3 → 0.2.4

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 (54) hide show
  1. package/dist/channels/base.cjs +16 -4
  2. package/dist/channels/base.d.ts +3 -0
  3. package/dist/channels/base.js +14 -3
  4. package/dist/constants.cjs +4 -1
  5. package/dist/constants.d.ts +3 -0
  6. package/dist/constants.js +3 -0
  7. package/dist/graph/annotation.cjs +5 -1
  8. package/dist/graph/annotation.d.ts +5 -4
  9. package/dist/graph/annotation.js +5 -1
  10. package/dist/graph/state.cjs +7 -6
  11. package/dist/graph/state.d.ts +5 -2
  12. package/dist/graph/state.js +6 -5
  13. package/dist/managed/base.cjs +163 -0
  14. package/dist/managed/base.d.ts +30 -0
  15. package/dist/managed/base.js +155 -0
  16. package/dist/managed/index.cjs +19 -0
  17. package/dist/managed/index.d.ts +3 -0
  18. package/dist/managed/index.js +3 -0
  19. package/dist/managed/is_last_step.cjs +11 -0
  20. package/dist/managed/is_last_step.d.ts +4 -0
  21. package/dist/managed/is_last_step.js +7 -0
  22. package/dist/managed/shared_value.cjs +109 -0
  23. package/dist/managed/shared_value.d.ts +23 -0
  24. package/dist/managed/shared_value.js +105 -0
  25. package/dist/pregel/algo.cjs +111 -50
  26. package/dist/pregel/algo.d.ts +7 -6
  27. package/dist/pregel/algo.js +112 -51
  28. package/dist/pregel/index.cjs +65 -6
  29. package/dist/pregel/index.d.ts +9 -2
  30. package/dist/pregel/index.js +67 -8
  31. package/dist/pregel/loop.cjs +40 -3
  32. package/dist/pregel/loop.d.ts +10 -0
  33. package/dist/pregel/loop.js +40 -3
  34. package/dist/pregel/types.d.ts +8 -2
  35. package/dist/pregel/validate.d.ts +3 -2
  36. package/dist/store/base.cjs +12 -0
  37. package/dist/store/base.d.ts +7 -0
  38. package/dist/store/base.js +8 -0
  39. package/dist/store/batch.cjs +126 -0
  40. package/dist/store/batch.d.ts +55 -0
  41. package/dist/store/batch.js +122 -0
  42. package/dist/store/index.cjs +19 -0
  43. package/dist/store/index.d.ts +3 -0
  44. package/dist/store/index.js +3 -0
  45. package/dist/store/memory.cjs +43 -0
  46. package/dist/store/memory.d.ts +6 -0
  47. package/dist/store/memory.js +39 -0
  48. package/dist/utils.cjs +26 -1
  49. package/dist/utils.d.ts +1 -0
  50. package/dist/utils.js +24 -0
  51. package/dist/web.cjs +2 -0
  52. package/dist/web.d.ts +2 -0
  53. package/dist/web.js +2 -0
  54. package/package.json +1 -1
@@ -16,6 +16,8 @@ const algo_js_1 = require("./algo.cjs");
16
16
  const utils_js_1 = require("./utils.cjs");
17
17
  const loop_js_1 = require("./loop.cjs");
18
18
  const retry_js_1 = require("./retry.cjs");
19
+ const base_js_2 = require("../managed/base.cjs");
20
+ const utils_js_2 = require("../utils.cjs");
19
21
  function isString(value) {
20
22
  return typeof value === "string";
21
23
  }
@@ -165,6 +167,12 @@ class Pregel extends runnables_1.Runnable {
165
167
  writable: true,
166
168
  value: void 0
167
169
  });
170
+ Object.defineProperty(this, "store", {
171
+ enumerable: true,
172
+ configurable: true,
173
+ writable: true,
174
+ value: void 0
175
+ });
168
176
  let { streamMode } = fields;
169
177
  if (streamMode != null && !Array.isArray(streamMode)) {
170
178
  streamMode = [streamMode];
@@ -182,6 +190,7 @@ class Pregel extends runnables_1.Runnable {
182
190
  this.debug = fields.debug ?? this.debug;
183
191
  this.checkpointer = fields.checkpointer;
184
192
  this.retryPolicy = fields.retryPolicy;
193
+ this.store = fields.store;
185
194
  if (this.autoValidate) {
186
195
  this.validate();
187
196
  }
@@ -227,7 +236,8 @@ class Pregel extends runnables_1.Runnable {
227
236
  const saved = await this.checkpointer.getTuple(config);
228
237
  const checkpoint = saved ? saved.checkpoint : (0, langgraph_checkpoint_1.emptyCheckpoint)();
229
238
  const channels = (0, base_js_1.emptyChannels)(this.channels, checkpoint);
230
- const nextTasks = (0, algo_js_1._prepareNextTasks)(checkpoint, this.nodes, channels, saved !== undefined ? saved.config : config, false, { step: saved ? (saved.metadata?.step ?? -1) + 1 : -1 });
239
+ const { managed } = await this.prepareSpecs(config);
240
+ const nextTasks = (0, algo_js_1._prepareNextTasks)(checkpoint, this.nodes, channels, managed, saved !== undefined ? saved.config : config, false, { step: saved ? (saved.metadata?.step ?? -1) + 1 : -1 });
231
241
  return {
232
242
  values: (0, io_js_1.readChannels)(channels, this.streamChannelsAsIs),
233
243
  next: nextTasks.map((task) => task.name),
@@ -245,9 +255,10 @@ class Pregel extends runnables_1.Runnable {
245
255
  if (!this.checkpointer) {
246
256
  throw new errors_js_1.GraphValueError("No checkpointer set");
247
257
  }
258
+ const { managed } = await this.prepareSpecs(config);
248
259
  for await (const saved of this.checkpointer.list(config, options)) {
249
260
  const channels = (0, base_js_1.emptyChannels)(this.channels, saved.checkpoint);
250
- const nextTasks = (0, algo_js_1._prepareNextTasks)(saved.checkpoint, this.nodes, channels, saved.config, false, { step: -1 });
261
+ const nextTasks = (0, algo_js_1._prepareNextTasks)(saved.checkpoint, this.nodes, channels, managed, saved.config, false, { step: -1 });
251
262
  yield {
252
263
  values: (0, io_js_1.readChannels)(channels, this.streamChannelsAsIs),
253
264
  next: nextTasks.map((task) => task.name),
@@ -336,6 +347,7 @@ class Pregel extends runnables_1.Runnable {
336
347
  }
337
348
  // update channels
338
349
  const channels = (0, base_js_1.emptyChannels)(this.channels, checkpoint);
350
+ const { managed } = await this.prepareSpecs(config);
339
351
  // run all writers of the chosen node
340
352
  const writers = this.nodes[asNode].getWriters();
341
353
  if (!writers.length) {
@@ -356,9 +368,9 @@ class Pregel extends runnables_1.Runnable {
356
368
  runName: config.runName ?? `${this.getName()}UpdateState`,
357
369
  configurable: {
358
370
  [constants_js_1.CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
359
- [constants_js_1.CONFIG_KEY_READ]: algo_js_1._localRead.bind(undefined, checkpoint, channels,
371
+ [constants_js_1.CONFIG_KEY_READ]: (select_, fresh_ = false) => (0, algo_js_1._localRead)(step, checkpoint, channels, managed,
360
372
  // TODO: Why does keyof StrRecord allow number and symbol?
361
- task),
373
+ task, select_, fresh_),
362
374
  },
363
375
  }));
364
376
  if (saved !== undefined) {
@@ -442,6 +454,43 @@ class Pregel extends runnables_1.Runnable {
442
454
  async stream(input, options) {
443
455
  return super.stream(input, options);
444
456
  }
457
+ async prepareSpecs(config) {
458
+ const configForManaged = (0, utils_js_2.patchConfigurable)(config, {
459
+ [constants_js_1.CONFIG_KEY_STORE]: this.store,
460
+ });
461
+ const channelSpecs = {};
462
+ const managedSpecs = {};
463
+ for (const [name, spec] of Object.entries(this.channels)) {
464
+ if ((0, base_js_1.isBaseChannel)(spec)) {
465
+ channelSpecs[name] = spec;
466
+ }
467
+ else {
468
+ managedSpecs[name] = spec;
469
+ }
470
+ }
471
+ const managed = new base_js_2.ManagedValueMapping(await Object.entries(managedSpecs).reduce(async (accPromise, [key, value]) => {
472
+ const acc = await accPromise;
473
+ let initializedValue;
474
+ if ((0, base_js_2.isConfiguredManagedValue)(value)) {
475
+ if ("key" in value.params &&
476
+ value.params.key === base_js_2.ChannelKeyPlaceholder) {
477
+ value.params.key = key;
478
+ }
479
+ initializedValue = await value.cls.initialize(configForManaged, value.params);
480
+ }
481
+ else {
482
+ initializedValue = await value.initialize(configForManaged);
483
+ }
484
+ if (initializedValue !== undefined) {
485
+ acc.push([key, initializedValue]);
486
+ }
487
+ return acc;
488
+ }, Promise.resolve([])));
489
+ return {
490
+ channelSpecs,
491
+ managed,
492
+ };
493
+ }
445
494
  async *_streamIterator(input, options) {
446
495
  const inputConfig = (0, runnables_1.ensureConfig)(options);
447
496
  if (inputConfig.recursionLimit === undefined ||
@@ -457,6 +506,7 @@ class Pregel extends runnables_1.Runnable {
457
506
  delete inputConfig.runId;
458
507
  // assign defaults
459
508
  const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer,] = this._defaults(inputConfig);
509
+ const { channelSpecs, managed } = await this.prepareSpecs(config);
460
510
  let loop;
461
511
  try {
462
512
  loop = await loop_js_1.PregelLoop.initialize({
@@ -464,9 +514,11 @@ class Pregel extends runnables_1.Runnable {
464
514
  config,
465
515
  checkpointer,
466
516
  nodes: this.nodes,
467
- channelSpecs: this.channels,
517
+ channelSpecs,
518
+ managed,
468
519
  outputKeys,
469
520
  streamKeys: this.streamChannelsAsIs,
521
+ store: this.store,
470
522
  });
471
523
  while (await loop.tick({
472
524
  inputKeys: this.inputChannels,
@@ -568,7 +620,14 @@ class Pregel extends runnables_1.Runnable {
568
620
  throw e;
569
621
  }
570
622
  finally {
571
- await Promise.all(loop?.checkpointerPromises ?? []);
623
+ // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
624
+ if (loop) {
625
+ loop.store?.stop();
626
+ }
627
+ await Promise.all([
628
+ loop?.checkpointerPromises ?? [],
629
+ ...Array.from(managed.values()).map((mv) => mv.promises()),
630
+ ]);
572
631
  }
573
632
  }
574
633
  /**
@@ -7,6 +7,8 @@ import { ChannelWrite } from "./write.js";
7
7
  import { PregelInterface, PregelParams, StateSnapshot, StreamMode } from "./types.js";
8
8
  import { StrRecord } from "./algo.js";
9
9
  import { RetryPolicy } from "./utils.js";
10
+ import { BaseStore } from "../store/base.js";
11
+ import { ManagedValueMapping, type ManagedValueSpec } from "../managed/base.js";
10
12
  type WriteValue = Runnable | RunnableFunc<unknown, unknown> | unknown;
11
13
  export declare class Channel {
12
14
  static subscribeTo(channels: string, options?: {
@@ -21,7 +23,7 @@ export declare class Channel {
21
23
  /**
22
24
  * Config for executing the graph.
23
25
  */
24
- export interface PregelOptions<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>> extends RunnableConfig {
26
+ export interface PregelOptions<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel | ManagedValueSpec>> extends RunnableConfig {
25
27
  /** The stream mode for the graph run. Default is ["values"]. */
26
28
  streamMode?: StreamMode | StreamMode[];
27
29
  inputKeys?: keyof Cc | Array<keyof Cc>;
@@ -36,7 +38,7 @@ export interface PregelOptions<Nn extends StrRecord<string, PregelNode>, Cc exte
36
38
  }
37
39
  export type PregelInputType = any;
38
40
  export type PregelOutputType = any;
39
- export declare class Pregel<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>> extends Runnable<PregelInputType, PregelOutputType, PregelOptions<Nn, Cc>> implements PregelInterface<Nn, Cc> {
41
+ export declare class Pregel<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel | ManagedValueSpec>> extends Runnable<PregelInputType, PregelOutputType, PregelOptions<Nn, Cc>> implements PregelInterface<Nn, Cc> {
40
42
  static lc_name(): string;
41
43
  lc_namespace: string[];
42
44
  nodes: Nn;
@@ -52,6 +54,7 @@ export declare class Pregel<Nn extends StrRecord<string, PregelNode>, Cc extends
52
54
  debug: boolean;
53
55
  checkpointer?: BaseCheckpointSaver;
54
56
  retryPolicy?: RetryPolicy;
57
+ store?: BaseStore;
55
58
  constructor(fields: PregelParams<Nn, Cc>);
56
59
  validate(): this;
57
60
  get streamChannelsList(): Array<keyof Cc>;
@@ -101,6 +104,10 @@ export declare class Pregel<Nn extends StrRecord<string, PregelNode>, Cc extends
101
104
  * @param options.debug Whether to print debug information during execution.
102
105
  */
103
106
  stream(input: PregelInputType, options?: Partial<PregelOptions<Nn, Cc>>): Promise<IterableReadableStream<PregelOutputType>>;
107
+ protected prepareSpecs(config: RunnableConfig): Promise<{
108
+ channelSpecs: Record<string, BaseChannel<unknown, unknown, unknown>>;
109
+ managed: ManagedValueMapping;
110
+ }>;
104
111
  _streamIterator(input: PregelInputType, options?: Partial<PregelOptions<Nn, Cc>>): AsyncGenerator<PregelOutputType>;
105
112
  /**
106
113
  * Run the graph with a single input and config.
@@ -1,18 +1,20 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  import { Runnable, RunnableSequence, _coerceToRunnable, ensureConfig, getCallbackManagerForConfig, patchConfig, } from "@langchain/core/runnables";
3
3
  import { compareChannelVersions, copyCheckpoint, emptyCheckpoint, uuid5, } from "@langchain/langgraph-checkpoint";
4
- import { createCheckpoint, emptyChannels, } from "../channels/base.js";
4
+ import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
5
5
  import { PregelNode } from "./read.js";
6
6
  import { validateGraph, validateKeys } from "./validate.js";
7
7
  import { readChannels } from "./io.js";
8
8
  import { printStepCheckpoint, printStepTasks, printStepWrites, tasksWithWrites, } from "./debug.js";
9
9
  import { ChannelWrite, PASSTHROUGH } from "./write.js";
10
- import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, ERROR, INTERRUPT, } from "../constants.js";
10
+ import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, CONFIG_KEY_STORE, ERROR, INTERRUPT, } from "../constants.js";
11
11
  import { GraphRecursionError, GraphValueError, InvalidUpdateError, isGraphInterrupt, } from "../errors.js";
12
12
  import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
13
13
  import { _coerceToDict, getNewChannelVersions } from "./utils.js";
14
14
  import { PregelLoop } from "./loop.js";
15
15
  import { executeTasksWithRetry } from "./retry.js";
16
+ import { ChannelKeyPlaceholder, isConfiguredManagedValue, ManagedValueMapping, } from "../managed/base.js";
17
+ import { patchConfigurable } from "../utils.js";
16
18
  function isString(value) {
17
19
  return typeof value === "string";
18
20
  }
@@ -161,6 +163,12 @@ export class Pregel extends Runnable {
161
163
  writable: true,
162
164
  value: void 0
163
165
  });
166
+ Object.defineProperty(this, "store", {
167
+ enumerable: true,
168
+ configurable: true,
169
+ writable: true,
170
+ value: void 0
171
+ });
164
172
  let { streamMode } = fields;
165
173
  if (streamMode != null && !Array.isArray(streamMode)) {
166
174
  streamMode = [streamMode];
@@ -178,6 +186,7 @@ export class Pregel extends Runnable {
178
186
  this.debug = fields.debug ?? this.debug;
179
187
  this.checkpointer = fields.checkpointer;
180
188
  this.retryPolicy = fields.retryPolicy;
189
+ this.store = fields.store;
181
190
  if (this.autoValidate) {
182
191
  this.validate();
183
192
  }
@@ -223,7 +232,8 @@ export class Pregel extends Runnable {
223
232
  const saved = await this.checkpointer.getTuple(config);
224
233
  const checkpoint = saved ? saved.checkpoint : emptyCheckpoint();
225
234
  const channels = emptyChannels(this.channels, checkpoint);
226
- const nextTasks = _prepareNextTasks(checkpoint, this.nodes, channels, saved !== undefined ? saved.config : config, false, { step: saved ? (saved.metadata?.step ?? -1) + 1 : -1 });
235
+ const { managed } = await this.prepareSpecs(config);
236
+ const nextTasks = _prepareNextTasks(checkpoint, this.nodes, channels, managed, saved !== undefined ? saved.config : config, false, { step: saved ? (saved.metadata?.step ?? -1) + 1 : -1 });
227
237
  return {
228
238
  values: readChannels(channels, this.streamChannelsAsIs),
229
239
  next: nextTasks.map((task) => task.name),
@@ -241,9 +251,10 @@ export class Pregel extends Runnable {
241
251
  if (!this.checkpointer) {
242
252
  throw new GraphValueError("No checkpointer set");
243
253
  }
254
+ const { managed } = await this.prepareSpecs(config);
244
255
  for await (const saved of this.checkpointer.list(config, options)) {
245
256
  const channels = emptyChannels(this.channels, saved.checkpoint);
246
- const nextTasks = _prepareNextTasks(saved.checkpoint, this.nodes, channels, saved.config, false, { step: -1 });
257
+ const nextTasks = _prepareNextTasks(saved.checkpoint, this.nodes, channels, managed, saved.config, false, { step: -1 });
247
258
  yield {
248
259
  values: readChannels(channels, this.streamChannelsAsIs),
249
260
  next: nextTasks.map((task) => task.name),
@@ -332,6 +343,7 @@ export class Pregel extends Runnable {
332
343
  }
333
344
  // update channels
334
345
  const channels = emptyChannels(this.channels, checkpoint);
346
+ const { managed } = await this.prepareSpecs(config);
335
347
  // run all writers of the chosen node
336
348
  const writers = this.nodes[asNode].getWriters();
337
349
  if (!writers.length) {
@@ -352,9 +364,9 @@ export class Pregel extends Runnable {
352
364
  runName: config.runName ?? `${this.getName()}UpdateState`,
353
365
  configurable: {
354
366
  [CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
355
- [CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels,
367
+ [CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed,
356
368
  // TODO: Why does keyof StrRecord allow number and symbol?
357
- task),
369
+ task, select_, fresh_),
358
370
  },
359
371
  }));
360
372
  if (saved !== undefined) {
@@ -438,6 +450,43 @@ export class Pregel extends Runnable {
438
450
  async stream(input, options) {
439
451
  return super.stream(input, options);
440
452
  }
453
+ async prepareSpecs(config) {
454
+ const configForManaged = patchConfigurable(config, {
455
+ [CONFIG_KEY_STORE]: this.store,
456
+ });
457
+ const channelSpecs = {};
458
+ const managedSpecs = {};
459
+ for (const [name, spec] of Object.entries(this.channels)) {
460
+ if (isBaseChannel(spec)) {
461
+ channelSpecs[name] = spec;
462
+ }
463
+ else {
464
+ managedSpecs[name] = spec;
465
+ }
466
+ }
467
+ const managed = new ManagedValueMapping(await Object.entries(managedSpecs).reduce(async (accPromise, [key, value]) => {
468
+ const acc = await accPromise;
469
+ let initializedValue;
470
+ if (isConfiguredManagedValue(value)) {
471
+ if ("key" in value.params &&
472
+ value.params.key === ChannelKeyPlaceholder) {
473
+ value.params.key = key;
474
+ }
475
+ initializedValue = await value.cls.initialize(configForManaged, value.params);
476
+ }
477
+ else {
478
+ initializedValue = await value.initialize(configForManaged);
479
+ }
480
+ if (initializedValue !== undefined) {
481
+ acc.push([key, initializedValue]);
482
+ }
483
+ return acc;
484
+ }, Promise.resolve([])));
485
+ return {
486
+ channelSpecs,
487
+ managed,
488
+ };
489
+ }
441
490
  async *_streamIterator(input, options) {
442
491
  const inputConfig = ensureConfig(options);
443
492
  if (inputConfig.recursionLimit === undefined ||
@@ -453,6 +502,7 @@ export class Pregel extends Runnable {
453
502
  delete inputConfig.runId;
454
503
  // assign defaults
455
504
  const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer,] = this._defaults(inputConfig);
505
+ const { channelSpecs, managed } = await this.prepareSpecs(config);
456
506
  let loop;
457
507
  try {
458
508
  loop = await PregelLoop.initialize({
@@ -460,9 +510,11 @@ export class Pregel extends Runnable {
460
510
  config,
461
511
  checkpointer,
462
512
  nodes: this.nodes,
463
- channelSpecs: this.channels,
513
+ channelSpecs,
514
+ managed,
464
515
  outputKeys,
465
516
  streamKeys: this.streamChannelsAsIs,
517
+ store: this.store,
466
518
  });
467
519
  while (await loop.tick({
468
520
  inputKeys: this.inputChannels,
@@ -564,7 +616,14 @@ export class Pregel extends Runnable {
564
616
  throw e;
565
617
  }
566
618
  finally {
567
- await Promise.all(loop?.checkpointerPromises ?? []);
619
+ // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
620
+ if (loop) {
621
+ loop.store?.stop();
622
+ }
623
+ await Promise.all([
624
+ loop?.checkpointerPromises ?? [],
625
+ ...Array.from(managed.values()).map((mv) => mv.promises()),
626
+ ]);
568
627
  }
569
628
  }
570
629
  /**
@@ -14,6 +14,7 @@ const io_js_1 = require("./io.cjs");
14
14
  const errors_js_1 = require("../errors.cjs");
15
15
  const utils_js_2 = require("./utils.cjs");
16
16
  const debug_js_1 = require("./debug.cjs");
17
+ const batch_js_1 = require("../store/batch.cjs");
17
18
  const INPUT_DONE = Symbol.for("INPUT_DONE");
18
19
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
19
20
  const DEFAULT_LOOP_LIMIT = 25;
@@ -50,6 +51,12 @@ class PregelLoop {
50
51
  writable: true,
51
52
  value: void 0
52
53
  });
54
+ Object.defineProperty(this, "managed", {
55
+ enumerable: true,
56
+ configurable: true,
57
+ writable: true,
58
+ value: void 0
59
+ });
53
60
  Object.defineProperty(this, "checkpoint", {
54
61
  enumerable: true,
55
62
  configurable: true,
@@ -154,6 +161,12 @@ class PregelLoop {
154
161
  writable: true,
155
162
  value: Promise.resolve()
156
163
  });
164
+ Object.defineProperty(this, "store", {
165
+ enumerable: true,
166
+ configurable: true,
167
+ writable: true,
168
+ value: void 0
169
+ });
157
170
  this.input = params.input;
158
171
  this.config = params.config;
159
172
  this.checkpointer = params.checkpointer;
@@ -170,6 +183,7 @@ class PregelLoop {
170
183
  this.checkpointMetadata = params.checkpointMetadata;
171
184
  this.checkpointPreviousVersions = params.checkpointPreviousVersions;
172
185
  this.channels = params.channels;
186
+ this.managed = params.managed;
173
187
  this.checkpointPendingWrites = params.checkpointPendingWrites;
174
188
  this.step = params.step;
175
189
  this.stop = params.stop;
@@ -178,6 +192,7 @@ class PregelLoop {
178
192
  this.streamKeys = params.streamKeys;
179
193
  this.nodes = params.nodes;
180
194
  this.skipDoneTasks = this.config.configurable?.checkpoint_id === undefined;
195
+ this.store = params.store;
181
196
  }
182
197
  static async initialize(params) {
183
198
  const saved = (await params.checkpointer?.getTuple(params.config)) ?? {
@@ -205,6 +220,13 @@ class PregelLoop {
205
220
  const step = (checkpointMetadata.step ?? 0) + 1;
206
221
  const stop = step + (params.config.recursionLimit ?? DEFAULT_LOOP_LIMIT) + 1;
207
222
  const checkpointPreviousVersions = { ...checkpoint.channel_versions };
223
+ const store = params.store
224
+ ? new batch_js_1.AsyncBatchedStore(params.store)
225
+ : undefined;
226
+ if (store) {
227
+ // Start the store. This is a batch store, so it will run continuously
228
+ store.start();
229
+ }
208
230
  return new PregelLoop({
209
231
  input: params.input,
210
232
  config: params.config,
@@ -213,6 +235,7 @@ class PregelLoop {
213
235
  checkpointMetadata,
214
236
  checkpointConfig,
215
237
  channels,
238
+ managed: params.managed,
216
239
  step,
217
240
  stop,
218
241
  checkpointPreviousVersions,
@@ -220,6 +243,7 @@ class PregelLoop {
220
243
  outputKeys: params.outputKeys ?? [],
221
244
  streamKeys: params.streamKeys ?? [],
222
245
  nodes: params.nodes,
246
+ store,
223
247
  });
224
248
  }
225
249
  _checkpointerPutAfterPrevious(input) {
@@ -228,6 +252,13 @@ class PregelLoop {
228
252
  });
229
253
  this.checkpointerPromises.push(this._checkpointerChainedPromise);
230
254
  }
255
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
256
+ async updateManagedValues(key, values) {
257
+ const mv = this.managed.get(key);
258
+ if (mv && "update" in mv && typeof mv.update === "function") {
259
+ await mv.update(values);
260
+ }
261
+ }
231
262
  /**
232
263
  * Put writes for a task, to be read by the next tick.
233
264
  * @param taskId
@@ -261,6 +292,9 @@ class PregelLoop {
261
292
  * @param params
262
293
  */
263
294
  async tick(params) {
295
+ if (this.store && !this.store.isRunning) {
296
+ this.store?.start();
297
+ }
264
298
  const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
265
299
  if (this.status !== "pending") {
266
300
  throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
@@ -271,7 +305,10 @@ class PregelLoop {
271
305
  else if (this.tasks.every((task) => task.writes.length > 0)) {
272
306
  const writes = this.tasks.flatMap((t) => t.writes);
273
307
  // All tasks have finished
274
- (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, this.tasks, this.checkpointerGetNextVersion);
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);
311
+ }
275
312
  // produce values output
276
313
  const valuesOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, writes, this.channels), "values"));
277
314
  this.stream.push(...valuesOutput);
@@ -299,7 +336,7 @@ class PregelLoop {
299
336
  this.status = "out_of_steps";
300
337
  return false;
301
338
  }
302
- const nextTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.nodes, this.channels, this.config, true, {
339
+ const nextTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.nodes, this.channels, this.managed, this.config, true, {
303
340
  step: this.step,
304
341
  checkpointer: this.checkpointer,
305
342
  isResuming: this.input === INPUT_RESUMING,
@@ -377,7 +414,7 @@ class PregelLoop {
377
414
  if (inputWrites.length === 0) {
378
415
  throw new errors_js_1.EmptyInputError(`Received no input writes for ${JSON.stringify(inputKeys, null, 2)}`);
379
416
  }
380
- const discardTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.nodes, this.channels, this.config, true, { step: this.step });
417
+ const discardTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.nodes, this.channels, this.managed, this.config, true, { step: this.step });
381
418
  (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, discardTasks.concat([
382
419
  {
383
420
  name: constants_js_1.INPUT,
@@ -5,6 +5,9 @@ import { BaseCheckpointSaver, Checkpoint, PendingWrite, CheckpointPendingWrite,
5
5
  import { BaseChannel } from "../channels/base.js";
6
6
  import { PregelExecutableTask, StreamMode } from "./types.js";
7
7
  import { PregelNode } from "./read.js";
8
+ import { BaseStore } from "../store/base.js";
9
+ import { AsyncBatchedStore } from "../store/batch.js";
10
+ import { ManagedValueMapping } from "../managed/base.js";
8
11
  export type PregelLoopInitializeParams = {
9
12
  input?: any;
10
13
  config: RunnableConfig;
@@ -13,6 +16,8 @@ export type PregelLoopInitializeParams = {
13
16
  streamKeys: string | string[];
14
17
  nodes: Record<string, PregelNode>;
15
18
  channelSpecs: Record<string, BaseChannel>;
19
+ managed: ManagedValueMapping;
20
+ store?: BaseStore;
16
21
  };
17
22
  type PregelLoopParams = {
18
23
  input?: any;
@@ -24,11 +29,13 @@ type PregelLoopParams = {
24
29
  checkpointPendingWrites: CheckpointPendingWrite[];
25
30
  checkpointConfig: RunnableConfig;
26
31
  channels: Record<string, BaseChannel>;
32
+ managed: ManagedValueMapping;
27
33
  step: number;
28
34
  stop: number;
29
35
  outputKeys: string | string[];
30
36
  streamKeys: string | string[];
31
37
  nodes: Record<string, PregelNode>;
38
+ store?: AsyncBatchedStore;
32
39
  };
33
40
  export declare class PregelLoop {
34
41
  protected input?: any;
@@ -36,6 +43,7 @@ export declare class PregelLoop {
36
43
  protected checkpointer?: BaseCheckpointSaver;
37
44
  protected checkpointerGetNextVersion: (current: number | undefined, channel: BaseChannel) => number;
38
45
  channels: Record<string, BaseChannel>;
46
+ managed: ManagedValueMapping;
39
47
  protected checkpoint: Checkpoint;
40
48
  protected checkpointConfig: RunnableConfig;
41
49
  checkpointMetadata: CheckpointMetadata;
@@ -53,6 +61,7 @@ export declare class PregelLoop {
53
61
  checkpointerPromises: Promise<unknown>[];
54
62
  protected isNested: boolean;
55
63
  protected _checkpointerChainedPromise: Promise<unknown>;
64
+ store?: AsyncBatchedStore;
56
65
  constructor(params: PregelLoopParams);
57
66
  static initialize(params: PregelLoopInitializeParams): Promise<PregelLoop>;
58
67
  protected _checkpointerPutAfterPrevious(input: {
@@ -61,6 +70,7 @@ export declare class PregelLoop {
61
70
  metadata: CheckpointMetadata;
62
71
  newVersions: Record<string, string | number>;
63
72
  }): void;
73
+ protected updateManagedValues(key: string, values: any[]): Promise<void>;
64
74
  /**
65
75
  * Put writes for a task, to be read by the next tick.
66
76
  * @param taskId
@@ -8,6 +8,7 @@ import { mapInput, mapOutputUpdates, mapOutputValues } from "./io.js";
8
8
  import { EmptyInputError, GraphInterrupt } from "../errors.js";
9
9
  import { getNewChannelVersions } from "./utils.js";
10
10
  import { mapDebugTasks, mapDebugCheckpoint, mapDebugTaskResults, } from "./debug.js";
11
+ import { AsyncBatchedStore } from "../store/batch.js";
11
12
  const INPUT_DONE = Symbol.for("INPUT_DONE");
12
13
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
13
14
  const DEFAULT_LOOP_LIMIT = 25;
@@ -44,6 +45,12 @@ export class PregelLoop {
44
45
  writable: true,
45
46
  value: void 0
46
47
  });
48
+ Object.defineProperty(this, "managed", {
49
+ enumerable: true,
50
+ configurable: true,
51
+ writable: true,
52
+ value: void 0
53
+ });
47
54
  Object.defineProperty(this, "checkpoint", {
48
55
  enumerable: true,
49
56
  configurable: true,
@@ -148,6 +155,12 @@ export class PregelLoop {
148
155
  writable: true,
149
156
  value: Promise.resolve()
150
157
  });
158
+ Object.defineProperty(this, "store", {
159
+ enumerable: true,
160
+ configurable: true,
161
+ writable: true,
162
+ value: void 0
163
+ });
151
164
  this.input = params.input;
152
165
  this.config = params.config;
153
166
  this.checkpointer = params.checkpointer;
@@ -164,6 +177,7 @@ export class PregelLoop {
164
177
  this.checkpointMetadata = params.checkpointMetadata;
165
178
  this.checkpointPreviousVersions = params.checkpointPreviousVersions;
166
179
  this.channels = params.channels;
180
+ this.managed = params.managed;
167
181
  this.checkpointPendingWrites = params.checkpointPendingWrites;
168
182
  this.step = params.step;
169
183
  this.stop = params.stop;
@@ -172,6 +186,7 @@ export class PregelLoop {
172
186
  this.streamKeys = params.streamKeys;
173
187
  this.nodes = params.nodes;
174
188
  this.skipDoneTasks = this.config.configurable?.checkpoint_id === undefined;
189
+ this.store = params.store;
175
190
  }
176
191
  static async initialize(params) {
177
192
  const saved = (await params.checkpointer?.getTuple(params.config)) ?? {
@@ -199,6 +214,13 @@ export class PregelLoop {
199
214
  const step = (checkpointMetadata.step ?? 0) + 1;
200
215
  const stop = step + (params.config.recursionLimit ?? DEFAULT_LOOP_LIMIT) + 1;
201
216
  const checkpointPreviousVersions = { ...checkpoint.channel_versions };
217
+ const store = params.store
218
+ ? new AsyncBatchedStore(params.store)
219
+ : undefined;
220
+ if (store) {
221
+ // Start the store. This is a batch store, so it will run continuously
222
+ store.start();
223
+ }
202
224
  return new PregelLoop({
203
225
  input: params.input,
204
226
  config: params.config,
@@ -207,6 +229,7 @@ export class PregelLoop {
207
229
  checkpointMetadata,
208
230
  checkpointConfig,
209
231
  channels,
232
+ managed: params.managed,
210
233
  step,
211
234
  stop,
212
235
  checkpointPreviousVersions,
@@ -214,6 +237,7 @@ export class PregelLoop {
214
237
  outputKeys: params.outputKeys ?? [],
215
238
  streamKeys: params.streamKeys ?? [],
216
239
  nodes: params.nodes,
240
+ store,
217
241
  });
218
242
  }
219
243
  _checkpointerPutAfterPrevious(input) {
@@ -222,6 +246,13 @@ export class PregelLoop {
222
246
  });
223
247
  this.checkpointerPromises.push(this._checkpointerChainedPromise);
224
248
  }
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ async updateManagedValues(key, values) {
251
+ const mv = this.managed.get(key);
252
+ if (mv && "update" in mv && typeof mv.update === "function") {
253
+ await mv.update(values);
254
+ }
255
+ }
225
256
  /**
226
257
  * Put writes for a task, to be read by the next tick.
227
258
  * @param taskId
@@ -255,6 +286,9 @@ export class PregelLoop {
255
286
  * @param params
256
287
  */
257
288
  async tick(params) {
289
+ if (this.store && !this.store.isRunning) {
290
+ this.store?.start();
291
+ }
258
292
  const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
259
293
  if (this.status !== "pending") {
260
294
  throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
@@ -265,7 +299,10 @@ export class PregelLoop {
265
299
  else if (this.tasks.every((task) => task.writes.length > 0)) {
266
300
  const writes = this.tasks.flatMap((t) => t.writes);
267
301
  // All tasks have finished
268
- _applyWrites(this.checkpoint, this.channels, this.tasks, this.checkpointerGetNextVersion);
302
+ const myWrites = _applyWrites(this.checkpoint, this.channels, this.tasks, this.checkpointerGetNextVersion);
303
+ for (const [key, values] of Object.entries(myWrites)) {
304
+ await this.updateManagedValues(key, values);
305
+ }
269
306
  // produce values output
270
307
  const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, writes, this.channels), "values"));
271
308
  this.stream.push(...valuesOutput);
@@ -293,7 +330,7 @@ export class PregelLoop {
293
330
  this.status = "out_of_steps";
294
331
  return false;
295
332
  }
296
- const nextTasks = _prepareNextTasks(this.checkpoint, this.nodes, this.channels, this.config, true, {
333
+ const nextTasks = _prepareNextTasks(this.checkpoint, this.nodes, this.channels, this.managed, this.config, true, {
297
334
  step: this.step,
298
335
  checkpointer: this.checkpointer,
299
336
  isResuming: this.input === INPUT_RESUMING,
@@ -371,7 +408,7 @@ export class PregelLoop {
371
408
  if (inputWrites.length === 0) {
372
409
  throw new EmptyInputError(`Received no input writes for ${JSON.stringify(inputKeys, null, 2)}`);
373
410
  }
374
- const discardTasks = _prepareNextTasks(this.checkpoint, this.nodes, this.channels, this.config, true, { step: this.step });
411
+ const discardTasks = _prepareNextTasks(this.checkpoint, this.nodes, this.channels, this.managed, this.config, true, { step: this.step });
375
412
  _applyWrites(this.checkpoint, this.channels, discardTasks.concat([
376
413
  {
377
414
  name: INPUT,