@langchain/langgraph 0.0.12 → 0.0.14

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 (138) hide show
  1. package/checkpoint/sqlite.cjs +1 -0
  2. package/checkpoint/sqlite.d.cts +1 -0
  3. package/checkpoint/sqlite.d.ts +1 -0
  4. package/checkpoint/sqlite.js +1 -0
  5. package/dist/channels/any_value.cjs +57 -0
  6. package/dist/channels/any_value.d.ts +16 -0
  7. package/dist/channels/any_value.js +53 -0
  8. package/dist/channels/base.cjs +19 -28
  9. package/dist/channels/base.d.ts +13 -19
  10. package/dist/channels/base.js +17 -24
  11. package/dist/channels/binop.cjs +4 -3
  12. package/dist/channels/binop.d.ts +1 -1
  13. package/dist/channels/binop.js +3 -2
  14. package/dist/channels/dynamic_barrier_value.cjs +88 -0
  15. package/dist/channels/dynamic_barrier_value.d.ts +26 -0
  16. package/dist/channels/dynamic_barrier_value.js +84 -0
  17. package/dist/channels/ephemeral_value.cjs +64 -0
  18. package/dist/channels/ephemeral_value.d.ts +14 -0
  19. package/dist/channels/ephemeral_value.js +60 -0
  20. package/dist/channels/index.cjs +1 -3
  21. package/dist/channels/index.d.ts +1 -1
  22. package/dist/channels/index.js +1 -1
  23. package/dist/channels/last_value.cjs +11 -5
  24. package/dist/channels/last_value.d.ts +5 -1
  25. package/dist/channels/last_value.js +9 -3
  26. package/dist/channels/named_barrier_value.cjs +71 -0
  27. package/dist/channels/named_barrier_value.d.ts +18 -0
  28. package/dist/channels/named_barrier_value.js +66 -0
  29. package/dist/channels/topic.cjs +5 -3
  30. package/dist/channels/topic.d.ts +3 -3
  31. package/dist/channels/topic.js +5 -3
  32. package/dist/checkpoint/base.cjs +30 -12
  33. package/dist/checkpoint/base.d.ts +39 -22
  34. package/dist/checkpoint/base.js +28 -11
  35. package/dist/checkpoint/id.cjs +40 -0
  36. package/dist/checkpoint/id.d.ts +2 -0
  37. package/dist/checkpoint/id.js +35 -0
  38. package/dist/checkpoint/index.cjs +2 -2
  39. package/dist/checkpoint/index.d.ts +2 -2
  40. package/dist/checkpoint/index.js +2 -2
  41. package/dist/checkpoint/memory.cjs +66 -49
  42. package/dist/checkpoint/memory.d.ts +7 -10
  43. package/dist/checkpoint/memory.js +65 -47
  44. package/dist/checkpoint/sqlite.cjs +173 -0
  45. package/dist/checkpoint/sqlite.d.ts +14 -0
  46. package/dist/checkpoint/sqlite.js +166 -0
  47. package/dist/constants.cjs +3 -1
  48. package/dist/constants.d.ts +2 -0
  49. package/dist/constants.js +2 -0
  50. package/dist/errors.cjs +31 -0
  51. package/dist/errors.d.ts +12 -0
  52. package/dist/errors.js +24 -0
  53. package/dist/graph/graph.cjs +235 -95
  54. package/dist/graph/graph.d.ts +52 -23
  55. package/dist/graph/graph.js +234 -96
  56. package/dist/graph/index.cjs +2 -2
  57. package/dist/graph/index.d.ts +2 -2
  58. package/dist/graph/index.js +2 -2
  59. package/dist/graph/message.cjs +4 -3
  60. package/dist/graph/message.d.ts +4 -1
  61. package/dist/graph/message.js +4 -3
  62. package/dist/graph/state.cjs +237 -102
  63. package/dist/graph/state.d.ts +41 -18
  64. package/dist/graph/state.js +238 -104
  65. package/dist/index.cjs +12 -7
  66. package/dist/index.d.ts +3 -2
  67. package/dist/index.js +3 -2
  68. package/dist/prebuilt/agent_executor.cjs +22 -36
  69. package/dist/prebuilt/agent_executor.d.ts +7 -10
  70. package/dist/prebuilt/agent_executor.js +23 -37
  71. package/dist/prebuilt/chat_agent_executor.cjs +13 -13
  72. package/dist/prebuilt/chat_agent_executor.d.ts +3 -1
  73. package/dist/prebuilt/chat_agent_executor.js +15 -15
  74. package/dist/prebuilt/index.cjs +4 -1
  75. package/dist/prebuilt/index.d.ts +1 -0
  76. package/dist/prebuilt/index.js +1 -0
  77. package/dist/prebuilt/tool_node.cjs +59 -0
  78. package/dist/prebuilt/tool_node.d.ts +17 -0
  79. package/dist/prebuilt/tool_node.js +54 -0
  80. package/dist/pregel/debug.cjs +6 -8
  81. package/dist/pregel/debug.d.ts +2 -2
  82. package/dist/pregel/debug.js +5 -7
  83. package/dist/pregel/index.cjs +504 -232
  84. package/dist/pregel/index.d.ts +80 -41
  85. package/dist/pregel/index.js +507 -238
  86. package/dist/pregel/io.cjs +117 -30
  87. package/dist/pregel/io.d.ts +11 -3
  88. package/dist/pregel/io.js +111 -28
  89. package/dist/pregel/read.cjs +126 -46
  90. package/dist/pregel/read.d.ts +27 -18
  91. package/dist/pregel/read.js +125 -45
  92. package/dist/pregel/types.cjs +2 -0
  93. package/dist/pregel/types.d.ts +37 -0
  94. package/dist/pregel/types.js +1 -0
  95. package/dist/pregel/validate.cjs +58 -51
  96. package/dist/pregel/validate.d.ts +14 -13
  97. package/dist/pregel/validate.js +56 -50
  98. package/dist/pregel/write.cjs +46 -30
  99. package/dist/pregel/write.d.ts +18 -8
  100. package/dist/pregel/write.js +45 -29
  101. package/dist/serde/base.cjs +2 -0
  102. package/dist/serde/base.d.ts +4 -0
  103. package/dist/serde/base.js +1 -0
  104. package/dist/setup/async_local_storage.cjs +2 -2
  105. package/dist/setup/async_local_storage.js +1 -1
  106. package/dist/tests/channels.test.d.ts +1 -0
  107. package/dist/tests/channels.test.js +151 -0
  108. package/dist/tests/chatbot.int.test.d.ts +1 -0
  109. package/dist/tests/chatbot.int.test.js +61 -0
  110. package/dist/tests/checkpoints.test.d.ts +1 -0
  111. package/dist/tests/checkpoints.test.js +190 -0
  112. package/dist/tests/graph.test.d.ts +1 -0
  113. package/dist/tests/graph.test.js +15 -0
  114. package/dist/tests/prebuilt.int.test.d.ts +1 -0
  115. package/dist/tests/prebuilt.int.test.js +101 -0
  116. package/dist/tests/prebuilt.test.d.ts +1 -0
  117. package/dist/tests/prebuilt.test.js +195 -0
  118. package/dist/tests/pregel.io.test.d.ts +1 -0
  119. package/dist/tests/pregel.io.test.js +332 -0
  120. package/dist/tests/pregel.read.test.d.ts +1 -0
  121. package/dist/tests/pregel.read.test.js +109 -0
  122. package/dist/tests/pregel.test.d.ts +1 -0
  123. package/dist/tests/pregel.test.js +1879 -0
  124. package/dist/tests/pregel.validate.test.d.ts +1 -0
  125. package/dist/tests/pregel.validate.test.js +198 -0
  126. package/dist/tests/pregel.write.test.d.ts +1 -0
  127. package/dist/tests/pregel.write.test.js +44 -0
  128. package/dist/tests/tracing.int.test.d.ts +1 -0
  129. package/dist/tests/tracing.int.test.js +449 -0
  130. package/dist/tests/utils.d.ts +22 -0
  131. package/dist/tests/utils.js +76 -0
  132. package/dist/utils.cjs +74 -0
  133. package/dist/utils.d.ts +18 -0
  134. package/dist/utils.js +70 -0
  135. package/package.json +33 -8
  136. package/dist/pregel/reserved.cjs +0 -6
  137. package/dist/pregel/reserved.d.ts +0 -3
  138. package/dist/pregel/reserved.js +0 -3
@@ -1,83 +1,71 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import { Runnable, _coerceToRunnable, ensureConfig, patchConfig, } from "@langchain/core/runnables";
2
+ import { Runnable, RunnableSequence, _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,365 @@ 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];
194
+ }
195
+ else {
196
+ return Object.keys(this.channels);
197
+ }
198
+ }
199
+ get streamChannelsAsIs() {
200
+ if (this.streamChannels) {
201
+ return this.streamChannels;
178
202
  }
179
203
  else {
180
- for (const chan in this.channels) {
181
- if (!this.hidden.includes(chan)) {
182
- outputKeys.push(chan);
204
+ return Object.keys(this.channels);
205
+ }
206
+ }
207
+ async getState(config) {
208
+ if (!this.checkpointer) {
209
+ throw new GraphValueError("No checkpointer set");
210
+ }
211
+ const saved = await this.checkpointer.getTuple(config);
212
+ const checkpoint = saved ? saved.checkpoint : emptyCheckpoint();
213
+ const channels = emptyChannels(this.channels, checkpoint);
214
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
215
+ const [_, nextTasks] = _prepareNextTasks(checkpoint, this.nodes, channels, false);
216
+ return {
217
+ values: readChannels(channels, this.streamChannelsAsIs),
218
+ next: nextTasks.map((task) => task.name),
219
+ metadata: saved?.metadata,
220
+ config: saved ? saved.config : config,
221
+ parentConfig: saved?.parentConfig,
222
+ };
223
+ }
224
+ async *getStateHistory(config, limit, before) {
225
+ if (!this.checkpointer) {
226
+ throw new GraphValueError("No checkpointer set");
227
+ }
228
+ for await (const saved of this.checkpointer.list(config, limit, before)) {
229
+ const channels = emptyChannels(this.channels, saved.checkpoint);
230
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
231
+ const [_, nextTasks] = _prepareNextTasks(saved.checkpoint, this.nodes, channels, false);
232
+ yield {
233
+ values: readChannels(channels, this.streamChannelsAsIs),
234
+ next: nextTasks.map((task) => task.name),
235
+ metadata: saved.metadata,
236
+ config: saved.config,
237
+ parentConfig: saved.parentConfig,
238
+ };
239
+ }
240
+ }
241
+ async updateState(config, values, asNode) {
242
+ if (!this.checkpointer) {
243
+ throw new GraphValueError("No checkpointer set");
244
+ }
245
+ // Get the latest checkpoint
246
+ const saved = await this.checkpointer.getTuple(config);
247
+ const checkpoint = saved
248
+ ? copyCheckpoint(saved.checkpoint)
249
+ : emptyCheckpoint();
250
+ // Find last that updated the state, if not provided
251
+ const maxSeens = Object.entries(checkpoint.versions_seen).reduce((acc, [node, versions]) => {
252
+ const maxSeen = Math.max(...Object.values(versions));
253
+ if (maxSeen) {
254
+ if (!acc[maxSeen]) {
255
+ acc[maxSeen] = [];
183
256
  }
257
+ acc[maxSeen].push(node);
258
+ }
259
+ return acc;
260
+ }, {});
261
+ if (!asNode && !Object.keys(maxSeens).length) {
262
+ if (!Array.isArray(this.inputs) && this.inputs in this.nodes) {
263
+ asNode = this.inputs;
264
+ }
265
+ }
266
+ else if (!asNode) {
267
+ const maxSeen = Math.max(...Object.keys(maxSeens).map(Number));
268
+ const nodes = maxSeens[maxSeen];
269
+ if (nodes.length === 1) {
270
+ asNode = nodes[0];
184
271
  }
185
272
  }
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);
273
+ if (!asNode) {
274
+ throw new InvalidUpdateError("Ambiguous update, specify as_node");
192
275
  }
193
- checkpoint = checkpoint ?? emptyCheckpoint();
194
- // create channels from checkpoint
276
+ // update channels
195
277
  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
- }
278
+ // create task to run all writers of the chosen node
279
+ const writers = this.nodes[asNode].getWriters();
280
+ if (!writers.length) {
281
+ throw new InvalidUpdateError(`No writers found for node ${asNode}`);
282
+ }
283
+ const task = {
284
+ name: asNode,
285
+ input: values,
286
+ proc:
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ writers.length > 1 ? RunnableSequence.from(writers) : writers[0],
289
+ writes: [],
290
+ config: undefined,
291
+ };
292
+ // execute task
293
+ await task.proc.invoke(task.input, patchConfig(config, {
294
+ runName: `${this.name}UpdateState`,
295
+ configurable: {
296
+ [CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
297
+ [CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, task.writes),
298
+ },
299
+ }));
300
+ // apply to checkpoint and save
301
+ _applyWrites(checkpoint, channels, task.writes);
302
+ const step = (saved?.metadata?.step ?? -2) + 1;
303
+ return await this.checkpointer.put(saved?.config ?? config, createCheckpoint(checkpoint, channels, step), {
304
+ source: "update",
305
+ step,
306
+ writes: { [asNode]: values },
307
+ });
308
+ }
309
+ _defaults(config) {
310
+ const { debug, streamMode, inputKeys, outputKeys, interruptAfter, interruptBefore, ...rest } = config;
311
+ const defaultDebug = debug !== undefined ? debug : this.debug;
312
+ let defaultOutputKeys = outputKeys;
313
+ if (defaultOutputKeys === undefined) {
314
+ defaultOutputKeys = this.streamChannelsAsIs;
315
+ }
316
+ else {
317
+ validateKeys(defaultOutputKeys, this.channels);
318
+ }
319
+ let defaultInputKeys = inputKeys;
320
+ if (defaultInputKeys === undefined) {
321
+ defaultInputKeys = this.inputs;
322
+ }
323
+ else {
324
+ validateKeys(defaultInputKeys, this.channels);
325
+ }
326
+ const defaultInterruptBefore = interruptBefore ?? this.interruptBefore ?? [];
327
+ const defaultInterruptAfter = interruptAfter ?? this.interruptAfter ?? [];
328
+ let defaultStreamMode;
329
+ if (streamMode !== undefined) {
330
+ defaultStreamMode = streamMode;
331
+ }
332
+ else {
333
+ defaultStreamMode = this.streamMode;
334
+ }
335
+ if (config.configurable !== undefined &&
336
+ config.configurable[CONFIG_KEY_READ] !== undefined) {
337
+ defaultStreamMode = "values";
204
338
  }
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;
339
+ return [
340
+ defaultDebug,
341
+ defaultStreamMode,
342
+ defaultInputKeys,
343
+ defaultOutputKeys,
344
+ rest,
345
+ defaultInterruptBefore,
346
+ defaultInterruptAfter,
347
+ ];
348
+ }
349
+ async *_transform(input, runManager, config = {}) {
350
+ const bg = [];
351
+ try {
352
+ if (config.recursionLimit && config.recursionLimit < 1) {
353
+ throw new GraphValueError(`Recursion limit must be greater than 0, got ${config.recursionLimit}`);
218
354
  }
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.`);
355
+ if (this.checkpointer && !config.configurable) {
356
+ throw new GraphValueError(`Checkpointer requires one or more of the following 'configurable' keys: thread_id, checkpoint_id`);
221
357
  }
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);
358
+ // assign defaults
359
+ const [debug, streamMode, inputKeys, outputKeys, restConfig, interruptBefore, interruptAfter,] = this._defaults(config);
360
+ // copy nodes to ignore mutations during execution
361
+ const processes = { ...this.nodes };
362
+ // get checkpoint, or create an empty one
363
+ const saved = this.checkpointer
364
+ ? await this.checkpointer.getTuple(config)
365
+ : null;
366
+ let checkpoint = saved ? saved.checkpoint : emptyCheckpoint();
367
+ let checkpointConfig = saved ? saved.config : config;
368
+ let start = (saved?.metadata?.step ?? -2) + 1;
369
+ // create channels from checkpoint
370
+ const channels = emptyChannels(this.channels, checkpoint);
371
+ // map inputs to channel updates
372
+ const inputPendingWrites = [];
373
+ for await (const c of input) {
374
+ for (const value of mapInput(inputKeys, c)) {
375
+ inputPendingWrites.push(value);
250
376
  }
251
377
  }
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);
378
+ if (inputPendingWrites.length) {
379
+ // discard any unfinished tasks from previous checkpoint
380
+ const discarded = _prepareNextTasks(checkpoint, processes, channels, true);
381
+ checkpoint = discarded[0]; // eslint-disable-line prefer-destructuring
382
+ // apply input writes
383
+ _applyWrites(checkpoint, channels, inputPendingWrites);
384
+ // save input checkpoint
385
+ if (this.checkpointer) {
386
+ checkpoint = createCheckpoint(checkpoint, channels, start);
387
+ bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
388
+ source: "input",
389
+ step: start,
390
+ writes: Object.fromEntries(inputPendingWrites),
391
+ }));
392
+ checkpointConfig = {
393
+ configurable: {
394
+ ...checkpointConfig.configurable,
395
+ checkpoint_id: checkpoint.id,
396
+ },
397
+ };
398
+ }
399
+ // increment start to 0
400
+ start += 1;
257
401
  }
258
- // interrupt if any channel written to is in interrupt list
259
- if (pendingWrites.some(([chan]) => this.interrupt?.some((i) => i === chan))) {
260
- break;
402
+ else {
403
+ checkpoint = copyCheckpoint(checkpoint);
404
+ for (const k of this.streamChannelsList) {
405
+ const version = checkpoint.channel_versions[k];
406
+ checkpoint.versions_seen[INTERRUPT][k] = version;
407
+ }
408
+ }
409
+ // Similarly to Bulk Synchronous Parallel / Pregel model
410
+ // computation proceeds in steps, while there are channel updates
411
+ // channel updates from step N are only visible in step N+1
412
+ // channels are guaranteed to be immutable for the duration of the step,
413
+ // with channel updates applied only at the transition between steps
414
+ const stop = start + (config.recursionLimit ?? DEFAULT_LOOP_LIMIT);
415
+ for (let step = start; step < stop + 1; step += 1) {
416
+ const [nextCheckpoint, nextTasks] = _prepareNextTasks(checkpoint, processes, channels, true);
417
+ // if no more tasks, we're done
418
+ if (nextTasks.length === 0 && step === start) {
419
+ throw new GraphValueError(`No tasks to run in graph.`);
420
+ }
421
+ else if (nextTasks.length === 0) {
422
+ break;
423
+ }
424
+ else if (step === stop) {
425
+ 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.`);
426
+ }
427
+ // before execution, check if we should interrupt
428
+ if (_shouldInterrupt(checkpoint, interruptBefore, this.streamChannelsList, nextTasks)) {
429
+ break;
430
+ }
431
+ else {
432
+ checkpoint = nextCheckpoint;
433
+ }
434
+ if (debug) {
435
+ console.log(nextTasks);
436
+ }
437
+ const tasksWithConfig = nextTasks.map(
438
+ // eslint-disable-next-line no-loop-func
439
+ (task) => [
440
+ task.proc,
441
+ task.input,
442
+ patchConfig(restConfig, {
443
+ callbacks: runManager?.getChild(`graph:step:${step}`),
444
+ runName: task.name,
445
+ configurable: {
446
+ ...config.configurable,
447
+ [CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
448
+ [CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels, task.writes),
449
+ },
450
+ }),
451
+ ]);
452
+ // execute tasks, and wait for one to fail or all to finish.
453
+ // each task is independent from all other concurrent tasks
454
+ const tasks = tasksWithConfig.map(([proc, input, updatedConfig]) => () => proc.invoke(input, updatedConfig));
455
+ await executeTasks(tasks, this.stepTimeout);
456
+ // combine pending writes from all tasks
457
+ const pendingWrites = [];
458
+ for (const task of nextTasks) {
459
+ pendingWrites.push(...task.writes);
460
+ }
461
+ // apply writes to channels
462
+ _applyWrites(checkpoint, channels, pendingWrites);
463
+ // yield current value and checkpoint view
464
+ if (streamMode === "values") {
465
+ yield* mapOutputValues(outputKeys, pendingWrites, channels);
466
+ }
467
+ else if (streamMode === "updates") {
468
+ yield* mapOutputUpdates(outputKeys, nextTasks);
469
+ }
470
+ // save end of step checkpoint
471
+ if (this.checkpointer) {
472
+ checkpoint = createCheckpoint(checkpoint, channels, step);
473
+ bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
474
+ source: "loop",
475
+ step,
476
+ writes: single(streamMode === "values"
477
+ ? mapOutputValues(outputKeys, pendingWrites, channels)
478
+ : mapOutputUpdates(outputKeys, nextTasks)),
479
+ }));
480
+ checkpointConfig = {
481
+ configurable: {
482
+ ...checkpointConfig.configurable,
483
+ checkpoint_id: checkpoint.id,
484
+ },
485
+ };
486
+ }
487
+ if (_shouldInterrupt(checkpoint, interruptAfter, this.streamChannelsList, nextTasks)) {
488
+ break;
489
+ }
261
490
  }
262
491
  }
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);
492
+ finally {
493
+ await Promise.all(bg);
267
494
  }
268
495
  }
269
496
  async invoke(input, options) {
270
497
  const config = ensureConfig(options);
271
498
  if (!config?.outputKeys) {
272
- config.outputKeys = this.output;
499
+ config.outputKeys = this.outputs;
500
+ }
501
+ if (!config?.streamMode) {
502
+ config.streamMode = "values";
273
503
  }
274
504
  let latest;
275
505
  for await (const chunk of await this.stream(input, config)) {
@@ -315,30 +545,31 @@ async function executeTasks(tasks, stepTimeout) {
315
545
  }
316
546
  }
317
547
  }
318
- function _readChannel(channels, chan) {
319
- try {
320
- return channels[chan].get();
321
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
548
+ export function _shouldInterrupt(checkpoint, interruptNodes, snapshotChannels, tasks) {
549
+ const seen = checkpoint.versions_seen[INTERRUPT];
550
+ const anySnapshotChannelUpdated = snapshotChannels.some((chan) => checkpoint.channel_versions[chan] > seen?.[chan]);
551
+ const anyTaskNodeInInterruptNodes = tasks.some((task) => interruptNodes === "*"
552
+ ? !task.config?.tags?.includes(TAG_HIDDEN)
553
+ : interruptNodes.includes(task.name));
554
+ return anySnapshotChannelUpdated && anyTaskNodeInInterruptNodes;
555
+ }
556
+ export function _localRead(checkpoint, channels, writes, select, fresh = false) {
557
+ if (fresh) {
558
+ const newCheckpoint = createCheckpoint(checkpoint, channels, -1);
559
+ // create a new copy of channels
560
+ const newChannels = emptyChannels(channels, newCheckpoint);
561
+ // Note: _applyWrites contains side effects
562
+ _applyWrites(copyCheckpoint(newCheckpoint), newChannels, writes);
563
+ return readChannels(newChannels, select);
322
564
  }
323
- catch (e) {
324
- if (e.name === EmptyChannelError.name) {
325
- return null;
326
- }
327
- throw e;
565
+ else {
566
+ return readChannels(channels, select);
328
567
  }
329
568
  }
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
569
+ export function _applyWrites(checkpoint, channels, pendingWrites) {
334
570
  const pendingWritesByChannel = {};
335
571
  // Group writes by channel
336
572
  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
573
  if (chan in pendingWritesByChannel) {
343
574
  pendingWritesByChannel[chan].push(val);
344
575
  }
@@ -346,104 +577,142 @@ pendingWrites, config, forStep) {
346
577
  pendingWritesByChannel[chan] = [val];
347
578
  }
348
579
  }
349
- // Update reserved channels
350
- pendingWritesByChannel[ReservedChannelsMap.isLastStep] = [
351
- forStep + 1 === config.recursionLimit,
352
- ];
580
+ // find the highest version of all channels
581
+ let maxVersion = 0;
582
+ if (Object.keys(checkpoint.channel_versions).length > 0) {
583
+ maxVersion = Math.max(...Object.values(checkpoint.channel_versions));
584
+ }
353
585
  const updatedChannels = new Set();
354
586
  // 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) {
587
+ for (const [chan, vals] of Object.entries(pendingWritesByChannel)) {
588
+ if (chan in channels) {
589
+ // side effect: update channels
590
+ try {
359
591
  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);
592
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
367
593
  }
368
- else {
369
- console.warn(`Skipping write for channel ${chan} which has no readers`);
594
+ catch (e) {
595
+ if (e.name === InvalidUpdateError.name) {
596
+ throw new InvalidUpdateError(`Invalid update for channel ${chan}. Values: ${vals}`);
597
+ }
370
598
  }
599
+ // side effect: update checkpoint channel versions
600
+ checkpoint.channel_versions[chan] = maxVersion + 1;
601
+ updatedChannels.add(chan);
602
+ }
603
+ else {
604
+ console.warn(`Skipping write for channel ${chan} which has no readers`);
371
605
  }
372
606
  }
373
607
  // Channels that weren't updated in this step are notified of a new step
374
608
  for (const chan in channels) {
375
609
  if (!updatedChannels.has(chan)) {
610
+ // side effect: update channels
376
611
  channels[chan].update([]);
377
612
  }
378
613
  }
379
614
  }
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) {
615
+ export function _prepareNextTasks(checkpoint, processes, channels, forExecution) {
616
+ const newCheckpoint = copyCheckpoint(checkpoint);
393
617
  const tasks = [];
618
+ const taskDescriptions = [];
394
619
  // Check if any processes should be run in next step
395
620
  // If so, prepare the values to be passed to them
396
621
  for (const [name, proc] of Object.entries(processes)) {
397
- let seen = checkpoint.versionsSeen[name];
622
+ let seen = newCheckpoint.versions_seen[name];
398
623
  if (!seen) {
399
- checkpoint.versionsSeen[name] = {};
400
- seen = checkpoint.versionsSeen[name];
624
+ newCheckpoint.versions_seen[name] = {};
625
+ seen = newCheckpoint.versions_seen[name];
401
626
  }
402
627
  // 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
628
+ if (proc.triggers
629
+ .filter((chan) => {
405
630
  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);
631
+ readChannel(channels, chan, false);
632
+ return true;
633
+ }
634
+ catch (e) {
635
+ return false;
636
+ }
637
+ })
638
+ .some((chan) => newCheckpoint.channel_versions[chan] > (seen[chan] ?? 0))) {
639
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
640
+ let val;
641
+ // If all trigger channels subscribed by this process are not empty
642
+ // then invoke the process with the values of all non-empty channels
643
+ if (Array.isArray(proc.channels)) {
644
+ let emptyChannels = 0;
645
+ for (const chan of proc.channels) {
646
+ try {
647
+ val = readChannel(channels, chan, false);
648
+ break;
649
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
650
+ }
651
+ catch (e) {
652
+ if (e.name === EmptyChannelError.name) {
653
+ emptyChannels += 1;
654
+ continue;
655
+ }
656
+ else {
657
+ throw e;
658
+ }
659
+ }
410
660
  }
411
- else {
661
+ if (emptyChannels === proc.channels.length) {
662
+ continue;
663
+ }
664
+ }
665
+ else if (typeof proc.channels === "object") {
666
+ val = {};
667
+ try {
412
668
  for (const [k, chan] of Object.entries(proc.channels)) {
413
- val[k] = _readChannel(channels, chan);
669
+ val[k] = readChannel(channels, chan, !proc.triggers.includes(chan));
414
670
  }
671
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
415
672
  }
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]];
673
+ catch (e) {
674
+ if (e.name === EmptyChannelError.name) {
675
+ continue;
676
+ }
677
+ else {
678
+ throw e;
679
+ }
424
680
  }
681
+ }
682
+ else {
683
+ throw new Error(`Invalid channels type, expected list or dict, got ${proc.channels}`);
684
+ }
685
+ // If the process has a mapper, apply it to the value
686
+ if (proc.mapper !== undefined) {
687
+ val = proc.mapper(val);
688
+ }
689
+ if (forExecution) {
425
690
  // Update seen versions
426
691
  proc.triggers.forEach((chan) => {
427
- const version = checkpoint.channelVersions[chan];
692
+ const version = newCheckpoint.channel_versions[chan];
428
693
  if (version !== undefined) {
694
+ // side effect: updates newCheckpoint
429
695
  seen[chan] = version;
430
696
  }
431
697
  });
432
- // skip if condition is not met
433
- if (proc.when === undefined || proc.when(val)) {
434
- tasks.push([proc, val, name]);
698
+ const node = proc.getNode();
699
+ if (node !== undefined) {
700
+ tasks.push({
701
+ name,
702
+ input: val,
703
+ proc: node,
704
+ writes: [],
705
+ config: proc.config,
706
+ });
435
707
  }
436
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
437
708
  }
438
- catch (error) {
439
- if (error.name === EmptyChannelError.name) {
440
- continue;
441
- }
442
- else {
443
- throw error;
444
- }
709
+ else {
710
+ taskDescriptions.push({
711
+ name,
712
+ input: val,
713
+ });
445
714
  }
446
715
  }
447
716
  }
448
- return tasks;
717
+ return [newCheckpoint, forExecution ? tasks : taskDescriptions];
449
718
  }