@langchain/langgraph 0.2.41 → 0.2.43-rc.0
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.
- package/README.md +237 -154
- package/dist/channels/any_value.cjs +10 -10
- package/dist/channels/any_value.d.ts +1 -1
- package/dist/channels/any_value.js +10 -10
- package/dist/channels/ephemeral_value.cjs +10 -9
- package/dist/channels/ephemeral_value.d.ts +1 -1
- package/dist/channels/ephemeral_value.js +10 -9
- package/dist/channels/last_value.cjs +8 -7
- package/dist/channels/last_value.d.ts +1 -1
- package/dist/channels/last_value.js +8 -7
- package/dist/constants.cjs +33 -6
- package/dist/constants.d.ts +17 -2
- package/dist/constants.js +32 -5
- package/dist/errors.d.ts +3 -3
- package/dist/func/index.cjs +272 -0
- package/dist/func/index.d.ts +310 -0
- package/dist/func/index.js +267 -0
- package/dist/func/types.cjs +15 -0
- package/dist/func/types.d.ts +59 -0
- package/dist/func/types.js +11 -0
- package/dist/graph/graph.cjs +31 -35
- package/dist/graph/graph.d.ts +1 -5
- package/dist/graph/graph.js +1 -5
- package/dist/graph/index.cjs +1 -3
- package/dist/graph/index.d.ts +1 -1
- package/dist/graph/index.js +1 -1
- package/dist/graph/message.d.ts +1 -1
- package/dist/graph/state.cjs +17 -17
- package/dist/graph/state.d.ts +2 -1
- package/dist/graph/state.js +2 -2
- package/dist/index.cjs +8 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/interrupt.cjs +21 -34
- package/dist/interrupt.d.ts +1 -1
- package/dist/interrupt.js +22 -35
- package/dist/prebuilt/agent_executor.cjs +3 -3
- package/dist/prebuilt/agent_executor.d.ts +1 -1
- package/dist/prebuilt/agent_executor.js +1 -1
- package/dist/prebuilt/chat_agent_executor.cjs +3 -3
- package/dist/prebuilt/chat_agent_executor.d.ts +1 -1
- package/dist/prebuilt/chat_agent_executor.js +1 -1
- package/dist/prebuilt/react_agent_executor.cjs +33 -8
- package/dist/prebuilt/react_agent_executor.d.ts +4 -1
- package/dist/prebuilt/react_agent_executor.js +31 -6
- package/dist/prebuilt/tool_node.cjs +1 -2
- package/dist/prebuilt/tool_node.d.ts +1 -1
- package/dist/prebuilt/tool_node.js +1 -2
- package/dist/pregel/algo.cjs +121 -12
- package/dist/pregel/algo.d.ts +8 -6
- package/dist/pregel/algo.js +122 -13
- package/dist/pregel/call.cjs +77 -0
- package/dist/pregel/call.d.ts +15 -0
- package/dist/pregel/call.js +71 -0
- package/dist/pregel/index.cjs +59 -96
- package/dist/pregel/index.d.ts +1 -10
- package/dist/pregel/index.js +61 -98
- package/dist/pregel/io.cjs +6 -1
- package/dist/pregel/io.js +7 -2
- package/dist/pregel/loop.cjs +109 -75
- package/dist/pregel/loop.d.ts +17 -23
- package/dist/pregel/loop.js +110 -75
- package/dist/pregel/messages.d.ts +1 -1
- package/dist/pregel/retry.cjs +22 -50
- package/dist/pregel/retry.d.ts +6 -6
- package/dist/pregel/retry.js +22 -50
- package/dist/pregel/runner.cjs +275 -0
- package/dist/pregel/runner.d.ts +64 -0
- package/dist/pregel/runner.js +271 -0
- package/dist/pregel/stream.cjs +71 -0
- package/dist/pregel/stream.d.ts +17 -0
- package/dist/pregel/stream.js +67 -0
- package/dist/pregel/types.cjs +54 -0
- package/dist/pregel/types.d.ts +78 -6
- package/dist/pregel/types.js +51 -1
- package/dist/pregel/utils/config.cjs +26 -1
- package/dist/pregel/utils/config.d.ts +14 -0
- package/dist/pregel/utils/config.js +22 -0
- package/dist/pregel/write.d.ts +1 -1
- package/dist/utils.cjs +15 -1
- package/dist/utils.d.ts +3 -1
- package/dist/utils.js +12 -0
- package/dist/web.cjs +7 -5
- package/dist/web.d.ts +4 -4
- package/dist/web.js +3 -3
- package/package.json +8 -8
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PregelRunner = void 0;
|
|
4
|
+
const types_js_1 = require("./types.cjs");
|
|
5
|
+
const constants_js_1 = require("../constants.cjs");
|
|
6
|
+
const errors_js_1 = require("../errors.cjs");
|
|
7
|
+
const retry_js_1 = require("./retry.cjs");
|
|
8
|
+
/**
|
|
9
|
+
* Responsible for handling task execution on each tick of the {@link PregelLoop}.
|
|
10
|
+
*/
|
|
11
|
+
class PregelRunner {
|
|
12
|
+
/**
|
|
13
|
+
* Construct a new PregelRunner, which executes tasks from the provided PregelLoop.
|
|
14
|
+
* @param loop - The PregelLoop that produces tasks for this runner to execute.
|
|
15
|
+
*/
|
|
16
|
+
constructor({ loop, nodeFinished, }) {
|
|
17
|
+
Object.defineProperty(this, "nodeFinished", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(this, "loop", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
29
|
+
this.loop = loop;
|
|
30
|
+
this.nodeFinished = nodeFinished;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Execute tasks from the current step of the PregelLoop.
|
|
34
|
+
*
|
|
35
|
+
* Note: this method does NOT call {@link PregelLoop}#tick. That must be handled externally.
|
|
36
|
+
* @param options - Options for the execution.
|
|
37
|
+
*/
|
|
38
|
+
async tick(options = {}) {
|
|
39
|
+
const { timeout, signal, retryPolicy, onStepWrite } = options;
|
|
40
|
+
let graphInterrupt;
|
|
41
|
+
// Start task execution
|
|
42
|
+
const pendingTasks = Object.values(this.loop.tasks).filter((t) => t.writes.length === 0);
|
|
43
|
+
const taskStream = this._executeTasksWithRetry(pendingTasks, {
|
|
44
|
+
stepTimeout: timeout,
|
|
45
|
+
signal,
|
|
46
|
+
retryPolicy,
|
|
47
|
+
});
|
|
48
|
+
for await (const { task, error } of taskStream) {
|
|
49
|
+
graphInterrupt = this._commit(task, error) ?? graphInterrupt;
|
|
50
|
+
}
|
|
51
|
+
onStepWrite?.(this.loop.step, Object.values(this.loop.tasks)
|
|
52
|
+
.map((task) => task.writes)
|
|
53
|
+
.flat());
|
|
54
|
+
if (graphInterrupt) {
|
|
55
|
+
throw graphInterrupt;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Concurrently executes tasks with the requested retry policy, yielding a {@link SettledPregelTask} for each task as it completes.
|
|
60
|
+
* @param tasks - The tasks to execute.
|
|
61
|
+
* @param options - Options for the execution.
|
|
62
|
+
*/
|
|
63
|
+
async *_executeTasksWithRetry(tasks, options) {
|
|
64
|
+
const { stepTimeout, retryPolicy } = options ?? {};
|
|
65
|
+
let signal = options?.signal;
|
|
66
|
+
const promiseAddedSymbol = Symbol.for("promiseAdded");
|
|
67
|
+
let addedPromiseSignal;
|
|
68
|
+
let addedPromiseWait;
|
|
69
|
+
function waitHandler(resolve) {
|
|
70
|
+
addedPromiseSignal = () => {
|
|
71
|
+
addedPromiseWait = new Promise(waitHandler);
|
|
72
|
+
resolve(promiseAddedSymbol);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
addedPromiseWait = new Promise(waitHandler);
|
|
76
|
+
const executingTasksMap = {};
|
|
77
|
+
const writer = (task, writes, { calls } = {}) => {
|
|
78
|
+
if (writes.every(([channel]) => channel !== constants_js_1.PUSH)) {
|
|
79
|
+
return task.config?.configurable?.[constants_js_1.CONFIG_KEY_SEND]?.(writes) ?? [];
|
|
80
|
+
}
|
|
81
|
+
// Schedule PUSH tasks, collect promises
|
|
82
|
+
const scratchpad = task.config?.configurable?.[constants_js_1.CONFIG_KEY_SCRATCHPAD];
|
|
83
|
+
const rtn = {};
|
|
84
|
+
for (const [idx, write] of writes.entries()) {
|
|
85
|
+
const [channel] = write;
|
|
86
|
+
if (channel !== constants_js_1.PUSH) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const wcall = calls?.[idx];
|
|
90
|
+
const cnt = scratchpad.callCounter;
|
|
91
|
+
scratchpad.callCounter += 1;
|
|
92
|
+
if (wcall == null) {
|
|
93
|
+
throw new Error("BUG: No call found");
|
|
94
|
+
}
|
|
95
|
+
const nextTask = this.loop.acceptPush(task, cnt, wcall);
|
|
96
|
+
if (!nextTask) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
// Check if this task is already running
|
|
100
|
+
const existingPromise = executingTasksMap[nextTask.id];
|
|
101
|
+
if (existingPromise !== undefined) {
|
|
102
|
+
// If the parent task was retried, the next task might already be running
|
|
103
|
+
rtn[idx] = existingPromise;
|
|
104
|
+
}
|
|
105
|
+
else if (nextTask.writes.length > 0) {
|
|
106
|
+
// If it already ran, return the result
|
|
107
|
+
const returns = nextTask.writes.filter(([c]) => c === constants_js_1.RETURN);
|
|
108
|
+
const errors = nextTask.writes.filter(([c]) => c === constants_js_1.ERROR);
|
|
109
|
+
if (returns.length > 0) {
|
|
110
|
+
// Task completed successfully
|
|
111
|
+
if (returns.length === 1) {
|
|
112
|
+
rtn[idx] = Promise.resolve(returns[0][1]);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// should be unreachable
|
|
116
|
+
throw new Error(`BUG: multiple returns found for task ${nextTask.name}__${nextTask.id}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (errors.length > 0) {
|
|
120
|
+
if (errors.length === 1) {
|
|
121
|
+
const errorValue = errors[0][1];
|
|
122
|
+
// Task failed
|
|
123
|
+
const error =
|
|
124
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
125
|
+
errorValue instanceof Error
|
|
126
|
+
? errorValue
|
|
127
|
+
: new Error(String(errorValue));
|
|
128
|
+
rtn[idx] = Promise.reject(error);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// the only way this should happen is if the task executes multiple times and writes aren't cleared
|
|
132
|
+
throw new Error(`BUG: multiple errors found for task ${nextTask.name}__${nextTask.id}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
// Schedule the next task with retry
|
|
138
|
+
const prom = (0, retry_js_1._runWithRetry)(nextTask, retryPolicy, {
|
|
139
|
+
[constants_js_1.CONFIG_KEY_SEND]: writer.bind(this, nextTask),
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
141
|
+
[constants_js_1.CONFIG_KEY_CALL]: call.bind(this, nextTask),
|
|
142
|
+
});
|
|
143
|
+
executingTasksMap[nextTask.id] = prom;
|
|
144
|
+
addedPromiseSignal();
|
|
145
|
+
rtn[idx] = prom.then(({ result, error }) => {
|
|
146
|
+
if (error) {
|
|
147
|
+
return Promise.reject(error);
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return Object.values(rtn);
|
|
154
|
+
};
|
|
155
|
+
const call = (task, func, name, input, options = {}) => {
|
|
156
|
+
const result = writer(task, [[constants_js_1.PUSH, null]], {
|
|
157
|
+
calls: [
|
|
158
|
+
new types_js_1.Call({
|
|
159
|
+
func,
|
|
160
|
+
name,
|
|
161
|
+
input,
|
|
162
|
+
retry: options.retry,
|
|
163
|
+
callbacks: options.callbacks,
|
|
164
|
+
}),
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
168
|
+
if (result !== undefined) {
|
|
169
|
+
if (result.length === 1) {
|
|
170
|
+
return result[0];
|
|
171
|
+
}
|
|
172
|
+
return Promise.all(result);
|
|
173
|
+
}
|
|
174
|
+
return Promise.resolve();
|
|
175
|
+
};
|
|
176
|
+
if (stepTimeout && signal) {
|
|
177
|
+
if ("any" in AbortSignal) {
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
179
|
+
signal = AbortSignal.any([
|
|
180
|
+
signal,
|
|
181
|
+
AbortSignal.timeout(stepTimeout),
|
|
182
|
+
]);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else if (stepTimeout) {
|
|
186
|
+
signal = AbortSignal.timeout(stepTimeout);
|
|
187
|
+
}
|
|
188
|
+
if (signal?.aborted) {
|
|
189
|
+
// note: don't use throwIfAborted here because it throws a DOMException,
|
|
190
|
+
// which isn't consistent with how we throw on abort below.
|
|
191
|
+
throw new Error("Abort");
|
|
192
|
+
}
|
|
193
|
+
// Start tasks
|
|
194
|
+
Object.assign(executingTasksMap, Object.fromEntries(tasks.map((pregelTask) => {
|
|
195
|
+
return [
|
|
196
|
+
pregelTask.id,
|
|
197
|
+
(0, retry_js_1._runWithRetry)(pregelTask, retryPolicy, {
|
|
198
|
+
[constants_js_1.CONFIG_KEY_SEND]: writer?.bind(this, pregelTask),
|
|
199
|
+
[constants_js_1.CONFIG_KEY_CALL]: call?.bind(this, pregelTask),
|
|
200
|
+
}).catch((error) => {
|
|
201
|
+
return { task: pregelTask, error };
|
|
202
|
+
}),
|
|
203
|
+
];
|
|
204
|
+
})));
|
|
205
|
+
let listener;
|
|
206
|
+
const signalPromise = new Promise((_resolve, reject) => {
|
|
207
|
+
listener = () => reject(new Error("Abort"));
|
|
208
|
+
signal?.addEventListener("abort", listener);
|
|
209
|
+
}).finally(() => signal?.removeEventListener("abort", listener));
|
|
210
|
+
while (Object.keys(executingTasksMap).length > 0) {
|
|
211
|
+
const settledTask = await Promise.race([
|
|
212
|
+
...Object.values(executingTasksMap),
|
|
213
|
+
signalPromise,
|
|
214
|
+
addedPromiseWait,
|
|
215
|
+
]);
|
|
216
|
+
if (settledTask === promiseAddedSymbol) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
yield settledTask;
|
|
220
|
+
delete executingTasksMap[settledTask.task.id];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Determines what writes to apply based on whether the task completed successfully, and what type of error occurred.
|
|
225
|
+
*
|
|
226
|
+
* Throws an error if the error is a {@link GraphBubbleUp} error and {@link PregelLoop}#isNested is true.
|
|
227
|
+
*
|
|
228
|
+
* Note that in the case of a {@link GraphBubbleUp} error that is not a {@link GraphInterrupt}, like a {@link Command}, this method does not apply any writes.
|
|
229
|
+
*
|
|
230
|
+
* @param task - The task to commit.
|
|
231
|
+
* @param error - The error that occurred, if any.
|
|
232
|
+
* @returns The {@link GraphInterrupt} that occurred, if the user's code threw one.
|
|
233
|
+
*/
|
|
234
|
+
_commit(task, error) {
|
|
235
|
+
let graphInterrupt;
|
|
236
|
+
if (error !== undefined) {
|
|
237
|
+
if ((0, errors_js_1.isGraphBubbleUp)(error)) {
|
|
238
|
+
if (this.loop.isNested) {
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
if ((0, errors_js_1.isGraphInterrupt)(error)) {
|
|
242
|
+
graphInterrupt = error;
|
|
243
|
+
if (error.interrupts.length) {
|
|
244
|
+
const interrupts = error.interrupts.map((interrupt) => [constants_js_1.INTERRUPT, interrupt]);
|
|
245
|
+
const resumes = task.writes.filter((w) => w[0] === constants_js_1.RESUME);
|
|
246
|
+
if (resumes.length) {
|
|
247
|
+
interrupts.push(...resumes);
|
|
248
|
+
}
|
|
249
|
+
this.loop.putWrites(task.id, interrupts);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
this.loop.putWrites(task.id, [
|
|
255
|
+
[constants_js_1.ERROR, { message: error.message, name: error.name }],
|
|
256
|
+
]);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
if (this.nodeFinished &&
|
|
262
|
+
(task.config?.tags == null || !task.config.tags.includes(constants_js_1.TAG_HIDDEN))) {
|
|
263
|
+
this.nodeFinished(String(task.name));
|
|
264
|
+
}
|
|
265
|
+
if (task.writes.length === 0) {
|
|
266
|
+
// Add no writes marker
|
|
267
|
+
task.writes.push([constants_js_1.NO_WRITES, null]);
|
|
268
|
+
}
|
|
269
|
+
// Save task writes to checkpointer
|
|
270
|
+
this.loop.putWrites(task.id, task.writes);
|
|
271
|
+
}
|
|
272
|
+
return graphInterrupt;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.PregelRunner = PregelRunner;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { PendingWrite } from "@langchain/langgraph-checkpoint";
|
|
2
|
+
import { RetryPolicy } from "./utils/index.js";
|
|
3
|
+
import { PregelLoop } from "./loop.js";
|
|
4
|
+
/**
|
|
5
|
+
* Options for the {@link PregelRunner#tick} method.
|
|
6
|
+
*/
|
|
7
|
+
export type TickOptions = {
|
|
8
|
+
/**
|
|
9
|
+
* The deadline before which all tasks must be completed.
|
|
10
|
+
*/
|
|
11
|
+
timeout?: number;
|
|
12
|
+
/**
|
|
13
|
+
* An optional {@link AbortSignal} to cancel processing of tasks.
|
|
14
|
+
*/
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
/**
|
|
17
|
+
* The {@link RetryPolicy} to use for the tick.
|
|
18
|
+
*/
|
|
19
|
+
retryPolicy?: RetryPolicy;
|
|
20
|
+
/**
|
|
21
|
+
* An optional callback to be called after all task writes are completed.
|
|
22
|
+
*/
|
|
23
|
+
onStepWrite?: (step: number, writes: PendingWrite[]) => void;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Responsible for handling task execution on each tick of the {@link PregelLoop}.
|
|
27
|
+
*/
|
|
28
|
+
export declare class PregelRunner {
|
|
29
|
+
private nodeFinished?;
|
|
30
|
+
private loop;
|
|
31
|
+
/**
|
|
32
|
+
* Construct a new PregelRunner, which executes tasks from the provided PregelLoop.
|
|
33
|
+
* @param loop - The PregelLoop that produces tasks for this runner to execute.
|
|
34
|
+
*/
|
|
35
|
+
constructor({ loop, nodeFinished, }: {
|
|
36
|
+
loop: PregelLoop;
|
|
37
|
+
nodeFinished?: (id: string) => void;
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Execute tasks from the current step of the PregelLoop.
|
|
41
|
+
*
|
|
42
|
+
* Note: this method does NOT call {@link PregelLoop}#tick. That must be handled externally.
|
|
43
|
+
* @param options - Options for the execution.
|
|
44
|
+
*/
|
|
45
|
+
tick(options?: TickOptions): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Concurrently executes tasks with the requested retry policy, yielding a {@link SettledPregelTask} for each task as it completes.
|
|
48
|
+
* @param tasks - The tasks to execute.
|
|
49
|
+
* @param options - Options for the execution.
|
|
50
|
+
*/
|
|
51
|
+
private _executeTasksWithRetry;
|
|
52
|
+
/**
|
|
53
|
+
* Determines what writes to apply based on whether the task completed successfully, and what type of error occurred.
|
|
54
|
+
*
|
|
55
|
+
* Throws an error if the error is a {@link GraphBubbleUp} error and {@link PregelLoop}#isNested is true.
|
|
56
|
+
*
|
|
57
|
+
* Note that in the case of a {@link GraphBubbleUp} error that is not a {@link GraphInterrupt}, like a {@link Command}, this method does not apply any writes.
|
|
58
|
+
*
|
|
59
|
+
* @param task - The task to commit.
|
|
60
|
+
* @param error - The error that occurred, if any.
|
|
61
|
+
* @returns The {@link GraphInterrupt} that occurred, if the user's code threw one.
|
|
62
|
+
*/
|
|
63
|
+
private _commit;
|
|
64
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { Call } from "./types.js";
|
|
2
|
+
import { CONFIG_KEY_SEND, CONFIG_KEY_SCRATCHPAD, PUSH, ERROR, INTERRUPT, RESUME, NO_WRITES, TAG_HIDDEN, RETURN, CONFIG_KEY_CALL, } from "../constants.js";
|
|
3
|
+
import { isGraphBubbleUp, isGraphInterrupt, } from "../errors.js";
|
|
4
|
+
import { _runWithRetry } from "./retry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Responsible for handling task execution on each tick of the {@link PregelLoop}.
|
|
7
|
+
*/
|
|
8
|
+
export class PregelRunner {
|
|
9
|
+
/**
|
|
10
|
+
* Construct a new PregelRunner, which executes tasks from the provided PregelLoop.
|
|
11
|
+
* @param loop - The PregelLoop that produces tasks for this runner to execute.
|
|
12
|
+
*/
|
|
13
|
+
constructor({ loop, nodeFinished, }) {
|
|
14
|
+
Object.defineProperty(this, "nodeFinished", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: void 0
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "loop", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
this.loop = loop;
|
|
27
|
+
this.nodeFinished = nodeFinished;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Execute tasks from the current step of the PregelLoop.
|
|
31
|
+
*
|
|
32
|
+
* Note: this method does NOT call {@link PregelLoop}#tick. That must be handled externally.
|
|
33
|
+
* @param options - Options for the execution.
|
|
34
|
+
*/
|
|
35
|
+
async tick(options = {}) {
|
|
36
|
+
const { timeout, signal, retryPolicy, onStepWrite } = options;
|
|
37
|
+
let graphInterrupt;
|
|
38
|
+
// Start task execution
|
|
39
|
+
const pendingTasks = Object.values(this.loop.tasks).filter((t) => t.writes.length === 0);
|
|
40
|
+
const taskStream = this._executeTasksWithRetry(pendingTasks, {
|
|
41
|
+
stepTimeout: timeout,
|
|
42
|
+
signal,
|
|
43
|
+
retryPolicy,
|
|
44
|
+
});
|
|
45
|
+
for await (const { task, error } of taskStream) {
|
|
46
|
+
graphInterrupt = this._commit(task, error) ?? graphInterrupt;
|
|
47
|
+
}
|
|
48
|
+
onStepWrite?.(this.loop.step, Object.values(this.loop.tasks)
|
|
49
|
+
.map((task) => task.writes)
|
|
50
|
+
.flat());
|
|
51
|
+
if (graphInterrupt) {
|
|
52
|
+
throw graphInterrupt;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Concurrently executes tasks with the requested retry policy, yielding a {@link SettledPregelTask} for each task as it completes.
|
|
57
|
+
* @param tasks - The tasks to execute.
|
|
58
|
+
* @param options - Options for the execution.
|
|
59
|
+
*/
|
|
60
|
+
async *_executeTasksWithRetry(tasks, options) {
|
|
61
|
+
const { stepTimeout, retryPolicy } = options ?? {};
|
|
62
|
+
let signal = options?.signal;
|
|
63
|
+
const promiseAddedSymbol = Symbol.for("promiseAdded");
|
|
64
|
+
let addedPromiseSignal;
|
|
65
|
+
let addedPromiseWait;
|
|
66
|
+
function waitHandler(resolve) {
|
|
67
|
+
addedPromiseSignal = () => {
|
|
68
|
+
addedPromiseWait = new Promise(waitHandler);
|
|
69
|
+
resolve(promiseAddedSymbol);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
addedPromiseWait = new Promise(waitHandler);
|
|
73
|
+
const executingTasksMap = {};
|
|
74
|
+
const writer = (task, writes, { calls } = {}) => {
|
|
75
|
+
if (writes.every(([channel]) => channel !== PUSH)) {
|
|
76
|
+
return task.config?.configurable?.[CONFIG_KEY_SEND]?.(writes) ?? [];
|
|
77
|
+
}
|
|
78
|
+
// Schedule PUSH tasks, collect promises
|
|
79
|
+
const scratchpad = task.config?.configurable?.[CONFIG_KEY_SCRATCHPAD];
|
|
80
|
+
const rtn = {};
|
|
81
|
+
for (const [idx, write] of writes.entries()) {
|
|
82
|
+
const [channel] = write;
|
|
83
|
+
if (channel !== PUSH) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const wcall = calls?.[idx];
|
|
87
|
+
const cnt = scratchpad.callCounter;
|
|
88
|
+
scratchpad.callCounter += 1;
|
|
89
|
+
if (wcall == null) {
|
|
90
|
+
throw new Error("BUG: No call found");
|
|
91
|
+
}
|
|
92
|
+
const nextTask = this.loop.acceptPush(task, cnt, wcall);
|
|
93
|
+
if (!nextTask) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// Check if this task is already running
|
|
97
|
+
const existingPromise = executingTasksMap[nextTask.id];
|
|
98
|
+
if (existingPromise !== undefined) {
|
|
99
|
+
// If the parent task was retried, the next task might already be running
|
|
100
|
+
rtn[idx] = existingPromise;
|
|
101
|
+
}
|
|
102
|
+
else if (nextTask.writes.length > 0) {
|
|
103
|
+
// If it already ran, return the result
|
|
104
|
+
const returns = nextTask.writes.filter(([c]) => c === RETURN);
|
|
105
|
+
const errors = nextTask.writes.filter(([c]) => c === ERROR);
|
|
106
|
+
if (returns.length > 0) {
|
|
107
|
+
// Task completed successfully
|
|
108
|
+
if (returns.length === 1) {
|
|
109
|
+
rtn[idx] = Promise.resolve(returns[0][1]);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// should be unreachable
|
|
113
|
+
throw new Error(`BUG: multiple returns found for task ${nextTask.name}__${nextTask.id}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (errors.length > 0) {
|
|
117
|
+
if (errors.length === 1) {
|
|
118
|
+
const errorValue = errors[0][1];
|
|
119
|
+
// Task failed
|
|
120
|
+
const error =
|
|
121
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
122
|
+
errorValue instanceof Error
|
|
123
|
+
? errorValue
|
|
124
|
+
: new Error(String(errorValue));
|
|
125
|
+
rtn[idx] = Promise.reject(error);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// the only way this should happen is if the task executes multiple times and writes aren't cleared
|
|
129
|
+
throw new Error(`BUG: multiple errors found for task ${nextTask.name}__${nextTask.id}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Schedule the next task with retry
|
|
135
|
+
const prom = _runWithRetry(nextTask, retryPolicy, {
|
|
136
|
+
[CONFIG_KEY_SEND]: writer.bind(this, nextTask),
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
138
|
+
[CONFIG_KEY_CALL]: call.bind(this, nextTask),
|
|
139
|
+
});
|
|
140
|
+
executingTasksMap[nextTask.id] = prom;
|
|
141
|
+
addedPromiseSignal();
|
|
142
|
+
rtn[idx] = prom.then(({ result, error }) => {
|
|
143
|
+
if (error) {
|
|
144
|
+
return Promise.reject(error);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return Object.values(rtn);
|
|
151
|
+
};
|
|
152
|
+
const call = (task, func, name, input, options = {}) => {
|
|
153
|
+
const result = writer(task, [[PUSH, null]], {
|
|
154
|
+
calls: [
|
|
155
|
+
new Call({
|
|
156
|
+
func,
|
|
157
|
+
name,
|
|
158
|
+
input,
|
|
159
|
+
retry: options.retry,
|
|
160
|
+
callbacks: options.callbacks,
|
|
161
|
+
}),
|
|
162
|
+
],
|
|
163
|
+
});
|
|
164
|
+
// eslint-disable-next-line no-instanceof/no-instanceof
|
|
165
|
+
if (result !== undefined) {
|
|
166
|
+
if (result.length === 1) {
|
|
167
|
+
return result[0];
|
|
168
|
+
}
|
|
169
|
+
return Promise.all(result);
|
|
170
|
+
}
|
|
171
|
+
return Promise.resolve();
|
|
172
|
+
};
|
|
173
|
+
if (stepTimeout && signal) {
|
|
174
|
+
if ("any" in AbortSignal) {
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
176
|
+
signal = AbortSignal.any([
|
|
177
|
+
signal,
|
|
178
|
+
AbortSignal.timeout(stepTimeout),
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else if (stepTimeout) {
|
|
183
|
+
signal = AbortSignal.timeout(stepTimeout);
|
|
184
|
+
}
|
|
185
|
+
if (signal?.aborted) {
|
|
186
|
+
// note: don't use throwIfAborted here because it throws a DOMException,
|
|
187
|
+
// which isn't consistent with how we throw on abort below.
|
|
188
|
+
throw new Error("Abort");
|
|
189
|
+
}
|
|
190
|
+
// Start tasks
|
|
191
|
+
Object.assign(executingTasksMap, Object.fromEntries(tasks.map((pregelTask) => {
|
|
192
|
+
return [
|
|
193
|
+
pregelTask.id,
|
|
194
|
+
_runWithRetry(pregelTask, retryPolicy, {
|
|
195
|
+
[CONFIG_KEY_SEND]: writer?.bind(this, pregelTask),
|
|
196
|
+
[CONFIG_KEY_CALL]: call?.bind(this, pregelTask),
|
|
197
|
+
}).catch((error) => {
|
|
198
|
+
return { task: pregelTask, error };
|
|
199
|
+
}),
|
|
200
|
+
];
|
|
201
|
+
})));
|
|
202
|
+
let listener;
|
|
203
|
+
const signalPromise = new Promise((_resolve, reject) => {
|
|
204
|
+
listener = () => reject(new Error("Abort"));
|
|
205
|
+
signal?.addEventListener("abort", listener);
|
|
206
|
+
}).finally(() => signal?.removeEventListener("abort", listener));
|
|
207
|
+
while (Object.keys(executingTasksMap).length > 0) {
|
|
208
|
+
const settledTask = await Promise.race([
|
|
209
|
+
...Object.values(executingTasksMap),
|
|
210
|
+
signalPromise,
|
|
211
|
+
addedPromiseWait,
|
|
212
|
+
]);
|
|
213
|
+
if (settledTask === promiseAddedSymbol) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
yield settledTask;
|
|
217
|
+
delete executingTasksMap[settledTask.task.id];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Determines what writes to apply based on whether the task completed successfully, and what type of error occurred.
|
|
222
|
+
*
|
|
223
|
+
* Throws an error if the error is a {@link GraphBubbleUp} error and {@link PregelLoop}#isNested is true.
|
|
224
|
+
*
|
|
225
|
+
* Note that in the case of a {@link GraphBubbleUp} error that is not a {@link GraphInterrupt}, like a {@link Command}, this method does not apply any writes.
|
|
226
|
+
*
|
|
227
|
+
* @param task - The task to commit.
|
|
228
|
+
* @param error - The error that occurred, if any.
|
|
229
|
+
* @returns The {@link GraphInterrupt} that occurred, if the user's code threw one.
|
|
230
|
+
*/
|
|
231
|
+
_commit(task, error) {
|
|
232
|
+
let graphInterrupt;
|
|
233
|
+
if (error !== undefined) {
|
|
234
|
+
if (isGraphBubbleUp(error)) {
|
|
235
|
+
if (this.loop.isNested) {
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
if (isGraphInterrupt(error)) {
|
|
239
|
+
graphInterrupt = error;
|
|
240
|
+
if (error.interrupts.length) {
|
|
241
|
+
const interrupts = error.interrupts.map((interrupt) => [INTERRUPT, interrupt]);
|
|
242
|
+
const resumes = task.writes.filter((w) => w[0] === RESUME);
|
|
243
|
+
if (resumes.length) {
|
|
244
|
+
interrupts.push(...resumes);
|
|
245
|
+
}
|
|
246
|
+
this.loop.putWrites(task.id, interrupts);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
this.loop.putWrites(task.id, [
|
|
252
|
+
[ERROR, { message: error.message, name: error.name }],
|
|
253
|
+
]);
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
if (this.nodeFinished &&
|
|
259
|
+
(task.config?.tags == null || !task.config.tags.includes(TAG_HIDDEN))) {
|
|
260
|
+
this.nodeFinished(String(task.name));
|
|
261
|
+
}
|
|
262
|
+
if (task.writes.length === 0) {
|
|
263
|
+
// Add no writes marker
|
|
264
|
+
task.writes.push([NO_WRITES, null]);
|
|
265
|
+
}
|
|
266
|
+
// Save task writes to checkpointer
|
|
267
|
+
this.loop.putWrites(task.id, task.writes);
|
|
268
|
+
}
|
|
269
|
+
return graphInterrupt;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IterableReadableWritableStream = void 0;
|
|
4
|
+
const stream_1 = require("@langchain/core/utils/stream");
|
|
5
|
+
class IterableReadableWritableStream extends stream_1.IterableReadableStream {
|
|
6
|
+
get closed() {
|
|
7
|
+
return this._closed;
|
|
8
|
+
}
|
|
9
|
+
constructor(params) {
|
|
10
|
+
let streamControllerPromiseResolver;
|
|
11
|
+
const streamControllerPromise = new Promise((resolve) => {
|
|
12
|
+
streamControllerPromiseResolver = resolve;
|
|
13
|
+
});
|
|
14
|
+
super({
|
|
15
|
+
start: (controller) => {
|
|
16
|
+
streamControllerPromiseResolver(controller);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(this, "modes", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: void 0
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "controller", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: void 0
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(this, "passthroughFn", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: void 0
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "_closed", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: false
|
|
42
|
+
});
|
|
43
|
+
// .start() will always be called before the stream can be interacted
|
|
44
|
+
// with anyway
|
|
45
|
+
void streamControllerPromise.then((controller) => {
|
|
46
|
+
this.controller = controller;
|
|
47
|
+
});
|
|
48
|
+
this.passthroughFn = params.passthroughFn;
|
|
49
|
+
this.modes = params.modes;
|
|
50
|
+
}
|
|
51
|
+
push(chunk) {
|
|
52
|
+
this.passthroughFn?.(chunk);
|
|
53
|
+
this.controller.enqueue(chunk);
|
|
54
|
+
}
|
|
55
|
+
close() {
|
|
56
|
+
try {
|
|
57
|
+
this.controller.close();
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
// pass
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
this._closed = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
error(e) {
|
|
68
|
+
this.controller.error(e);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.IterableReadableWritableStream = IterableReadableWritableStream;
|