@langchain/langgraph 0.2.8 → 0.2.9

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