@langchain/langgraph 0.2.27 → 0.2.29
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 +36 -2
- package/dist/constants.d.ts +34 -4
- package/dist/constants.js +35 -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 +48 -8
- package/dist/graph/graph.d.ts +8 -5
- package/dist/graph/graph.js +49 -9
- package/dist/graph/state.cjs +78 -16
- package/dist/graph/state.d.ts +3 -1
- package/dist/graph/state.js +81 -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/read.cjs +8 -1
- package/dist/pregel/read.d.ts +2 -0
- package/dist/pregel/read.js +8 -1
- 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.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;
|
|
@@ -303,6 +304,14 @@ class StateGraph extends graph_js_1.Graph {
|
|
|
303
304
|
for (const [key, node] of Object.entries(this.nodes)) {
|
|
304
305
|
compiled.attachNode(key, node);
|
|
305
306
|
}
|
|
307
|
+
compiled.attachBranch(graph_js_1.START, constants_js_1.SELF, _getControlBranch(), {
|
|
308
|
+
withReader: false,
|
|
309
|
+
});
|
|
310
|
+
for (const [key] of Object.entries(this.nodes)) {
|
|
311
|
+
compiled.attachBranch(key, constants_js_1.SELF, _getControlBranch(), {
|
|
312
|
+
withReader: false,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
306
315
|
for (const [start, end] of this.edges) {
|
|
307
316
|
compiled.attachEdge(start, end);
|
|
308
317
|
}
|
|
@@ -339,13 +348,30 @@ function _getChannels(schema) {
|
|
|
339
348
|
class CompiledStateGraph extends graph_js_1.CompiledGraph {
|
|
340
349
|
attachNode(key, node) {
|
|
341
350
|
const stateKeys = Object.keys(this.builder.channels);
|
|
351
|
+
function _getRoot(input) {
|
|
352
|
+
if ((0, constants_js_1._isCommand)(input)) {
|
|
353
|
+
if (input.graph === constants_js_1.Command.PARENT) {
|
|
354
|
+
return write_js_1.SKIP_WRITE;
|
|
355
|
+
}
|
|
356
|
+
return input.update;
|
|
357
|
+
}
|
|
358
|
+
return input;
|
|
359
|
+
}
|
|
360
|
+
// to avoid name collision below
|
|
361
|
+
const nodeKey = key;
|
|
342
362
|
function getStateKey(key, input) {
|
|
343
363
|
if (!input) {
|
|
344
364
|
return write_js_1.SKIP_WRITE;
|
|
345
365
|
}
|
|
366
|
+
else if ((0, constants_js_1._isCommand)(input)) {
|
|
367
|
+
if (input.graph === constants_js_1.Command.PARENT) {
|
|
368
|
+
return write_js_1.SKIP_WRITE;
|
|
369
|
+
}
|
|
370
|
+
return getStateKey(key, input.update);
|
|
371
|
+
}
|
|
346
372
|
else if (typeof input !== "object" || Array.isArray(input)) {
|
|
347
373
|
const typeofInput = Array.isArray(input) ? "array" : typeof input;
|
|
348
|
-
throw new errors_js_1.InvalidUpdateError(`Expected node "${
|
|
374
|
+
throw new errors_js_1.InvalidUpdateError(`Expected node "${nodeKey.toString()}" to return an object, received ${typeofInput}`, {
|
|
349
375
|
lc_error_code: "INVALID_GRAPH_NODE_RETURN_VALUE",
|
|
350
376
|
});
|
|
351
377
|
}
|
|
@@ -355,7 +381,16 @@ class CompiledStateGraph extends graph_js_1.CompiledGraph {
|
|
|
355
381
|
}
|
|
356
382
|
// state updaters
|
|
357
383
|
const stateWriteEntries = stateKeys.map((key) => key === ROOT
|
|
358
|
-
? {
|
|
384
|
+
? {
|
|
385
|
+
channel: key,
|
|
386
|
+
value: write_js_1.PASSTHROUGH,
|
|
387
|
+
skipNone: true,
|
|
388
|
+
mapper: new utils_js_1.RunnableCallable({
|
|
389
|
+
func: _getRoot,
|
|
390
|
+
trace: false,
|
|
391
|
+
recurse: false,
|
|
392
|
+
}),
|
|
393
|
+
}
|
|
359
394
|
: {
|
|
360
395
|
channel: key,
|
|
361
396
|
value: write_js_1.PASSTHROUGH,
|
|
@@ -397,6 +432,7 @@ class CompiledStateGraph extends graph_js_1.CompiledGraph {
|
|
|
397
432
|
metadata: node?.metadata,
|
|
398
433
|
retryPolicy: node?.retryPolicy,
|
|
399
434
|
subgraphs: node?.subgraphs,
|
|
435
|
+
ends: node?.ends,
|
|
400
436
|
});
|
|
401
437
|
}
|
|
402
438
|
}
|
|
@@ -430,28 +466,29 @@ class CompiledStateGraph extends graph_js_1.CompiledGraph {
|
|
|
430
466
|
this.nodes[end].triggers.push(start);
|
|
431
467
|
}
|
|
432
468
|
}
|
|
433
|
-
attachBranch(start, name, branch) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
(dests) => {
|
|
438
|
-
const filteredDests = dests.filter((dest) => dest !== graph_js_1.END);
|
|
439
|
-
if (!filteredDests.length) {
|
|
469
|
+
attachBranch(start, name, branch, options = { withReader: true }) {
|
|
470
|
+
const branchWriter = async (packets, config) => {
|
|
471
|
+
const filteredPackets = packets.filter((p) => p !== graph_js_1.END);
|
|
472
|
+
if (!filteredPackets.length) {
|
|
440
473
|
return;
|
|
441
474
|
}
|
|
442
|
-
const writes =
|
|
443
|
-
if ((0, constants_js_1._isSend)(
|
|
444
|
-
return
|
|
475
|
+
const writes = filteredPackets.map((p) => {
|
|
476
|
+
if ((0, constants_js_1._isSend)(p)) {
|
|
477
|
+
return p;
|
|
445
478
|
}
|
|
446
479
|
return {
|
|
447
|
-
channel: `branch:${start}:${name}:${
|
|
480
|
+
channel: `branch:${start}:${name}:${p}`,
|
|
448
481
|
value: start,
|
|
449
482
|
};
|
|
450
483
|
});
|
|
451
|
-
|
|
452
|
-
}
|
|
484
|
+
await write_js_1.ChannelWrite.doWrite({ ...config, tags: (config.tags ?? []).concat([constants_js_1.TAG_HIDDEN]) }, writes);
|
|
485
|
+
};
|
|
486
|
+
// attach branch publisher
|
|
487
|
+
this.nodes[start].writers.push(branch.run(branchWriter,
|
|
453
488
|
// reader
|
|
454
|
-
|
|
489
|
+
options.withReader
|
|
490
|
+
? (config) => read_js_1.ChannelRead.doRead(config, this.streamChannels ?? this.outputChannels, true)
|
|
491
|
+
: undefined));
|
|
455
492
|
// attach branch subscribers
|
|
456
493
|
const ends = branch.ends
|
|
457
494
|
? Object.values(branch.ends)
|
|
@@ -499,3 +536,28 @@ function isStateGraphArgsWithInputOutputSchemas(obj) {
|
|
|
499
536
|
obj.input !== undefined &&
|
|
500
537
|
obj.output !== undefined);
|
|
501
538
|
}
|
|
539
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
540
|
+
function _controlBranch(value) {
|
|
541
|
+
if ((0, constants_js_1._isSend)(value)) {
|
|
542
|
+
return [value];
|
|
543
|
+
}
|
|
544
|
+
if (!(0, constants_js_1._isCommand)(value)) {
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
if (value.graph === constants_js_1.Command.PARENT) {
|
|
548
|
+
throw new errors_js_1.ParentCommand(value);
|
|
549
|
+
}
|
|
550
|
+
return Array.isArray(value.goto) ? value.goto : [value.goto];
|
|
551
|
+
}
|
|
552
|
+
function _getControlBranch() {
|
|
553
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
554
|
+
const CONTROL_BRANCH_PATH = new utils_js_1.RunnableCallable({
|
|
555
|
+
func: _controlBranch,
|
|
556
|
+
tags: [constants_js_1.TAG_HIDDEN],
|
|
557
|
+
trace: false,
|
|
558
|
+
recurse: false,
|
|
559
|
+
});
|
|
560
|
+
return new graph_js_1.Branch({
|
|
561
|
+
path: CONTROL_BRANCH_PATH,
|
|
562
|
+
});
|
|
563
|
+
}
|
package/dist/graph/state.d.ts
CHANGED
|
@@ -133,5 +133,7 @@ export declare class CompiledStateGraph<S, U, N extends string = typeof START, I
|
|
|
133
133
|
attachNode(key: typeof START, node?: never): void;
|
|
134
134
|
attachNode(key: N, node: StateGraphNodeSpec<S, U>): void;
|
|
135
135
|
attachEdge(start: N | N[] | "__start__", end: N | "__end__"): void;
|
|
136
|
-
attachBranch(start: N | typeof START, name: string, branch: Branch<S, N
|
|
136
|
+
attachBranch(start: N | typeof START, name: string, branch: Branch<S, N>, options?: {
|
|
137
|
+
withReader?: boolean;
|
|
138
|
+
}): void;
|
|
137
139
|
}
|
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";
|
|
@@ -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;
|
|
@@ -300,6 +301,14 @@ export class StateGraph extends Graph {
|
|
|
300
301
|
for (const [key, node] of Object.entries(this.nodes)) {
|
|
301
302
|
compiled.attachNode(key, node);
|
|
302
303
|
}
|
|
304
|
+
compiled.attachBranch(START, SELF, _getControlBranch(), {
|
|
305
|
+
withReader: false,
|
|
306
|
+
});
|
|
307
|
+
for (const [key] of Object.entries(this.nodes)) {
|
|
308
|
+
compiled.attachBranch(key, SELF, _getControlBranch(), {
|
|
309
|
+
withReader: false,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
303
312
|
for (const [start, end] of this.edges) {
|
|
304
313
|
compiled.attachEdge(start, end);
|
|
305
314
|
}
|
|
@@ -335,13 +344,30 @@ function _getChannels(schema) {
|
|
|
335
344
|
export class CompiledStateGraph extends CompiledGraph {
|
|
336
345
|
attachNode(key, node) {
|
|
337
346
|
const stateKeys = Object.keys(this.builder.channels);
|
|
347
|
+
function _getRoot(input) {
|
|
348
|
+
if (_isCommand(input)) {
|
|
349
|
+
if (input.graph === Command.PARENT) {
|
|
350
|
+
return SKIP_WRITE;
|
|
351
|
+
}
|
|
352
|
+
return input.update;
|
|
353
|
+
}
|
|
354
|
+
return input;
|
|
355
|
+
}
|
|
356
|
+
// to avoid name collision below
|
|
357
|
+
const nodeKey = key;
|
|
338
358
|
function getStateKey(key, input) {
|
|
339
359
|
if (!input) {
|
|
340
360
|
return SKIP_WRITE;
|
|
341
361
|
}
|
|
362
|
+
else if (_isCommand(input)) {
|
|
363
|
+
if (input.graph === Command.PARENT) {
|
|
364
|
+
return SKIP_WRITE;
|
|
365
|
+
}
|
|
366
|
+
return getStateKey(key, input.update);
|
|
367
|
+
}
|
|
342
368
|
else if (typeof input !== "object" || Array.isArray(input)) {
|
|
343
369
|
const typeofInput = Array.isArray(input) ? "array" : typeof input;
|
|
344
|
-
throw new InvalidUpdateError(`Expected node "${
|
|
370
|
+
throw new InvalidUpdateError(`Expected node "${nodeKey.toString()}" to return an object, received ${typeofInput}`, {
|
|
345
371
|
lc_error_code: "INVALID_GRAPH_NODE_RETURN_VALUE",
|
|
346
372
|
});
|
|
347
373
|
}
|
|
@@ -351,7 +377,16 @@ export class CompiledStateGraph extends CompiledGraph {
|
|
|
351
377
|
}
|
|
352
378
|
// state updaters
|
|
353
379
|
const stateWriteEntries = stateKeys.map((key) => key === ROOT
|
|
354
|
-
? {
|
|
380
|
+
? {
|
|
381
|
+
channel: key,
|
|
382
|
+
value: PASSTHROUGH,
|
|
383
|
+
skipNone: true,
|
|
384
|
+
mapper: new RunnableCallable({
|
|
385
|
+
func: _getRoot,
|
|
386
|
+
trace: false,
|
|
387
|
+
recurse: false,
|
|
388
|
+
}),
|
|
389
|
+
}
|
|
355
390
|
: {
|
|
356
391
|
channel: key,
|
|
357
392
|
value: PASSTHROUGH,
|
|
@@ -393,6 +428,7 @@ export class CompiledStateGraph extends CompiledGraph {
|
|
|
393
428
|
metadata: node?.metadata,
|
|
394
429
|
retryPolicy: node?.retryPolicy,
|
|
395
430
|
subgraphs: node?.subgraphs,
|
|
431
|
+
ends: node?.ends,
|
|
396
432
|
});
|
|
397
433
|
}
|
|
398
434
|
}
|
|
@@ -426,28 +462,29 @@ export class CompiledStateGraph extends CompiledGraph {
|
|
|
426
462
|
this.nodes[end].triggers.push(start);
|
|
427
463
|
}
|
|
428
464
|
}
|
|
429
|
-
attachBranch(start, name, branch) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
(dests) => {
|
|
434
|
-
const filteredDests = dests.filter((dest) => dest !== END);
|
|
435
|
-
if (!filteredDests.length) {
|
|
465
|
+
attachBranch(start, name, branch, options = { withReader: true }) {
|
|
466
|
+
const branchWriter = async (packets, config) => {
|
|
467
|
+
const filteredPackets = packets.filter((p) => p !== END);
|
|
468
|
+
if (!filteredPackets.length) {
|
|
436
469
|
return;
|
|
437
470
|
}
|
|
438
|
-
const writes =
|
|
439
|
-
if (_isSend(
|
|
440
|
-
return
|
|
471
|
+
const writes = filteredPackets.map((p) => {
|
|
472
|
+
if (_isSend(p)) {
|
|
473
|
+
return p;
|
|
441
474
|
}
|
|
442
475
|
return {
|
|
443
|
-
channel: `branch:${start}:${name}:${
|
|
476
|
+
channel: `branch:${start}:${name}:${p}`,
|
|
444
477
|
value: start,
|
|
445
478
|
};
|
|
446
479
|
});
|
|
447
|
-
|
|
448
|
-
}
|
|
480
|
+
await ChannelWrite.doWrite({ ...config, tags: (config.tags ?? []).concat([TAG_HIDDEN]) }, writes);
|
|
481
|
+
};
|
|
482
|
+
// attach branch publisher
|
|
483
|
+
this.nodes[start].writers.push(branch.run(branchWriter,
|
|
449
484
|
// reader
|
|
450
|
-
|
|
485
|
+
options.withReader
|
|
486
|
+
? (config) => ChannelRead.doRead(config, this.streamChannels ?? this.outputChannels, true)
|
|
487
|
+
: undefined));
|
|
451
488
|
// attach branch subscribers
|
|
452
489
|
const ends = branch.ends
|
|
453
490
|
? Object.values(branch.ends)
|
|
@@ -494,3 +531,28 @@ function isStateGraphArgsWithInputOutputSchemas(obj) {
|
|
|
494
531
|
obj.input !== undefined &&
|
|
495
532
|
obj.output !== undefined);
|
|
496
533
|
}
|
|
534
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
535
|
+
function _controlBranch(value) {
|
|
536
|
+
if (_isSend(value)) {
|
|
537
|
+
return [value];
|
|
538
|
+
}
|
|
539
|
+
if (!_isCommand(value)) {
|
|
540
|
+
return [];
|
|
541
|
+
}
|
|
542
|
+
if (value.graph === Command.PARENT) {
|
|
543
|
+
throw new ParentCommand(value);
|
|
544
|
+
}
|
|
545
|
+
return Array.isArray(value.goto) ? value.goto : [value.goto];
|
|
546
|
+
}
|
|
547
|
+
function _getControlBranch() {
|
|
548
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
549
|
+
const CONTROL_BRANCH_PATH = new RunnableCallable({
|
|
550
|
+
func: _controlBranch,
|
|
551
|
+
tags: [TAG_HIDDEN],
|
|
552
|
+
trace: false,
|
|
553
|
+
recurse: false,
|
|
554
|
+
});
|
|
555
|
+
return new Branch({
|
|
556
|
+
path: CONTROL_BRANCH_PATH,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
@@ -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) {
|