@langchain/langgraph 0.2.55 → 0.2.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -302
- package/dist/constants.cjs +2 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/interrupt.cjs +4 -0
- package/dist/interrupt.d.ts +4 -0
- package/dist/interrupt.js +4 -0
- package/dist/interrupt.js.map +1 -1
- package/dist/pregel/index.cjs +306 -210
- package/dist/pregel/index.d.ts +22 -0
- package/dist/pregel/index.js +308 -212
- package/dist/pregel/index.js.map +1 -1
- package/package.json +2 -2
package/dist/pregel/index.js
CHANGED
|
@@ -4,10 +4,10 @@ import { compareChannelVersions, copyCheckpoint, emptyCheckpoint, SCHEDULED, uui
|
|
|
4
4
|
import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
|
|
5
5
|
import { PregelNode } from "./read.js";
|
|
6
6
|
import { validateGraph, validateKeys } from "./validate.js";
|
|
7
|
-
import { readChannels } from "./io.js";
|
|
7
|
+
import { mapInput, 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, COPY, END, PUSH, } from "../constants.js";
|
|
11
11
|
import { GraphRecursionError, GraphValueError, InvalidUpdateError, } from "../errors.js";
|
|
12
12
|
import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
|
|
13
13
|
import { _coerceToDict, getNewChannelVersions, patchCheckpointMap, } from "./utils/index.js";
|
|
@@ -673,89 +673,192 @@ export class Pregel extends Runnable {
|
|
|
673
673
|
}
|
|
674
674
|
}
|
|
675
675
|
/**
|
|
676
|
-
*
|
|
676
|
+
* Apply updates to the graph state in bulk.
|
|
677
677
|
* Requires a checkpointer to be configured.
|
|
678
678
|
*
|
|
679
|
-
* This method
|
|
680
|
-
*
|
|
681
|
-
*
|
|
682
|
-
* - Integrating external inputs into the graph
|
|
679
|
+
* This method is useful for recreating a thread
|
|
680
|
+
* from a list of updates, especially if a checkpoint
|
|
681
|
+
* is created as a result of multiple tasks.
|
|
683
682
|
*
|
|
684
|
-
* @
|
|
685
|
-
*
|
|
686
|
-
* @param
|
|
683
|
+
* @internal The API might change in the future.
|
|
684
|
+
*
|
|
685
|
+
* @param startConfig - Configuration for the update
|
|
686
|
+
* @param updates - The list of updates to apply to graph state
|
|
687
687
|
* @returns Updated configuration
|
|
688
688
|
* @throws {GraphValueError} If no checkpointer is configured
|
|
689
|
-
* @throws {InvalidUpdateError} If the update cannot be attributed to a node
|
|
689
|
+
* @throws {InvalidUpdateError} If the update cannot be attributed to a node or an update can be only applied in sequence.
|
|
690
690
|
*/
|
|
691
|
-
async
|
|
692
|
-
const checkpointer =
|
|
691
|
+
async bulkUpdateState(startConfig, supersteps) {
|
|
692
|
+
const checkpointer = startConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] ?? this.checkpointer;
|
|
693
693
|
if (!checkpointer) {
|
|
694
694
|
throw new GraphValueError("No checkpointer set");
|
|
695
695
|
}
|
|
696
|
+
if (supersteps.length === 0) {
|
|
697
|
+
throw new Error("No supersteps provided");
|
|
698
|
+
}
|
|
699
|
+
if (supersteps.some((s) => s.updates.length === 0)) {
|
|
700
|
+
throw new Error("No updates provided");
|
|
701
|
+
}
|
|
696
702
|
// delegate to subgraph
|
|
697
|
-
const checkpointNamespace =
|
|
703
|
+
const checkpointNamespace = startConfig.configurable?.checkpoint_ns ?? "";
|
|
698
704
|
if (checkpointNamespace !== "" &&
|
|
699
|
-
|
|
705
|
+
startConfig.configurable?.[CONFIG_KEY_CHECKPOINTER] === undefined) {
|
|
700
706
|
// remove task_ids from checkpoint_ns
|
|
701
707
|
const recastNamespace = recastCheckpointNamespace(checkpointNamespace);
|
|
702
708
|
// find the subgraph with the matching name
|
|
703
709
|
// eslint-disable-next-line no-unreachable-loop
|
|
704
710
|
for await (const [, pregel] of this.getSubgraphsAsync(recastNamespace, true)) {
|
|
705
|
-
return await pregel.
|
|
711
|
+
return await pregel.bulkUpdateState(patchConfigurable(startConfig, {
|
|
706
712
|
[CONFIG_KEY_CHECKPOINTER]: checkpointer,
|
|
707
|
-
}),
|
|
713
|
+
}), supersteps);
|
|
708
714
|
}
|
|
709
715
|
throw new Error(`Subgraph "${recastNamespace}" not found`);
|
|
710
716
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
const step = saved?.metadata?.step ?? -1;
|
|
723
|
-
// merge configurable fields with previous checkpoint config
|
|
724
|
-
let checkpointConfig = patchConfigurable(config, {
|
|
725
|
-
checkpoint_ns: config.configurable?.checkpoint_ns ?? "",
|
|
726
|
-
});
|
|
727
|
-
let checkpointMetadata = config.metadata ?? {};
|
|
728
|
-
if (saved?.config.configurable) {
|
|
729
|
-
checkpointConfig = patchConfigurable(config, saved.config.configurable);
|
|
730
|
-
checkpointMetadata = {
|
|
731
|
-
...saved.metadata,
|
|
732
|
-
...checkpointMetadata,
|
|
717
|
+
const updateSuperStep = async (inputConfig, updates) => {
|
|
718
|
+
// get last checkpoint
|
|
719
|
+
const config = this.config
|
|
720
|
+
? mergeConfigs(this.config, inputConfig)
|
|
721
|
+
: inputConfig;
|
|
722
|
+
const saved = await checkpointer.getTuple(config);
|
|
723
|
+
const checkpoint = saved !== undefined
|
|
724
|
+
? copyCheckpoint(saved.checkpoint)
|
|
725
|
+
: emptyCheckpoint();
|
|
726
|
+
const checkpointPreviousVersions = {
|
|
727
|
+
...saved?.checkpoint.channel_versions,
|
|
733
728
|
};
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
729
|
+
const step = saved?.metadata?.step ?? -1;
|
|
730
|
+
// merge configurable fields with previous checkpoint config
|
|
731
|
+
let checkpointConfig = patchConfigurable(config, {
|
|
732
|
+
checkpoint_ns: config.configurable?.checkpoint_ns ?? "",
|
|
733
|
+
});
|
|
734
|
+
let checkpointMetadata = config.metadata ?? {};
|
|
735
|
+
if (saved?.config.configurable) {
|
|
736
|
+
checkpointConfig = patchConfigurable(config, saved.config.configurable);
|
|
737
|
+
checkpointMetadata = {
|
|
738
|
+
...saved.metadata,
|
|
739
|
+
...checkpointMetadata,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
// Find last node that updated the state, if not provided
|
|
743
|
+
const { values, asNode } = updates[0];
|
|
744
|
+
if (values == null && asNode === undefined) {
|
|
745
|
+
if (updates.length > 1) {
|
|
746
|
+
throw new InvalidUpdateError(`Cannot create empty checkpoint with multiple updates`);
|
|
747
|
+
}
|
|
748
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, undefined, step), {
|
|
749
|
+
source: "update",
|
|
750
|
+
step: step + 1,
|
|
751
|
+
writes: {},
|
|
752
|
+
parents: saved?.metadata?.parents ?? {},
|
|
753
|
+
}, {});
|
|
754
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
755
|
+
}
|
|
756
|
+
// update channels
|
|
757
|
+
const channels = emptyChannels(this.channels, checkpoint);
|
|
758
|
+
// Pass `skipManaged: true` as managed values are not used/relevant in update state calls.
|
|
759
|
+
const { managed } = await this.prepareSpecs(config, {
|
|
760
|
+
skipManaged: true,
|
|
761
|
+
});
|
|
762
|
+
if (values === null && asNode === END) {
|
|
763
|
+
if (updates.length > 1) {
|
|
764
|
+
throw new InvalidUpdateError(`Cannot apply multiple updates when clearing state`);
|
|
765
|
+
}
|
|
766
|
+
if (saved) {
|
|
767
|
+
// tasks for this checkpoint
|
|
768
|
+
const nextTasks = _prepareNextTasks(checkpoint, saved.pendingWrites || [], this.nodes, channels, managed, saved.config, true, {
|
|
769
|
+
step: (saved.metadata?.step ?? -1) + 1,
|
|
770
|
+
checkpointer: this.checkpointer || undefined,
|
|
771
|
+
store: this.store,
|
|
772
|
+
});
|
|
773
|
+
// apply null writes
|
|
774
|
+
const nullWrites = (saved.pendingWrites || [])
|
|
775
|
+
.filter((w) => w[0] === NULL_TASK_ID)
|
|
776
|
+
.map((w) => w.slice(1));
|
|
777
|
+
if (nullWrites.length > 0) {
|
|
778
|
+
_applyWrites(saved.checkpoint, channels, [
|
|
779
|
+
{
|
|
780
|
+
name: INPUT,
|
|
781
|
+
writes: nullWrites,
|
|
782
|
+
triggers: [],
|
|
783
|
+
},
|
|
784
|
+
]);
|
|
785
|
+
}
|
|
786
|
+
// apply writes from tasks that already ran
|
|
787
|
+
for (const [taskId, k, v] of saved.pendingWrites || []) {
|
|
788
|
+
if ([ERROR, INTERRUPT, SCHEDULED].includes(k)) {
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (!(taskId in nextTasks)) {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
nextTasks[taskId].writes.push([k, v]);
|
|
795
|
+
}
|
|
796
|
+
// clear all current tasks
|
|
797
|
+
_applyWrites(checkpoint, channels, Object.values(nextTasks));
|
|
798
|
+
}
|
|
799
|
+
// save checkpoint
|
|
800
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, undefined, step), {
|
|
801
|
+
...checkpointMetadata,
|
|
802
|
+
source: "update",
|
|
803
|
+
step: step + 1,
|
|
804
|
+
writes: {},
|
|
805
|
+
parents: saved?.metadata?.parents ?? {},
|
|
806
|
+
}, {});
|
|
807
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
808
|
+
}
|
|
809
|
+
if (values == null && asNode === COPY) {
|
|
810
|
+
if (updates.length > 1) {
|
|
811
|
+
throw new InvalidUpdateError(`Cannot copy checkpoint with multiple updates`);
|
|
812
|
+
}
|
|
813
|
+
const nextConfig = await checkpointer.put(saved?.parentConfig ?? checkpointConfig, createCheckpoint(checkpoint, undefined, step), {
|
|
814
|
+
source: "fork",
|
|
815
|
+
step: step + 1,
|
|
816
|
+
writes: {},
|
|
817
|
+
parents: saved?.metadata?.parents ?? {},
|
|
818
|
+
}, {});
|
|
819
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
820
|
+
}
|
|
821
|
+
if (asNode === INPUT) {
|
|
822
|
+
if (updates.length > 1) {
|
|
823
|
+
throw new InvalidUpdateError(`Cannot apply multiple updates when updating as input`);
|
|
824
|
+
}
|
|
825
|
+
const inputWrites = await gatherIterator(mapInput(this.inputChannels, values));
|
|
826
|
+
if (inputWrites.length === 0) {
|
|
827
|
+
throw new InvalidUpdateError(`Received no input writes for ${JSON.stringify(this.inputChannels, null, 2)}`);
|
|
828
|
+
}
|
|
829
|
+
// apply to checkpoint
|
|
830
|
+
_applyWrites(checkpoint, channels, [
|
|
831
|
+
{
|
|
832
|
+
name: INPUT,
|
|
833
|
+
writes: inputWrites,
|
|
834
|
+
triggers: [],
|
|
835
|
+
},
|
|
836
|
+
], checkpointer.getNextVersion.bind(this.checkpointer));
|
|
837
|
+
// apply input write to channels
|
|
838
|
+
const nextStep = saved?.metadata?.step != null ? saved.metadata.step + 1 : -1;
|
|
839
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, channels, nextStep), {
|
|
840
|
+
source: "input",
|
|
841
|
+
step: nextStep,
|
|
842
|
+
writes: Object.fromEntries(inputWrites),
|
|
843
|
+
parents: saved?.metadata?.parents ?? {},
|
|
844
|
+
}, getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions));
|
|
845
|
+
// Store the writes
|
|
846
|
+
await checkpointer.putWrites(nextConfig, inputWrites, uuid5(INPUT, checkpoint.id));
|
|
847
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
848
|
+
}
|
|
849
|
+
// apply pending writes, if not on specific checkpoint
|
|
850
|
+
if (config.configurable?.checkpoint_id === undefined &&
|
|
851
|
+
saved?.pendingWrites !== undefined &&
|
|
852
|
+
saved.pendingWrites.length > 0) {
|
|
751
853
|
// tasks for this checkpoint
|
|
752
|
-
const nextTasks = _prepareNextTasks(checkpoint, saved.pendingWrites
|
|
753
|
-
step: (saved.metadata?.step ?? -1) + 1,
|
|
754
|
-
checkpointer: this.checkpointer || undefined,
|
|
854
|
+
const nextTasks = _prepareNextTasks(checkpoint, saved.pendingWrites, this.nodes, channels, managed, saved.config, true, {
|
|
755
855
|
store: this.store,
|
|
856
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
857
|
+
checkpointer: this.checkpointer,
|
|
858
|
+
step: (saved.metadata?.step ?? -1) + 1,
|
|
756
859
|
});
|
|
757
860
|
// apply null writes
|
|
758
|
-
const nullWrites = (saved.pendingWrites
|
|
861
|
+
const nullWrites = (saved.pendingWrites ?? [])
|
|
759
862
|
.filter((w) => w[0] === NULL_TASK_ID)
|
|
760
863
|
.map((w) => w.slice(1));
|
|
761
864
|
if (nullWrites.length > 0) {
|
|
@@ -767,172 +870,165 @@ export class Pregel extends Runnable {
|
|
|
767
870
|
},
|
|
768
871
|
]);
|
|
769
872
|
}
|
|
770
|
-
// apply writes
|
|
771
|
-
for (const [
|
|
772
|
-
if ([ERROR, INTERRUPT, SCHEDULED].includes(k)
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
if (!(taskId in nextTasks)) {
|
|
873
|
+
// apply writes
|
|
874
|
+
for (const [tid, k, v] of saved.pendingWrites) {
|
|
875
|
+
if ([ERROR, INTERRUPT, SCHEDULED].includes(k) ||
|
|
876
|
+
nextTasks[tid] === undefined) {
|
|
776
877
|
continue;
|
|
777
878
|
}
|
|
778
|
-
nextTasks[
|
|
879
|
+
nextTasks[tid].writes.push([k, v]);
|
|
880
|
+
}
|
|
881
|
+
const tasks = Object.values(nextTasks).filter((task) => {
|
|
882
|
+
return task.writes.length > 0;
|
|
883
|
+
});
|
|
884
|
+
if (tasks.length > 0) {
|
|
885
|
+
_applyWrites(checkpoint, channels, tasks);
|
|
779
886
|
}
|
|
780
|
-
// clear all current tasks
|
|
781
|
-
_applyWrites(checkpoint, channels, Object.values(nextTasks));
|
|
782
887
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
888
|
+
const nonNullVersion = Object.values(checkpoint.versions_seen)
|
|
889
|
+
.map((seenVersions) => {
|
|
890
|
+
return Object.values(seenVersions);
|
|
891
|
+
})
|
|
892
|
+
.flat()
|
|
893
|
+
.find((v) => !!v);
|
|
894
|
+
const validUpdates = [];
|
|
895
|
+
if (updates.length === 1) {
|
|
896
|
+
// eslint-disable-next-line prefer-const
|
|
897
|
+
let { values, asNode } = updates[0];
|
|
898
|
+
if (asNode === undefined && nonNullVersion === undefined) {
|
|
899
|
+
if (typeof this.inputChannels === "string" &&
|
|
900
|
+
this.nodes[this.inputChannels] !== undefined) {
|
|
901
|
+
asNode = this.inputChannels;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
else if (asNode === undefined) {
|
|
905
|
+
const lastSeenByNode = Object.entries(checkpoint.versions_seen)
|
|
906
|
+
.map(([n, seen]) => {
|
|
907
|
+
return Object.values(seen).map((v) => {
|
|
908
|
+
return [v, n];
|
|
909
|
+
});
|
|
910
|
+
})
|
|
911
|
+
.flat()
|
|
912
|
+
.sort(([aNumber], [bNumber]) => compareChannelVersions(aNumber, bNumber));
|
|
913
|
+
// if two nodes updated the state at the same time, it's ambiguous
|
|
914
|
+
if (lastSeenByNode) {
|
|
915
|
+
if (lastSeenByNode.length === 1) {
|
|
916
|
+
// eslint-disable-next-line prefer-destructuring
|
|
917
|
+
asNode = lastSeenByNode[0][1];
|
|
918
|
+
}
|
|
919
|
+
else if (lastSeenByNode[lastSeenByNode.length - 1][0] !==
|
|
920
|
+
lastSeenByNode[lastSeenByNode.length - 2][0]) {
|
|
921
|
+
// eslint-disable-next-line prefer-destructuring
|
|
922
|
+
asNode = lastSeenByNode[lastSeenByNode.length - 1][1];
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (asNode === undefined) {
|
|
927
|
+
throw new InvalidUpdateError(`Ambiguous update, specify "asNode"`);
|
|
928
|
+
}
|
|
929
|
+
validUpdates.push({ values, asNode });
|
|
825
930
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
931
|
+
else {
|
|
932
|
+
for (const { asNode, values } of updates) {
|
|
933
|
+
if (asNode == null) {
|
|
934
|
+
throw new InvalidUpdateError(`"asNode" is required when applying multiple updates`);
|
|
935
|
+
}
|
|
936
|
+
validUpdates.push({ values, asNode });
|
|
831
937
|
}
|
|
832
|
-
nextTasks[tid].writes.push([k, v]);
|
|
833
938
|
}
|
|
834
|
-
const tasks =
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
939
|
+
const tasks = [];
|
|
940
|
+
for (const { asNode, values } of validUpdates) {
|
|
941
|
+
if (this.nodes[asNode] === undefined) {
|
|
942
|
+
throw new InvalidUpdateError(`Node "${asNode.toString()}" does not exist`);
|
|
943
|
+
}
|
|
944
|
+
// run all writers of the chosen node
|
|
945
|
+
const writers = this.nodes[asNode].getWriters();
|
|
946
|
+
if (!writers.length) {
|
|
947
|
+
throw new InvalidUpdateError(`No writers found for node "${asNode.toString()}"`);
|
|
948
|
+
}
|
|
949
|
+
tasks.push({
|
|
950
|
+
name: asNode,
|
|
951
|
+
input: values,
|
|
952
|
+
proc: writers.length > 1
|
|
953
|
+
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
954
|
+
RunnableSequence.from(writers, {
|
|
955
|
+
omitSequenceTags: true,
|
|
956
|
+
})
|
|
957
|
+
: writers[0],
|
|
958
|
+
writes: [],
|
|
959
|
+
triggers: [INTERRUPT],
|
|
960
|
+
id: uuid5(INTERRUPT, checkpoint.id),
|
|
961
|
+
writers: [],
|
|
962
|
+
});
|
|
839
963
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
964
|
+
for (const task of tasks) {
|
|
965
|
+
// execute task
|
|
966
|
+
await task.proc.invoke(task.input, patchConfig({
|
|
967
|
+
...config,
|
|
968
|
+
store: config?.store ?? this.store,
|
|
969
|
+
}, {
|
|
970
|
+
runName: config.runName ?? `${this.getName()}UpdateState`,
|
|
971
|
+
configurable: {
|
|
972
|
+
[CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
|
|
973
|
+
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed,
|
|
974
|
+
// TODO: Why does keyof StrRecord allow number and symbol?
|
|
975
|
+
task, select_, fresh_),
|
|
976
|
+
},
|
|
977
|
+
}));
|
|
851
978
|
}
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
});
|
|
859
|
-
})
|
|
860
|
-
.flat()
|
|
861
|
-
.sort(([aNumber], [bNumber]) => compareChannelVersions(aNumber, bNumber));
|
|
862
|
-
// if two nodes updated the state at the same time, it's ambiguous
|
|
863
|
-
if (lastSeenByNode) {
|
|
864
|
-
if (lastSeenByNode.length === 1) {
|
|
865
|
-
// eslint-disable-next-line prefer-destructuring
|
|
866
|
-
asNode = lastSeenByNode[0][1];
|
|
979
|
+
for (const task of tasks) {
|
|
980
|
+
// channel writes are saved to current checkpoint
|
|
981
|
+
const channelWrites = task.writes.filter((w) => w[0] !== PUSH);
|
|
982
|
+
// save task writes
|
|
983
|
+
if (saved !== undefined && channelWrites.length > 0) {
|
|
984
|
+
await checkpointer.putWrites(checkpointConfig, channelWrites, task.id);
|
|
867
985
|
}
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
986
|
+
}
|
|
987
|
+
// apply to checkpoint
|
|
988
|
+
// TODO: Why does keyof StrRecord allow number and symbol?
|
|
989
|
+
_applyWrites(checkpoint, channels, tasks, checkpointer.getNextVersion.bind(this.checkpointer));
|
|
990
|
+
const newVersions = getNewChannelVersions(checkpointPreviousVersions, checkpoint.channel_versions);
|
|
991
|
+
const nextConfig = await checkpointer.put(checkpointConfig, createCheckpoint(checkpoint, channels, step + 1), {
|
|
992
|
+
source: "update",
|
|
993
|
+
step: step + 1,
|
|
994
|
+
writes: Object.fromEntries(validUpdates.map((update) => [update.asNode, update.values])),
|
|
995
|
+
parents: saved?.metadata?.parents ?? {},
|
|
996
|
+
}, newVersions);
|
|
997
|
+
for (const task of tasks) {
|
|
998
|
+
// push writes are saved to next checkpoint
|
|
999
|
+
const pushWrites = task.writes.filter((w) => w[0] === PUSH);
|
|
1000
|
+
if (pushWrites.length > 0) {
|
|
1001
|
+
await checkpointer.putWrites(nextConfig, pushWrites, task.id);
|
|
872
1002
|
}
|
|
873
1003
|
}
|
|
874
|
-
|
|
875
|
-
if (asNode === undefined) {
|
|
876
|
-
throw new InvalidUpdateError(`Ambiguous update, specify "asNode"`);
|
|
877
|
-
}
|
|
878
|
-
if (this.nodes[asNode] === undefined) {
|
|
879
|
-
throw new InvalidUpdateError(`Node "${asNode.toString()}" does not exist`);
|
|
880
|
-
}
|
|
881
|
-
// run all writers of the chosen node
|
|
882
|
-
const writers = this.nodes[asNode].getWriters();
|
|
883
|
-
if (!writers.length) {
|
|
884
|
-
throw new InvalidUpdateError(`No writers found for node "${asNode.toString()}"`);
|
|
885
|
-
}
|
|
886
|
-
const task = {
|
|
887
|
-
name: asNode,
|
|
888
|
-
input: values,
|
|
889
|
-
proc: writers.length > 1
|
|
890
|
-
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
891
|
-
RunnableSequence.from(writers, { omitSequenceTags: true })
|
|
892
|
-
: writers[0],
|
|
893
|
-
writes: [],
|
|
894
|
-
triggers: [INTERRUPT],
|
|
895
|
-
id: uuid5(INTERRUPT, checkpoint.id),
|
|
896
|
-
writers: [],
|
|
1004
|
+
return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
|
|
897
1005
|
};
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
store: config?.store ?? this.store,
|
|
902
|
-
}, {
|
|
903
|
-
runName: config.runName ?? `${this.getName()}UpdateState`,
|
|
904
|
-
configurable: {
|
|
905
|
-
[CONFIG_KEY_SEND]: (items) => task.writes.push(...items),
|
|
906
|
-
[CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(step, checkpoint, channels, managed,
|
|
907
|
-
// TODO: Why does keyof StrRecord allow number and symbol?
|
|
908
|
-
task, select_, fresh_),
|
|
909
|
-
},
|
|
910
|
-
}));
|
|
911
|
-
// save task writes
|
|
912
|
-
// channel writes are saved to current checkpoint
|
|
913
|
-
// push writes are saved to next checkpoint
|
|
914
|
-
const [channelWrites, pushWrites] = [
|
|
915
|
-
task.writes.filter((w) => w[0] !== PUSH),
|
|
916
|
-
task.writes.filter((w) => w[0] === PUSH),
|
|
917
|
-
];
|
|
918
|
-
// save task writes
|
|
919
|
-
if (saved !== undefined && channelWrites.length > 0) {
|
|
920
|
-
await checkpointer.putWrites(checkpointConfig, channelWrites, task.id);
|
|
1006
|
+
let currentConfig = startConfig;
|
|
1007
|
+
for (const { updates } of supersteps) {
|
|
1008
|
+
currentConfig = await updateSuperStep(currentConfig, updates);
|
|
921
1009
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1010
|
+
return currentConfig;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Updates the state of the graph with new values.
|
|
1014
|
+
* Requires a checkpointer to be configured.
|
|
1015
|
+
*
|
|
1016
|
+
* This method can be used for:
|
|
1017
|
+
* - Implementing human-in-the-loop workflows
|
|
1018
|
+
* - Modifying graph state during breakpoints
|
|
1019
|
+
* - Integrating external inputs into the graph
|
|
1020
|
+
*
|
|
1021
|
+
* @param inputConfig - Configuration for the update
|
|
1022
|
+
* @param values - The values to update the state with
|
|
1023
|
+
* @param asNode - Optional node name to attribute the update to
|
|
1024
|
+
* @returns Updated configuration
|
|
1025
|
+
* @throws {GraphValueError} If no checkpointer is configured
|
|
1026
|
+
* @throws {InvalidUpdateError} If the update cannot be attributed to a node
|
|
1027
|
+
*/
|
|
1028
|
+
async updateState(inputConfig, values, asNode) {
|
|
1029
|
+
return this.bulkUpdateState(inputConfig, [
|
|
1030
|
+
{ updates: [{ values, asNode }] },
|
|
1031
|
+
]);
|
|
936
1032
|
}
|
|
937
1033
|
/**
|
|
938
1034
|
* Gets the default values for various graph configuration options.
|