@langchain/langgraph 0.2.28 → 0.2.30
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/dist/constants.cjs +7 -1
- package/dist/constants.d.ts +24 -0
- package/dist/constants.js +6 -0
- package/dist/graph/graph.cjs +14 -0
- package/dist/graph/graph.d.ts +2 -0
- package/dist/graph/graph.js +14 -0
- package/dist/graph/state.cjs +2 -0
- package/dist/graph/state.js +2 -0
- package/dist/interrupt.cjs +85 -11
- package/dist/interrupt.d.ts +39 -0
- package/dist/interrupt.js +86 -12
- package/dist/pregel/algo.cjs +13 -14
- package/dist/pregel/algo.d.ts +1 -1
- package/dist/pregel/algo.js +14 -15
- package/dist/pregel/index.cjs +6 -1
- package/dist/pregel/index.js +7 -2
- package/dist/pregel/io.cjs +6 -2
- package/dist/pregel/io.d.ts +2 -2
- package/dist/pregel/io.js +6 -2
- package/dist/pregel/loop.cjs +21 -9
- package/dist/pregel/loop.js +22 -10
- package/dist/pregel/read.cjs +8 -1
- package/dist/pregel/read.d.ts +2 -0
- package/dist/pregel/read.js +8 -1
- package/dist/pregel/types.d.ts +5 -0
- package/package.json +1 -1
package/dist/constants.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports._isCommand = exports.Command = exports._isSend = exports.Send = exports._isSendInterface = exports.CHECKPOINT_NAMESPACE_END = exports.CHECKPOINT_NAMESPACE_SEPARATOR = exports.RESERVED = exports.NULL_TASK_ID = exports.TASK_NAMESPACE = exports.PULL = exports.PUSH = exports.TASKS = exports.SELF = exports.TAG_NOSTREAM = exports.TAG_HIDDEN = exports.RECURSION_LIMIT_DEFAULT = exports.RUNTIME_PLACEHOLDER = exports.RESUME = exports.INTERRUPT = exports.CONFIG_KEY_CHECKPOINT_MAP = exports.CONFIG_KEY_RESUME_VALUE = exports.CONFIG_KEY_STREAM = exports.CONFIG_KEY_TASK_ID = exports.CONFIG_KEY_RESUMING = exports.CONFIG_KEY_CHECKPOINTER = exports.CONFIG_KEY_READ = exports.CONFIG_KEY_SEND = exports.ERROR = exports.INPUT = exports.MISSING = void 0;
|
|
3
|
+
exports._isCommand = exports.Command = exports._isSend = exports.Send = exports._isSendInterface = exports.CHECKPOINT_NAMESPACE_END = exports.CHECKPOINT_NAMESPACE_SEPARATOR = exports.RESERVED = exports.NULL_TASK_ID = exports.TASK_NAMESPACE = exports.PULL = exports.PUSH = exports.TASKS = exports.SELF = exports.TAG_NOSTREAM = exports.TAG_HIDDEN = exports.RECURSION_LIMIT_DEFAULT = exports.RUNTIME_PLACEHOLDER = exports.RESUME = exports.INTERRUPT = exports.CONFIG_KEY_CHECKPOINT_MAP = exports.CONFIG_KEY_CHECKPOINT_NS = exports.CONFIG_KEY_SCRATCHPAD = exports.CONFIG_KEY_WRITES = exports.CONFIG_KEY_RESUME_VALUE = exports.CONFIG_KEY_STREAM = exports.CONFIG_KEY_TASK_ID = exports.CONFIG_KEY_RESUMING = exports.CONFIG_KEY_CHECKPOINTER = exports.CONFIG_KEY_READ = exports.CONFIG_KEY_SEND = exports.ERROR = exports.INPUT = exports.MISSING = void 0;
|
|
4
4
|
exports.MISSING = Symbol.for("__missing__");
|
|
5
5
|
exports.INPUT = "__input__";
|
|
6
6
|
exports.ERROR = "__error__";
|
|
@@ -11,6 +11,9 @@ exports.CONFIG_KEY_RESUMING = "__pregel_resuming";
|
|
|
11
11
|
exports.CONFIG_KEY_TASK_ID = "__pregel_task_id";
|
|
12
12
|
exports.CONFIG_KEY_STREAM = "__pregel_stream";
|
|
13
13
|
exports.CONFIG_KEY_RESUME_VALUE = "__pregel_resume_value";
|
|
14
|
+
exports.CONFIG_KEY_WRITES = "__pregel_writes";
|
|
15
|
+
exports.CONFIG_KEY_SCRATCHPAD = "__pregel_scratchpad";
|
|
16
|
+
exports.CONFIG_KEY_CHECKPOINT_NS = "checkpoint_ns";
|
|
14
17
|
// this one is part of public API
|
|
15
18
|
exports.CONFIG_KEY_CHECKPOINT_MAP = "checkpoint_map";
|
|
16
19
|
exports.INTERRUPT = "__interrupt__";
|
|
@@ -121,6 +124,9 @@ function _isSend(x) {
|
|
|
121
124
|
return operation !== undefined && operation.lg_name === "Send";
|
|
122
125
|
}
|
|
123
126
|
exports._isSend = _isSend;
|
|
127
|
+
/**
|
|
128
|
+
* One or more commands to update the graph's state and send messages to nodes.
|
|
129
|
+
*/
|
|
124
130
|
class Command {
|
|
125
131
|
constructor(args) {
|
|
126
132
|
Object.defineProperty(this, "lg_name", {
|
package/dist/constants.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export declare const CONFIG_KEY_RESUMING = "__pregel_resuming";
|
|
|
8
8
|
export declare const CONFIG_KEY_TASK_ID = "__pregel_task_id";
|
|
9
9
|
export declare const CONFIG_KEY_STREAM = "__pregel_stream";
|
|
10
10
|
export declare const CONFIG_KEY_RESUME_VALUE = "__pregel_resume_value";
|
|
11
|
+
export declare const CONFIG_KEY_WRITES = "__pregel_writes";
|
|
12
|
+
export declare const CONFIG_KEY_SCRATCHPAD = "__pregel_scratchpad";
|
|
13
|
+
export declare const CONFIG_KEY_CHECKPOINT_NS = "checkpoint_ns";
|
|
11
14
|
export declare const CONFIG_KEY_CHECKPOINT_MAP = "checkpoint_map";
|
|
12
15
|
export declare const INTERRUPT = "__interrupt__";
|
|
13
16
|
export declare const RESUME = "__resume__";
|
|
@@ -89,11 +92,32 @@ export type Interrupt = {
|
|
|
89
92
|
ns?: string[];
|
|
90
93
|
};
|
|
91
94
|
export type CommandParams<R> = {
|
|
95
|
+
/**
|
|
96
|
+
* Value to resume execution with. To be used together with {@link interrupt}.
|
|
97
|
+
*/
|
|
92
98
|
resume?: R;
|
|
99
|
+
/**
|
|
100
|
+
* Graph to send the command to. Supported values are:
|
|
101
|
+
* - None: the current graph (default)
|
|
102
|
+
* - GraphCommand.PARENT: closest parent graph
|
|
103
|
+
*/
|
|
93
104
|
graph?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Update to apply to the graph's state.
|
|
107
|
+
*/
|
|
94
108
|
update?: Record<string, any>;
|
|
109
|
+
/**
|
|
110
|
+
* Can be one of the following:
|
|
111
|
+
* - name of the node to navigate to next (any node that belongs to the specified `graph`)
|
|
112
|
+
* - sequence of node names to navigate to next
|
|
113
|
+
* - `Send` object (to execute a node with the input provided)
|
|
114
|
+
* - sequence of `Send` objects
|
|
115
|
+
*/
|
|
95
116
|
goto?: string | Send | (string | Send)[];
|
|
96
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* One or more commands to update the graph's state and send messages to nodes.
|
|
120
|
+
*/
|
|
97
121
|
export declare class Command<R = unknown> {
|
|
98
122
|
lg_name: string;
|
|
99
123
|
resume?: R;
|
package/dist/constants.js
CHANGED
|
@@ -8,6 +8,9 @@ export const CONFIG_KEY_RESUMING = "__pregel_resuming";
|
|
|
8
8
|
export const CONFIG_KEY_TASK_ID = "__pregel_task_id";
|
|
9
9
|
export const CONFIG_KEY_STREAM = "__pregel_stream";
|
|
10
10
|
export const CONFIG_KEY_RESUME_VALUE = "__pregel_resume_value";
|
|
11
|
+
export const CONFIG_KEY_WRITES = "__pregel_writes";
|
|
12
|
+
export const CONFIG_KEY_SCRATCHPAD = "__pregel_scratchpad";
|
|
13
|
+
export const CONFIG_KEY_CHECKPOINT_NS = "checkpoint_ns";
|
|
11
14
|
// this one is part of public API
|
|
12
15
|
export const CONFIG_KEY_CHECKPOINT_MAP = "checkpoint_map";
|
|
13
16
|
export const INTERRUPT = "__interrupt__";
|
|
@@ -115,6 +118,9 @@ export function _isSend(x) {
|
|
|
115
118
|
const operation = x;
|
|
116
119
|
return operation !== undefined && operation.lg_name === "Send";
|
|
117
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* One or more commands to update the graph's state and send messages to nodes.
|
|
123
|
+
*/
|
|
118
124
|
export class Command {
|
|
119
125
|
constructor(args) {
|
|
120
126
|
Object.defineProperty(this, "lg_name", {
|
package/dist/graph/graph.cjs
CHANGED
|
@@ -159,6 +159,7 @@ class Graph {
|
|
|
159
159
|
runnable,
|
|
160
160
|
metadata: options?.metadata,
|
|
161
161
|
subgraphs: (0, subgraph_js_1.isPregelLike)(runnable) ? [runnable] : options?.subgraphs,
|
|
162
|
+
ends: options?.ends,
|
|
162
163
|
};
|
|
163
164
|
return this;
|
|
164
165
|
}
|
|
@@ -290,6 +291,11 @@ class Graph {
|
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
293
|
}
|
|
294
|
+
for (const node of Object.values(this.nodes)) {
|
|
295
|
+
for (const target of node.ends ?? []) {
|
|
296
|
+
allTargets.add(target);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
293
299
|
// validate targets
|
|
294
300
|
for (const node of Object.keys(this.nodes)) {
|
|
295
301
|
if (!allTargets.has(node)) {
|
|
@@ -331,6 +337,7 @@ class CompiledGraph extends index_js_1.Pregel {
|
|
|
331
337
|
triggers: [],
|
|
332
338
|
metadata: node.metadata,
|
|
333
339
|
subgraphs: node.subgraphs,
|
|
340
|
+
ends: node.ends,
|
|
334
341
|
})
|
|
335
342
|
.pipe(node.runnable)
|
|
336
343
|
.pipe(new write_js_1.ChannelWrite([{ channel: key, value: write_js_1.PASSTHROUGH }], [constants_js_1.TAG_HIDDEN]));
|
|
@@ -526,6 +533,13 @@ class CompiledGraph extends index_js_1.Pregel {
|
|
|
526
533
|
}
|
|
527
534
|
}
|
|
528
535
|
}
|
|
536
|
+
for (const [key, node] of Object.entries(this.builder.nodes)) {
|
|
537
|
+
if (node.ends !== undefined) {
|
|
538
|
+
for (const end of node.ends) {
|
|
539
|
+
addEdge(_escapeMermaidKeywords(key), _escapeMermaidKeywords(end), undefined, true);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
529
543
|
return graph;
|
|
530
544
|
}
|
|
531
545
|
/**
|
package/dist/graph/graph.d.ts
CHANGED
|
@@ -30,10 +30,12 @@ export type NodeSpec<RunInput, RunOutput> = {
|
|
|
30
30
|
runnable: Runnable<RunInput, RunOutput>;
|
|
31
31
|
metadata?: Record<string, unknown>;
|
|
32
32
|
subgraphs?: Pregel<any, any>[];
|
|
33
|
+
ends?: string[];
|
|
33
34
|
};
|
|
34
35
|
export type AddNodeOptions = {
|
|
35
36
|
metadata?: Record<string, unknown>;
|
|
36
37
|
subgraphs?: Pregel<any, any>[];
|
|
38
|
+
ends?: string[];
|
|
37
39
|
};
|
|
38
40
|
export declare class Graph<N extends string = typeof END, RunInput = any, RunOutput = any, NodeSpecType extends NodeSpec<RunInput, RunOutput> = NodeSpec<RunInput, RunOutput>, C extends StateDefinition = StateDefinition> {
|
|
39
41
|
nodes: Record<N, NodeSpecType>;
|
package/dist/graph/graph.js
CHANGED
|
@@ -155,6 +155,7 @@ export class Graph {
|
|
|
155
155
|
runnable,
|
|
156
156
|
metadata: options?.metadata,
|
|
157
157
|
subgraphs: isPregelLike(runnable) ? [runnable] : options?.subgraphs,
|
|
158
|
+
ends: options?.ends,
|
|
158
159
|
};
|
|
159
160
|
return this;
|
|
160
161
|
}
|
|
@@ -286,6 +287,11 @@ export class Graph {
|
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
}
|
|
290
|
+
for (const node of Object.values(this.nodes)) {
|
|
291
|
+
for (const target of node.ends ?? []) {
|
|
292
|
+
allTargets.add(target);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
289
295
|
// validate targets
|
|
290
296
|
for (const node of Object.keys(this.nodes)) {
|
|
291
297
|
if (!allTargets.has(node)) {
|
|
@@ -326,6 +332,7 @@ export class CompiledGraph extends Pregel {
|
|
|
326
332
|
triggers: [],
|
|
327
333
|
metadata: node.metadata,
|
|
328
334
|
subgraphs: node.subgraphs,
|
|
335
|
+
ends: node.ends,
|
|
329
336
|
})
|
|
330
337
|
.pipe(node.runnable)
|
|
331
338
|
.pipe(new ChannelWrite([{ channel: key, value: PASSTHROUGH }], [TAG_HIDDEN]));
|
|
@@ -521,6 +528,13 @@ export class CompiledGraph extends Pregel {
|
|
|
521
528
|
}
|
|
522
529
|
}
|
|
523
530
|
}
|
|
531
|
+
for (const [key, node] of Object.entries(this.builder.nodes)) {
|
|
532
|
+
if (node.ends !== undefined) {
|
|
533
|
+
for (const end of node.ends) {
|
|
534
|
+
addEdge(_escapeMermaidKeywords(key), _escapeMermaidKeywords(end), undefined, true);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
524
538
|
return graph;
|
|
525
539
|
}
|
|
526
540
|
/**
|
package/dist/graph/state.cjs
CHANGED
|
@@ -240,6 +240,7 @@ class StateGraph extends graph_js_1.Graph {
|
|
|
240
240
|
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
241
241
|
[runnable]
|
|
242
242
|
: options?.subgraphs,
|
|
243
|
+
ends: options?.ends,
|
|
243
244
|
};
|
|
244
245
|
this.nodes[key] = nodeSpec;
|
|
245
246
|
return this;
|
|
@@ -431,6 +432,7 @@ class CompiledStateGraph extends graph_js_1.CompiledGraph {
|
|
|
431
432
|
metadata: node?.metadata,
|
|
432
433
|
retryPolicy: node?.retryPolicy,
|
|
433
434
|
subgraphs: node?.subgraphs,
|
|
435
|
+
ends: node?.ends,
|
|
434
436
|
});
|
|
435
437
|
}
|
|
436
438
|
}
|
package/dist/graph/state.js
CHANGED
|
@@ -237,6 +237,7 @@ export class StateGraph extends Graph {
|
|
|
237
237
|
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
238
238
|
[runnable]
|
|
239
239
|
: options?.subgraphs,
|
|
240
|
+
ends: options?.ends,
|
|
240
241
|
};
|
|
241
242
|
this.nodes[key] = nodeSpec;
|
|
242
243
|
return this;
|
|
@@ -427,6 +428,7 @@ export class CompiledStateGraph extends CompiledGraph {
|
|
|
427
428
|
metadata: node?.metadata,
|
|
428
429
|
retryPolicy: node?.retryPolicy,
|
|
429
430
|
subgraphs: node?.subgraphs,
|
|
431
|
+
ends: node?.ends,
|
|
430
432
|
});
|
|
431
433
|
}
|
|
432
434
|
}
|
package/dist/interrupt.cjs
CHANGED
|
@@ -4,24 +4,98 @@ exports.interrupt = void 0;
|
|
|
4
4
|
const singletons_1 = require("@langchain/core/singletons");
|
|
5
5
|
const errors_js_1 = require("./errors.cjs");
|
|
6
6
|
const constants_js_1 = require("./constants.cjs");
|
|
7
|
+
/**
|
|
8
|
+
* Interrupts the execution of a graph node.
|
|
9
|
+
* This function can be used to pause execution of a node, and return the value of the `resume`
|
|
10
|
+
* input when the graph is re-invoked using `Command`.
|
|
11
|
+
* Multiple interrupts can be called within a single node, and each will be handled sequentially.
|
|
12
|
+
*
|
|
13
|
+
* When an interrupt is called:
|
|
14
|
+
* 1. If there's a `resume` value available (from a previous `Command`), it returns that value.
|
|
15
|
+
* 2. Otherwise, it throws a `GraphInterrupt` with the provided value
|
|
16
|
+
* 3. The graph can be resumed by passing a `Command` with a `resume` value
|
|
17
|
+
*
|
|
18
|
+
* @param value - The value to include in the interrupt. This will be available in task.interrupts[].value
|
|
19
|
+
* @returns The `resume` value provided when the graph is re-invoked with a Command
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Define a node that uses multiple interrupts
|
|
24
|
+
* const nodeWithInterrupts = () => {
|
|
25
|
+
* // First interrupt - will pause execution and include {value: 1} in task values
|
|
26
|
+
* const answer1 = interrupt({ value: 1 });
|
|
27
|
+
*
|
|
28
|
+
* // Second interrupt - only called after first interrupt is resumed
|
|
29
|
+
* const answer2 = interrupt({ value: 2 });
|
|
30
|
+
*
|
|
31
|
+
* // Use the resume values
|
|
32
|
+
* return { myKey: answer1 + " " + answer2 };
|
|
33
|
+
* };
|
|
34
|
+
*
|
|
35
|
+
* // Resume the graph after first interrupt
|
|
36
|
+
* await graph.stream(new Command({ resume: "answer 1" }));
|
|
37
|
+
*
|
|
38
|
+
* // Resume the graph after second interrupt
|
|
39
|
+
* await graph.stream(new Command({ resume: "answer 2" }));
|
|
40
|
+
* // Final result: { myKey: "answer 1 answer 2" }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @throws {Error} If called outside the context of a graph
|
|
44
|
+
* @throws {GraphInterrupt} When no resume value is available
|
|
45
|
+
*/
|
|
7
46
|
function interrupt(value) {
|
|
8
47
|
const config = singletons_1.AsyncLocalStorageProviderSingleton.getRunnableConfig();
|
|
9
48
|
if (!config) {
|
|
10
49
|
throw new Error("Called interrupt() outside the context of a graph.");
|
|
11
50
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
51
|
+
// Track interrupt index
|
|
52
|
+
const scratchpad = config.configurable?.[constants_js_1.CONFIG_KEY_SCRATCHPAD];
|
|
53
|
+
if (scratchpad.interruptCounter === undefined) {
|
|
54
|
+
scratchpad.interruptCounter = 0;
|
|
15
55
|
}
|
|
16
56
|
else {
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
value,
|
|
20
|
-
when: "during",
|
|
21
|
-
resumable: true,
|
|
22
|
-
ns: config.configurable?.checkpoint_ns?.split("|"),
|
|
23
|
-
},
|
|
24
|
-
]);
|
|
57
|
+
scratchpad.interruptCounter += 1;
|
|
25
58
|
}
|
|
59
|
+
const idx = scratchpad.interruptCounter;
|
|
60
|
+
// Find previous resume values
|
|
61
|
+
const taskId = config.configurable?.[constants_js_1.CONFIG_KEY_TASK_ID];
|
|
62
|
+
const writes = config.configurable?.[constants_js_1.CONFIG_KEY_WRITES] ?? [];
|
|
63
|
+
if (!scratchpad.resume) {
|
|
64
|
+
const newResume = (writes.find((w) => w[0] === taskId && w[1] === constants_js_1.RESUME)?.[2] || []);
|
|
65
|
+
scratchpad.resume = Array.isArray(newResume) ? newResume : [newResume];
|
|
66
|
+
}
|
|
67
|
+
if (scratchpad.resume) {
|
|
68
|
+
if (idx < scratchpad.resume.length) {
|
|
69
|
+
return scratchpad.resume[idx];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Find current resume value
|
|
73
|
+
if (!scratchpad.usedNullResume) {
|
|
74
|
+
scratchpad.usedNullResume = true;
|
|
75
|
+
const sortedWrites = [...writes].sort((a, b) => b[0].localeCompare(a[0]) // Sort in reverse order
|
|
76
|
+
);
|
|
77
|
+
for (const [tid, c, v] of sortedWrites) {
|
|
78
|
+
if (tid === constants_js_1.NULL_TASK_ID && c === constants_js_1.RESUME) {
|
|
79
|
+
if (scratchpad.resume.length !== idx) {
|
|
80
|
+
throw new Error(`Resume length mismatch: ${scratchpad.resume.length} !== ${idx}`);
|
|
81
|
+
}
|
|
82
|
+
scratchpad.resume.push(v);
|
|
83
|
+
const send = config.configurable?.[constants_js_1.CONFIG_KEY_SEND];
|
|
84
|
+
if (send) {
|
|
85
|
+
send([[constants_js_1.RESUME, scratchpad.resume]]);
|
|
86
|
+
}
|
|
87
|
+
return v;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// No resume value found
|
|
92
|
+
throw new errors_js_1.GraphInterrupt([
|
|
93
|
+
{
|
|
94
|
+
value,
|
|
95
|
+
when: "during",
|
|
96
|
+
resumable: true,
|
|
97
|
+
ns: config.configurable?.[constants_js_1.CONFIG_KEY_CHECKPOINT_NS]?.split(constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR),
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
26
100
|
}
|
|
27
101
|
exports.interrupt = interrupt;
|
package/dist/interrupt.d.ts
CHANGED
|
@@ -1 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interrupts the execution of a graph node.
|
|
3
|
+
* This function can be used to pause execution of a node, and return the value of the `resume`
|
|
4
|
+
* input when the graph is re-invoked using `Command`.
|
|
5
|
+
* Multiple interrupts can be called within a single node, and each will be handled sequentially.
|
|
6
|
+
*
|
|
7
|
+
* When an interrupt is called:
|
|
8
|
+
* 1. If there's a `resume` value available (from a previous `Command`), it returns that value.
|
|
9
|
+
* 2. Otherwise, it throws a `GraphInterrupt` with the provided value
|
|
10
|
+
* 3. The graph can be resumed by passing a `Command` with a `resume` value
|
|
11
|
+
*
|
|
12
|
+
* @param value - The value to include in the interrupt. This will be available in task.interrupts[].value
|
|
13
|
+
* @returns The `resume` value provided when the graph is re-invoked with a Command
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Define a node that uses multiple interrupts
|
|
18
|
+
* const nodeWithInterrupts = () => {
|
|
19
|
+
* // First interrupt - will pause execution and include {value: 1} in task values
|
|
20
|
+
* const answer1 = interrupt({ value: 1 });
|
|
21
|
+
*
|
|
22
|
+
* // Second interrupt - only called after first interrupt is resumed
|
|
23
|
+
* const answer2 = interrupt({ value: 2 });
|
|
24
|
+
*
|
|
25
|
+
* // Use the resume values
|
|
26
|
+
* return { myKey: answer1 + " " + answer2 };
|
|
27
|
+
* };
|
|
28
|
+
*
|
|
29
|
+
* // Resume the graph after first interrupt
|
|
30
|
+
* await graph.stream(new Command({ resume: "answer 1" }));
|
|
31
|
+
*
|
|
32
|
+
* // Resume the graph after second interrupt
|
|
33
|
+
* await graph.stream(new Command({ resume: "answer 2" }));
|
|
34
|
+
* // Final result: { myKey: "answer 1 answer 2" }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @throws {Error} If called outside the context of a graph
|
|
38
|
+
* @throws {GraphInterrupt} When no resume value is available
|
|
39
|
+
*/
|
|
1
40
|
export declare function interrupt<I = unknown, R = unknown>(value: I): R;
|
package/dist/interrupt.js
CHANGED
|
@@ -1,23 +1,97 @@
|
|
|
1
1
|
import { AsyncLocalStorageProviderSingleton } from "@langchain/core/singletons";
|
|
2
2
|
import { GraphInterrupt } from "./errors.js";
|
|
3
|
-
import {
|
|
3
|
+
import { CONFIG_KEY_CHECKPOINT_NS, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_TASK_ID, CONFIG_KEY_WRITES, CONFIG_KEY_SEND, CHECKPOINT_NAMESPACE_SEPARATOR, NULL_TASK_ID, RESUME, } from "./constants.js";
|
|
4
|
+
/**
|
|
5
|
+
* Interrupts the execution of a graph node.
|
|
6
|
+
* This function can be used to pause execution of a node, and return the value of the `resume`
|
|
7
|
+
* input when the graph is re-invoked using `Command`.
|
|
8
|
+
* Multiple interrupts can be called within a single node, and each will be handled sequentially.
|
|
9
|
+
*
|
|
10
|
+
* When an interrupt is called:
|
|
11
|
+
* 1. If there's a `resume` value available (from a previous `Command`), it returns that value.
|
|
12
|
+
* 2. Otherwise, it throws a `GraphInterrupt` with the provided value
|
|
13
|
+
* 3. The graph can be resumed by passing a `Command` with a `resume` value
|
|
14
|
+
*
|
|
15
|
+
* @param value - The value to include in the interrupt. This will be available in task.interrupts[].value
|
|
16
|
+
* @returns The `resume` value provided when the graph is re-invoked with a Command
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* // Define a node that uses multiple interrupts
|
|
21
|
+
* const nodeWithInterrupts = () => {
|
|
22
|
+
* // First interrupt - will pause execution and include {value: 1} in task values
|
|
23
|
+
* const answer1 = interrupt({ value: 1 });
|
|
24
|
+
*
|
|
25
|
+
* // Second interrupt - only called after first interrupt is resumed
|
|
26
|
+
* const answer2 = interrupt({ value: 2 });
|
|
27
|
+
*
|
|
28
|
+
* // Use the resume values
|
|
29
|
+
* return { myKey: answer1 + " " + answer2 };
|
|
30
|
+
* };
|
|
31
|
+
*
|
|
32
|
+
* // Resume the graph after first interrupt
|
|
33
|
+
* await graph.stream(new Command({ resume: "answer 1" }));
|
|
34
|
+
*
|
|
35
|
+
* // Resume the graph after second interrupt
|
|
36
|
+
* await graph.stream(new Command({ resume: "answer 2" }));
|
|
37
|
+
* // Final result: { myKey: "answer 1 answer 2" }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @throws {Error} If called outside the context of a graph
|
|
41
|
+
* @throws {GraphInterrupt} When no resume value is available
|
|
42
|
+
*/
|
|
4
43
|
export function interrupt(value) {
|
|
5
44
|
const config = AsyncLocalStorageProviderSingleton.getRunnableConfig();
|
|
6
45
|
if (!config) {
|
|
7
46
|
throw new Error("Called interrupt() outside the context of a graph.");
|
|
8
47
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
48
|
+
// Track interrupt index
|
|
49
|
+
const scratchpad = config.configurable?.[CONFIG_KEY_SCRATCHPAD];
|
|
50
|
+
if (scratchpad.interruptCounter === undefined) {
|
|
51
|
+
scratchpad.interruptCounter = 0;
|
|
12
52
|
}
|
|
13
53
|
else {
|
|
14
|
-
|
|
15
|
-
{
|
|
16
|
-
value,
|
|
17
|
-
when: "during",
|
|
18
|
-
resumable: true,
|
|
19
|
-
ns: config.configurable?.checkpoint_ns?.split("|"),
|
|
20
|
-
},
|
|
21
|
-
]);
|
|
54
|
+
scratchpad.interruptCounter += 1;
|
|
22
55
|
}
|
|
56
|
+
const idx = scratchpad.interruptCounter;
|
|
57
|
+
// Find previous resume values
|
|
58
|
+
const taskId = config.configurable?.[CONFIG_KEY_TASK_ID];
|
|
59
|
+
const writes = config.configurable?.[CONFIG_KEY_WRITES] ?? [];
|
|
60
|
+
if (!scratchpad.resume) {
|
|
61
|
+
const newResume = (writes.find((w) => w[0] === taskId && w[1] === RESUME)?.[2] || []);
|
|
62
|
+
scratchpad.resume = Array.isArray(newResume) ? newResume : [newResume];
|
|
63
|
+
}
|
|
64
|
+
if (scratchpad.resume) {
|
|
65
|
+
if (idx < scratchpad.resume.length) {
|
|
66
|
+
return scratchpad.resume[idx];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Find current resume value
|
|
70
|
+
if (!scratchpad.usedNullResume) {
|
|
71
|
+
scratchpad.usedNullResume = true;
|
|
72
|
+
const sortedWrites = [...writes].sort((a, b) => b[0].localeCompare(a[0]) // Sort in reverse order
|
|
73
|
+
);
|
|
74
|
+
for (const [tid, c, v] of sortedWrites) {
|
|
75
|
+
if (tid === NULL_TASK_ID && c === RESUME) {
|
|
76
|
+
if (scratchpad.resume.length !== idx) {
|
|
77
|
+
throw new Error(`Resume length mismatch: ${scratchpad.resume.length} !== ${idx}`);
|
|
78
|
+
}
|
|
79
|
+
scratchpad.resume.push(v);
|
|
80
|
+
const send = config.configurable?.[CONFIG_KEY_SEND];
|
|
81
|
+
if (send) {
|
|
82
|
+
send([[RESUME, scratchpad.resume]]);
|
|
83
|
+
}
|
|
84
|
+
return v;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// No resume value found
|
|
89
|
+
throw new GraphInterrupt([
|
|
90
|
+
{
|
|
91
|
+
value,
|
|
92
|
+
when: "during",
|
|
93
|
+
resumable: true,
|
|
94
|
+
ns: config.configurable?.[CONFIG_KEY_CHECKPOINT_NS]?.split(CHECKPOINT_NAMESPACE_SEPARATOR),
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
23
97
|
}
|
package/dist/pregel/algo.cjs
CHANGED
|
@@ -75,7 +75,7 @@ function _localRead(step, checkpoint, channels, managed, task, select, fresh = f
|
|
|
75
75
|
exports._localRead = _localRead;
|
|
76
76
|
function _localWrite(step,
|
|
77
77
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
78
|
-
commit, processes,
|
|
78
|
+
commit, processes, managed,
|
|
79
79
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
80
|
writes) {
|
|
81
81
|
for (const [chan, value] of writes) {
|
|
@@ -89,9 +89,6 @@ writes) {
|
|
|
89
89
|
// replace any runtime values with placeholders
|
|
90
90
|
managed.replaceRuntimeValues(step, value.args);
|
|
91
91
|
}
|
|
92
|
-
else if (!(chan in channels) && !managed.get(chan)) {
|
|
93
|
-
console.warn(`Skipping write for channel '${chan}' which has no readers`);
|
|
94
|
-
}
|
|
95
92
|
}
|
|
96
93
|
commit(writes);
|
|
97
94
|
}
|
|
@@ -307,7 +304,6 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
307
304
|
metadata = { ...metadata, ...proc.metadata };
|
|
308
305
|
}
|
|
309
306
|
const writes = [];
|
|
310
|
-
const resume = pendingWrites?.find((w) => [taskId, constants_js_1.NULL_TASK_ID].includes(w[0]) && w[1] === constants_js_1.RESUME);
|
|
311
307
|
return {
|
|
312
308
|
name: packet.node,
|
|
313
309
|
input: packet.args,
|
|
@@ -324,7 +320,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
324
320
|
configurable: {
|
|
325
321
|
[constants_js_1.CONFIG_KEY_TASK_ID]: taskId,
|
|
326
322
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
327
|
-
[constants_js_1.CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes,
|
|
323
|
+
[constants_js_1.CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes, managed, writes_),
|
|
328
324
|
[constants_js_1.CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
|
|
329
325
|
name: packet.node,
|
|
330
326
|
writes: writes,
|
|
@@ -336,9 +332,11 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
336
332
|
...configurable[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP],
|
|
337
333
|
[parentNamespace]: checkpoint.id,
|
|
338
334
|
},
|
|
339
|
-
[constants_js_1.
|
|
340
|
-
|
|
341
|
-
|
|
335
|
+
[constants_js_1.CONFIG_KEY_WRITES]: [
|
|
336
|
+
...(pendingWrites || []),
|
|
337
|
+
...(configurable[constants_js_1.CONFIG_KEY_WRITES] || []),
|
|
338
|
+
].filter((w) => w[0] === constants_js_1.NULL_TASK_ID || w[0] === taskId),
|
|
339
|
+
[constants_js_1.CONFIG_KEY_SCRATCHPAD]: {},
|
|
342
340
|
checkpoint_id: undefined,
|
|
343
341
|
checkpoint_ns: taskCheckpointNamespace,
|
|
344
342
|
},
|
|
@@ -409,7 +407,6 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
409
407
|
metadata = { ...metadata, ...proc.metadata };
|
|
410
408
|
}
|
|
411
409
|
const writes = [];
|
|
412
|
-
const resume = pendingWrites?.find((w) => [taskId, constants_js_1.NULL_TASK_ID].includes(w[0]) && w[1] === constants_js_1.RESUME);
|
|
413
410
|
const taskCheckpointNamespace = `${checkpointNamespace}${constants_js_1.CHECKPOINT_NAMESPACE_END}${taskId}`;
|
|
414
411
|
return {
|
|
415
412
|
name,
|
|
@@ -429,7 +426,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
429
426
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
430
427
|
[constants_js_1.CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => {
|
|
431
428
|
writes.push(...items);
|
|
432
|
-
}, processes,
|
|
429
|
+
}, processes, managed, writes_),
|
|
433
430
|
[constants_js_1.CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
|
|
434
431
|
name,
|
|
435
432
|
writes: writes,
|
|
@@ -441,9 +438,11 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
441
438
|
...configurable[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP],
|
|
442
439
|
[parentNamespace]: checkpoint.id,
|
|
443
440
|
},
|
|
444
|
-
[constants_js_1.
|
|
445
|
-
|
|
446
|
-
|
|
441
|
+
[constants_js_1.CONFIG_KEY_WRITES]: [
|
|
442
|
+
...(pendingWrites || []),
|
|
443
|
+
...(configurable[constants_js_1.CONFIG_KEY_WRITES] || []),
|
|
444
|
+
].filter((w) => w[0] === constants_js_1.NULL_TASK_ID || w[0] === taskId),
|
|
445
|
+
[constants_js_1.CONFIG_KEY_SCRATCHPAD]: {},
|
|
447
446
|
checkpoint_id: undefined,
|
|
448
447
|
checkpoint_ns: taskCheckpointNamespace,
|
|
449
448
|
},
|
package/dist/pregel/algo.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export type WritesProtocol<C = string> = {
|
|
|
20
20
|
export declare const increment: (current?: number) => number;
|
|
21
21
|
export declare function shouldInterrupt<N extends PropertyKey, C extends PropertyKey>(checkpoint: Checkpoint, interruptNodes: All | N[], tasks: PregelExecutableTask<N, C>[]): boolean;
|
|
22
22
|
export declare function _localRead<Cc extends Record<string, BaseChannel>>(step: number, checkpoint: ReadonlyCheckpoint, channels: Cc, managed: ManagedValueMapping, task: WritesProtocol<keyof Cc>, select: Array<keyof Cc> | keyof Cc, fresh?: boolean): Record<string, unknown> | unknown;
|
|
23
|
-
export declare function _localWrite(step: number, commit: (writes: [string, any][]) => any, processes: Record<string, PregelNode>,
|
|
23
|
+
export declare function _localWrite(step: number, commit: (writes: [string, any][]) => any, processes: Record<string, PregelNode>, managed: ManagedValueMapping, writes: [string, any][]): void;
|
|
24
24
|
export declare function _applyWrites<Cc extends Record<string, BaseChannel>>(checkpoint: Checkpoint, channels: Cc, tasks: WritesProtocol<keyof Cc>[], getNextVersion?: (version: any, channel: BaseChannel) => any): Record<string, PendingWriteValue[]>;
|
|
25
25
|
export type NextTaskExtraFields = {
|
|
26
26
|
step: number;
|
package/dist/pregel/algo.js
CHANGED
|
@@ -3,7 +3,7 @@ import { mergeConfigs, patchConfig, } from "@langchain/core/runnables";
|
|
|
3
3
|
import { copyCheckpoint, uuid5, maxChannelVersion, } from "@langchain/langgraph-checkpoint";
|
|
4
4
|
import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
|
|
5
5
|
import { readChannel, readChannels } from "./io.js";
|
|
6
|
-
import { _isSend, _isSendInterface, CONFIG_KEY_CHECKPOINT_MAP, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_TASK_ID, CONFIG_KEY_SEND, INTERRUPT, RESERVED, TAG_HIDDEN, TASKS, CHECKPOINT_NAMESPACE_END, PUSH, PULL, RESUME,
|
|
6
|
+
import { _isSend, _isSendInterface, CONFIG_KEY_CHECKPOINT_MAP, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_TASK_ID, CONFIG_KEY_SEND, INTERRUPT, RESERVED, TAG_HIDDEN, TASKS, CHECKPOINT_NAMESPACE_END, PUSH, PULL, RESUME, NULL_TASK_ID, CONFIG_KEY_SCRATCHPAD, CONFIG_KEY_WRITES, } from "../constants.js";
|
|
7
7
|
import { EmptyChannelError, InvalidUpdateError } from "../errors.js";
|
|
8
8
|
import { getNullChannelVersion } from "./utils/index.js";
|
|
9
9
|
export const increment = (current) => {
|
|
@@ -69,7 +69,7 @@ export function _localRead(step, checkpoint, channels, managed, task, select, fr
|
|
|
69
69
|
}
|
|
70
70
|
export function _localWrite(step,
|
|
71
71
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
-
commit, processes,
|
|
72
|
+
commit, processes, managed,
|
|
73
73
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
74
|
writes) {
|
|
75
75
|
for (const [chan, value] of writes) {
|
|
@@ -83,9 +83,6 @@ writes) {
|
|
|
83
83
|
// replace any runtime values with placeholders
|
|
84
84
|
managed.replaceRuntimeValues(step, value.args);
|
|
85
85
|
}
|
|
86
|
-
else if (!(chan in channels) && !managed.get(chan)) {
|
|
87
|
-
console.warn(`Skipping write for channel '${chan}' which has no readers`);
|
|
88
|
-
}
|
|
89
86
|
}
|
|
90
87
|
commit(writes);
|
|
91
88
|
}
|
|
@@ -298,7 +295,6 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
298
295
|
metadata = { ...metadata, ...proc.metadata };
|
|
299
296
|
}
|
|
300
297
|
const writes = [];
|
|
301
|
-
const resume = pendingWrites?.find((w) => [taskId, NULL_TASK_ID].includes(w[0]) && w[1] === RESUME);
|
|
302
298
|
return {
|
|
303
299
|
name: packet.node,
|
|
304
300
|
input: packet.args,
|
|
@@ -315,7 +311,7 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
315
311
|
configurable: {
|
|
316
312
|
[CONFIG_KEY_TASK_ID]: taskId,
|
|
317
313
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
|
-
[CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes,
|
|
314
|
+
[CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => writes.push(...items), processes, managed, writes_),
|
|
319
315
|
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
|
|
320
316
|
name: packet.node,
|
|
321
317
|
writes: writes,
|
|
@@ -327,9 +323,11 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
327
323
|
...configurable[CONFIG_KEY_CHECKPOINT_MAP],
|
|
328
324
|
[parentNamespace]: checkpoint.id,
|
|
329
325
|
},
|
|
330
|
-
[
|
|
331
|
-
|
|
332
|
-
|
|
326
|
+
[CONFIG_KEY_WRITES]: [
|
|
327
|
+
...(pendingWrites || []),
|
|
328
|
+
...(configurable[CONFIG_KEY_WRITES] || []),
|
|
329
|
+
].filter((w) => w[0] === NULL_TASK_ID || w[0] === taskId),
|
|
330
|
+
[CONFIG_KEY_SCRATCHPAD]: {},
|
|
333
331
|
checkpoint_id: undefined,
|
|
334
332
|
checkpoint_ns: taskCheckpointNamespace,
|
|
335
333
|
},
|
|
@@ -400,7 +398,6 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
400
398
|
metadata = { ...metadata, ...proc.metadata };
|
|
401
399
|
}
|
|
402
400
|
const writes = [];
|
|
403
|
-
const resume = pendingWrites?.find((w) => [taskId, NULL_TASK_ID].includes(w[0]) && w[1] === RESUME);
|
|
404
401
|
const taskCheckpointNamespace = `${checkpointNamespace}${CHECKPOINT_NAMESPACE_END}${taskId}`;
|
|
405
402
|
return {
|
|
406
403
|
name,
|
|
@@ -420,7 +417,7 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
420
417
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
421
418
|
[CONFIG_KEY_SEND]: (writes_) => _localWrite(step, (items) => {
|
|
422
419
|
writes.push(...items);
|
|
423
|
-
}, processes,
|
|
420
|
+
}, processes, managed, writes_),
|
|
424
421
|
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed, {
|
|
425
422
|
name,
|
|
426
423
|
writes: writes,
|
|
@@ -432,9 +429,11 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
432
429
|
...configurable[CONFIG_KEY_CHECKPOINT_MAP],
|
|
433
430
|
[parentNamespace]: checkpoint.id,
|
|
434
431
|
},
|
|
435
|
-
[
|
|
436
|
-
|
|
437
|
-
|
|
432
|
+
[CONFIG_KEY_WRITES]: [
|
|
433
|
+
...(pendingWrites || []),
|
|
434
|
+
...(configurable[CONFIG_KEY_WRITES] || []),
|
|
435
|
+
].filter((w) => w[0] === NULL_TASK_ID || w[0] === taskId),
|
|
436
|
+
[CONFIG_KEY_SCRATCHPAD]: {},
|
|
438
437
|
checkpoint_id: undefined,
|
|
439
438
|
checkpoint_ns: taskCheckpointNamespace,
|
|
440
439
|
},
|
package/dist/pregel/index.cjs
CHANGED
|
@@ -863,7 +863,12 @@ class Pregel extends runnables_1.Runnable {
|
|
|
863
863
|
throw error;
|
|
864
864
|
}
|
|
865
865
|
if ((0, errors_js_1.isGraphInterrupt)(error) && error.interrupts.length) {
|
|
866
|
-
|
|
866
|
+
const interrupts = error.interrupts.map((interrupt) => [constants_js_1.INTERRUPT, interrupt]);
|
|
867
|
+
const resumes = task.writes.filter((w) => w[0] === constants_js_1.RESUME);
|
|
868
|
+
if (resumes.length) {
|
|
869
|
+
interrupts.push(...resumes);
|
|
870
|
+
}
|
|
871
|
+
loop.putWrites(task.id, interrupts);
|
|
867
872
|
}
|
|
868
873
|
}
|
|
869
874
|
else {
|
package/dist/pregel/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { validateGraph, validateKeys } from "./validate.js";
|
|
|
7
7
|
import { readChannels } from "./io.js";
|
|
8
8
|
import { printStepCheckpoint, printStepTasks, printStepWrites, tasksWithWrites, } from "./debug.js";
|
|
9
9
|
import { ChannelWrite, PASSTHROUGH } from "./write.js";
|
|
10
|
-
import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, ERROR, INTERRUPT, CHECKPOINT_NAMESPACE_SEPARATOR, CHECKPOINT_NAMESPACE_END, CONFIG_KEY_STREAM, CONFIG_KEY_TASK_ID, NULL_TASK_ID, INPUT, PUSH, } from "../constants.js";
|
|
10
|
+
import { CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_READ, CONFIG_KEY_SEND, ERROR, INTERRUPT, CHECKPOINT_NAMESPACE_SEPARATOR, CHECKPOINT_NAMESPACE_END, CONFIG_KEY_STREAM, CONFIG_KEY_TASK_ID, NULL_TASK_ID, INPUT, RESUME, PUSH, } from "../constants.js";
|
|
11
11
|
import { GraphRecursionError, GraphValueError, InvalidUpdateError, isGraphBubbleUp, isGraphInterrupt, } from "../errors.js";
|
|
12
12
|
import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
|
|
13
13
|
import { _coerceToDict, getNewChannelVersions, patchCheckpointMap, } from "./utils/index.js";
|
|
@@ -859,7 +859,12 @@ export class Pregel extends Runnable {
|
|
|
859
859
|
throw error;
|
|
860
860
|
}
|
|
861
861
|
if (isGraphInterrupt(error) && error.interrupts.length) {
|
|
862
|
-
|
|
862
|
+
const interrupts = error.interrupts.map((interrupt) => [INTERRUPT, interrupt]);
|
|
863
|
+
const resumes = task.writes.filter((w) => w[0] === RESUME);
|
|
864
|
+
if (resumes.length) {
|
|
865
|
+
interrupts.push(...resumes);
|
|
866
|
+
}
|
|
867
|
+
loop.putWrites(task.id, interrupts);
|
|
863
868
|
}
|
|
864
869
|
}
|
|
865
870
|
else {
|
package/dist/pregel/io.cjs
CHANGED
|
@@ -49,7 +49,7 @@ exports.readChannels = readChannels;
|
|
|
49
49
|
/**
|
|
50
50
|
* Map input chunk to a sequence of pending writes in the form (channel, value).
|
|
51
51
|
*/
|
|
52
|
-
function* mapCommand(cmd) {
|
|
52
|
+
function* mapCommand(cmd, pendingWrites) {
|
|
53
53
|
if (cmd.graph === constants_js_1.Command.PARENT) {
|
|
54
54
|
throw new errors_js_1.InvalidUpdateError("There is no parent graph.");
|
|
55
55
|
}
|
|
@@ -80,7 +80,11 @@ function* mapCommand(cmd) {
|
|
|
80
80
|
Object.keys(cmd.resume).length &&
|
|
81
81
|
Object.keys(cmd.resume).every(uuid_1.validate)) {
|
|
82
82
|
for (const [tid, resume] of Object.entries(cmd.resume)) {
|
|
83
|
-
|
|
83
|
+
// Find existing resume values for this task ID
|
|
84
|
+
const existing = (pendingWrites.find(([id, type]) => id === tid && type === constants_js_1.RESUME)?.[2] ?? []);
|
|
85
|
+
// Ensure we have an array and append the resume value
|
|
86
|
+
existing.push(resume);
|
|
87
|
+
yield [tid, constants_js_1.RESUME, existing];
|
|
84
88
|
}
|
|
85
89
|
}
|
|
86
90
|
else {
|
package/dist/pregel/io.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PendingWrite } from "@langchain/langgraph-checkpoint";
|
|
1
|
+
import type { CheckpointPendingWrite, PendingWrite } from "@langchain/langgraph-checkpoint";
|
|
2
2
|
import type { BaseChannel } from "../channels/base.js";
|
|
3
3
|
import type { PregelExecutableTask } from "./types.js";
|
|
4
4
|
import { Command } from "../constants.js";
|
|
@@ -7,7 +7,7 @@ export declare function readChannels<C extends PropertyKey>(channels: Record<C,
|
|
|
7
7
|
/**
|
|
8
8
|
* Map input chunk to a sequence of pending writes in the form (channel, value).
|
|
9
9
|
*/
|
|
10
|
-
export declare function mapCommand(cmd: Command): Generator<[string, string, unknown]>;
|
|
10
|
+
export declare function mapCommand(cmd: Command, pendingWrites: CheckpointPendingWrite<string>[]): Generator<[string, string, unknown]>;
|
|
11
11
|
/**
|
|
12
12
|
* Map input chunk to a sequence of pending writes in the form [channel, value].
|
|
13
13
|
*/
|
package/dist/pregel/io.js
CHANGED
|
@@ -44,7 +44,7 @@ export function readChannels(channels, select, skipEmpty = true
|
|
|
44
44
|
/**
|
|
45
45
|
* Map input chunk to a sequence of pending writes in the form (channel, value).
|
|
46
46
|
*/
|
|
47
|
-
export function* mapCommand(cmd) {
|
|
47
|
+
export function* mapCommand(cmd, pendingWrites) {
|
|
48
48
|
if (cmd.graph === Command.PARENT) {
|
|
49
49
|
throw new InvalidUpdateError("There is no parent graph.");
|
|
50
50
|
}
|
|
@@ -75,7 +75,11 @@ export function* mapCommand(cmd) {
|
|
|
75
75
|
Object.keys(cmd.resume).length &&
|
|
76
76
|
Object.keys(cmd.resume).every(validate)) {
|
|
77
77
|
for (const [tid, resume] of Object.entries(cmd.resume)) {
|
|
78
|
-
|
|
78
|
+
// Find existing resume values for this task ID
|
|
79
|
+
const existing = (pendingWrites.find(([id, type]) => id === tid && type === RESUME)?.[2] ?? []);
|
|
80
|
+
// Ensure we have an array and append the resume value
|
|
81
|
+
existing.push(resume);
|
|
82
|
+
yield [tid, RESUME, existing];
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
else {
|
package/dist/pregel/loop.cjs
CHANGED
|
@@ -399,14 +399,26 @@ class PregelLoop {
|
|
|
399
399
|
* @param writes
|
|
400
400
|
*/
|
|
401
401
|
putWrites(taskId, writes) {
|
|
402
|
-
|
|
402
|
+
let writesCopy = writes;
|
|
403
|
+
if (writesCopy.length === 0) {
|
|
403
404
|
return;
|
|
404
405
|
}
|
|
406
|
+
// deduplicate writes to special channels, last write wins
|
|
407
|
+
if (writesCopy.every(([key]) => key in langgraph_checkpoint_1.WRITES_IDX_MAP)) {
|
|
408
|
+
writesCopy = Array.from(new Map(writesCopy.map((w) => [w[0], w])).values());
|
|
409
|
+
}
|
|
405
410
|
// save writes
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
411
|
+
for (const [c, v] of writesCopy) {
|
|
412
|
+
if (c in langgraph_checkpoint_1.WRITES_IDX_MAP) {
|
|
413
|
+
const idx = this.checkpointPendingWrites.findIndex((w) => w[0] === taskId && w[1] === c);
|
|
414
|
+
if (idx !== -1) {
|
|
415
|
+
this.checkpointPendingWrites[idx] = [taskId, c, v];
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
this.checkpointPendingWrites.push([taskId, c, v]);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
410
422
|
const putWritePromise = this.checkpointer?.putWrites({
|
|
411
423
|
...this.checkpointConfig,
|
|
412
424
|
configurable: {
|
|
@@ -414,12 +426,12 @@ class PregelLoop {
|
|
|
414
426
|
checkpoint_ns: this.config.configurable?.checkpoint_ns ?? "",
|
|
415
427
|
checkpoint_id: this.checkpoint.id,
|
|
416
428
|
},
|
|
417
|
-
},
|
|
429
|
+
}, writesCopy, taskId);
|
|
418
430
|
if (putWritePromise !== undefined) {
|
|
419
431
|
this.checkpointerPromises.push(putWritePromise);
|
|
420
432
|
}
|
|
421
433
|
if (this.tasks) {
|
|
422
|
-
this._outputWrites(taskId,
|
|
434
|
+
this._outputWrites(taskId, writesCopy);
|
|
423
435
|
}
|
|
424
436
|
}
|
|
425
437
|
_outputWrites(taskId, writes, cached = false) {
|
|
@@ -457,7 +469,7 @@ class PregelLoop {
|
|
|
457
469
|
if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
|
|
458
470
|
await this._first(inputKeys);
|
|
459
471
|
}
|
|
460
|
-
else if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
|
|
472
|
+
else if (Object.values(this.tasks).every((task) => task.writes.filter(([c]) => !(c in langgraph_checkpoint_1.WRITES_IDX_MAP)).length > 0)) {
|
|
461
473
|
const writes = Object.values(this.tasks).flatMap((t) => t.writes);
|
|
462
474
|
// All tasks have finished
|
|
463
475
|
const managedValueWrites = (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
|
|
@@ -581,7 +593,7 @@ class PregelLoop {
|
|
|
581
593
|
if ((0, constants_js_1._isCommand)(this.input)) {
|
|
582
594
|
const writes = {};
|
|
583
595
|
// group writes by task id
|
|
584
|
-
for (const [tid, key, value] of (0, io_js_1.mapCommand)(this.input)) {
|
|
596
|
+
for (const [tid, key, value] of (0, io_js_1.mapCommand)(this.input, this.checkpointPendingWrites)) {
|
|
585
597
|
if (writes[tid] === undefined) {
|
|
586
598
|
writes[tid] = [];
|
|
587
599
|
}
|
package/dist/pregel/loop.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IterableReadableStream } from "@langchain/core/utils/stream";
|
|
2
|
-
import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, } from "@langchain/langgraph-checkpoint";
|
|
2
|
+
import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, WRITES_IDX_MAP, } from "@langchain/langgraph-checkpoint";
|
|
3
3
|
import { createCheckpoint, emptyChannels, } from "../channels/base.js";
|
|
4
4
|
import { _isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, RESUME, TAG_HIDDEN, } from "../constants.js";
|
|
5
5
|
import { _applyWrites, _prepareNextTasks, increment, shouldInterrupt, } from "./algo.js";
|
|
@@ -395,14 +395,26 @@ export class PregelLoop {
|
|
|
395
395
|
* @param writes
|
|
396
396
|
*/
|
|
397
397
|
putWrites(taskId, writes) {
|
|
398
|
-
|
|
398
|
+
let writesCopy = writes;
|
|
399
|
+
if (writesCopy.length === 0) {
|
|
399
400
|
return;
|
|
400
401
|
}
|
|
402
|
+
// deduplicate writes to special channels, last write wins
|
|
403
|
+
if (writesCopy.every(([key]) => key in WRITES_IDX_MAP)) {
|
|
404
|
+
writesCopy = Array.from(new Map(writesCopy.map((w) => [w[0], w])).values());
|
|
405
|
+
}
|
|
401
406
|
// save writes
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
407
|
+
for (const [c, v] of writesCopy) {
|
|
408
|
+
if (c in WRITES_IDX_MAP) {
|
|
409
|
+
const idx = this.checkpointPendingWrites.findIndex((w) => w[0] === taskId && w[1] === c);
|
|
410
|
+
if (idx !== -1) {
|
|
411
|
+
this.checkpointPendingWrites[idx] = [taskId, c, v];
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
this.checkpointPendingWrites.push([taskId, c, v]);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
406
418
|
const putWritePromise = this.checkpointer?.putWrites({
|
|
407
419
|
...this.checkpointConfig,
|
|
408
420
|
configurable: {
|
|
@@ -410,12 +422,12 @@ export class PregelLoop {
|
|
|
410
422
|
checkpoint_ns: this.config.configurable?.checkpoint_ns ?? "",
|
|
411
423
|
checkpoint_id: this.checkpoint.id,
|
|
412
424
|
},
|
|
413
|
-
},
|
|
425
|
+
}, writesCopy, taskId);
|
|
414
426
|
if (putWritePromise !== undefined) {
|
|
415
427
|
this.checkpointerPromises.push(putWritePromise);
|
|
416
428
|
}
|
|
417
429
|
if (this.tasks) {
|
|
418
|
-
this._outputWrites(taskId,
|
|
430
|
+
this._outputWrites(taskId, writesCopy);
|
|
419
431
|
}
|
|
420
432
|
}
|
|
421
433
|
_outputWrites(taskId, writes, cached = false) {
|
|
@@ -453,7 +465,7 @@ export class PregelLoop {
|
|
|
453
465
|
if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
|
|
454
466
|
await this._first(inputKeys);
|
|
455
467
|
}
|
|
456
|
-
else if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
|
|
468
|
+
else if (Object.values(this.tasks).every((task) => task.writes.filter(([c]) => !(c in WRITES_IDX_MAP)).length > 0)) {
|
|
457
469
|
const writes = Object.values(this.tasks).flatMap((t) => t.writes);
|
|
458
470
|
// All tasks have finished
|
|
459
471
|
const managedValueWrites = _applyWrites(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
|
|
@@ -577,7 +589,7 @@ export class PregelLoop {
|
|
|
577
589
|
if (_isCommand(this.input)) {
|
|
578
590
|
const writes = {};
|
|
579
591
|
// group writes by task id
|
|
580
|
-
for (const [tid, key, value] of mapCommand(this.input)) {
|
|
592
|
+
for (const [tid, key, value] of mapCommand(this.input, this.checkpointPendingWrites)) {
|
|
581
593
|
if (writes[tid] === undefined) {
|
|
582
594
|
writes[tid] = [];
|
|
583
595
|
}
|
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, metadata, retryPolicy, tags, subgraphs, } = fields;
|
|
66
|
+
const { channels, triggers, mapper, writers, bound, kwargs, metadata, retryPolicy, tags, subgraphs, ends, } = fields;
|
|
67
67
|
const mergedTags = [
|
|
68
68
|
...(fields.config?.tags ? fields.config.tags : []),
|
|
69
69
|
...(tags ?? []),
|
|
@@ -145,6 +145,12 @@ class PregelNode extends runnables_1.RunnableBinding {
|
|
|
145
145
|
writable: true,
|
|
146
146
|
value: void 0
|
|
147
147
|
});
|
|
148
|
+
Object.defineProperty(this, "ends", {
|
|
149
|
+
enumerable: true,
|
|
150
|
+
configurable: true,
|
|
151
|
+
writable: true,
|
|
152
|
+
value: void 0
|
|
153
|
+
});
|
|
148
154
|
this.channels = channels;
|
|
149
155
|
this.triggers = triggers;
|
|
150
156
|
this.mapper = mapper;
|
|
@@ -155,6 +161,7 @@ class PregelNode extends runnables_1.RunnableBinding {
|
|
|
155
161
|
this.tags = mergedTags;
|
|
156
162
|
this.retryPolicy = retryPolicy;
|
|
157
163
|
this.subgraphs = subgraphs;
|
|
164
|
+
this.ends = ends;
|
|
158
165
|
}
|
|
159
166
|
getWriters() {
|
|
160
167
|
const newWriters = [...this.writers];
|
package/dist/pregel/read.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ interface PregelNodeArgs<RunInput, RunOutput> extends Partial<RunnableBindingArg
|
|
|
21
21
|
metadata?: Record<string, unknown>;
|
|
22
22
|
retryPolicy?: RetryPolicy;
|
|
23
23
|
subgraphs?: Runnable[];
|
|
24
|
+
ends?: string[];
|
|
24
25
|
}
|
|
25
26
|
export type PregelNodeInputType = any;
|
|
26
27
|
export type PregelNodeOutputType = any;
|
|
@@ -36,6 +37,7 @@ export declare class PregelNode<RunInput = PregelNodeInputType, RunOutput = Preg
|
|
|
36
37
|
tags: string[];
|
|
37
38
|
retryPolicy?: RetryPolicy;
|
|
38
39
|
subgraphs?: Runnable[];
|
|
40
|
+
ends?: string[];
|
|
39
41
|
constructor(fields: PregelNodeArgs<RunInput, RunOutput>);
|
|
40
42
|
getWriters(): Array<Runnable>;
|
|
41
43
|
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, metadata, retryPolicy, tags, subgraphs, } = fields;
|
|
62
|
+
const { channels, triggers, mapper, writers, bound, kwargs, metadata, retryPolicy, tags, subgraphs, ends, } = fields;
|
|
63
63
|
const mergedTags = [
|
|
64
64
|
...(fields.config?.tags ? fields.config.tags : []),
|
|
65
65
|
...(tags ?? []),
|
|
@@ -141,6 +141,12 @@ export class PregelNode extends RunnableBinding {
|
|
|
141
141
|
writable: true,
|
|
142
142
|
value: void 0
|
|
143
143
|
});
|
|
144
|
+
Object.defineProperty(this, "ends", {
|
|
145
|
+
enumerable: true,
|
|
146
|
+
configurable: true,
|
|
147
|
+
writable: true,
|
|
148
|
+
value: void 0
|
|
149
|
+
});
|
|
144
150
|
this.channels = channels;
|
|
145
151
|
this.triggers = triggers;
|
|
146
152
|
this.mapper = mapper;
|
|
@@ -151,6 +157,7 @@ export class PregelNode extends RunnableBinding {
|
|
|
151
157
|
this.tags = mergedTags;
|
|
152
158
|
this.retryPolicy = retryPolicy;
|
|
153
159
|
this.subgraphs = subgraphs;
|
|
160
|
+
this.ends = ends;
|
|
154
161
|
}
|
|
155
162
|
getWriters() {
|
|
156
163
|
const newWriters = [...this.writers];
|
package/dist/pregel/types.d.ts
CHANGED