@langchain/langgraph 0.2.40 → 0.2.42
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 +79 -12
- package/dist/prebuilt/react_agent_executor.d.ts +35 -4
- package/dist/prebuilt/react_agent_executor.js +79 -13
- 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
package/dist/pregel/retry.cjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports._runWithRetry = exports.DEFAULT_MAX_RETRIES = exports.DEFAULT_MAX_INTERVAL = exports.DEFAULT_BACKOFF_FACTOR = exports.DEFAULT_INITIAL_INTERVAL = void 0;
|
|
4
4
|
const constants_js_1 = require("../constants.cjs");
|
|
5
5
|
const errors_js_1 = require("../errors.cjs");
|
|
6
|
+
const index_js_1 = require("./utils/index.cjs");
|
|
6
7
|
exports.DEFAULT_INITIAL_INTERVAL = 500;
|
|
7
8
|
exports.DEFAULT_BACKOFF_FACTOR = 2;
|
|
8
9
|
exports.DEFAULT_MAX_INTERVAL = 128000;
|
|
@@ -41,47 +42,9 @@ const DEFAULT_RETRY_ON_HANDLER = (error) => {
|
|
|
41
42
|
}
|
|
42
43
|
return true;
|
|
43
44
|
};
|
|
44
|
-
async function* executeTasksWithRetry(
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
|
-
tasks, options) {
|
|
47
|
-
const { stepTimeout, retryPolicy } = options ?? {};
|
|
48
|
-
let signal = options?.signal;
|
|
49
|
-
// Start tasks
|
|
50
|
-
const executingTasksMap = Object.fromEntries(tasks.map((pregelTask) => {
|
|
51
|
-
return [pregelTask.id, _runWithRetry(pregelTask, retryPolicy)];
|
|
52
|
-
}));
|
|
53
|
-
if (stepTimeout && signal) {
|
|
54
|
-
if ("any" in AbortSignal) {
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
-
signal = AbortSignal.any([
|
|
57
|
-
signal,
|
|
58
|
-
AbortSignal.timeout(stepTimeout),
|
|
59
|
-
]);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
else if (stepTimeout) {
|
|
63
|
-
signal = AbortSignal.timeout(stepTimeout);
|
|
64
|
-
}
|
|
65
|
-
// Abort if signal is aborted
|
|
66
|
-
signal?.throwIfAborted();
|
|
67
|
-
let listener;
|
|
68
|
-
const signalPromise = new Promise((_resolve, reject) => {
|
|
69
|
-
listener = () => reject(new Error("Abort"));
|
|
70
|
-
signal?.addEventListener("abort", listener);
|
|
71
|
-
}).finally(() => signal?.removeEventListener("abort", listener));
|
|
72
|
-
while (Object.keys(executingTasksMap).length > 0) {
|
|
73
|
-
const settledTask = await Promise.race([
|
|
74
|
-
...Object.values(executingTasksMap),
|
|
75
|
-
signalPromise,
|
|
76
|
-
]);
|
|
77
|
-
yield settledTask;
|
|
78
|
-
delete executingTasksMap[settledTask.task.id];
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
exports.executeTasksWithRetry = executeTasksWithRetry;
|
|
82
45
|
async function _runWithRetry(
|
|
83
46
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
-
pregelTask, retryPolicy) {
|
|
47
|
+
pregelTask, retryPolicy, configurable, signal) {
|
|
85
48
|
const resolvedRetryPolicy = pregelTask.retry_policy ?? retryPolicy;
|
|
86
49
|
let interval = resolvedRetryPolicy !== undefined
|
|
87
50
|
? resolvedRetryPolicy.initialInterval ?? exports.DEFAULT_INITIAL_INTERVAL
|
|
@@ -89,28 +52,34 @@ pregelTask, retryPolicy) {
|
|
|
89
52
|
let attempts = 0;
|
|
90
53
|
let error;
|
|
91
54
|
let result;
|
|
55
|
+
let { config } = pregelTask;
|
|
56
|
+
if (configurable) {
|
|
57
|
+
config = (0, index_js_1.patchConfigurable)(config, configurable);
|
|
58
|
+
}
|
|
92
59
|
// eslint-disable-next-line no-constant-condition
|
|
93
60
|
while (true) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
61
|
+
if (signal?.aborted) {
|
|
62
|
+
// no need to throw here - we'll throw from the runner, instead.
|
|
63
|
+
// there's just no point in retrying if the user has requested an abort.
|
|
64
|
+
break;
|
|
97
65
|
}
|
|
66
|
+
// Clear any writes from previous attempts
|
|
67
|
+
pregelTask.writes.splice(0, pregelTask.writes.length);
|
|
98
68
|
error = undefined;
|
|
99
69
|
try {
|
|
100
|
-
result = await pregelTask.proc.invoke(pregelTask.input,
|
|
70
|
+
result = await pregelTask.proc.invoke(pregelTask.input, config);
|
|
101
71
|
break;
|
|
102
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
72
|
}
|
|
104
73
|
catch (e) {
|
|
105
74
|
error = e;
|
|
106
75
|
error.pregelTaskId = pregelTask.id;
|
|
107
76
|
if ((0, errors_js_1.isParentCommand)(error)) {
|
|
108
|
-
const ns =
|
|
77
|
+
const ns = config?.configurable?.checkpoint_ns;
|
|
109
78
|
const cmd = error.command;
|
|
110
79
|
if (cmd.graph === ns) {
|
|
111
80
|
// this command is for the current graph, handle it
|
|
112
81
|
for (const writer of pregelTask.writers) {
|
|
113
|
-
await writer.invoke(cmd,
|
|
82
|
+
await writer.invoke(cmd, config);
|
|
114
83
|
}
|
|
115
84
|
break;
|
|
116
85
|
}
|
|
@@ -153,11 +122,13 @@ pregelTask, retryPolicy) {
|
|
|
153
122
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
123
|
error.constructor.unminifiable_name ??
|
|
155
124
|
error.constructor.name;
|
|
156
|
-
console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
|
|
125
|
+
console.log(`Retrying task "${String(pregelTask.name)}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
|
|
126
|
+
// signal subgraphs to resume (if available)
|
|
127
|
+
config = (0, index_js_1.patchConfigurable)(config, { [constants_js_1.CONFIG_KEY_RESUMING]: true });
|
|
157
128
|
}
|
|
158
129
|
finally {
|
|
159
130
|
// Clear checkpoint_ns seen (for subgraph detection)
|
|
160
|
-
const checkpointNs =
|
|
131
|
+
const checkpointNs = config?.configurable?.checkpoint_ns;
|
|
161
132
|
if (checkpointNs) {
|
|
162
133
|
(0, errors_js_1.getSubgraphsSeenSet)().delete(checkpointNs);
|
|
163
134
|
}
|
|
@@ -166,6 +137,7 @@ pregelTask, retryPolicy) {
|
|
|
166
137
|
return {
|
|
167
138
|
task: pregelTask,
|
|
168
139
|
result,
|
|
169
|
-
error,
|
|
140
|
+
error: error,
|
|
170
141
|
};
|
|
171
142
|
}
|
|
143
|
+
exports._runWithRetry = _runWithRetry;
|
package/dist/pregel/retry.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PregelExecutableTask } from "./types.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type RetryPolicy } from "./utils/index.js";
|
|
3
3
|
export declare const DEFAULT_INITIAL_INTERVAL = 500;
|
|
4
4
|
export declare const DEFAULT_BACKOFF_FACTOR = 2;
|
|
5
5
|
export declare const DEFAULT_MAX_INTERVAL = 128000;
|
|
@@ -8,8 +8,8 @@ export type SettledPregelTask = {
|
|
|
8
8
|
task: PregelExecutableTask<any, any>;
|
|
9
9
|
error: Error;
|
|
10
10
|
};
|
|
11
|
-
export declare function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
11
|
+
export declare function _runWithRetry<N extends PropertyKey, C extends PropertyKey>(pregelTask: PregelExecutableTask<N, C>, retryPolicy?: RetryPolicy, configurable?: Record<string, unknown>, signal?: AbortSignal): Promise<{
|
|
12
|
+
task: PregelExecutableTask<N, C>;
|
|
13
|
+
result: unknown;
|
|
14
|
+
error: Error | undefined;
|
|
15
|
+
}>;
|
package/dist/pregel/retry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { CHECKPOINT_NAMESPACE_SEPARATOR, Command } from "../constants.js";
|
|
1
|
+
import { CHECKPOINT_NAMESPACE_SEPARATOR, Command, CONFIG_KEY_RESUMING, } from "../constants.js";
|
|
2
2
|
import { getSubgraphsSeenSet, isGraphBubbleUp, isParentCommand, } from "../errors.js";
|
|
3
|
+
import { patchConfigurable } from "./utils/index.js";
|
|
3
4
|
export const DEFAULT_INITIAL_INTERVAL = 500;
|
|
4
5
|
export const DEFAULT_BACKOFF_FACTOR = 2;
|
|
5
6
|
export const DEFAULT_MAX_INTERVAL = 128000;
|
|
@@ -38,46 +39,9 @@ const DEFAULT_RETRY_ON_HANDLER = (error) => {
|
|
|
38
39
|
}
|
|
39
40
|
return true;
|
|
40
41
|
};
|
|
41
|
-
export async function
|
|
42
|
+
export async function _runWithRetry(
|
|
42
43
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
-
|
|
44
|
-
const { stepTimeout, retryPolicy } = options ?? {};
|
|
45
|
-
let signal = options?.signal;
|
|
46
|
-
// Start tasks
|
|
47
|
-
const executingTasksMap = Object.fromEntries(tasks.map((pregelTask) => {
|
|
48
|
-
return [pregelTask.id, _runWithRetry(pregelTask, retryPolicy)];
|
|
49
|
-
}));
|
|
50
|
-
if (stepTimeout && signal) {
|
|
51
|
-
if ("any" in AbortSignal) {
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
-
signal = AbortSignal.any([
|
|
54
|
-
signal,
|
|
55
|
-
AbortSignal.timeout(stepTimeout),
|
|
56
|
-
]);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else if (stepTimeout) {
|
|
60
|
-
signal = AbortSignal.timeout(stepTimeout);
|
|
61
|
-
}
|
|
62
|
-
// Abort if signal is aborted
|
|
63
|
-
signal?.throwIfAborted();
|
|
64
|
-
let listener;
|
|
65
|
-
const signalPromise = new Promise((_resolve, reject) => {
|
|
66
|
-
listener = () => reject(new Error("Abort"));
|
|
67
|
-
signal?.addEventListener("abort", listener);
|
|
68
|
-
}).finally(() => signal?.removeEventListener("abort", listener));
|
|
69
|
-
while (Object.keys(executingTasksMap).length > 0) {
|
|
70
|
-
const settledTask = await Promise.race([
|
|
71
|
-
...Object.values(executingTasksMap),
|
|
72
|
-
signalPromise,
|
|
73
|
-
]);
|
|
74
|
-
yield settledTask;
|
|
75
|
-
delete executingTasksMap[settledTask.task.id];
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
async function _runWithRetry(
|
|
79
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
|
-
pregelTask, retryPolicy) {
|
|
44
|
+
pregelTask, retryPolicy, configurable, signal) {
|
|
81
45
|
const resolvedRetryPolicy = pregelTask.retry_policy ?? retryPolicy;
|
|
82
46
|
let interval = resolvedRetryPolicy !== undefined
|
|
83
47
|
? resolvedRetryPolicy.initialInterval ?? DEFAULT_INITIAL_INTERVAL
|
|
@@ -85,28 +49,34 @@ pregelTask, retryPolicy) {
|
|
|
85
49
|
let attempts = 0;
|
|
86
50
|
let error;
|
|
87
51
|
let result;
|
|
52
|
+
let { config } = pregelTask;
|
|
53
|
+
if (configurable) {
|
|
54
|
+
config = patchConfigurable(config, configurable);
|
|
55
|
+
}
|
|
88
56
|
// eslint-disable-next-line no-constant-condition
|
|
89
57
|
while (true) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
58
|
+
if (signal?.aborted) {
|
|
59
|
+
// no need to throw here - we'll throw from the runner, instead.
|
|
60
|
+
// there's just no point in retrying if the user has requested an abort.
|
|
61
|
+
break;
|
|
93
62
|
}
|
|
63
|
+
// Clear any writes from previous attempts
|
|
64
|
+
pregelTask.writes.splice(0, pregelTask.writes.length);
|
|
94
65
|
error = undefined;
|
|
95
66
|
try {
|
|
96
|
-
result = await pregelTask.proc.invoke(pregelTask.input,
|
|
67
|
+
result = await pregelTask.proc.invoke(pregelTask.input, config);
|
|
97
68
|
break;
|
|
98
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
69
|
}
|
|
100
70
|
catch (e) {
|
|
101
71
|
error = e;
|
|
102
72
|
error.pregelTaskId = pregelTask.id;
|
|
103
73
|
if (isParentCommand(error)) {
|
|
104
|
-
const ns =
|
|
74
|
+
const ns = config?.configurable?.checkpoint_ns;
|
|
105
75
|
const cmd = error.command;
|
|
106
76
|
if (cmd.graph === ns) {
|
|
107
77
|
// this command is for the current graph, handle it
|
|
108
78
|
for (const writer of pregelTask.writers) {
|
|
109
|
-
await writer.invoke(cmd,
|
|
79
|
+
await writer.invoke(cmd, config);
|
|
110
80
|
}
|
|
111
81
|
break;
|
|
112
82
|
}
|
|
@@ -149,11 +119,13 @@ pregelTask, retryPolicy) {
|
|
|
149
119
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
150
120
|
error.constructor.unminifiable_name ??
|
|
151
121
|
error.constructor.name;
|
|
152
|
-
console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
|
|
122
|
+
console.log(`Retrying task "${String(pregelTask.name)}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
|
|
123
|
+
// signal subgraphs to resume (if available)
|
|
124
|
+
config = patchConfigurable(config, { [CONFIG_KEY_RESUMING]: true });
|
|
153
125
|
}
|
|
154
126
|
finally {
|
|
155
127
|
// Clear checkpoint_ns seen (for subgraph detection)
|
|
156
|
-
const checkpointNs =
|
|
128
|
+
const checkpointNs = config?.configurable?.checkpoint_ns;
|
|
157
129
|
if (checkpointNs) {
|
|
158
130
|
getSubgraphsSeenSet().delete(checkpointNs);
|
|
159
131
|
}
|
|
@@ -162,6 +134,6 @@ pregelTask, retryPolicy) {
|
|
|
162
134
|
return {
|
|
163
135
|
task: pregelTask,
|
|
164
136
|
result,
|
|
165
|
-
error,
|
|
137
|
+
error: error,
|
|
166
138
|
};
|
|
167
139
|
}
|
|
@@ -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
|
+
}
|