@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,15 +1,14 @@
1
- import { RunnableBinding, RunnableLambda, RunnablePassthrough, _coerceToRunnable, } from "@langchain/core/runnables";
1
+ import { RunnableBinding, RunnablePassthrough, RunnableSequence, _coerceToRunnable, } from "@langchain/core/runnables";
2
2
  import { CONFIG_KEY_READ } from "../constants.js";
3
- export class ChannelRead extends RunnableLambda {
4
- constructor(channel) {
3
+ import { ChannelWrite } from "./write.js";
4
+ import { RunnableCallable } from "../utils.js";
5
+ export class ChannelRead extends RunnableCallable {
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ constructor(channel,
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ mapper, fresh = false) {
5
10
  super({
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- func: (input, options) => {
8
- if ("config" in options) {
9
- return this._read(input, options.config);
10
- }
11
- return this._read(input, options ?? {});
12
- },
11
+ func: (_, config) => ChannelRead.doRead(config, this.channel, this.fresh, this.mapper),
13
12
  });
14
13
  Object.defineProperty(this, "lc_graph_name", {
15
14
  enumerable: true,
@@ -23,41 +22,44 @@ export class ChannelRead extends RunnableLambda {
23
22
  writable: true,
24
23
  value: void 0
25
24
  });
25
+ Object.defineProperty(this, "fresh", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: false
30
+ });
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ Object.defineProperty(this, "mapper", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: void 0
37
+ });
38
+ this.fresh = fresh;
39
+ this.mapper = mapper;
26
40
  this.channel = channel;
27
- this.name = `ChannelRead<${channel}>`;
41
+ this.name = Array.isArray(channel)
42
+ ? `ChannelRead<${channel.join(",")}>`
43
+ : `ChannelRead<${channel}>`;
28
44
  }
29
- get configSpecs() {
30
- return [
31
- {
32
- id: CONFIG_KEY_READ,
33
- name: CONFIG_KEY_READ,
34
- description: null,
35
- default: null,
36
- // TODO FIX THIS
37
- annotation: "Callable[[BaseChannel], Any]",
38
- isShared: true,
39
- dependencies: null,
40
- },
41
- ];
42
- }
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- _read(_, config) {
45
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ static doRead(config, channel, fresh, mapper) {
46
46
  const read = config.configurable?.[CONFIG_KEY_READ];
47
47
  if (!read) {
48
48
  throw new Error(`Runnable ${this} is not configured with a read function. Make sure to call in the context of a Pregel process`);
49
49
  }
50
- if (Array.isArray(this.channel)) {
51
- const results = Object.fromEntries(this.channel.map((chan) => [chan, read(chan)]));
52
- return results;
50
+ if (mapper) {
51
+ return mapper(read(channel, fresh));
52
+ }
53
+ else {
54
+ return read(channel, fresh);
53
55
  }
54
- return read(this.channel);
55
56
  }
56
57
  }
57
- const defaultRunnableBound = /* #__PURE__ */ new RunnablePassthrough();
58
- export class ChannelInvoke extends RunnableBinding {
58
+ const defaultRunnableBound =
59
+ /* #__PURE__ */ new RunnablePassthrough();
60
+ export class PregelNode extends RunnableBinding {
59
61
  constructor(fields) {
60
- const { channels, triggers, when } = fields;
62
+ const { channels, triggers, mapper, writers, bound, kwargs } = fields;
61
63
  const mergedTags = [
62
64
  ...(fields.config?.tags ? fields.config.tags : []),
63
65
  ...(fields.tags ? fields.tags : []),
@@ -75,7 +77,7 @@ export class ChannelInvoke extends RunnableBinding {
75
77
  enumerable: true,
76
78
  configurable: true,
77
79
  writable: true,
78
- value: "ChannelInvoke"
80
+ value: "PregelNode"
79
81
  });
80
82
  Object.defineProperty(this, "channels", {
81
83
  enumerable: true,
@@ -90,48 +92,126 @@ export class ChannelInvoke extends RunnableBinding {
90
92
  value: []
91
93
  });
92
94
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
- Object.defineProperty(this, "when", {
95
+ Object.defineProperty(this, "mapper", {
94
96
  enumerable: true,
95
97
  configurable: true,
96
98
  writable: true,
97
99
  value: void 0
98
100
  });
101
+ Object.defineProperty(this, "writers", {
102
+ enumerable: true,
103
+ configurable: true,
104
+ writable: true,
105
+ value: []
106
+ });
107
+ Object.defineProperty(this, "bound", {
108
+ enumerable: true,
109
+ configurable: true,
110
+ writable: true,
111
+ value: defaultRunnableBound
112
+ });
113
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
114
+ Object.defineProperty(this, "kwargs", {
115
+ enumerable: true,
116
+ configurable: true,
117
+ writable: true,
118
+ value: {}
119
+ });
99
120
  this.channels = channels;
100
121
  this.triggers = triggers;
101
- this.when = when;
122
+ this.mapper = mapper;
123
+ this.writers = writers ?? this.writers;
124
+ this.bound = bound ?? this.bound;
125
+ this.kwargs = kwargs ?? this.kwargs;
126
+ }
127
+ getWriters() {
128
+ const newWriters = [...this.writers];
129
+ while (newWriters.length > 1 &&
130
+ // eslint-disable-next-line no-instanceof/no-instanceof
131
+ newWriters[newWriters.length - 1] instanceof ChannelWrite &&
132
+ // eslint-disable-next-line no-instanceof/no-instanceof
133
+ newWriters[newWriters.length - 2] instanceof ChannelWrite) {
134
+ // we can combine writes if they are consecutive
135
+ newWriters[newWriters.length - 2].writes.push(...newWriters[newWriters.length - 1].writes);
136
+ newWriters.pop();
137
+ }
138
+ return newWriters;
139
+ }
140
+ getNode() {
141
+ const writers = this.getWriters();
142
+ if (this.bound === defaultRunnableBound && writers.length === 0) {
143
+ return undefined;
144
+ }
145
+ else if (this.bound === defaultRunnableBound && writers.length === 1) {
146
+ return writers[0];
147
+ }
148
+ else if (this.bound === defaultRunnableBound) {
149
+ return new RunnableSequence({
150
+ first: writers[0],
151
+ middle: writers.slice(1, writers.length - 1),
152
+ last: writers[writers.length - 1],
153
+ });
154
+ }
155
+ else if (writers.length > 0) {
156
+ return new RunnableSequence({
157
+ first: this.bound,
158
+ middle: writers.slice(0, writers.length - 1),
159
+ last: writers[writers.length - 1],
160
+ });
161
+ }
162
+ else {
163
+ return this.bound;
164
+ }
102
165
  }
103
166
  join(channels) {
167
+ if (!Array.isArray(channels)) {
168
+ throw new Error("channels must be a list");
169
+ }
104
170
  if (typeof this.channels !== "object") {
105
171
  throw new Error("all channels must be named when using .join()");
106
172
  }
107
- return new ChannelInvoke({
173
+ return new PregelNode({
108
174
  channels: {
109
175
  ...this.channels,
110
176
  ...Object.fromEntries(channels.map((chan) => [chan, chan])),
111
177
  },
112
178
  triggers: this.triggers,
113
- when: this.when,
179
+ mapper: this.mapper,
180
+ writers: this.writers,
114
181
  bound: this.bound,
115
182
  kwargs: this.kwargs,
116
183
  config: this.config,
117
184
  });
118
185
  }
119
186
  pipe(coerceable) {
120
- if (this.bound === defaultRunnableBound) {
121
- return new ChannelInvoke({
187
+ if (ChannelWrite.isWriter(coerceable)) {
188
+ return new PregelNode({
189
+ channels: this.channels,
190
+ triggers: this.triggers,
191
+ mapper: this.mapper,
192
+ writers: [...this.writers, coerceable],
193
+ bound: this.bound,
194
+ config: this.config,
195
+ kwargs: this.kwargs,
196
+ });
197
+ }
198
+ else if (this.bound === defaultRunnableBound) {
199
+ return new PregelNode({
122
200
  channels: this.channels,
123
201
  triggers: this.triggers,
124
- when: this.when,
202
+ mapper: this.mapper,
203
+ writers: this.writers,
125
204
  bound: _coerceToRunnable(coerceable),
126
205
  config: this.config,
127
206
  kwargs: this.kwargs,
128
207
  });
129
208
  }
130
209
  else {
131
- return new ChannelInvoke({
210
+ return new PregelNode({
132
211
  channels: this.channels,
133
212
  triggers: this.triggers,
134
- when: this.when,
213
+ mapper: this.mapper,
214
+ writers: this.writers,
135
215
  bound: this.bound.pipe(coerceable),
136
216
  config: this.config,
137
217
  kwargs: this.kwargs,
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,32 @@
1
+ import { Runnable, RunnableConfig } from "@langchain/core/runnables";
2
+ export interface PregelTaskDescription {
3
+ readonly name: string;
4
+ readonly input: unknown;
5
+ }
6
+ export interface PregelExecutableTask<N extends PropertyKey, C extends PropertyKey> {
7
+ readonly name: N;
8
+ readonly input: unknown;
9
+ readonly proc: Runnable;
10
+ readonly writes: Array<[C, unknown]>;
11
+ readonly config: RunnableConfig | undefined;
12
+ }
13
+ export interface StateSnapshot {
14
+ /**
15
+ * Current values of channels
16
+ */
17
+ readonly values: Record<string, any> | any;
18
+ /**
19
+ * Nodes to execute in the next step, if any
20
+ */
21
+ readonly next: Array<string>;
22
+ /**
23
+ * Config used to fetch this snapshot
24
+ */
25
+ readonly config: RunnableConfig;
26
+ /**
27
+ * Config used to fetch the parent snapshot, if any
28
+ * @default undefined
29
+ */
30
+ readonly parentConfig?: RunnableConfig | undefined;
31
+ }
32
+ export type All = "*";
@@ -0,0 +1 @@
1
+ export {};
@@ -1,86 +1,93 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateKeys = exports.validateGraph = void 0;
4
- const last_value_js_1 = require("../channels/last_value.cjs");
5
- const reserved_js_1 = require("./reserved.cjs");
6
- function validateGraph({ nodes, channels, input, output, hidden, interrupt, }) {
7
- const newChannels = channels;
3
+ exports.validateKeys = exports.validateGraph = exports.GraphValidationError = void 0;
4
+ const constants_js_1 = require("../constants.cjs");
5
+ const read_js_1 = require("./read.cjs");
6
+ class GraphValidationError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "GraphValidationError";
10
+ }
11
+ }
12
+ exports.GraphValidationError = GraphValidationError;
13
+ function validateGraph({ nodes, channels, inputChannels, outputChannels, streamChannels, interruptAfterNodes, interruptBeforeNodes, }) {
14
+ if (!channels) {
15
+ throw new GraphValidationError("Channels not provided");
16
+ }
8
17
  const subscribedChannels = new Set();
9
- for (const node of Object.values(nodes)) {
10
- if (node.lc_graph_name === "ChannelInvoke" && "channels" in node) {
11
- if (typeof node.channels === "string") {
12
- subscribedChannels.add(node.channels);
13
- }
14
- else {
15
- Object.values(node.channels).map((channel) => subscribedChannels.add(channel));
16
- }
18
+ const allOutputChannels = new Set();
19
+ for (const [name, node] of Object.entries(nodes)) {
20
+ if (name === constants_js_1.INTERRUPT) {
21
+ throw new GraphValidationError(`"Node name ${constants_js_1.INTERRUPT} is reserved"`);
22
+ }
23
+ if (node.constructor === read_js_1.PregelNode) {
24
+ node.triggers.forEach((trigger) => subscribedChannels.add(trigger));
17
25
  }
18
26
  else {
19
- console.error(node);
20
- throw new Error(`Invalid node type: ${JSON.stringify(node, null, 2)}, expected Channel.subscribeTo()`);
27
+ throw new GraphValidationError(`Invalid node type ${typeof node}, expected PregelNode`);
21
28
  }
22
29
  }
23
- for (const chan of [...subscribedChannels]) {
24
- if (!(chan in newChannels)) {
25
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
- newChannels[chan] = new last_value_js_1.LastValue();
30
+ // side effect: update channels
31
+ for (const chan of subscribedChannels) {
32
+ if (!(chan in channels)) {
33
+ throw new GraphValidationError(`Subcribed channel '${String(chan)}' not in channels`);
27
34
  }
28
35
  }
29
- if (typeof input === "string") {
30
- if (!(input in newChannels)) {
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
- newChannels[input] = new last_value_js_1.LastValue();
33
- }
34
- if (!subscribedChannels.has(input)) {
35
- throw new Error(`Input channel ${input} is not subscribed to by any node.`);
36
+ if (!Array.isArray(inputChannels)) {
37
+ if (!subscribedChannels.has(inputChannels)) {
38
+ throw new GraphValidationError(`Input channel ${String(inputChannels)} is not subscribed to by any node`);
36
39
  }
37
40
  }
38
41
  else {
39
- for (const chan of input) {
40
- if (!(chan in newChannels)) {
41
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42
- newChannels[chan] = new last_value_js_1.LastValue();
43
- }
44
- }
45
- if (input.every((chan) => !subscribedChannels.has(chan))) {
46
- throw new Error(`None of the input channels ${input} are subscribed to by any node`);
42
+ if (inputChannels.every((channel) => !subscribedChannels.has(channel))) {
43
+ throw new GraphValidationError(`None of the input channels ${inputChannels} are subscribed to by any node`);
47
44
  }
48
45
  }
49
- if (typeof output === "string") {
50
- if (!(output in newChannels)) {
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- newChannels[output] = new last_value_js_1.LastValue();
53
- }
46
+ if (!Array.isArray(outputChannels)) {
47
+ allOutputChannels.add(outputChannels);
54
48
  }
55
49
  else {
56
- for (const chan of output) {
57
- if (!(chan in newChannels)) {
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
- newChannels[chan] = new last_value_js_1.LastValue();
50
+ outputChannels.forEach((chan) => allOutputChannels.add(chan));
51
+ }
52
+ if (streamChannels && !Array.isArray(streamChannels)) {
53
+ allOutputChannels.add(streamChannels);
54
+ }
55
+ else if (Array.isArray(streamChannels)) {
56
+ streamChannels.forEach((chan) => allOutputChannels.add(chan));
57
+ }
58
+ for (const chan of allOutputChannels) {
59
+ if (!(chan in channels)) {
60
+ throw new GraphValidationError(`Output channel '${String(chan)}' not in channels`);
61
+ }
62
+ }
63
+ // validate interrupt before/after
64
+ if (interruptAfterNodes && interruptAfterNodes !== "*") {
65
+ for (const node of interruptAfterNodes) {
66
+ if (!(node in nodes)) {
67
+ throw new GraphValidationError(`Node ${String(node)} not in nodes`);
60
68
  }
61
69
  }
62
70
  }
63
- for (const chan in reserved_js_1.ReservedChannelsMap) {
64
- if (!(chan in newChannels)) {
65
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
- newChannels[chan] = new last_value_js_1.LastValue();
71
+ if (interruptBeforeNodes && interruptBeforeNodes !== "*") {
72
+ for (const node of interruptBeforeNodes) {
73
+ if (!(node in nodes)) {
74
+ throw new GraphValidationError(`Node ${String(node)} not in nodes`);
75
+ }
67
76
  }
68
77
  }
69
- validateKeys(hidden, newChannels);
70
- validateKeys(interrupt, newChannels);
71
78
  }
72
79
  exports.validateGraph = validateGraph;
73
80
  function validateKeys(keys, channels) {
74
81
  if (Array.isArray(keys)) {
75
82
  for (const key of keys) {
76
83
  if (!(key in channels)) {
77
- throw new Error(`Key ${key} not found in channels`);
84
+ throw new Error(`Key ${String(key)} not found in channels`);
78
85
  }
79
86
  }
80
87
  }
81
88
  else {
82
89
  if (!(keys in channels)) {
83
- throw new Error(`Key ${keys} not found in channels`);
90
+ throw new Error(`Key ${String(keys)} not found in channels`);
84
91
  }
85
92
  }
86
93
  }
@@ -1,15 +1,16 @@
1
1
  import { BaseChannel } from "../channels/index.js";
2
- import { ChannelInvoke } from "./read.js";
3
- export declare function validateGraph({ nodes, channels, input, output, hidden, interrupt, }: {
4
- nodes: Record<string, ChannelInvoke>;
5
- channels: {
6
- [key: string]: BaseChannel;
7
- };
8
- input: string | Array<string>;
9
- output: string | Array<string>;
10
- hidden: Array<string>;
11
- interrupt: Array<string>;
12
- }): void;
13
- export declare function validateKeys(keys: string | Array<string>, channels: {
14
- [key: string]: BaseChannel;
2
+ import { PregelNode } from "./read.js";
3
+ import { All } from "./types.js";
4
+ export declare class GraphValidationError extends Error {
5
+ constructor(message?: string);
6
+ }
7
+ export declare function validateGraph<Nn extends Record<string, PregelNode>, Cc extends Record<string, BaseChannel>>({ nodes, channels, inputChannels, outputChannels, streamChannels, interruptAfterNodes, interruptBeforeNodes, }: {
8
+ nodes: Nn;
9
+ channels: Cc;
10
+ inputChannels: keyof Cc | Array<keyof Cc>;
11
+ outputChannels: keyof Cc | Array<keyof Cc>;
12
+ streamChannels?: keyof Cc | Array<keyof Cc>;
13
+ interruptAfterNodes?: Array<keyof Nn> | All;
14
+ interruptBeforeNodes?: Array<keyof Nn> | All;
15
15
  }): void;
16
+ export declare function validateKeys<Cc extends Record<string, BaseChannel>>(keys: keyof Cc | Array<keyof Cc>, channels: Cc): void;
@@ -1,82 +1,88 @@
1
- import { LastValue } from "../channels/last_value.js";
2
- import { ReservedChannelsMap } from "./reserved.js";
3
- export function validateGraph({ nodes, channels, input, output, hidden, interrupt, }) {
4
- const newChannels = channels;
1
+ import { INTERRUPT } from "../constants.js";
2
+ import { PregelNode } from "./read.js";
3
+ export class GraphValidationError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = "GraphValidationError";
7
+ }
8
+ }
9
+ export function validateGraph({ nodes, channels, inputChannels, outputChannels, streamChannels, interruptAfterNodes, interruptBeforeNodes, }) {
10
+ if (!channels) {
11
+ throw new GraphValidationError("Channels not provided");
12
+ }
5
13
  const subscribedChannels = new Set();
6
- for (const node of Object.values(nodes)) {
7
- if (node.lc_graph_name === "ChannelInvoke" && "channels" in node) {
8
- if (typeof node.channels === "string") {
9
- subscribedChannels.add(node.channels);
10
- }
11
- else {
12
- Object.values(node.channels).map((channel) => subscribedChannels.add(channel));
13
- }
14
+ const allOutputChannels = new Set();
15
+ for (const [name, node] of Object.entries(nodes)) {
16
+ if (name === INTERRUPT) {
17
+ throw new GraphValidationError(`"Node name ${INTERRUPT} is reserved"`);
18
+ }
19
+ if (node.constructor === PregelNode) {
20
+ node.triggers.forEach((trigger) => subscribedChannels.add(trigger));
14
21
  }
15
22
  else {
16
- console.error(node);
17
- throw new Error(`Invalid node type: ${JSON.stringify(node, null, 2)}, expected Channel.subscribeTo()`);
23
+ throw new GraphValidationError(`Invalid node type ${typeof node}, expected PregelNode`);
18
24
  }
19
25
  }
20
- for (const chan of [...subscribedChannels]) {
21
- if (!(chan in newChannels)) {
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- newChannels[chan] = new LastValue();
26
+ // side effect: update channels
27
+ for (const chan of subscribedChannels) {
28
+ if (!(chan in channels)) {
29
+ throw new GraphValidationError(`Subcribed channel '${String(chan)}' not in channels`);
24
30
  }
25
31
  }
26
- if (typeof input === "string") {
27
- if (!(input in newChannels)) {
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
- newChannels[input] = new LastValue();
30
- }
31
- if (!subscribedChannels.has(input)) {
32
- throw new Error(`Input channel ${input} is not subscribed to by any node.`);
32
+ if (!Array.isArray(inputChannels)) {
33
+ if (!subscribedChannels.has(inputChannels)) {
34
+ throw new GraphValidationError(`Input channel ${String(inputChannels)} is not subscribed to by any node`);
33
35
  }
34
36
  }
35
37
  else {
36
- for (const chan of input) {
37
- if (!(chan in newChannels)) {
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- newChannels[chan] = new LastValue();
40
- }
41
- }
42
- if (input.every((chan) => !subscribedChannels.has(chan))) {
43
- throw new Error(`None of the input channels ${input} are subscribed to by any node`);
38
+ if (inputChannels.every((channel) => !subscribedChannels.has(channel))) {
39
+ throw new GraphValidationError(`None of the input channels ${inputChannels} are subscribed to by any node`);
44
40
  }
45
41
  }
46
- if (typeof output === "string") {
47
- if (!(output in newChannels)) {
48
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
- newChannels[output] = new LastValue();
50
- }
42
+ if (!Array.isArray(outputChannels)) {
43
+ allOutputChannels.add(outputChannels);
51
44
  }
52
45
  else {
53
- for (const chan of output) {
54
- if (!(chan in newChannels)) {
55
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
56
- newChannels[chan] = new LastValue();
46
+ outputChannels.forEach((chan) => allOutputChannels.add(chan));
47
+ }
48
+ if (streamChannels && !Array.isArray(streamChannels)) {
49
+ allOutputChannels.add(streamChannels);
50
+ }
51
+ else if (Array.isArray(streamChannels)) {
52
+ streamChannels.forEach((chan) => allOutputChannels.add(chan));
53
+ }
54
+ for (const chan of allOutputChannels) {
55
+ if (!(chan in channels)) {
56
+ throw new GraphValidationError(`Output channel '${String(chan)}' not in channels`);
57
+ }
58
+ }
59
+ // validate interrupt before/after
60
+ if (interruptAfterNodes && interruptAfterNodes !== "*") {
61
+ for (const node of interruptAfterNodes) {
62
+ if (!(node in nodes)) {
63
+ throw new GraphValidationError(`Node ${String(node)} not in nodes`);
57
64
  }
58
65
  }
59
66
  }
60
- for (const chan in ReservedChannelsMap) {
61
- if (!(chan in newChannels)) {
62
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
- newChannels[chan] = new LastValue();
67
+ if (interruptBeforeNodes && interruptBeforeNodes !== "*") {
68
+ for (const node of interruptBeforeNodes) {
69
+ if (!(node in nodes)) {
70
+ throw new GraphValidationError(`Node ${String(node)} not in nodes`);
71
+ }
64
72
  }
65
73
  }
66
- validateKeys(hidden, newChannels);
67
- validateKeys(interrupt, newChannels);
68
74
  }
69
75
  export function validateKeys(keys, channels) {
70
76
  if (Array.isArray(keys)) {
71
77
  for (const key of keys) {
72
78
  if (!(key in channels)) {
73
- throw new Error(`Key ${key} not found in channels`);
79
+ throw new Error(`Key ${String(key)} not found in channels`);
74
80
  }
75
81
  }
76
82
  }
77
83
  else {
78
84
  if (!(keys in channels)) {
79
- throw new Error(`Key ${keys} not found in channels`);
85
+ throw new Error(`Key ${String(keys)} not found in channels`);
80
86
  }
81
87
  }
82
88
  }