@langchain/langgraph 0.1.1 → 0.1.2
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 +2 -2
- package/dist/constants.cjs +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/graph/annotation.cjs +78 -4
- package/dist/graph/annotation.d.ts +82 -5
- package/dist/graph/annotation.js +77 -2
- package/dist/graph/graph.cjs +14 -8
- package/dist/graph/graph.d.ts +14 -5
- package/dist/graph/graph.js +14 -8
- package/dist/graph/message.cjs +6 -0
- package/dist/graph/message.d.ts +6 -0
- package/dist/graph/message.js +6 -0
- package/dist/graph/messages_annotation.cjs +5 -0
- package/dist/graph/messages_annotation.d.ts +5 -0
- package/dist/graph/messages_annotation.js +5 -0
- package/dist/graph/state.cjs +84 -3
- package/dist/graph/state.d.ts +75 -5
- package/dist/graph/state.js +86 -5
- package/dist/prebuilt/agent_executor.cjs +1 -0
- package/dist/prebuilt/agent_executor.d.ts +2 -0
- package/dist/prebuilt/agent_executor.js +1 -0
- package/dist/prebuilt/chat_agent_executor.cjs +1 -0
- package/dist/prebuilt/chat_agent_executor.d.ts +2 -0
- package/dist/prebuilt/chat_agent_executor.js +1 -0
- package/dist/pregel/algo.cjs +3 -41
- package/dist/pregel/algo.d.ts +0 -5
- package/dist/pregel/algo.js +2 -39
- package/dist/pregel/debug.d.ts +1 -1
- package/dist/pregel/index.cjs +17 -30
- package/dist/pregel/index.d.ts +2 -0
- package/dist/pregel/index.js +18 -31
- package/dist/pregel/loop.cjs +5 -2
- package/dist/pregel/loop.js +5 -2
- package/dist/pregel/read.cjs +19 -1
- package/dist/pregel/read.d.ts +5 -0
- package/dist/pregel/read.js +19 -1
- package/dist/pregel/retry.cjs +147 -0
- package/dist/pregel/retry.d.ts +11 -0
- package/dist/pregel/retry.js +143 -0
- package/dist/pregel/types.d.ts +4 -2
- package/dist/pregel/utils.d.ts +26 -0
- package/dist/web.d.ts +1 -0
- package/package.json +5 -4
package/dist/pregel/index.js
CHANGED
|
@@ -9,10 +9,11 @@ import { mapDebugTaskResults, printStepCheckpoint, printStepTasks, printStepWrit
|
|
|
9
9
|
import { ChannelWrite, PASSTHROUGH } from "./write.js";
|
|
10
10
|
import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, ERROR, INTERRUPT, } from "../constants.js";
|
|
11
11
|
import { GraphRecursionError, GraphValueError, InvalidUpdateError, } from "../errors.js";
|
|
12
|
-
import {
|
|
12
|
+
import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
|
|
13
13
|
import { prefixGenerator } from "../utils.js";
|
|
14
14
|
import { _coerceToDict, getNewChannelVersions } from "./utils.js";
|
|
15
15
|
import { PregelLoop } from "./loop.js";
|
|
16
|
+
import { executeTasksWithRetry } from "./retry.js";
|
|
16
17
|
function isString(value) {
|
|
17
18
|
return typeof value === "string";
|
|
18
19
|
}
|
|
@@ -155,6 +156,12 @@ export class Pregel extends Runnable {
|
|
|
155
156
|
writable: true,
|
|
156
157
|
value: void 0
|
|
157
158
|
});
|
|
159
|
+
Object.defineProperty(this, "retryPolicy", {
|
|
160
|
+
enumerable: true,
|
|
161
|
+
configurable: true,
|
|
162
|
+
writable: true,
|
|
163
|
+
value: void 0
|
|
164
|
+
});
|
|
158
165
|
let { streamMode } = fields;
|
|
159
166
|
if (streamMode != null && !Array.isArray(streamMode)) {
|
|
160
167
|
streamMode = [streamMode];
|
|
@@ -171,6 +178,7 @@ export class Pregel extends Runnable {
|
|
|
171
178
|
this.stepTimeout = fields.stepTimeout ?? this.stepTimeout;
|
|
172
179
|
this.debug = fields.debug ?? this.debug;
|
|
173
180
|
this.checkpointer = fields.checkpointer;
|
|
181
|
+
this.retryPolicy = fields.retryPolicy;
|
|
174
182
|
if (this.autoValidate) {
|
|
175
183
|
this.validate();
|
|
176
184
|
}
|
|
@@ -340,12 +348,11 @@ export class Pregel extends Runnable {
|
|
|
340
348
|
writers.length > 1 ? RunnableSequence.from(writers) : writers[0],
|
|
341
349
|
writes: [],
|
|
342
350
|
triggers: [INTERRUPT],
|
|
343
|
-
config: undefined,
|
|
344
351
|
id: uuid5(INTERRUPT, checkpoint.id),
|
|
345
352
|
};
|
|
346
353
|
// execute task
|
|
347
354
|
await task.proc.invoke(task.input, patchConfig(config, {
|
|
348
|
-
runName: `${this.
|
|
355
|
+
runName: config.runName ?? `${this.getName()}UpdateState`,
|
|
349
356
|
configurable: {
|
|
350
357
|
[CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
|
|
351
358
|
[CONFIG_KEY_READ]: _localRead.bind(undefined, checkpoint, channels,
|
|
@@ -486,35 +493,15 @@ export class Pregel extends Runnable {
|
|
|
486
493
|
if (debug) {
|
|
487
494
|
printStepTasks(loop.step, loop.tasks);
|
|
488
495
|
}
|
|
489
|
-
// execute tasks, and wait for one to fail or all to finish.
|
|
490
|
-
// each task is independent from all other concurrent tasks
|
|
491
|
-
// yield updates/debug output as each task finishes
|
|
492
|
-
const tasks = Object.fromEntries(loop.tasks
|
|
493
|
-
.filter((task) => task.writes.length === 0)
|
|
494
|
-
.map((pregelTask) => {
|
|
495
|
-
return [
|
|
496
|
-
pregelTask.id,
|
|
497
|
-
async () => {
|
|
498
|
-
let error;
|
|
499
|
-
let result;
|
|
500
|
-
try {
|
|
501
|
-
result = await pregelTask.proc.invoke(pregelTask.input, pregelTask.config);
|
|
502
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
503
|
-
}
|
|
504
|
-
catch (e) {
|
|
505
|
-
error = e;
|
|
506
|
-
error.pregelTaskId = pregelTask.id;
|
|
507
|
-
}
|
|
508
|
-
return {
|
|
509
|
-
task: pregelTask,
|
|
510
|
-
result,
|
|
511
|
-
error,
|
|
512
|
-
};
|
|
513
|
-
},
|
|
514
|
-
];
|
|
515
|
-
}));
|
|
516
496
|
try {
|
|
517
|
-
for
|
|
497
|
+
// execute tasks, and wait for one to fail or all to finish.
|
|
498
|
+
// each task is independent from all other concurrent tasks
|
|
499
|
+
// yield updates/debug output as each task finishes
|
|
500
|
+
for await (const task of executeTasksWithRetry(loop.tasks.filter((task) => task.writes.length === 0), {
|
|
501
|
+
stepTimeout: this.stepTimeout,
|
|
502
|
+
signal: config.signal,
|
|
503
|
+
retryPolicy: this.retryPolicy,
|
|
504
|
+
})) {
|
|
518
505
|
loop.putWrites(task.id, task.writes);
|
|
519
506
|
if (streamMode.includes("updates")) {
|
|
520
507
|
yield* prefixGenerator(mapOutputUpdates(outputKeys, [task]), streamMode.length > 1 ? "updates" : undefined);
|
package/dist/pregel/loop.cjs
CHANGED
|
@@ -269,7 +269,7 @@ class PregelLoop {
|
|
|
269
269
|
: (0, io_js_1.mapOutputValues)(outputKeys, writes, this.channels).next().value;
|
|
270
270
|
await this._putCheckpoint({
|
|
271
271
|
source: "loop",
|
|
272
|
-
writes: metadataWrites,
|
|
272
|
+
writes: metadataWrites ?? null,
|
|
273
273
|
});
|
|
274
274
|
// after execution, check if we should interrupt
|
|
275
275
|
if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptAfter, this.tasks)) {
|
|
@@ -377,7 +377,10 @@ class PregelLoop {
|
|
|
377
377
|
},
|
|
378
378
|
]), this.checkpointerGetNextVersion);
|
|
379
379
|
// save input checkpoint
|
|
380
|
-
await this._putCheckpoint({
|
|
380
|
+
await this._putCheckpoint({
|
|
381
|
+
source: "input",
|
|
382
|
+
writes: this.input ?? null,
|
|
383
|
+
});
|
|
381
384
|
}
|
|
382
385
|
// done with input
|
|
383
386
|
this.input = isResuming ? INPUT_RESUMING : INPUT_DONE;
|
package/dist/pregel/loop.js
CHANGED
|
@@ -263,7 +263,7 @@ export class PregelLoop {
|
|
|
263
263
|
: mapOutputValues(outputKeys, writes, this.channels).next().value;
|
|
264
264
|
await this._putCheckpoint({
|
|
265
265
|
source: "loop",
|
|
266
|
-
writes: metadataWrites,
|
|
266
|
+
writes: metadataWrites ?? null,
|
|
267
267
|
});
|
|
268
268
|
// after execution, check if we should interrupt
|
|
269
269
|
if (shouldInterrupt(this.checkpoint, interruptAfter, this.tasks)) {
|
|
@@ -371,7 +371,10 @@ export class PregelLoop {
|
|
|
371
371
|
},
|
|
372
372
|
]), this.checkpointerGetNextVersion);
|
|
373
373
|
// save input checkpoint
|
|
374
|
-
await this._putCheckpoint({
|
|
374
|
+
await this._putCheckpoint({
|
|
375
|
+
source: "input",
|
|
376
|
+
writes: this.input ?? null,
|
|
377
|
+
});
|
|
375
378
|
}
|
|
376
379
|
// done with input
|
|
377
380
|
this.input = isResuming ? INPUT_RESUMING : INPUT_DONE;
|
package/dist/pregel/read.cjs
CHANGED
|
@@ -63,7 +63,7 @@ const defaultRunnableBound =
|
|
|
63
63
|
/* #__PURE__ */ new runnables_1.RunnablePassthrough();
|
|
64
64
|
class PregelNode extends runnables_1.RunnableBinding {
|
|
65
65
|
constructor(fields) {
|
|
66
|
-
const { channels, triggers, mapper, writers, bound, kwargs } = fields;
|
|
66
|
+
const { channels, triggers, mapper, writers, bound, kwargs, metadata, retryPolicy, } = fields;
|
|
67
67
|
const mergedTags = [
|
|
68
68
|
...(fields.config?.tags ? fields.config.tags : []),
|
|
69
69
|
...(fields.tags ? fields.tags : []),
|
|
@@ -121,12 +121,26 @@ class PregelNode extends runnables_1.RunnableBinding {
|
|
|
121
121
|
writable: true,
|
|
122
122
|
value: {}
|
|
123
123
|
});
|
|
124
|
+
Object.defineProperty(this, "metadata", {
|
|
125
|
+
enumerable: true,
|
|
126
|
+
configurable: true,
|
|
127
|
+
writable: true,
|
|
128
|
+
value: {}
|
|
129
|
+
});
|
|
130
|
+
Object.defineProperty(this, "retryPolicy", {
|
|
131
|
+
enumerable: true,
|
|
132
|
+
configurable: true,
|
|
133
|
+
writable: true,
|
|
134
|
+
value: void 0
|
|
135
|
+
});
|
|
124
136
|
this.channels = channels;
|
|
125
137
|
this.triggers = triggers;
|
|
126
138
|
this.mapper = mapper;
|
|
127
139
|
this.writers = writers ?? this.writers;
|
|
128
140
|
this.bound = bound ?? this.bound;
|
|
129
141
|
this.kwargs = kwargs ?? this.kwargs;
|
|
142
|
+
this.metadata = metadata ?? this.metadata;
|
|
143
|
+
this.retryPolicy = retryPolicy;
|
|
130
144
|
}
|
|
131
145
|
getWriters() {
|
|
132
146
|
const newWriters = [...this.writers];
|
|
@@ -188,6 +202,7 @@ class PregelNode extends runnables_1.RunnableBinding {
|
|
|
188
202
|
bound: this.bound,
|
|
189
203
|
kwargs: this.kwargs,
|
|
190
204
|
config: this.config,
|
|
205
|
+
retryPolicy: this.retryPolicy,
|
|
191
206
|
});
|
|
192
207
|
}
|
|
193
208
|
pipe(coerceable) {
|
|
@@ -200,6 +215,7 @@ class PregelNode extends runnables_1.RunnableBinding {
|
|
|
200
215
|
bound: this.bound,
|
|
201
216
|
config: this.config,
|
|
202
217
|
kwargs: this.kwargs,
|
|
218
|
+
retryPolicy: this.retryPolicy,
|
|
203
219
|
});
|
|
204
220
|
}
|
|
205
221
|
else if (this.bound === defaultRunnableBound) {
|
|
@@ -211,6 +227,7 @@ class PregelNode extends runnables_1.RunnableBinding {
|
|
|
211
227
|
bound: (0, runnables_1._coerceToRunnable)(coerceable),
|
|
212
228
|
config: this.config,
|
|
213
229
|
kwargs: this.kwargs,
|
|
230
|
+
retryPolicy: this.retryPolicy,
|
|
214
231
|
});
|
|
215
232
|
}
|
|
216
233
|
else {
|
|
@@ -222,6 +239,7 @@ class PregelNode extends runnables_1.RunnableBinding {
|
|
|
222
239
|
bound: this.bound.pipe(coerceable),
|
|
223
240
|
config: this.config,
|
|
224
241
|
kwargs: this.kwargs,
|
|
242
|
+
retryPolicy: this.retryPolicy,
|
|
225
243
|
});
|
|
226
244
|
}
|
|
227
245
|
}
|
package/dist/pregel/read.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Runnable, RunnableBinding, RunnableBindingArgs, RunnableConfig, RunnableLike } from "@langchain/core/runnables";
|
|
2
2
|
import { RunnableCallable } from "../utils.js";
|
|
3
|
+
import type { RetryPolicy } from "./utils.js";
|
|
3
4
|
export declare class ChannelRead<RunInput = any> extends RunnableCallable {
|
|
4
5
|
lc_graph_name: string;
|
|
5
6
|
channel: string | Array<string>;
|
|
@@ -17,6 +18,8 @@ interface PregelNodeArgs<RunInput, RunOutput> extends Partial<RunnableBindingArg
|
|
|
17
18
|
bound?: Runnable<RunInput, RunOutput>;
|
|
18
19
|
kwargs?: Record<string, any>;
|
|
19
20
|
config?: RunnableConfig;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
retryPolicy?: RetryPolicy;
|
|
20
23
|
}
|
|
21
24
|
export type PregelNodeInputType = any;
|
|
22
25
|
export type PregelNodeOutputType = any;
|
|
@@ -28,6 +31,8 @@ export declare class PregelNode<RunInput = PregelNodeInputType, RunOutput = Preg
|
|
|
28
31
|
writers: Runnable[];
|
|
29
32
|
bound: Runnable<RunInput, RunOutput>;
|
|
30
33
|
kwargs: Record<string, any>;
|
|
34
|
+
metadata: Record<string, unknown>;
|
|
35
|
+
retryPolicy?: RetryPolicy;
|
|
31
36
|
constructor(fields: PregelNodeArgs<RunInput, RunOutput>);
|
|
32
37
|
getWriters(): Array<Runnable>;
|
|
33
38
|
getNode(): Runnable<RunInput, RunOutput> | undefined;
|
package/dist/pregel/read.js
CHANGED
|
@@ -59,7 +59,7 @@ const defaultRunnableBound =
|
|
|
59
59
|
/* #__PURE__ */ new RunnablePassthrough();
|
|
60
60
|
export class PregelNode extends RunnableBinding {
|
|
61
61
|
constructor(fields) {
|
|
62
|
-
const { channels, triggers, mapper, writers, bound, kwargs } = fields;
|
|
62
|
+
const { channels, triggers, mapper, writers, bound, kwargs, metadata, retryPolicy, } = fields;
|
|
63
63
|
const mergedTags = [
|
|
64
64
|
...(fields.config?.tags ? fields.config.tags : []),
|
|
65
65
|
...(fields.tags ? fields.tags : []),
|
|
@@ -117,12 +117,26 @@ export class PregelNode extends RunnableBinding {
|
|
|
117
117
|
writable: true,
|
|
118
118
|
value: {}
|
|
119
119
|
});
|
|
120
|
+
Object.defineProperty(this, "metadata", {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
configurable: true,
|
|
123
|
+
writable: true,
|
|
124
|
+
value: {}
|
|
125
|
+
});
|
|
126
|
+
Object.defineProperty(this, "retryPolicy", {
|
|
127
|
+
enumerable: true,
|
|
128
|
+
configurable: true,
|
|
129
|
+
writable: true,
|
|
130
|
+
value: void 0
|
|
131
|
+
});
|
|
120
132
|
this.channels = channels;
|
|
121
133
|
this.triggers = triggers;
|
|
122
134
|
this.mapper = mapper;
|
|
123
135
|
this.writers = writers ?? this.writers;
|
|
124
136
|
this.bound = bound ?? this.bound;
|
|
125
137
|
this.kwargs = kwargs ?? this.kwargs;
|
|
138
|
+
this.metadata = metadata ?? this.metadata;
|
|
139
|
+
this.retryPolicy = retryPolicy;
|
|
126
140
|
}
|
|
127
141
|
getWriters() {
|
|
128
142
|
const newWriters = [...this.writers];
|
|
@@ -184,6 +198,7 @@ export class PregelNode extends RunnableBinding {
|
|
|
184
198
|
bound: this.bound,
|
|
185
199
|
kwargs: this.kwargs,
|
|
186
200
|
config: this.config,
|
|
201
|
+
retryPolicy: this.retryPolicy,
|
|
187
202
|
});
|
|
188
203
|
}
|
|
189
204
|
pipe(coerceable) {
|
|
@@ -196,6 +211,7 @@ export class PregelNode extends RunnableBinding {
|
|
|
196
211
|
bound: this.bound,
|
|
197
212
|
config: this.config,
|
|
198
213
|
kwargs: this.kwargs,
|
|
214
|
+
retryPolicy: this.retryPolicy,
|
|
199
215
|
});
|
|
200
216
|
}
|
|
201
217
|
else if (this.bound === defaultRunnableBound) {
|
|
@@ -207,6 +223,7 @@ export class PregelNode extends RunnableBinding {
|
|
|
207
223
|
bound: _coerceToRunnable(coerceable),
|
|
208
224
|
config: this.config,
|
|
209
225
|
kwargs: this.kwargs,
|
|
226
|
+
retryPolicy: this.retryPolicy,
|
|
210
227
|
});
|
|
211
228
|
}
|
|
212
229
|
else {
|
|
@@ -218,6 +235,7 @@ export class PregelNode extends RunnableBinding {
|
|
|
218
235
|
bound: this.bound.pipe(coerceable),
|
|
219
236
|
config: this.config,
|
|
220
237
|
kwargs: this.kwargs,
|
|
238
|
+
retryPolicy: this.retryPolicy,
|
|
221
239
|
});
|
|
222
240
|
}
|
|
223
241
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.executeTasksWithRetry = exports.DEFAULT_MAX_RETRIES = exports.DEFAULT_MAX_INTERVAL = exports.DEFAULT_BACKOFF_FACTOR = exports.DEFAULT_INITIAL_INTERVAL = void 0;
|
|
4
|
+
const errors_js_1 = require("../errors.cjs");
|
|
5
|
+
exports.DEFAULT_INITIAL_INTERVAL = 500;
|
|
6
|
+
exports.DEFAULT_BACKOFF_FACTOR = 2;
|
|
7
|
+
exports.DEFAULT_MAX_INTERVAL = 128000;
|
|
8
|
+
exports.DEFAULT_MAX_RETRIES = 3;
|
|
9
|
+
const DEFAULT_STATUS_NO_RETRY = [
|
|
10
|
+
400,
|
|
11
|
+
401,
|
|
12
|
+
402,
|
|
13
|
+
403,
|
|
14
|
+
404,
|
|
15
|
+
405,
|
|
16
|
+
406,
|
|
17
|
+
407,
|
|
18
|
+
409, // Conflict
|
|
19
|
+
];
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
|
+
const DEFAULT_RETRY_ON_HANDLER = (error) => {
|
|
22
|
+
if (error.message.startsWith("Cancel") ||
|
|
23
|
+
error.message.startsWith("AbortError") ||
|
|
24
|
+
error.name === "AbortError") {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
if (error?.code === "ECONNABORTED") {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const status =
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
error?.response?.status ?? error?.status;
|
|
34
|
+
if (status && DEFAULT_STATUS_NO_RETRY.includes(+status)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
if (error?.error?.code === "insufficient_quota") {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
};
|
|
43
|
+
async function* executeTasksWithRetry(
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
tasks, options
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
) {
|
|
48
|
+
const { stepTimeout, retryPolicy } = options ?? {};
|
|
49
|
+
let signal = options?.signal;
|
|
50
|
+
// Start tasks
|
|
51
|
+
const executingTasksMap = Object.fromEntries(tasks.map((pregelTask) => {
|
|
52
|
+
return [pregelTask.id, _runWithRetry(pregelTask, retryPolicy)];
|
|
53
|
+
}));
|
|
54
|
+
if (stepTimeout && signal) {
|
|
55
|
+
if ("any" in AbortSignal) {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
signal = AbortSignal.any([
|
|
58
|
+
signal,
|
|
59
|
+
AbortSignal.timeout(stepTimeout),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (stepTimeout) {
|
|
64
|
+
signal = AbortSignal.timeout(stepTimeout);
|
|
65
|
+
}
|
|
66
|
+
// Abort if signal is aborted
|
|
67
|
+
signal?.throwIfAborted();
|
|
68
|
+
let listener;
|
|
69
|
+
const signalPromise = new Promise((_resolve, reject) => {
|
|
70
|
+
listener = () => reject(new Error("Abort"));
|
|
71
|
+
signal?.addEventListener("abort", listener);
|
|
72
|
+
}).finally(() => signal?.removeEventListener("abort", listener));
|
|
73
|
+
while (Object.keys(executingTasksMap).length > 0) {
|
|
74
|
+
const { task, error } = await Promise.race([
|
|
75
|
+
...Object.values(executingTasksMap),
|
|
76
|
+
signalPromise,
|
|
77
|
+
]);
|
|
78
|
+
if (error !== undefined) {
|
|
79
|
+
// TODO: don't stop others if exception is interrupt
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
yield task;
|
|
83
|
+
delete executingTasksMap[task.id];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.executeTasksWithRetry = executeTasksWithRetry;
|
|
87
|
+
async function _runWithRetry(
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
pregelTask, retryPolicy) {
|
|
90
|
+
const resolvedRetryPolicy = pregelTask.retry_policy ?? retryPolicy;
|
|
91
|
+
let interval = resolvedRetryPolicy !== undefined
|
|
92
|
+
? resolvedRetryPolicy.initialInterval ?? exports.DEFAULT_INITIAL_INTERVAL
|
|
93
|
+
: 0;
|
|
94
|
+
let attempts = 0;
|
|
95
|
+
let error;
|
|
96
|
+
let result;
|
|
97
|
+
// eslint-disable-next-line no-constant-condition
|
|
98
|
+
while (true) {
|
|
99
|
+
// Modify writes in place to clear any previous retries
|
|
100
|
+
while (pregelTask.writes.length > 0) {
|
|
101
|
+
pregelTask.writes.pop();
|
|
102
|
+
}
|
|
103
|
+
error = undefined;
|
|
104
|
+
try {
|
|
105
|
+
result = await pregelTask.proc.invoke(pregelTask.input, pregelTask.config);
|
|
106
|
+
break;
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
error = e;
|
|
111
|
+
error.pregelTaskId = pregelTask.id;
|
|
112
|
+
if (error.name === errors_js_1.GraphInterrupt.unminifiable_name) {
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (resolvedRetryPolicy === undefined) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
attempts += 1;
|
|
119
|
+
// check if we should give up
|
|
120
|
+
if (attempts >= (resolvedRetryPolicy.maxAttempts ?? exports.DEFAULT_MAX_RETRIES)) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
const retryOn = resolvedRetryPolicy.retryOn ?? DEFAULT_RETRY_ON_HANDLER;
|
|
124
|
+
if (!retryOn(error)) {
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
interval = Math.min(resolvedRetryPolicy.maxInterval ?? exports.DEFAULT_MAX_INTERVAL, interval * (resolvedRetryPolicy.backoffFactor ?? exports.DEFAULT_BACKOFF_FACTOR));
|
|
128
|
+
const intervalWithJitter = resolvedRetryPolicy.jitter
|
|
129
|
+
? Math.floor(interval + Math.random() * 1000)
|
|
130
|
+
: interval;
|
|
131
|
+
// sleep before retrying
|
|
132
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, intervalWithJitter));
|
|
134
|
+
// log the retry
|
|
135
|
+
const errorName = error.name ??
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
137
|
+
error.constructor.unminifiable_name ??
|
|
138
|
+
error.constructor.name;
|
|
139
|
+
console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)} seconds (attempt ${attempts}) after ${errorName}: ${error}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
task: pregelTask,
|
|
144
|
+
result,
|
|
145
|
+
error,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PregelExecutableTask } from "./types.js";
|
|
2
|
+
import type { RetryPolicy } from "./utils.js";
|
|
3
|
+
export declare const DEFAULT_INITIAL_INTERVAL = 500;
|
|
4
|
+
export declare const DEFAULT_BACKOFF_FACTOR = 2;
|
|
5
|
+
export declare const DEFAULT_MAX_INTERVAL = 128000;
|
|
6
|
+
export declare const DEFAULT_MAX_RETRIES = 3;
|
|
7
|
+
export declare function executeTasksWithRetry(tasks: PregelExecutableTask<any, any>[], options?: {
|
|
8
|
+
stepTimeout?: number;
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
retryPolicy?: RetryPolicy;
|
|
11
|
+
}): AsyncGenerator<PregelExecutableTask<any, any>>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { GraphInterrupt } from "../errors.js";
|
|
2
|
+
export const DEFAULT_INITIAL_INTERVAL = 500;
|
|
3
|
+
export const DEFAULT_BACKOFF_FACTOR = 2;
|
|
4
|
+
export const DEFAULT_MAX_INTERVAL = 128000;
|
|
5
|
+
export const DEFAULT_MAX_RETRIES = 3;
|
|
6
|
+
const DEFAULT_STATUS_NO_RETRY = [
|
|
7
|
+
400,
|
|
8
|
+
401,
|
|
9
|
+
402,
|
|
10
|
+
403,
|
|
11
|
+
404,
|
|
12
|
+
405,
|
|
13
|
+
406,
|
|
14
|
+
407,
|
|
15
|
+
409, // Conflict
|
|
16
|
+
];
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
const DEFAULT_RETRY_ON_HANDLER = (error) => {
|
|
19
|
+
if (error.message.startsWith("Cancel") ||
|
|
20
|
+
error.message.startsWith("AbortError") ||
|
|
21
|
+
error.name === "AbortError") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
+
if (error?.code === "ECONNABORTED") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const status =
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
error?.response?.status ?? error?.status;
|
|
31
|
+
if (status && DEFAULT_STATUS_NO_RETRY.includes(+status)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
35
|
+
if (error?.error?.code === "insufficient_quota") {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
};
|
|
40
|
+
export async function* executeTasksWithRetry(
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
+
tasks, options
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
) {
|
|
45
|
+
const { stepTimeout, retryPolicy } = options ?? {};
|
|
46
|
+
let signal = options?.signal;
|
|
47
|
+
// Start tasks
|
|
48
|
+
const executingTasksMap = Object.fromEntries(tasks.map((pregelTask) => {
|
|
49
|
+
return [pregelTask.id, _runWithRetry(pregelTask, retryPolicy)];
|
|
50
|
+
}));
|
|
51
|
+
if (stepTimeout && signal) {
|
|
52
|
+
if ("any" in AbortSignal) {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
54
|
+
signal = AbortSignal.any([
|
|
55
|
+
signal,
|
|
56
|
+
AbortSignal.timeout(stepTimeout),
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (stepTimeout) {
|
|
61
|
+
signal = AbortSignal.timeout(stepTimeout);
|
|
62
|
+
}
|
|
63
|
+
// Abort if signal is aborted
|
|
64
|
+
signal?.throwIfAborted();
|
|
65
|
+
let listener;
|
|
66
|
+
const signalPromise = new Promise((_resolve, reject) => {
|
|
67
|
+
listener = () => reject(new Error("Abort"));
|
|
68
|
+
signal?.addEventListener("abort", listener);
|
|
69
|
+
}).finally(() => signal?.removeEventListener("abort", listener));
|
|
70
|
+
while (Object.keys(executingTasksMap).length > 0) {
|
|
71
|
+
const { task, error } = await Promise.race([
|
|
72
|
+
...Object.values(executingTasksMap),
|
|
73
|
+
signalPromise,
|
|
74
|
+
]);
|
|
75
|
+
if (error !== undefined) {
|
|
76
|
+
// TODO: don't stop others if exception is interrupt
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
yield task;
|
|
80
|
+
delete executingTasksMap[task.id];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function _runWithRetry(
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
pregelTask, retryPolicy) {
|
|
86
|
+
const resolvedRetryPolicy = pregelTask.retry_policy ?? retryPolicy;
|
|
87
|
+
let interval = resolvedRetryPolicy !== undefined
|
|
88
|
+
? resolvedRetryPolicy.initialInterval ?? DEFAULT_INITIAL_INTERVAL
|
|
89
|
+
: 0;
|
|
90
|
+
let attempts = 0;
|
|
91
|
+
let error;
|
|
92
|
+
let result;
|
|
93
|
+
// eslint-disable-next-line no-constant-condition
|
|
94
|
+
while (true) {
|
|
95
|
+
// Modify writes in place to clear any previous retries
|
|
96
|
+
while (pregelTask.writes.length > 0) {
|
|
97
|
+
pregelTask.writes.pop();
|
|
98
|
+
}
|
|
99
|
+
error = undefined;
|
|
100
|
+
try {
|
|
101
|
+
result = await pregelTask.proc.invoke(pregelTask.input, pregelTask.config);
|
|
102
|
+
break;
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
error = e;
|
|
107
|
+
error.pregelTaskId = pregelTask.id;
|
|
108
|
+
if (error.name === GraphInterrupt.unminifiable_name) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
if (resolvedRetryPolicy === undefined) {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
attempts += 1;
|
|
115
|
+
// check if we should give up
|
|
116
|
+
if (attempts >= (resolvedRetryPolicy.maxAttempts ?? DEFAULT_MAX_RETRIES)) {
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
const retryOn = resolvedRetryPolicy.retryOn ?? DEFAULT_RETRY_ON_HANDLER;
|
|
120
|
+
if (!retryOn(error)) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
interval = Math.min(resolvedRetryPolicy.maxInterval ?? DEFAULT_MAX_INTERVAL, interval * (resolvedRetryPolicy.backoffFactor ?? DEFAULT_BACKOFF_FACTOR));
|
|
124
|
+
const intervalWithJitter = resolvedRetryPolicy.jitter
|
|
125
|
+
? Math.floor(interval + Math.random() * 1000)
|
|
126
|
+
: interval;
|
|
127
|
+
// sleep before retrying
|
|
128
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
129
|
+
await new Promise((resolve) => setTimeout(resolve, intervalWithJitter));
|
|
130
|
+
// log the retry
|
|
131
|
+
const errorName = error.name ??
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
133
|
+
error.constructor.unminifiable_name ??
|
|
134
|
+
error.constructor.name;
|
|
135
|
+
console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)} seconds (attempt ${attempts}) after ${errorName}: ${error}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
task: pregelTask,
|
|
140
|
+
result,
|
|
141
|
+
error,
|
|
142
|
+
};
|
|
143
|
+
}
|
package/dist/pregel/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Runnable, RunnableConfig } from "@langchain/core/runnables";
|
|
|
2
2
|
import type { PendingWrite, CheckpointMetadata, BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
|
|
3
3
|
import type { BaseChannel } from "../channels/base.js";
|
|
4
4
|
import type { PregelNode } from "./read.js";
|
|
5
|
+
import { RetryPolicy } from "./utils.js";
|
|
5
6
|
export type StreamMode = "values" | "updates" | "debug";
|
|
6
7
|
/**
|
|
7
8
|
* Construct a type with a set of properties K of type T
|
|
@@ -41,6 +42,7 @@ export interface PregelInterface<Nn extends StrRecord<string, PregelNode>, Cc ex
|
|
|
41
42
|
*/
|
|
42
43
|
debug?: boolean;
|
|
43
44
|
checkpointer?: BaseCheckpointSaver;
|
|
45
|
+
retryPolicy?: RetryPolicy;
|
|
44
46
|
}
|
|
45
47
|
export type PregelParams<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>> = Omit<PregelInterface<Nn, Cc>, "streamChannelsAsIs">;
|
|
46
48
|
export interface PregelTaskDescription {
|
|
@@ -53,9 +55,9 @@ export interface PregelExecutableTask<N extends PropertyKey, C extends PropertyK
|
|
|
53
55
|
readonly input: unknown;
|
|
54
56
|
readonly proc: Runnable;
|
|
55
57
|
readonly writes: PendingWrite<C>[];
|
|
56
|
-
readonly config
|
|
58
|
+
readonly config?: RunnableConfig;
|
|
57
59
|
readonly triggers: Array<string>;
|
|
58
|
-
readonly retry_policy?:
|
|
60
|
+
readonly retry_policy?: RetryPolicy;
|
|
59
61
|
readonly id: string;
|
|
60
62
|
}
|
|
61
63
|
export interface StateSnapshot {
|
package/dist/pregel/utils.d.ts
CHANGED
|
@@ -8,3 +8,29 @@ export declare function _getIdMetadata(metadata: Record<string, unknown>): {
|
|
|
8
8
|
langgraph_triggers: unknown;
|
|
9
9
|
langgraph_task_idx: unknown;
|
|
10
10
|
};
|
|
11
|
+
export type RetryPolicy = {
|
|
12
|
+
/**
|
|
13
|
+
* Amount of time that must elapse before the first retry occurs in milliseconds.
|
|
14
|
+
* @default 500
|
|
15
|
+
*/
|
|
16
|
+
initialInterval?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Multiplier by which the interval increases after each retry.
|
|
19
|
+
* @default 2
|
|
20
|
+
*/
|
|
21
|
+
backoffFactor?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Maximum amount of time that may elapse between retries in milliseconds.
|
|
24
|
+
* @default 128000
|
|
25
|
+
*/
|
|
26
|
+
maxInterval?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Maximum amount of time that may elapse between retries.
|
|
29
|
+
* @default 3
|
|
30
|
+
*/
|
|
31
|
+
maxAttempts?: number;
|
|
32
|
+
/** Whether to add random jitter to the interval between retries. */
|
|
33
|
+
jitter?: boolean;
|
|
34
|
+
/** A function that returns True for exceptions that should trigger a retry. */
|
|
35
|
+
retryOn?: (e: any) => boolean;
|
|
36
|
+
};
|
package/dist/web.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { END, Graph, type StateGraphArgs, START, StateGraph, type CompiledStateGraph, MessageGraph, messagesStateReducer, Annotation, type StateType, type UpdateType, type CompiledGraph, } from "./graph/index.js";
|
|
2
2
|
export { GraphRecursionError, GraphValueError, InvalidUpdateError, EmptyChannelError, } from "./errors.js";
|
|
3
|
+
export { type RetryPolicy } from "./pregel/utils.js";
|
|
3
4
|
export { Send } from "./constants.js";
|
|
4
5
|
export { MemorySaver, type Checkpoint, type CheckpointMetadata, type CheckpointTuple, copyCheckpoint, emptyCheckpoint, BaseCheckpointSaver, } from "@langchain/langgraph-checkpoint";
|