@langchain/langgraph 0.2.27 → 0.2.28
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 +33 -2
- package/dist/constants.d.ts +13 -4
- package/dist/constants.js +32 -1
- package/dist/errors.cjs +23 -1
- package/dist/errors.d.ts +7 -1
- package/dist/errors.js +20 -0
- package/dist/graph/graph.cjs +34 -8
- package/dist/graph/graph.d.ts +6 -5
- package/dist/graph/graph.js +35 -9
- package/dist/graph/state.cjs +76 -16
- package/dist/graph/state.d.ts +3 -1
- package/dist/graph/state.js +79 -19
- package/dist/prebuilt/tool_node.cjs +7 -0
- package/dist/prebuilt/tool_node.js +7 -0
- package/dist/pregel/algo.cjs +32 -1
- package/dist/pregel/algo.d.ts +3 -2
- package/dist/pregel/algo.js +32 -1
- package/dist/pregel/index.cjs +13 -2
- package/dist/pregel/index.js +14 -3
- package/dist/pregel/io.cjs +35 -0
- package/dist/pregel/io.d.ts +3 -0
- package/dist/pregel/io.js +37 -2
- package/dist/pregel/loop.cjs +17 -16
- package/dist/pregel/loop.js +17 -16
- package/dist/pregel/retry.cjs +23 -0
- package/dist/pregel/retry.js +24 -1
- package/dist/pregel/types.d.ts +1 -0
- package/dist/pregel/write.cjs +27 -24
- package/dist/pregel/write.d.ts +1 -2
- package/dist/pregel/write.js +27 -24
- package/dist/web.d.ts +1 -1
- package/dist/web.js +1 -1
- package/package.json +1 -1
package/dist/graph/state.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
|
2
2
|
import { _coerceToRunnable, Runnable, } from "@langchain/core/runnables";
|
|
3
3
|
import { isBaseChannel } from "../channels/base.js";
|
|
4
|
-
import { END, CompiledGraph, Graph, START, } from "./graph.js";
|
|
4
|
+
import { END, CompiledGraph, Graph, START, Branch, } from "./graph.js";
|
|
5
5
|
import { ChannelWrite, PASSTHROUGH, SKIP_WRITE, } from "../pregel/write.js";
|
|
6
6
|
import { ChannelRead, PregelNode } from "../pregel/read.js";
|
|
7
7
|
import { NamedBarrierValue } from "../channels/named_barrier_value.js";
|
|
8
8
|
import { EphemeralValue } from "../channels/ephemeral_value.js";
|
|
9
9
|
import { RunnableCallable } from "../utils.js";
|
|
10
|
-
import { _isSend, CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, TAG_HIDDEN, } from "../constants.js";
|
|
11
|
-
import { InvalidUpdateError } from "../errors.js";
|
|
10
|
+
import { _isCommand, _isSend, CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, Command, SELF, TAG_HIDDEN, } from "../constants.js";
|
|
11
|
+
import { InvalidUpdateError, ParentCommand } from "../errors.js";
|
|
12
12
|
import { getChannel, } from "./annotation.js";
|
|
13
13
|
import { isConfiguredManagedValue } from "../managed/base.js";
|
|
14
14
|
import { isPregelLike } from "../pregel/utils/subgraph.js";
|
|
@@ -300,6 +300,14 @@ export class StateGraph extends Graph {
|
|
|
300
300
|
for (const [key, node] of Object.entries(this.nodes)) {
|
|
301
301
|
compiled.attachNode(key, node);
|
|
302
302
|
}
|
|
303
|
+
compiled.attachBranch(START, SELF, _getControlBranch(), {
|
|
304
|
+
withReader: false,
|
|
305
|
+
});
|
|
306
|
+
for (const [key] of Object.entries(this.nodes)) {
|
|
307
|
+
compiled.attachBranch(key, SELF, _getControlBranch(), {
|
|
308
|
+
withReader: false,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
303
311
|
for (const [start, end] of this.edges) {
|
|
304
312
|
compiled.attachEdge(start, end);
|
|
305
313
|
}
|
|
@@ -335,13 +343,30 @@ function _getChannels(schema) {
|
|
|
335
343
|
export class CompiledStateGraph extends CompiledGraph {
|
|
336
344
|
attachNode(key, node) {
|
|
337
345
|
const stateKeys = Object.keys(this.builder.channels);
|
|
346
|
+
function _getRoot(input) {
|
|
347
|
+
if (_isCommand(input)) {
|
|
348
|
+
if (input.graph === Command.PARENT) {
|
|
349
|
+
return SKIP_WRITE;
|
|
350
|
+
}
|
|
351
|
+
return input.update;
|
|
352
|
+
}
|
|
353
|
+
return input;
|
|
354
|
+
}
|
|
355
|
+
// to avoid name collision below
|
|
356
|
+
const nodeKey = key;
|
|
338
357
|
function getStateKey(key, input) {
|
|
339
358
|
if (!input) {
|
|
340
359
|
return SKIP_WRITE;
|
|
341
360
|
}
|
|
361
|
+
else if (_isCommand(input)) {
|
|
362
|
+
if (input.graph === Command.PARENT) {
|
|
363
|
+
return SKIP_WRITE;
|
|
364
|
+
}
|
|
365
|
+
return getStateKey(key, input.update);
|
|
366
|
+
}
|
|
342
367
|
else if (typeof input !== "object" || Array.isArray(input)) {
|
|
343
368
|
const typeofInput = Array.isArray(input) ? "array" : typeof input;
|
|
344
|
-
throw new InvalidUpdateError(`Expected node "${
|
|
369
|
+
throw new InvalidUpdateError(`Expected node "${nodeKey.toString()}" to return an object, received ${typeofInput}`, {
|
|
345
370
|
lc_error_code: "INVALID_GRAPH_NODE_RETURN_VALUE",
|
|
346
371
|
});
|
|
347
372
|
}
|
|
@@ -351,7 +376,16 @@ export class CompiledStateGraph extends CompiledGraph {
|
|
|
351
376
|
}
|
|
352
377
|
// state updaters
|
|
353
378
|
const stateWriteEntries = stateKeys.map((key) => key === ROOT
|
|
354
|
-
? {
|
|
379
|
+
? {
|
|
380
|
+
channel: key,
|
|
381
|
+
value: PASSTHROUGH,
|
|
382
|
+
skipNone: true,
|
|
383
|
+
mapper: new RunnableCallable({
|
|
384
|
+
func: _getRoot,
|
|
385
|
+
trace: false,
|
|
386
|
+
recurse: false,
|
|
387
|
+
}),
|
|
388
|
+
}
|
|
355
389
|
: {
|
|
356
390
|
channel: key,
|
|
357
391
|
value: PASSTHROUGH,
|
|
@@ -426,28 +460,29 @@ export class CompiledStateGraph extends CompiledGraph {
|
|
|
426
460
|
this.nodes[end].triggers.push(start);
|
|
427
461
|
}
|
|
428
462
|
}
|
|
429
|
-
attachBranch(start, name, branch) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
(dests) => {
|
|
434
|
-
const filteredDests = dests.filter((dest) => dest !== END);
|
|
435
|
-
if (!filteredDests.length) {
|
|
463
|
+
attachBranch(start, name, branch, options = { withReader: true }) {
|
|
464
|
+
const branchWriter = async (packets, config) => {
|
|
465
|
+
const filteredPackets = packets.filter((p) => p !== END);
|
|
466
|
+
if (!filteredPackets.length) {
|
|
436
467
|
return;
|
|
437
468
|
}
|
|
438
|
-
const writes =
|
|
439
|
-
if (_isSend(
|
|
440
|
-
return
|
|
469
|
+
const writes = filteredPackets.map((p) => {
|
|
470
|
+
if (_isSend(p)) {
|
|
471
|
+
return p;
|
|
441
472
|
}
|
|
442
473
|
return {
|
|
443
|
-
channel: `branch:${start}:${name}:${
|
|
474
|
+
channel: `branch:${start}:${name}:${p}`,
|
|
444
475
|
value: start,
|
|
445
476
|
};
|
|
446
477
|
});
|
|
447
|
-
|
|
448
|
-
}
|
|
478
|
+
await ChannelWrite.doWrite({ ...config, tags: (config.tags ?? []).concat([TAG_HIDDEN]) }, writes);
|
|
479
|
+
};
|
|
480
|
+
// attach branch publisher
|
|
481
|
+
this.nodes[start].writers.push(branch.run(branchWriter,
|
|
449
482
|
// reader
|
|
450
|
-
|
|
483
|
+
options.withReader
|
|
484
|
+
? (config) => ChannelRead.doRead(config, this.streamChannels ?? this.outputChannels, true)
|
|
485
|
+
: undefined));
|
|
451
486
|
// attach branch subscribers
|
|
452
487
|
const ends = branch.ends
|
|
453
488
|
? Object.values(branch.ends)
|
|
@@ -494,3 +529,28 @@ function isStateGraphArgsWithInputOutputSchemas(obj) {
|
|
|
494
529
|
obj.input !== undefined &&
|
|
495
530
|
obj.output !== undefined);
|
|
496
531
|
}
|
|
532
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
533
|
+
function _controlBranch(value) {
|
|
534
|
+
if (_isSend(value)) {
|
|
535
|
+
return [value];
|
|
536
|
+
}
|
|
537
|
+
if (!_isCommand(value)) {
|
|
538
|
+
return [];
|
|
539
|
+
}
|
|
540
|
+
if (value.graph === Command.PARENT) {
|
|
541
|
+
throw new ParentCommand(value);
|
|
542
|
+
}
|
|
543
|
+
return Array.isArray(value.goto) ? value.goto : [value.goto];
|
|
544
|
+
}
|
|
545
|
+
function _getControlBranch() {
|
|
546
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
547
|
+
const CONTROL_BRANCH_PATH = new RunnableCallable({
|
|
548
|
+
func: _controlBranch,
|
|
549
|
+
tags: [TAG_HIDDEN],
|
|
550
|
+
trace: false,
|
|
551
|
+
recurse: false,
|
|
552
|
+
});
|
|
553
|
+
return new Branch({
|
|
554
|
+
path: CONTROL_BRANCH_PATH,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
@@ -4,6 +4,7 @@ exports.toolsCondition = exports.ToolNode = void 0;
|
|
|
4
4
|
const messages_1 = require("@langchain/core/messages");
|
|
5
5
|
const utils_js_1 = require("../utils.cjs");
|
|
6
6
|
const graph_js_1 = require("../graph/graph.cjs");
|
|
7
|
+
const errors_js_1 = require("../errors.cjs");
|
|
7
8
|
/**
|
|
8
9
|
* A node that runs the tools requested in the last AIMessage. It can be used
|
|
9
10
|
* either in StateGraph with a "messages" key or in MessageGraph. If multiple
|
|
@@ -178,6 +179,12 @@ class ToolNode extends utils_js_1.RunnableCallable {
|
|
|
178
179
|
if (!this.handleToolErrors) {
|
|
179
180
|
throw e;
|
|
180
181
|
}
|
|
182
|
+
if ((0, errors_js_1.isGraphInterrupt)(e.name)) {
|
|
183
|
+
// `NodeInterrupt` errors are a breakpoint to bring a human into the loop.
|
|
184
|
+
// As such, they are not recoverable by the agent and shouldn't be fed
|
|
185
|
+
// back. Instead, re-throw these errors even when `handleToolErrors = true`.
|
|
186
|
+
throw e;
|
|
187
|
+
}
|
|
181
188
|
return new messages_1.ToolMessage({
|
|
182
189
|
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
183
190
|
name: call.name,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ToolMessage, isBaseMessage, } from "@langchain/core/messages";
|
|
2
2
|
import { RunnableCallable } from "../utils.js";
|
|
3
3
|
import { END } from "../graph/graph.js";
|
|
4
|
+
import { isGraphInterrupt } from "../errors.js";
|
|
4
5
|
/**
|
|
5
6
|
* A node that runs the tools requested in the last AIMessage. It can be used
|
|
6
7
|
* either in StateGraph with a "messages" key or in MessageGraph. If multiple
|
|
@@ -175,6 +176,12 @@ export class ToolNode extends RunnableCallable {
|
|
|
175
176
|
if (!this.handleToolErrors) {
|
|
176
177
|
throw e;
|
|
177
178
|
}
|
|
179
|
+
if (isGraphInterrupt(e.name)) {
|
|
180
|
+
// `NodeInterrupt` errors are a breakpoint to bring a human into the loop.
|
|
181
|
+
// As such, they are not recoverable by the agent and shouldn't be fed
|
|
182
|
+
// back. Instead, re-throw these errors even when `handleToolErrors = true`.
|
|
183
|
+
throw e;
|
|
184
|
+
}
|
|
178
185
|
return new ToolMessage({
|
|
179
186
|
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
180
187
|
name: call.name,
|
package/dist/pregel/algo.cjs
CHANGED
|
@@ -100,6 +100,21 @@ const IGNORE = new Set([constants_js_1.PUSH, constants_js_1.RESUME, constants_js
|
|
|
100
100
|
function _applyWrites(checkpoint, channels, tasks,
|
|
101
101
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
102
102
|
getNextVersion) {
|
|
103
|
+
// Sort tasks by first 3 path elements for deterministic order
|
|
104
|
+
// Later path parts (like task IDs) are ignored for sorting
|
|
105
|
+
tasks.sort((a, b) => {
|
|
106
|
+
const aPath = a.path?.slice(0, 3) || [];
|
|
107
|
+
const bPath = b.path?.slice(0, 3) || [];
|
|
108
|
+
// Compare each path element
|
|
109
|
+
for (let i = 0; i < Math.min(aPath.length, bPath.length); i += 1) {
|
|
110
|
+
if (aPath[i] < bPath[i])
|
|
111
|
+
return -1;
|
|
112
|
+
if (aPath[i] > bPath[i])
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
// If one path is shorter, it comes first
|
|
116
|
+
return aPath.length - bPath.length;
|
|
117
|
+
});
|
|
103
118
|
// if no task has triggers this is applying writes from the null task only
|
|
104
119
|
// so we don't do anything other than update the channels written to
|
|
105
120
|
const bumpStep = tasks.some((task) => task.triggers.length > 0);
|
|
@@ -146,6 +161,7 @@ getNextVersion) {
|
|
|
146
161
|
// do nothing
|
|
147
162
|
}
|
|
148
163
|
else if (chan === constants_js_1.TASKS) {
|
|
164
|
+
// TODO: remove branch in 1.0
|
|
149
165
|
checkpoint.pending_sends.push({
|
|
150
166
|
node: val.node,
|
|
151
167
|
args: val.args,
|
|
@@ -214,6 +230,11 @@ getNextVersion) {
|
|
|
214
230
|
return pendingWritesByManaged;
|
|
215
231
|
}
|
|
216
232
|
exports._applyWrites = _applyWrites;
|
|
233
|
+
/**
|
|
234
|
+
* Prepare the set of tasks that will make up the next Pregel step.
|
|
235
|
+
* This is the union of all PUSH tasks (Sends) and PULL tasks (nodes triggered
|
|
236
|
+
* by edges).
|
|
237
|
+
*/
|
|
217
238
|
function _prepareNextTasks(checkpoint, pendingWrites, processes, channels, managed, config, forExecution, extra) {
|
|
218
239
|
const tasks = {};
|
|
219
240
|
// Consume pending packets
|
|
@@ -234,12 +255,18 @@ function _prepareNextTasks(checkpoint, pendingWrites, processes, channels, manag
|
|
|
234
255
|
return tasks;
|
|
235
256
|
}
|
|
236
257
|
exports._prepareNextTasks = _prepareNextTasks;
|
|
258
|
+
/**
|
|
259
|
+
* Prepares a single task for the next Pregel step, given a task path, which
|
|
260
|
+
* uniquely identifies a PUSH or PULL task within the graph.
|
|
261
|
+
*/
|
|
237
262
|
function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, channels, managed, config, forExecution, extra) {
|
|
238
263
|
const { step, checkpointer, manager } = extra;
|
|
239
264
|
const configurable = config.configurable ?? {};
|
|
240
265
|
const parentNamespace = configurable.checkpoint_ns ?? "";
|
|
241
266
|
if (taskPath[0] === constants_js_1.PUSH) {
|
|
242
|
-
const index = typeof taskPath[1] === "number"
|
|
267
|
+
const index = typeof taskPath[1] === "number"
|
|
268
|
+
? taskPath[1]
|
|
269
|
+
: parseInt(taskPath[1], 10);
|
|
243
270
|
if (index >= checkpoint.pending_sends.length) {
|
|
244
271
|
return undefined;
|
|
245
272
|
}
|
|
@@ -302,6 +329,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
302
329
|
name: packet.node,
|
|
303
330
|
writes: writes,
|
|
304
331
|
triggers,
|
|
332
|
+
path: taskPath,
|
|
305
333
|
}, select_, fresh_),
|
|
306
334
|
[constants_js_1.CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable[constants_js_1.CONFIG_KEY_CHECKPOINTER],
|
|
307
335
|
[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP]: {
|
|
@@ -319,6 +347,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
319
347
|
retry_policy: proc.retryPolicy,
|
|
320
348
|
id: taskId,
|
|
321
349
|
path: taskPath,
|
|
350
|
+
writers: proc.getWriters(),
|
|
322
351
|
};
|
|
323
352
|
}
|
|
324
353
|
}
|
|
@@ -405,6 +434,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
405
434
|
name,
|
|
406
435
|
writes: writes,
|
|
407
436
|
triggers,
|
|
437
|
+
path: taskPath,
|
|
408
438
|
}, select_, fresh_),
|
|
409
439
|
[constants_js_1.CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable[constants_js_1.CONFIG_KEY_CHECKPOINTER],
|
|
410
440
|
[constants_js_1.CONFIG_KEY_CHECKPOINT_MAP]: {
|
|
@@ -422,6 +452,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
422
452
|
retry_policy: proc.retryPolicy,
|
|
423
453
|
id: taskId,
|
|
424
454
|
path: taskPath,
|
|
455
|
+
writers: proc.getWriters(),
|
|
425
456
|
};
|
|
426
457
|
}
|
|
427
458
|
}
|
package/dist/pregel/algo.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export type WritesProtocol<C = string> = {
|
|
|
15
15
|
name: string;
|
|
16
16
|
writes: PendingWrite<C>[];
|
|
17
17
|
triggers: string[];
|
|
18
|
+
path?: [string, ...(string | number)[]];
|
|
18
19
|
};
|
|
19
20
|
export declare const increment: (current?: number) => number;
|
|
20
21
|
export declare function shouldInterrupt<N extends PropertyKey, C extends PropertyKey>(checkpoint: Checkpoint, interruptNodes: All | N[], tasks: PregelExecutableTask<N, C>[]): boolean;
|
|
@@ -37,5 +38,5 @@ export type NextTaskExtraFieldsWithoutStore = NextTaskExtraFields & {
|
|
|
37
38
|
export declare function _prepareNextTasks<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(checkpoint: ReadonlyCheckpoint, pendingWrites: [string, string, unknown][] | undefined, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: false, extra: NextTaskExtraFieldsWithoutStore): Record<string, PregelTaskDescription>;
|
|
38
39
|
export declare function _prepareNextTasks<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(checkpoint: ReadonlyCheckpoint, pendingWrites: [string, string, unknown][] | undefined, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: true, extra: NextTaskExtraFieldsWithStore): Record<string, PregelExecutableTask<keyof Nn, keyof Cc>>;
|
|
39
40
|
export declare function _prepareSingleTask<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(taskPath: [string, string | number], checkpoint: ReadonlyCheckpoint, pendingWrites: [string, string, unknown][] | undefined, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: false, extra: NextTaskExtraFields): PregelTaskDescription | undefined;
|
|
40
|
-
export declare function _prepareSingleTask<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(taskPath: [string, string | number], checkpoint: ReadonlyCheckpoint, pendingWrites: [string, string, unknown][] | undefined, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: true, extra: NextTaskExtraFields): PregelExecutableTask<keyof Nn, keyof Cc> | undefined;
|
|
41
|
-
export declare function _prepareSingleTask<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(taskPath: [string, string | number], checkpoint: ReadonlyCheckpoint, pendingWrites: [string, string, unknown][] | undefined, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: boolean, extra: NextTaskExtraFieldsWithStore): PregelTaskDescription | PregelExecutableTask<keyof Nn, keyof Cc> | undefined;
|
|
41
|
+
export declare function _prepareSingleTask<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(taskPath: [string, ...(string | number)[]], checkpoint: ReadonlyCheckpoint, pendingWrites: [string, string, unknown][] | undefined, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: true, extra: NextTaskExtraFields): PregelExecutableTask<keyof Nn, keyof Cc> | undefined;
|
|
42
|
+
export declare function _prepareSingleTask<Nn extends StrRecord<string, PregelNode>, Cc extends StrRecord<string, BaseChannel>>(taskPath: [string, ...(string | number)[]], checkpoint: ReadonlyCheckpoint, pendingWrites: [string, string, unknown][] | undefined, processes: Nn, channels: Cc, managed: ManagedValueMapping, config: RunnableConfig, forExecution: boolean, extra: NextTaskExtraFieldsWithStore): PregelTaskDescription | PregelExecutableTask<keyof Nn, keyof Cc> | undefined;
|
package/dist/pregel/algo.js
CHANGED
|
@@ -93,6 +93,21 @@ const IGNORE = new Set([PUSH, RESUME, INTERRUPT]);
|
|
|
93
93
|
export function _applyWrites(checkpoint, channels, tasks,
|
|
94
94
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
95
|
getNextVersion) {
|
|
96
|
+
// Sort tasks by first 3 path elements for deterministic order
|
|
97
|
+
// Later path parts (like task IDs) are ignored for sorting
|
|
98
|
+
tasks.sort((a, b) => {
|
|
99
|
+
const aPath = a.path?.slice(0, 3) || [];
|
|
100
|
+
const bPath = b.path?.slice(0, 3) || [];
|
|
101
|
+
// Compare each path element
|
|
102
|
+
for (let i = 0; i < Math.min(aPath.length, bPath.length); i += 1) {
|
|
103
|
+
if (aPath[i] < bPath[i])
|
|
104
|
+
return -1;
|
|
105
|
+
if (aPath[i] > bPath[i])
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
// If one path is shorter, it comes first
|
|
109
|
+
return aPath.length - bPath.length;
|
|
110
|
+
});
|
|
96
111
|
// if no task has triggers this is applying writes from the null task only
|
|
97
112
|
// so we don't do anything other than update the channels written to
|
|
98
113
|
const bumpStep = tasks.some((task) => task.triggers.length > 0);
|
|
@@ -139,6 +154,7 @@ getNextVersion) {
|
|
|
139
154
|
// do nothing
|
|
140
155
|
}
|
|
141
156
|
else if (chan === TASKS) {
|
|
157
|
+
// TODO: remove branch in 1.0
|
|
142
158
|
checkpoint.pending_sends.push({
|
|
143
159
|
node: val.node,
|
|
144
160
|
args: val.args,
|
|
@@ -206,6 +222,11 @@ getNextVersion) {
|
|
|
206
222
|
// Return managed values writes to be applied externally
|
|
207
223
|
return pendingWritesByManaged;
|
|
208
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Prepare the set of tasks that will make up the next Pregel step.
|
|
227
|
+
* This is the union of all PUSH tasks (Sends) and PULL tasks (nodes triggered
|
|
228
|
+
* by edges).
|
|
229
|
+
*/
|
|
209
230
|
export function _prepareNextTasks(checkpoint, pendingWrites, processes, channels, managed, config, forExecution, extra) {
|
|
210
231
|
const tasks = {};
|
|
211
232
|
// Consume pending packets
|
|
@@ -225,12 +246,18 @@ export function _prepareNextTasks(checkpoint, pendingWrites, processes, channels
|
|
|
225
246
|
}
|
|
226
247
|
return tasks;
|
|
227
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Prepares a single task for the next Pregel step, given a task path, which
|
|
251
|
+
* uniquely identifies a PUSH or PULL task within the graph.
|
|
252
|
+
*/
|
|
228
253
|
export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, channels, managed, config, forExecution, extra) {
|
|
229
254
|
const { step, checkpointer, manager } = extra;
|
|
230
255
|
const configurable = config.configurable ?? {};
|
|
231
256
|
const parentNamespace = configurable.checkpoint_ns ?? "";
|
|
232
257
|
if (taskPath[0] === PUSH) {
|
|
233
|
-
const index = typeof taskPath[1] === "number"
|
|
258
|
+
const index = typeof taskPath[1] === "number"
|
|
259
|
+
? taskPath[1]
|
|
260
|
+
: parseInt(taskPath[1], 10);
|
|
234
261
|
if (index >= checkpoint.pending_sends.length) {
|
|
235
262
|
return undefined;
|
|
236
263
|
}
|
|
@@ -293,6 +320,7 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
293
320
|
name: packet.node,
|
|
294
321
|
writes: writes,
|
|
295
322
|
triggers,
|
|
323
|
+
path: taskPath,
|
|
296
324
|
}, select_, fresh_),
|
|
297
325
|
[CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable[CONFIG_KEY_CHECKPOINTER],
|
|
298
326
|
[CONFIG_KEY_CHECKPOINT_MAP]: {
|
|
@@ -310,6 +338,7 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
310
338
|
retry_policy: proc.retryPolicy,
|
|
311
339
|
id: taskId,
|
|
312
340
|
path: taskPath,
|
|
341
|
+
writers: proc.getWriters(),
|
|
313
342
|
};
|
|
314
343
|
}
|
|
315
344
|
}
|
|
@@ -396,6 +425,7 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
396
425
|
name,
|
|
397
426
|
writes: writes,
|
|
398
427
|
triggers,
|
|
428
|
+
path: taskPath,
|
|
399
429
|
}, select_, fresh_),
|
|
400
430
|
[CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable[CONFIG_KEY_CHECKPOINTER],
|
|
401
431
|
[CONFIG_KEY_CHECKPOINT_MAP]: {
|
|
@@ -413,6 +443,7 @@ export function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processe
|
|
|
413
443
|
retry_policy: proc.retryPolicy,
|
|
414
444
|
id: taskId,
|
|
415
445
|
path: taskPath,
|
|
446
|
+
writers: proc.getWriters(),
|
|
416
447
|
};
|
|
417
448
|
}
|
|
418
449
|
}
|
package/dist/pregel/index.cjs
CHANGED
|
@@ -602,6 +602,7 @@ class Pregel extends runnables_1.Runnable {
|
|
|
602
602
|
writes: [],
|
|
603
603
|
triggers: [constants_js_1.INTERRUPT],
|
|
604
604
|
id: (0, langgraph_checkpoint_1.uuid5)(constants_js_1.INTERRUPT, checkpoint.id),
|
|
605
|
+
writers: [],
|
|
605
606
|
};
|
|
606
607
|
// execute task
|
|
607
608
|
await task.proc.invoke(task.input, (0, runnables_1.patchConfig)({
|
|
@@ -617,8 +618,15 @@ class Pregel extends runnables_1.Runnable {
|
|
|
617
618
|
},
|
|
618
619
|
}));
|
|
619
620
|
// save task writes
|
|
620
|
-
|
|
621
|
-
|
|
621
|
+
// channel writes are saved to current checkpoint
|
|
622
|
+
// push writes are saved to next checkpoint
|
|
623
|
+
const [channelWrites, pushWrites] = [
|
|
624
|
+
task.writes.filter((w) => w[0] !== constants_js_1.PUSH),
|
|
625
|
+
task.writes.filter((w) => w[0] === constants_js_1.PUSH),
|
|
626
|
+
];
|
|
627
|
+
// save task writes
|
|
628
|
+
if (saved !== undefined && channelWrites.length > 0) {
|
|
629
|
+
await checkpointer.putWrites(checkpointConfig, channelWrites, task.id);
|
|
622
630
|
}
|
|
623
631
|
// apply to checkpoint
|
|
624
632
|
// TODO: Why does keyof StrRecord allow number and symbol?
|
|
@@ -630,6 +638,9 @@ class Pregel extends runnables_1.Runnable {
|
|
|
630
638
|
writes: { [asNode]: values },
|
|
631
639
|
parents: saved?.metadata?.parents ?? {},
|
|
632
640
|
}, newVersions);
|
|
641
|
+
if (pushWrites.length > 0) {
|
|
642
|
+
await checkpointer.putWrites(nextConfig, pushWrites, task.id);
|
|
643
|
+
}
|
|
633
644
|
return (0, index_js_1.patchCheckpointMap)(nextConfig, saved ? saved.metadata : undefined);
|
|
634
645
|
}
|
|
635
646
|
_defaults(config) {
|
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, } 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, 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";
|
|
@@ -598,6 +598,7 @@ export class Pregel extends Runnable {
|
|
|
598
598
|
writes: [],
|
|
599
599
|
triggers: [INTERRUPT],
|
|
600
600
|
id: uuid5(INTERRUPT, checkpoint.id),
|
|
601
|
+
writers: [],
|
|
601
602
|
};
|
|
602
603
|
// execute task
|
|
603
604
|
await task.proc.invoke(task.input, patchConfig({
|
|
@@ -613,8 +614,15 @@ export class Pregel extends Runnable {
|
|
|
613
614
|
},
|
|
614
615
|
}));
|
|
615
616
|
// save task writes
|
|
616
|
-
|
|
617
|
-
|
|
617
|
+
// channel writes are saved to current checkpoint
|
|
618
|
+
// push writes are saved to next checkpoint
|
|
619
|
+
const [channelWrites, pushWrites] = [
|
|
620
|
+
task.writes.filter((w) => w[0] !== PUSH),
|
|
621
|
+
task.writes.filter((w) => w[0] === PUSH),
|
|
622
|
+
];
|
|
623
|
+
// save task writes
|
|
624
|
+
if (saved !== undefined && channelWrites.length > 0) {
|
|
625
|
+
await checkpointer.putWrites(checkpointConfig, channelWrites, task.id);
|
|
618
626
|
}
|
|
619
627
|
// apply to checkpoint
|
|
620
628
|
// TODO: Why does keyof StrRecord allow number and symbol?
|
|
@@ -626,6 +634,9 @@ export class Pregel extends Runnable {
|
|
|
626
634
|
writes: { [asNode]: values },
|
|
627
635
|
parents: saved?.metadata?.parents ?? {},
|
|
628
636
|
}, newVersions);
|
|
637
|
+
if (pushWrites.length > 0) {
|
|
638
|
+
await checkpointer.putWrites(nextConfig, pushWrites, task.id);
|
|
639
|
+
}
|
|
629
640
|
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
630
641
|
}
|
|
631
642
|
_defaults(config) {
|
package/dist/pregel/io.cjs
CHANGED
|
@@ -46,7 +46,34 @@ function readChannels(channels, select, skipEmpty = true
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
exports.readChannels = readChannels;
|
|
49
|
+
/**
|
|
50
|
+
* Map input chunk to a sequence of pending writes in the form (channel, value).
|
|
51
|
+
*/
|
|
49
52
|
function* mapCommand(cmd) {
|
|
53
|
+
if (cmd.graph === constants_js_1.Command.PARENT) {
|
|
54
|
+
throw new errors_js_1.InvalidUpdateError("There is no parent graph.");
|
|
55
|
+
}
|
|
56
|
+
if (cmd.goto) {
|
|
57
|
+
let sends;
|
|
58
|
+
if (Array.isArray(cmd.goto)) {
|
|
59
|
+
sends = cmd.goto;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
sends = [cmd.goto];
|
|
63
|
+
}
|
|
64
|
+
for (const send of sends) {
|
|
65
|
+
if ((0, constants_js_1._isSend)(send)) {
|
|
66
|
+
yield [constants_js_1.NULL_TASK_ID, constants_js_1.TASKS, send];
|
|
67
|
+
}
|
|
68
|
+
else if (typeof send === "string") {
|
|
69
|
+
yield [constants_js_1.NULL_TASK_ID, `branch:__start__:${constants_js_1.SELF}:${send}`, "__start__"];
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error(`In Command.send, expected Send or string, got ${typeof send}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// TODO: handle goto str for state graph
|
|
76
|
+
}
|
|
50
77
|
if (cmd.resume) {
|
|
51
78
|
if (typeof cmd.resume === "object" &&
|
|
52
79
|
!!cmd.resume &&
|
|
@@ -60,6 +87,14 @@ function* mapCommand(cmd) {
|
|
|
60
87
|
yield [constants_js_1.NULL_TASK_ID, constants_js_1.RESUME, cmd.resume];
|
|
61
88
|
}
|
|
62
89
|
}
|
|
90
|
+
if (cmd.update) {
|
|
91
|
+
if (typeof cmd.update !== "object" || !cmd.update) {
|
|
92
|
+
throw new Error("Expected cmd.update to be a dict mapping channel names to update values");
|
|
93
|
+
}
|
|
94
|
+
for (const [k, v] of Object.entries(cmd.update)) {
|
|
95
|
+
yield [constants_js_1.NULL_TASK_ID, k, v];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
63
98
|
}
|
|
64
99
|
exports.mapCommand = mapCommand;
|
|
65
100
|
/**
|
package/dist/pregel/io.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ import type { PregelExecutableTask } from "./types.js";
|
|
|
4
4
|
import { Command } from "../constants.js";
|
|
5
5
|
export declare function readChannel<C extends PropertyKey>(channels: Record<C, BaseChannel>, chan: C, catchErrors?: boolean, returnException?: boolean): unknown | null;
|
|
6
6
|
export declare function readChannels<C extends PropertyKey>(channels: Record<C, BaseChannel>, select: C | Array<C>, skipEmpty?: boolean): Record<string, any> | any;
|
|
7
|
+
/**
|
|
8
|
+
* Map input chunk to a sequence of pending writes in the form (channel, value).
|
|
9
|
+
*/
|
|
7
10
|
export declare function mapCommand(cmd: Command): Generator<[string, string, unknown]>;
|
|
8
11
|
/**
|
|
9
12
|
* Map input chunk to a sequence of pending writes in the form [channel, value].
|
package/dist/pregel/io.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { validate } from "uuid";
|
|
2
|
-
import { NULL_TASK_ID, RESUME, TAG_HIDDEN } from "../constants.js";
|
|
3
|
-
import { EmptyChannelError } from "../errors.js";
|
|
2
|
+
import { _isSend, Command, NULL_TASK_ID, RESUME, SELF, TAG_HIDDEN, TASKS, } from "../constants.js";
|
|
3
|
+
import { EmptyChannelError, InvalidUpdateError } from "../errors.js";
|
|
4
4
|
export function readChannel(channels, chan, catchErrors = true, returnException = false) {
|
|
5
5
|
try {
|
|
6
6
|
return channels[chan].get();
|
|
@@ -41,7 +41,34 @@ export function readChannels(channels, select, skipEmpty = true
|
|
|
41
41
|
return readChannel(channels, select);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Map input chunk to a sequence of pending writes in the form (channel, value).
|
|
46
|
+
*/
|
|
44
47
|
export function* mapCommand(cmd) {
|
|
48
|
+
if (cmd.graph === Command.PARENT) {
|
|
49
|
+
throw new InvalidUpdateError("There is no parent graph.");
|
|
50
|
+
}
|
|
51
|
+
if (cmd.goto) {
|
|
52
|
+
let sends;
|
|
53
|
+
if (Array.isArray(cmd.goto)) {
|
|
54
|
+
sends = cmd.goto;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
sends = [cmd.goto];
|
|
58
|
+
}
|
|
59
|
+
for (const send of sends) {
|
|
60
|
+
if (_isSend(send)) {
|
|
61
|
+
yield [NULL_TASK_ID, TASKS, send];
|
|
62
|
+
}
|
|
63
|
+
else if (typeof send === "string") {
|
|
64
|
+
yield [NULL_TASK_ID, `branch:__start__:${SELF}:${send}`, "__start__"];
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new Error(`In Command.send, expected Send or string, got ${typeof send}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// TODO: handle goto str for state graph
|
|
71
|
+
}
|
|
45
72
|
if (cmd.resume) {
|
|
46
73
|
if (typeof cmd.resume === "object" &&
|
|
47
74
|
!!cmd.resume &&
|
|
@@ -55,6 +82,14 @@ export function* mapCommand(cmd) {
|
|
|
55
82
|
yield [NULL_TASK_ID, RESUME, cmd.resume];
|
|
56
83
|
}
|
|
57
84
|
}
|
|
85
|
+
if (cmd.update) {
|
|
86
|
+
if (typeof cmd.update !== "object" || !cmd.update) {
|
|
87
|
+
throw new Error("Expected cmd.update to be a dict mapping channel names to update values");
|
|
88
|
+
}
|
|
89
|
+
for (const [k, v] of Object.entries(cmd.update)) {
|
|
90
|
+
yield [NULL_TASK_ID, k, v];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
58
93
|
}
|
|
59
94
|
/**
|
|
60
95
|
* Map input chunk to a sequence of pending writes in the form [channel, value].
|
package/dist/pregel/loop.cjs
CHANGED
|
@@ -576,22 +576,9 @@ class PregelLoop {
|
|
|
576
576
|
async _first(inputKeys) {
|
|
577
577
|
const isResuming = Object.keys(this.checkpoint.channel_versions).length !== 0 &&
|
|
578
578
|
(this.config.configurable?.[constants_js_1.CONFIG_KEY_RESUMING] !== undefined ||
|
|
579
|
-
this.input === null
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (this.checkpoint.channel_versions[channelName] !== undefined) {
|
|
583
|
-
const version = this.checkpoint.channel_versions[channelName];
|
|
584
|
-
this.checkpoint.versions_seen[constants_js_1.INTERRUPT] = {
|
|
585
|
-
...this.checkpoint.versions_seen[constants_js_1.INTERRUPT],
|
|
586
|
-
[channelName]: version,
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
// produce values output
|
|
591
|
-
const valuesOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, true, this.channels), "values"));
|
|
592
|
-
this._emit(valuesOutput);
|
|
593
|
-
}
|
|
594
|
-
else if ((0, constants_js_1._isCommand)(this.input)) {
|
|
579
|
+
this.input === null ||
|
|
580
|
+
(0, constants_js_1._isCommand)(this.input));
|
|
581
|
+
if ((0, constants_js_1._isCommand)(this.input)) {
|
|
595
582
|
const writes = {};
|
|
596
583
|
// group writes by task id
|
|
597
584
|
for (const [tid, key, value] of (0, io_js_1.mapCommand)(this.input)) {
|
|
@@ -608,6 +595,20 @@ class PregelLoop {
|
|
|
608
595
|
this.putWrites(tid, ws);
|
|
609
596
|
}
|
|
610
597
|
}
|
|
598
|
+
if (isResuming) {
|
|
599
|
+
for (const channelName of Object.keys(this.channels)) {
|
|
600
|
+
if (this.checkpoint.channel_versions[channelName] !== undefined) {
|
|
601
|
+
const version = this.checkpoint.channel_versions[channelName];
|
|
602
|
+
this.checkpoint.versions_seen[constants_js_1.INTERRUPT] = {
|
|
603
|
+
...this.checkpoint.versions_seen[constants_js_1.INTERRUPT],
|
|
604
|
+
[channelName]: version,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
// produce values output
|
|
609
|
+
const valuesOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, true, this.channels), "values"));
|
|
610
|
+
this._emit(valuesOutput);
|
|
611
|
+
}
|
|
611
612
|
else {
|
|
612
613
|
// map inputs to channel updates
|
|
613
614
|
const inputWrites = await (0, utils_js_1.gatherIterator)((0, io_js_1.mapInput)(inputKeys, this.input));
|