@langchain/langgraph 0.2.54 → 0.2.56

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