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