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