@langchain/langgraph 0.0.11 → 0.0.13

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 (134) hide show
  1. package/dist/channels/any_value.cjs +57 -0
  2. package/dist/channels/any_value.d.ts +16 -0
  3. package/dist/channels/any_value.js +53 -0
  4. package/dist/channels/base.cjs +19 -28
  5. package/dist/channels/base.d.ts +13 -19
  6. package/dist/channels/base.js +17 -24
  7. package/dist/channels/binop.cjs +4 -3
  8. package/dist/channels/binop.d.ts +1 -1
  9. package/dist/channels/binop.js +3 -2
  10. package/dist/channels/dynamic_barrier_value.cjs +88 -0
  11. package/dist/channels/dynamic_barrier_value.d.ts +26 -0
  12. package/dist/channels/dynamic_barrier_value.js +84 -0
  13. package/dist/channels/ephemeral_value.cjs +64 -0
  14. package/dist/channels/ephemeral_value.d.ts +14 -0
  15. package/dist/channels/ephemeral_value.js +60 -0
  16. package/dist/channels/index.cjs +1 -3
  17. package/dist/channels/index.d.ts +1 -1
  18. package/dist/channels/index.js +1 -1
  19. package/dist/channels/last_value.cjs +11 -5
  20. package/dist/channels/last_value.d.ts +5 -1
  21. package/dist/channels/last_value.js +9 -3
  22. package/dist/channels/named_barrier_value.cjs +71 -0
  23. package/dist/channels/named_barrier_value.d.ts +18 -0
  24. package/dist/channels/named_barrier_value.js +66 -0
  25. package/dist/channels/topic.cjs +5 -3
  26. package/dist/channels/topic.d.ts +3 -3
  27. package/dist/channels/topic.js +5 -3
  28. package/dist/checkpoint/base.cjs +30 -12
  29. package/dist/checkpoint/base.d.ts +39 -22
  30. package/dist/checkpoint/base.js +28 -11
  31. package/dist/checkpoint/id.cjs +40 -0
  32. package/dist/checkpoint/id.d.ts +2 -0
  33. package/dist/checkpoint/id.js +35 -0
  34. package/dist/checkpoint/index.cjs +2 -2
  35. package/dist/checkpoint/index.d.ts +2 -2
  36. package/dist/checkpoint/index.js +2 -2
  37. package/dist/checkpoint/memory.cjs +63 -49
  38. package/dist/checkpoint/memory.d.ts +7 -10
  39. package/dist/checkpoint/memory.js +62 -47
  40. package/dist/checkpoint/sqlite.cjs +170 -0
  41. package/dist/checkpoint/sqlite.d.ts +14 -0
  42. package/dist/checkpoint/sqlite.js +163 -0
  43. package/dist/constants.cjs +3 -1
  44. package/dist/constants.d.ts +2 -0
  45. package/dist/constants.js +2 -0
  46. package/dist/errors.cjs +31 -0
  47. package/dist/errors.d.ts +12 -0
  48. package/dist/errors.js +24 -0
  49. package/dist/graph/graph.cjs +234 -96
  50. package/dist/graph/graph.d.ts +52 -23
  51. package/dist/graph/graph.js +233 -97
  52. package/dist/graph/index.cjs +2 -2
  53. package/dist/graph/index.d.ts +2 -2
  54. package/dist/graph/index.js +2 -2
  55. package/dist/graph/message.cjs +4 -3
  56. package/dist/graph/message.d.ts +4 -1
  57. package/dist/graph/message.js +4 -3
  58. package/dist/graph/state.cjs +237 -102
  59. package/dist/graph/state.d.ts +41 -18
  60. package/dist/graph/state.js +238 -104
  61. package/dist/index.cjs +6 -2
  62. package/dist/index.d.ts +3 -2
  63. package/dist/index.js +2 -1
  64. package/dist/prebuilt/agent_executor.cjs +22 -36
  65. package/dist/prebuilt/agent_executor.d.ts +7 -10
  66. package/dist/prebuilt/agent_executor.js +23 -37
  67. package/dist/prebuilt/chat_agent_executor.cjs +13 -13
  68. package/dist/prebuilt/chat_agent_executor.d.ts +3 -1
  69. package/dist/prebuilt/chat_agent_executor.js +15 -15
  70. package/dist/prebuilt/index.cjs +4 -1
  71. package/dist/prebuilt/index.d.ts +1 -0
  72. package/dist/prebuilt/index.js +1 -0
  73. package/dist/prebuilt/tool_node.cjs +59 -0
  74. package/dist/prebuilt/tool_node.d.ts +17 -0
  75. package/dist/prebuilt/tool_node.js +54 -0
  76. package/dist/pregel/debug.cjs +6 -8
  77. package/dist/pregel/debug.d.ts +2 -2
  78. package/dist/pregel/debug.js +5 -7
  79. package/dist/pregel/index.cjs +406 -236
  80. package/dist/pregel/index.d.ts +77 -41
  81. package/dist/pregel/index.js +408 -241
  82. package/dist/pregel/io.cjs +117 -30
  83. package/dist/pregel/io.d.ts +11 -3
  84. package/dist/pregel/io.js +111 -28
  85. package/dist/pregel/read.cjs +126 -46
  86. package/dist/pregel/read.d.ts +27 -18
  87. package/dist/pregel/read.js +125 -45
  88. package/dist/pregel/types.cjs +2 -0
  89. package/dist/pregel/types.d.ts +32 -0
  90. package/dist/pregel/types.js +1 -0
  91. package/dist/pregel/validate.cjs +58 -51
  92. package/dist/pregel/validate.d.ts +14 -13
  93. package/dist/pregel/validate.js +56 -50
  94. package/dist/pregel/write.cjs +46 -30
  95. package/dist/pregel/write.d.ts +18 -8
  96. package/dist/pregel/write.js +45 -29
  97. package/dist/serde/base.cjs +2 -0
  98. package/dist/serde/base.d.ts +4 -0
  99. package/dist/serde/base.js +1 -0
  100. package/dist/setup/async_local_storage.cjs +2 -2
  101. package/dist/setup/async_local_storage.js +1 -1
  102. package/dist/tests/channels.test.d.ts +1 -0
  103. package/dist/tests/channels.test.js +151 -0
  104. package/dist/tests/chatbot.int.test.d.ts +1 -0
  105. package/dist/tests/chatbot.int.test.js +61 -0
  106. package/dist/tests/checkpoints.test.d.ts +1 -0
  107. package/dist/tests/checkpoints.test.js +190 -0
  108. package/dist/tests/graph.test.d.ts +1 -0
  109. package/dist/tests/graph.test.js +15 -0
  110. package/dist/tests/prebuilt.int.test.d.ts +1 -0
  111. package/dist/tests/prebuilt.int.test.js +101 -0
  112. package/dist/tests/prebuilt.test.d.ts +1 -0
  113. package/dist/tests/prebuilt.test.js +195 -0
  114. package/dist/tests/pregel.io.test.d.ts +1 -0
  115. package/dist/tests/pregel.io.test.js +332 -0
  116. package/dist/tests/pregel.read.test.d.ts +1 -0
  117. package/dist/tests/pregel.read.test.js +109 -0
  118. package/dist/tests/pregel.test.d.ts +1 -0
  119. package/dist/tests/pregel.test.js +1879 -0
  120. package/dist/tests/pregel.validate.test.d.ts +1 -0
  121. package/dist/tests/pregel.validate.test.js +198 -0
  122. package/dist/tests/pregel.write.test.d.ts +1 -0
  123. package/dist/tests/pregel.write.test.js +44 -0
  124. package/dist/tests/tracing.int.test.d.ts +1 -0
  125. package/dist/tests/tracing.int.test.js +449 -0
  126. package/dist/tests/utils.d.ts +22 -0
  127. package/dist/tests/utils.js +76 -0
  128. package/dist/utils.cjs +74 -0
  129. package/dist/utils.d.ts +18 -0
  130. package/dist/utils.js +70 -0
  131. package/package.json +12 -8
  132. package/dist/pregel/reserved.cjs +0 -6
  133. package/dist/pregel/reserved.d.ts +0 -3
  134. package/dist/pregel/reserved.js +0 -3
@@ -1,83 +1,71 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  import { Runnable, _coerceToRunnable, ensureConfig, patchConfig, } from "@langchain/core/runnables";
3
3
  import { IterableReadableStream } from "@langchain/core/utils/stream";
4
- import { EmptyChannelError, createCheckpoint, emptyChannels, } from "../channels/base.js";
5
- import { emptyCheckpoint, } from "../checkpoint/base.js";
6
- import { ChannelInvoke } from "./read.js";
7
- import { validateGraph } from "./validate.js";
8
- import { ReservedChannelsMap } from "./reserved.js";
9
- import { mapInput, mapOutput } from "./io.js";
10
- import { ChannelWrite } from "./write.js";
11
- import { CONFIG_KEY_READ, CONFIG_KEY_SEND } from "../constants.js";
4
+ import { createCheckpoint, emptyChannels, } from "../channels/base.js";
5
+ import { copyCheckpoint, emptyCheckpoint, } from "../checkpoint/base.js";
6
+ import { PregelNode } from "./read.js";
7
+ import { validateGraph, validateKeys } from "./validate.js";
8
+ import { mapInput, mapOutputUpdates, mapOutputValues, readChannel, readChannels, single, } from "./io.js";
9
+ import { ChannelWrite, PASSTHROUGH } from "./write.js";
10
+ import { CONFIG_KEY_READ, CONFIG_KEY_SEND, INTERRUPT, TAG_HIDDEN, } from "../constants.js";
12
11
  import { initializeAsyncLocalStorageSingleton } from "../setup/async_local_storage.js";
13
- const DEFAULT_RECURSION_LIMIT = 25;
14
- export class GraphRecursionError extends Error {
15
- constructor(message) {
16
- super(message);
17
- this.name = "GraphRecursionError";
18
- }
19
- }
20
- function _coerceWriteValue(value) {
21
- if (!Runnable.isRunnable(value) && typeof value !== "function") {
22
- return _coerceToRunnable(() => value);
23
- }
24
- return _coerceToRunnable(value);
25
- }
12
+ import { EmptyChannelError, GraphRecursionError, GraphValueError, InvalidUpdateError, } from "../errors.js";
13
+ const DEFAULT_LOOP_LIMIT = 25;
26
14
  function isString(value) {
27
15
  return typeof value === "string";
28
16
  }
29
17
  export class Channel {
30
18
  static subscribeTo(channels, options) {
31
- const { key, when, tags } = options ?? {};
19
+ const { key, tags } = options ?? {};
32
20
  if (Array.isArray(channels) && key !== undefined) {
33
21
  throw new Error("Can't specify a key when subscribing to multiple channels");
34
22
  }
35
- let channelMappingOrString;
23
+ let channelMappingOrArray;
36
24
  if (isString(channels)) {
37
25
  if (key) {
38
- channelMappingOrString = { [key]: channels };
26
+ channelMappingOrArray = { [key]: channels };
39
27
  }
40
28
  else {
41
- channelMappingOrString = channels;
29
+ channelMappingOrArray = [channels];
42
30
  }
43
31
  }
44
32
  else {
45
- channelMappingOrString = Object.fromEntries(channels.map((chan) => [chan, chan]));
33
+ channelMappingOrArray = Object.fromEntries(channels.map((chan) => [chan, chan]));
46
34
  }
47
35
  const triggers = Array.isArray(channels) ? channels : [channels];
48
- return new ChannelInvoke({
49
- channels: channelMappingOrString,
36
+ return new PregelNode({
37
+ channels: channelMappingOrArray,
50
38
  triggers,
51
- when,
52
39
  tags,
53
40
  });
54
41
  }
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- static writeTo(...args) {
57
- // const channelPairs: Array<[string, WriteValue<RunInput, RunOutput>]> =
58
- // channels.map((c) => [c, undefined]);
59
- // return new ChannelWrite<RunInput, RunOutput>(channelPairs);
60
- const channelPairs = [];
61
- if (args.length === 1 && typeof args[0] === "object") {
62
- // Handle the case where named arguments are passed as an object
63
- const additionalArgs = args[0];
64
- Object.entries(additionalArgs).forEach(([key, value]) => {
65
- channelPairs.push([key, _coerceWriteValue(value)]);
42
+ static writeTo(channels, kwargs) {
43
+ const channelWriteEntries = [];
44
+ for (const channel of channels) {
45
+ channelWriteEntries.push({
46
+ channel,
47
+ value: PASSTHROUGH,
48
+ skipNone: false,
66
49
  });
67
50
  }
68
- else {
69
- args.forEach((channel) => {
70
- if (typeof channel === "string") {
71
- channelPairs.push([channel, undefined]);
72
- }
73
- else if (typeof channel === "object") {
74
- Object.entries(channel).forEach(([key, value]) => {
75
- channelPairs.push([key, _coerceWriteValue(value)]);
76
- });
77
- }
78
- });
51
+ for (const [key, value] of Object.entries(kwargs ?? {})) {
52
+ if (Runnable.isRunnable(value) || typeof value === "function") {
53
+ channelWriteEntries.push({
54
+ channel: key,
55
+ value: PASSTHROUGH,
56
+ skipNone: true,
57
+ mapper: _coerceToRunnable(value),
58
+ });
59
+ }
60
+ else {
61
+ channelWriteEntries.push({
62
+ channel: key,
63
+ value,
64
+ skipNone: false,
65
+ });
66
+ }
79
67
  }
80
- return new ChannelWrite(channelPairs);
68
+ return new ChannelWrite(channelWriteEntries);
81
69
  }
82
70
  }
83
71
  export class Pregel extends Runnable {
@@ -93,43 +81,55 @@ export class Pregel extends Runnable {
93
81
  writable: true,
94
82
  value: ["langgraph", "pregel"]
95
83
  });
84
+ Object.defineProperty(this, "nodes", {
85
+ enumerable: true,
86
+ configurable: true,
87
+ writable: true,
88
+ value: void 0
89
+ });
96
90
  Object.defineProperty(this, "channels", {
97
91
  enumerable: true,
98
92
  configurable: true,
99
93
  writable: true,
100
- value: {}
94
+ value: void 0
101
95
  });
102
- Object.defineProperty(this, "output", {
96
+ Object.defineProperty(this, "inputs", {
103
97
  enumerable: true,
104
98
  configurable: true,
105
99
  writable: true,
106
- value: "output"
100
+ value: void 0
107
101
  });
108
- Object.defineProperty(this, "input", {
102
+ Object.defineProperty(this, "outputs", {
109
103
  enumerable: true,
110
104
  configurable: true,
111
105
  writable: true,
112
- value: "input"
106
+ value: void 0
113
107
  });
114
- Object.defineProperty(this, "hidden", {
108
+ Object.defineProperty(this, "autoValidate", {
115
109
  enumerable: true,
116
110
  configurable: true,
117
111
  writable: true,
118
- value: []
112
+ value: true
119
113
  });
120
- Object.defineProperty(this, "debug", {
114
+ Object.defineProperty(this, "streamMode", {
121
115
  enumerable: true,
122
116
  configurable: true,
123
117
  writable: true,
124
- value: false
118
+ value: "values"
125
119
  });
126
- Object.defineProperty(this, "nodes", {
120
+ Object.defineProperty(this, "streamChannels", {
127
121
  enumerable: true,
128
122
  configurable: true,
129
123
  writable: true,
130
124
  value: void 0
131
125
  });
132
- Object.defineProperty(this, "checkpointer", {
126
+ Object.defineProperty(this, "interruptAfter", {
127
+ enumerable: true,
128
+ configurable: true,
129
+ writable: true,
130
+ value: void 0
131
+ });
132
+ Object.defineProperty(this, "interruptBefore", {
133
133
  enumerable: true,
134
134
  configurable: true,
135
135
  writable: true,
@@ -141,135 +141,263 @@ export class Pregel extends Runnable {
141
141
  writable: true,
142
142
  value: void 0
143
143
  });
144
- Object.defineProperty(this, "interrupt", {
144
+ Object.defineProperty(this, "debug", {
145
145
  enumerable: true,
146
146
  configurable: true,
147
147
  writable: true,
148
- value: []
148
+ value: false
149
+ });
150
+ Object.defineProperty(this, "checkpointer", {
151
+ enumerable: true,
152
+ configurable: true,
153
+ writable: true,
154
+ value: void 0
149
155
  });
150
156
  // Initialize global async local storage instance for tracing
151
157
  initializeAsyncLocalStorageSingleton();
152
- this.channels = fields.channels ?? this.channels;
153
- this.output = fields.output ?? this.output;
154
- this.input = fields.input ?? this.input;
155
- this.hidden = fields.hidden ?? this.hidden;
156
- this.debug = fields.debug ?? this.debug;
157
158
  this.nodes = fields.nodes;
159
+ this.channels = fields.channels;
160
+ this.autoValidate = fields.autoValidate ?? this.autoValidate;
161
+ this.streamMode = fields.streamMode ?? this.streamMode;
162
+ this.outputs = fields.outputs;
163
+ this.streamChannels = fields.streamChannels ?? this.streamChannels;
164
+ this.interruptAfter = fields.interruptAfter;
165
+ this.interruptBefore = fields.interruptBefore;
166
+ this.inputs = fields.inputs;
167
+ this.stepTimeout = fields.stepTimeout ?? this.stepTimeout;
168
+ this.debug = fields.debug ?? this.debug;
158
169
  this.checkpointer = fields.checkpointer;
159
- this.stepTimeout = fields.stepTimeout;
160
- this.interrupt = fields.interrupt ?? this.interrupt;
161
170
  // Bind the method to the instance
162
171
  this._transform = this._transform.bind(this);
172
+ if (this.autoValidate) {
173
+ this.validate();
174
+ }
175
+ }
176
+ validate() {
163
177
  validateGraph({
164
178
  nodes: this.nodes,
165
179
  channels: this.channels,
166
- output: this.output,
167
- input: this.input,
168
- hidden: this.hidden,
169
- interrupt: this.interrupt,
180
+ outputChannels: this.outputs,
181
+ inputChannels: this.inputs,
182
+ streamChannels: this.streamChannels,
183
+ interruptAfterNodes: this.interruptAfter,
184
+ interruptBeforeNodes: this.interruptBefore,
170
185
  });
186
+ return this;
171
187
  }
172
- async *_transform(input, runManager, config = {}) {
173
- // assign defaults
174
- let outputKeys = [];
175
- if (Array.isArray(config.outputKeys) ||
176
- typeof config.outputKeys === "string") {
177
- outputKeys = config.outputKeys;
188
+ get streamChannelsList() {
189
+ if (Array.isArray(this.streamChannels)) {
190
+ return this.streamChannels;
191
+ }
192
+ else if (this.streamChannels) {
193
+ return [this.streamChannels];
178
194
  }
179
195
  else {
180
- for (const chan in this.channels) {
181
- if (!this.hidden.includes(chan)) {
182
- outputKeys.push(chan);
183
- }
184
- }
196
+ return Object.keys(this.channels);
185
197
  }
186
- // copy nodes to ignore mutations during execution
187
- const processes = { ...this.nodes };
188
- // get checkpoint, or create an empty one
189
- let checkpoint;
190
- if (this.checkpointer) {
191
- checkpoint = this.checkpointer.get(config);
192
- }
193
- checkpoint = checkpoint ?? emptyCheckpoint();
194
- // create channels from checkpoint
195
- const channels = emptyChannels(this.channels, checkpoint);
196
- // map inputs to channel updates
197
- const thisInput = this.input;
198
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
- const inputPendingWrites = [];
200
- for await (const c of input) {
201
- for (const value of mapInput(thisInput, c)) {
202
- inputPendingWrites.push(value);
203
- }
198
+ }
199
+ get streamChannelsAsIs() {
200
+ if (this.streamChannels) {
201
+ return this.streamChannels;
202
+ }
203
+ else {
204
+ return Object.keys(this.channels);
205
+ }
206
+ }
207
+ _defaults(config) {
208
+ const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
209
+ const defaultDebug = debug !== undefined ? debug : this.debug;
210
+ let defaultOutputKeys = outputKeys;
211
+ if (defaultOutputKeys === undefined) {
212
+ defaultOutputKeys = this.streamChannelsAsIs;
213
+ }
214
+ else {
215
+ validateKeys(defaultOutputKeys, this.channels);
216
+ }
217
+ let defaultInputKeys = inputKeys;
218
+ if (defaultInputKeys === undefined) {
219
+ defaultInputKeys = this.inputs;
204
220
  }
205
- _applyWrites(checkpoint, channels, inputPendingWrites, config, 0);
206
- const read = (chan) => _readChannel(channels, chan);
207
- // Similarly to Bulk Synchronous Parallel / Pregel model
208
- // computation proceeds in steps, while there are channel updates
209
- // channel updates from step N are only visible in step N+1
210
- // channels are guaranteed to be immutable for the duration of the step,
211
- // with channel updates applied only at the transition between steps
212
- const recursionLimit = config.recursionLimit ?? DEFAULT_RECURSION_LIMIT;
213
- for (let step = 0; step < recursionLimit + 1; step += 1) {
214
- const nextTasks = _prepareNextTasks(checkpoint, processes, channels);
215
- // if no more tasks, we're done
216
- if (nextTasks.length === 0) {
217
- break;
221
+ else {
222
+ validateKeys(defaultInputKeys, this.channels);
223
+ }
224
+ const defaultInterruptBefore = interruptBefore ?? this.interruptBefore ?? [];
225
+ const defaultInterruptAfter = interruptAfter ?? this.interruptAfter ?? [];
226
+ let defaultStreamMode;
227
+ if (streamMode !== undefined) {
228
+ defaultStreamMode = streamMode;
229
+ }
230
+ else {
231
+ defaultStreamMode = this.streamMode;
232
+ }
233
+ if (config.configurable !== undefined &&
234
+ config.configurable[CONFIG_KEY_READ] !== undefined) {
235
+ defaultStreamMode = "values";
236
+ }
237
+ return [
238
+ defaultDebug,
239
+ defaultStreamMode,
240
+ defaultInputKeys,
241
+ defaultOutputKeys,
242
+ rest,
243
+ defaultInterruptBefore,
244
+ defaultInterruptAfter,
245
+ ];
246
+ }
247
+ async *_transform(input, runManager, config = {}) {
248
+ const bg = [];
249
+ try {
250
+ if (config.recursionLimit && config.recursionLimit < 1) {
251
+ throw new GraphValueError(`Recursion limit must be greater than 0, got ${config.recursionLimit}`);
218
252
  }
219
- else if (step === config.recursionLimit) {
220
- throw new GraphRecursionError(`Recursion limit of ${config.recursionLimit} reached without hitting a stop condition. You can increase the limit by setting the "recursionLimit" config key.`);
253
+ if (this.checkpointer && !config.configurable) {
254
+ throw new GraphValueError(`Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_id`);
221
255
  }
222
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
223
- const pendingWrites = [];
224
- const tasksWithConfig = nextTasks.map(([proc, input, name]) => [
225
- proc,
226
- input,
227
- patchConfig(config, {
228
- callbacks: runManager?.getChild(`graph:step:${step}`),
229
- runName: name,
230
- configurable: {
231
- ...config.configurable,
232
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
233
- [CONFIG_KEY_SEND]: (items) => pendingWrites.push(...items),
234
- [CONFIG_KEY_READ]: read,
235
- },
236
- }),
237
- ]);
238
- // execute tasks, and wait for one to fail or all to finish.
239
- // each task is independent from all other concurrent tasks
240
- const tasks = tasksWithConfig.map(([proc, input, updatedConfig]) => () => proc.invoke(input, updatedConfig));
241
- await executeTasks(tasks, this.stepTimeout);
242
- // apply writes to channels
243
- _applyWrites(checkpoint, channels, pendingWrites, config, step + 1);
244
- // yield current value and checkpoint view
245
- const stepOutput = mapOutput(outputKeys, pendingWrites, channels);
246
- if (stepOutput) {
247
- yield stepOutput;
248
- if (typeof outputKeys !== "string") {
249
- _applyWritesFromView(checkpoint, channels, stepOutput);
256
+ // assign defaults
257
+ const [debug, streamMode, inputKeys, outputKeys, restConfig, interruptBefore, interruptAfter,] = this._defaults(config);
258
+ // copy nodes to ignore mutations during execution
259
+ const processes = { ...this.nodes };
260
+ // get checkpoint, or create an empty one
261
+ const saved = this.checkpointer
262
+ ? await this.checkpointer.getTuple(config)
263
+ : null;
264
+ let checkpoint = saved ? saved.checkpoint : emptyCheckpoint();
265
+ let checkpointConfig = saved ? saved.config : config;
266
+ let start = (saved?.metadata?.step ?? -2) + 1;
267
+ // create channels from checkpoint
268
+ const channels = emptyChannels(this.channels, checkpoint);
269
+ // map inputs to channel updates
270
+ const inputPendingWrites = [];
271
+ for await (const c of input) {
272
+ for (const value of mapInput(inputKeys, c)) {
273
+ inputPendingWrites.push(value);
274
+ }
275
+ }
276
+ if (inputPendingWrites.length) {
277
+ // discard any unfinished tasks from previous checkpoint
278
+ const discarded = _prepareNextTasks(checkpoint, processes, channels, true);
279
+ checkpoint = discarded[0]; // eslint-disable-line prefer-destructuring
280
+ // apply input writes
281
+ _applyWrites(checkpoint, channels, inputPendingWrites);
282
+ // save input checkpoint
283
+ if (this.checkpointer) {
284
+ checkpoint = createCheckpoint(checkpoint, channels, start);
285
+ bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
286
+ source: "input",
287
+ step: start,
288
+ writes: Object.fromEntries(inputPendingWrites),
289
+ }));
290
+ checkpointConfig = {
291
+ configurable: {
292
+ ...checkpointConfig.configurable,
293
+ checkpoint_id: checkpoint.id,
294
+ },
295
+ };
250
296
  }
297
+ // increment start to 0
298
+ start += 1;
251
299
  }
252
- // save end of step checkpoint
253
- if (this.checkpointer &&
254
- this.checkpointer.at === "end_of_step" /* CheckpointAt.END_OF_STEP */) {
255
- checkpoint = await createCheckpoint(checkpoint, channels);
256
- this.checkpointer.put(config, checkpoint);
300
+ else {
301
+ checkpoint = copyCheckpoint(checkpoint);
302
+ for (const k of this.streamChannelsList) {
303
+ const version = checkpoint.channel_versions[k];
304
+ checkpoint.versions_seen[INTERRUPT][k] = version;
305
+ }
257
306
  }
258
- // interrupt if any channel written to is in interrupt list
259
- if (pendingWrites.some(([chan]) => this.interrupt?.some((i) => i === chan))) {
260
- break;
307
+ // Similarly to Bulk Synchronous Parallel / Pregel model
308
+ // computation proceeds in steps, while there are channel updates
309
+ // channel updates from step N are only visible in step N+1
310
+ // channels are guaranteed to be immutable for the duration of the step,
311
+ // with channel updates applied only at the transition between steps
312
+ const stop = start + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT);
313
+ for (let step = start; step < stop + 1; step += 1) {
314
+ const [nextCheckpoint, nextTasks] = _prepareNextTasks(checkpoint, processes, channels, true);
315
+ // if no more tasks, we're done
316
+ if (nextTasks.length === 0 && step === start) {
317
+ throw new GraphValueError(`No tasks to run in graph.`);
318
+ }
319
+ else if (nextTasks.length === 0) {
320
+ break;
321
+ }
322
+ else if (step === stop) {
323
+ throw new GraphRecursionError(`Recursion limit of ${config.recursionLimit} reached without hitting a stop condition. You can increase the limit by setting the "recursionLimit" config key.`);
324
+ }
325
+ // before execution, check if we should interrupt
326
+ if (_shouldInterrupt(checkpoint, interruptBefore, this.streamChannelsList, nextTasks)) {
327
+ break;
328
+ }
329
+ else {
330
+ checkpoint = nextCheckpoint;
331
+ }
332
+ if (debug) {
333
+ console.log(nextTasks);
334
+ }
335
+ const tasksWithConfig = nextTasks.map(
336
+ // eslint-disable-next-line no-loop-func
337
+ (task) => [
338
+ task.proc,
339
+ task.input,
340
+ patchConfig(restConfig, {
341
+ callbacks: runManager?.getChild(`graph:step:${step}`),
342
+ runName: task.name,
343
+ configurable: {
344
+ ...config.configurable,
345
+ [CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
346
+ [CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, task.writes),
347
+ },
348
+ }),
349
+ ]);
350
+ // execute tasks, and wait for one to fail or all to finish.
351
+ // each task is independent from all other concurrent tasks
352
+ const tasks = tasksWithConfig.map(([proc, input, updatedConfig]) => () => proc.invoke(input, updatedConfig));
353
+ await executeTasks(tasks, this.stepTimeout);
354
+ // combine pending writes from all tasks
355
+ const pendingWrites = [];
356
+ for (const task of nextTasks) {
357
+ pendingWrites.push(...task.writes);
358
+ }
359
+ // apply writes to channels
360
+ _applyWrites(checkpoint, channels, pendingWrites);
361
+ // yield current value and checkpoint view
362
+ if (streamMode === "values") {
363
+ yield* mapOutputValues(outputKeys, pendingWrites, channels);
364
+ }
365
+ else if (streamMode === "updates") {
366
+ yield* mapOutputUpdates(outputKeys, nextTasks);
367
+ }
368
+ // save end of step checkpoint
369
+ if (this.checkpointer) {
370
+ checkpoint = createCheckpoint(checkpoint, channels, step);
371
+ bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
372
+ source: "loop",
373
+ step,
374
+ writes: single(streamMode === "values"
375
+ ? mapOutputValues(outputKeys, pendingWrites, channels)
376
+ : mapOutputUpdates(outputKeys, nextTasks)),
377
+ }));
378
+ checkpointConfig = {
379
+ configurable: {
380
+ ...checkpointConfig.configurable,
381
+ checkpoint_id: checkpoint.id,
382
+ },
383
+ };
384
+ }
385
+ if (_shouldInterrupt(checkpoint, interruptAfter, this.streamChannelsList, nextTasks)) {
386
+ break;
387
+ }
261
388
  }
262
389
  }
263
- // save end of run checkpoint
264
- if (this.checkpointer && this.checkpointer.at === "end_of_run" /* CheckpointAt.END_OF_RUN */) {
265
- checkpoint = await createCheckpoint(checkpoint, channels);
266
- this.checkpointer.put(config, checkpoint);
390
+ finally {
391
+ await Promise.all(bg);
267
392
  }
268
393
  }
269
394
  async invoke(input, options) {
270
395
  const config = ensureConfig(options);
271
396
  if (!config?.outputKeys) {
272
- config.outputKeys = this.output;
397
+ config.outputKeys = this.outputs;
398
+ }
399
+ if (!config?.streamMode) {
400
+ config.streamMode = "values";
273
401
  }
274
402
  let latest;
275
403
  for await (const chunk of await this.stream(input, config)) {
@@ -315,30 +443,31 @@ async function executeTasks(tasks, stepTimeout) {
315
443
  }
316
444
  }
317
445
  }
318
- function _readChannel(channels, chan) {
319
- try {
320
- return channels[chan].get();
321
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
446
+ export function _shouldInterrupt(checkpoint, interruptNodes, snapshotChannels, tasks) {
447
+ const seen = checkpoint.versions_seen[INTERRUPT];
448
+ const anySnapshotChannelUpdated = snapshotChannels.some((chan) => checkpoint.channel_versions[chan] > seen?.[chan]);
449
+ const anyTaskNodeInInterruptNodes = tasks.some((task) => interruptNodes === "*"
450
+ ? !task.config?.tags?.includes(TAG_HIDDEN)
451
+ : interruptNodes.includes(task.name));
452
+ return anySnapshotChannelUpdated && anyTaskNodeInInterruptNodes;
453
+ }
454
+ export function _localRead(checkpoint, channels, writes, select, fresh = false) {
455
+ if (fresh) {
456
+ const newCheckpoint = createCheckpoint(checkpoint, channels, -1);
457
+ // create a new copy of channels
458
+ const newChannels = emptyChannels(channels, newCheckpoint);
459
+ // Note: _applyWrites contains side effects
460
+ _applyWrites(copyCheckpoint(newCheckpoint), newChannels, writes);
461
+ return readChannels(newChannels, select);
322
462
  }
323
- catch (e) {
324
- if (e.name === EmptyChannelError.name) {
325
- return null;
326
- }
327
- throw e;
463
+ else {
464
+ return readChannels(channels, select);
328
465
  }
329
466
  }
330
- function _applyWrites(checkpoint, channels,
331
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
332
- pendingWrites, config, forStep) {
333
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
467
+ export function _applyWrites(checkpoint, channels, pendingWrites) {
334
468
  const pendingWritesByChannel = {};
335
469
  // Group writes by channel
336
470
  for (const [chan, val] of pendingWrites) {
337
- for (const c in ReservedChannelsMap) {
338
- if (chan === c) {
339
- throw new Error(`Can't write to reserved channel ${chan}`);
340
- }
341
- }
342
471
  if (chan in pendingWritesByChannel) {
343
472
  pendingWritesByChannel[chan].push(val);
344
473
  }
@@ -346,104 +475,142 @@ pendingWrites, config, forStep) {
346
475
  pendingWritesByChannel[chan] = [val];
347
476
  }
348
477
  }
349
- // Update reserved channels
350
- pendingWritesByChannel[ReservedChannelsMap.isLastStep] = [
351
- forStep + 1 === config.recursionLimit,
352
- ];
478
+ // find the highest version of all channels
479
+ let maxVersion = 0;
480
+ if (Object.keys(checkpoint.channel_versions).length > 0) {
481
+ maxVersion = Math.max(...Object.values(checkpoint.channel_versions));
482
+ }
353
483
  const updatedChannels = new Set();
354
484
  // Apply writes to channels
355
- for (const chan in pendingWritesByChannel) {
356
- if (chan in pendingWritesByChannel) {
357
- const vals = pendingWritesByChannel[chan];
358
- if (chan in channels) {
485
+ for (const [chan, vals] of Object.entries(pendingWritesByChannel)) {
486
+ if (chan in channels) {
487
+ // side effect: update channels
488
+ try {
359
489
  channels[chan].update(vals);
360
- if (checkpoint.channelVersions[chan] === undefined) {
361
- checkpoint.channelVersions[chan] = 1;
362
- }
363
- else {
364
- checkpoint.channelVersions[chan] += 1;
365
- }
366
- updatedChannels.add(chan);
490
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
491
  }
368
- else {
369
- console.warn(`Skipping write for channel ${chan} which has no readers`);
492
+ catch (e) {
493
+ if (e.name === InvalidUpdateError.name) {
494
+ throw new InvalidUpdateError(`Invalid update for channel ${chan}. Values: ${vals}`);
495
+ }
370
496
  }
497
+ // side effect: update checkpoint channel versions
498
+ checkpoint.channel_versions[chan] = maxVersion + 1;
499
+ updatedChannels.add(chan);
500
+ }
501
+ else {
502
+ console.warn(`Skipping write for channel ${chan} which has no readers`);
371
503
  }
372
504
  }
373
505
  // Channels that weren't updated in this step are notified of a new step
374
506
  for (const chan in channels) {
375
507
  if (!updatedChannels.has(chan)) {
508
+ // side effect: update channels
376
509
  channels[chan].update([]);
377
510
  }
378
511
  }
379
512
  }
380
- function _applyWritesFromView(checkpoint, channels, values) {
381
- for (const [chan, val] of Object.entries(values)) {
382
- if (val === _readChannel(channels, chan)) {
383
- continue;
384
- }
385
- if (channels[chan].lc_graph_name === "LastValue") {
386
- throw new Error(`Can't modify channel ${chan} with LastValue`);
387
- }
388
- checkpoint.channelVersions[chan] += 1;
389
- channels[chan].update([values[chan]]);
390
- }
391
- }
392
- function _prepareNextTasks(checkpoint, processes, channels) {
513
+ export function _prepareNextTasks(checkpoint, processes, channels, forExecution) {
514
+ const newCheckpoint = copyCheckpoint(checkpoint);
393
515
  const tasks = [];
516
+ const taskDescriptions = [];
394
517
  // Check if any processes should be run in next step
395
518
  // If so, prepare the values to be passed to them
396
519
  for (const [name, proc] of Object.entries(processes)) {
397
- let seen = checkpoint.versionsSeen[name];
520
+ let seen = newCheckpoint.versions_seen[name];
398
521
  if (!seen) {
399
- checkpoint.versionsSeen[name] = {};
400
- seen = checkpoint.versionsSeen[name];
522
+ newCheckpoint.versions_seen[name] = {};
523
+ seen = newCheckpoint.versions_seen[name];
401
524
  }
402
525
  // If any of the channels read by this process were updated
403
- if (proc.triggers.some((chan) => checkpoint.channelVersions[chan] > (seen[chan] ?? 0))) {
404
- // If all channels subscribed by this process have been initialized
526
+ if (proc.triggers
527
+ .filter((chan) => {
405
528
  try {
406
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
- let val = {};
408
- if (typeof proc.channels === "string") {
409
- val[proc.channels] = _readChannel(channels, proc.channels);
529
+ readChannel(channels, chan, false);
530
+ return true;
531
+ }
532
+ catch (e) {
533
+ return false;
534
+ }
535
+ })
536
+ .some((chan) => newCheckpoint.channel_versions[chan] > (seen[chan] ?? 0))) {
537
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
538
+ let val;
539
+ // If all trigger channels subscribed by this process are not empty
540
+ // then invoke the process with the values of all non-empty channels
541
+ if (Array.isArray(proc.channels)) {
542
+ let emptyChannels = 0;
543
+ for (const chan of proc.channels) {
544
+ try {
545
+ val = readChannel(channels, chan, false);
546
+ break;
547
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
548
+ }
549
+ catch (e) {
550
+ if (e.name === EmptyChannelError.name) {
551
+ emptyChannels += 1;
552
+ continue;
553
+ }
554
+ else {
555
+ throw e;
556
+ }
557
+ }
410
558
  }
411
- else {
559
+ if (emptyChannels === proc.channels.length) {
560
+ continue;
561
+ }
562
+ }
563
+ else if (typeof proc.channels === "object") {
564
+ val = {};
565
+ try {
412
566
  for (const [k, chan] of Object.entries(proc.channels)) {
413
- val[k] = _readChannel(channels, chan);
567
+ val[k] = readChannel(channels, chan, !proc.triggers.includes(chan));
414
568
  }
569
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
415
570
  }
416
- // Processes that subscribe to a single keyless channel get
417
- // the value directly, instead of a dict
418
- if (typeof proc.channels === "string") {
419
- val = val[proc.channels];
420
- }
421
- else if (Object.keys(proc.channels).length === 1 &&
422
- proc.channels[Object.keys(proc.channels)[0]] === undefined) {
423
- val = val[Object.keys(proc.channels)[0]];
571
+ catch (e) {
572
+ if (e.name === EmptyChannelError.name) {
573
+ continue;
574
+ }
575
+ else {
576
+ throw e;
577
+ }
424
578
  }
579
+ }
580
+ else {
581
+ throw new Error(`Invalid channels type, expected list or dict, got ${proc.channels}`);
582
+ }
583
+ // If the process has a mapper, apply it to the value
584
+ if (proc.mapper !== undefined) {
585
+ val = proc.mapper(val);
586
+ }
587
+ if (forExecution) {
425
588
  // Update seen versions
426
589
  proc.triggers.forEach((chan) => {
427
- const version = checkpoint.channelVersions[chan];
590
+ const version = newCheckpoint.channel_versions[chan];
428
591
  if (version !== undefined) {
592
+ // side effect: updates newCheckpoint
429
593
  seen[chan] = version;
430
594
  }
431
595
  });
432
- // skip if condition is not met
433
- if (proc.when === undefined || proc.when(val)) {
434
- tasks.push([proc, val, name]);
596
+ const node = proc.getNode();
597
+ if (node !== undefined) {
598
+ tasks.push({
599
+ name,
600
+ input: val,
601
+ proc: node,
602
+ writes: [],
603
+ config: proc.config,
604
+ });
435
605
  }
436
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
437
606
  }
438
- catch (error) {
439
- if (error.name === EmptyChannelError.name) {
440
- continue;
441
- }
442
- else {
443
- throw error;
444
- }
607
+ else {
608
+ taskDescriptions.push({
609
+ name,
610
+ input: val,
611
+ });
445
612
  }
446
613
  }
447
614
  }
448
- return tasks;
615
+ return [newCheckpoint, forExecution ? tasks : taskDescriptions];
449
616
  }