@langchain/langgraph 0.2.37 → 0.2.39

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.
@@ -824,6 +824,92 @@ class Pregel extends runnables_1.Runnable {
824
824
  managed,
825
825
  };
826
826
  }
827
+ async _runLoop(params) {
828
+ const { loop, interruptAfter, interruptBefore, runManager, debug, config } = params;
829
+ let tickError;
830
+ try {
831
+ while (await loop.tick({
832
+ inputKeys: this.inputChannels,
833
+ interruptAfter,
834
+ interruptBefore,
835
+ manager: runManager,
836
+ })) {
837
+ if (debug) {
838
+ (0, debug_js_1.printStepCheckpoint)(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
839
+ }
840
+ if (debug) {
841
+ (0, debug_js_1.printStepTasks)(loop.step, Object.values(loop.tasks));
842
+ }
843
+ // execute tasks, and wait for one to fail or all to finish.
844
+ // each task is independent from all other concurrent tasks
845
+ // yield updates/debug output as each task finishes
846
+ const taskStream = (0, retry_js_1.executeTasksWithRetry)(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
847
+ stepTimeout: this.stepTimeout,
848
+ signal: config.signal,
849
+ retryPolicy: this.retryPolicy,
850
+ });
851
+ let graphInterrupt;
852
+ for await (const { task, error } of taskStream) {
853
+ if (error !== undefined) {
854
+ if ((0, errors_js_1.isGraphBubbleUp)(error)) {
855
+ if (loop.isNested) {
856
+ throw error;
857
+ }
858
+ if ((0, errors_js_1.isGraphInterrupt)(error)) {
859
+ graphInterrupt = error;
860
+ if (error.interrupts.length) {
861
+ const interrupts = error.interrupts.map((interrupt) => [constants_js_1.INTERRUPT, interrupt]);
862
+ const resumes = task.writes.filter((w) => w[0] === constants_js_1.RESUME);
863
+ if (resumes.length) {
864
+ interrupts.push(...resumes);
865
+ }
866
+ loop.putWrites(task.id, interrupts);
867
+ }
868
+ }
869
+ }
870
+ else {
871
+ loop.putWrites(task.id, [
872
+ [constants_js_1.ERROR, { message: error.message, name: error.name }],
873
+ ]);
874
+ throw error;
875
+ }
876
+ }
877
+ else {
878
+ loop.putWrites(task.id, task.writes);
879
+ }
880
+ }
881
+ if (debug) {
882
+ (0, debug_js_1.printStepWrites)(loop.step, Object.values(loop.tasks)
883
+ .map((task) => task.writes)
884
+ .flat(), this.streamChannelsList);
885
+ }
886
+ if (graphInterrupt !== undefined) {
887
+ throw graphInterrupt;
888
+ }
889
+ }
890
+ if (loop.status === "out_of_steps") {
891
+ throw new errors_js_1.GraphRecursionError([
892
+ `Recursion limit of ${config.recursionLimit} reached`,
893
+ "without hitting a stop condition. You can increase the",
894
+ `limit by setting the "recursionLimit" config key.`,
895
+ ].join(" "), {
896
+ lc_error_code: "GRAPH_RECURSION_LIMIT",
897
+ });
898
+ }
899
+ }
900
+ catch (e) {
901
+ tickError = e;
902
+ const suppress = await loop.finishAndHandleError(tickError);
903
+ if (!suppress) {
904
+ throw e;
905
+ }
906
+ }
907
+ finally {
908
+ if (tickError === undefined) {
909
+ await loop.finishAndHandleError();
910
+ }
911
+ }
912
+ }
827
913
  async *_streamIterator(input, options) {
828
914
  const streamSubgraphs = options?.subgraphs;
829
915
  const inputConfig = (0, config_js_1.ensureLangGraphConfig)(this.config, options);
@@ -867,7 +953,15 @@ class Pregel extends runnables_1.Runnable {
867
953
  const { channelSpecs, managed } = await this.prepareSpecs(config);
868
954
  let loop;
869
955
  let loopError;
870
- const runLoop = async () => {
956
+ /**
957
+ * The PregelLoop will yield events from concurrent tasks as soon as they are
958
+ * generated. Each task can push multiple events onto the stream in any order.
959
+ *
960
+ * We use a separate background method and stream here in order to yield events
961
+ * from the loop to the main stream and therefore back to the user as soon as
962
+ * they are available.
963
+ */
964
+ const createAndRunLoop = async () => {
871
965
  try {
872
966
  loop = await loop_js_1.PregelLoop.initialize({
873
967
  input,
@@ -887,68 +981,14 @@ class Pregel extends runnables_1.Runnable {
887
981
  [constants_js_1.CONFIG_KEY_STREAM]: loop.stream,
888
982
  };
889
983
  }
890
- while (await loop.tick({
891
- inputKeys: this.inputChannels,
984
+ await this._runLoop({
985
+ loop,
892
986
  interruptAfter,
893
987
  interruptBefore,
894
- manager: runManager,
895
- })) {
896
- if (debug) {
897
- (0, debug_js_1.printStepCheckpoint)(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
898
- }
899
- if (debug) {
900
- (0, debug_js_1.printStepTasks)(loop.step, Object.values(loop.tasks));
901
- }
902
- // execute tasks, and wait for one to fail or all to finish.
903
- // each task is independent from all other concurrent tasks
904
- // yield updates/debug output as each task finishes
905
- const taskStream = (0, retry_js_1.executeTasksWithRetry)(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
906
- stepTimeout: this.stepTimeout,
907
- signal: config.signal,
908
- retryPolicy: this.retryPolicy,
909
- });
910
- // Timeouts will be thrown
911
- for await (const { task, error } of taskStream) {
912
- if (error !== undefined) {
913
- if ((0, errors_js_1.isGraphBubbleUp)(error)) {
914
- if (loop.isNested) {
915
- throw error;
916
- }
917
- if ((0, errors_js_1.isGraphInterrupt)(error) && error.interrupts.length) {
918
- const interrupts = error.interrupts.map((interrupt) => [constants_js_1.INTERRUPT, interrupt]);
919
- const resumes = task.writes.filter((w) => w[0] === constants_js_1.RESUME);
920
- if (resumes.length) {
921
- interrupts.push(...resumes);
922
- }
923
- loop.putWrites(task.id, interrupts);
924
- }
925
- }
926
- else {
927
- loop.putWrites(task.id, [
928
- [constants_js_1.ERROR, { message: error.message, name: error.name }],
929
- ]);
930
- throw error;
931
- }
932
- }
933
- else {
934
- loop.putWrites(task.id, task.writes);
935
- }
936
- }
937
- if (debug) {
938
- (0, debug_js_1.printStepWrites)(loop.step, Object.values(loop.tasks)
939
- .map((task) => task.writes)
940
- .flat(), this.streamChannelsList);
941
- }
942
- }
943
- if (loop.status === "out_of_steps") {
944
- throw new errors_js_1.GraphRecursionError([
945
- `Recursion limit of ${config.recursionLimit} reached`,
946
- "without hitting a stop condition. You can increase the",
947
- `limit by setting the "recursionLimit" config key.`,
948
- ].join(" "), {
949
- lc_error_code: "GRAPH_RECURSION_LIMIT",
950
- });
951
- }
988
+ runManager,
989
+ debug,
990
+ config,
991
+ });
952
992
  }
953
993
  catch (e) {
954
994
  loopError = e;
@@ -982,7 +1022,7 @@ class Pregel extends runnables_1.Runnable {
982
1022
  }
983
1023
  }
984
1024
  };
985
- const runLoopPromise = runLoop();
1025
+ const runLoopPromise = createAndRunLoop();
986
1026
  try {
987
1027
  for await (const chunk of stream) {
988
1028
  if (chunk === undefined) {
@@ -1,5 +1,6 @@
1
1
  import { Runnable, RunnableConfig, RunnableFunc } from "@langchain/core/runnables";
2
2
  import { IterableReadableStream } from "@langchain/core/utils/stream";
3
+ import type { CallbackManagerForChainRun } from "@langchain/core/callbacks/manager";
3
4
  import { All, BaseCheckpointSaver, BaseStore, CheckpointListOptions, CheckpointTuple } from "@langchain/langgraph-checkpoint";
4
5
  import { BaseChannel } from "../channels/base.js";
5
6
  import { PregelNode } from "./read.js";
@@ -8,6 +9,7 @@ import { Command } from "../constants.js";
8
9
  import { PregelInterface, PregelParams, StateSnapshot, StreamMode, PregelInputType, PregelOutputType, PregelOptions } from "./types.js";
9
10
  import { StrRecord } from "./algo.js";
10
11
  import { RetryPolicy } from "./utils/index.js";
12
+ import { PregelLoop } from "./loop.js";
11
13
  import { ManagedValueMapping, type ManagedValueSpec } from "../managed/base.js";
12
14
  import { LangGraphRunnableConfig } from "./runnable_types.js";
13
15
  type WriteValue = Runnable | RunnableFunc<unknown, unknown> | unknown;
@@ -110,6 +112,14 @@ export declare class Pregel<Nn extends StrRecord<string, PregelNode>, Cc extends
110
112
  channelSpecs: Record<string, BaseChannel<unknown, unknown, unknown>>;
111
113
  managed: ManagedValueMapping;
112
114
  }>;
115
+ _runLoop(params: {
116
+ loop: PregelLoop;
117
+ interruptAfter: string[] | "*";
118
+ interruptBefore: string[] | "*";
119
+ runManager?: CallbackManagerForChainRun;
120
+ debug: boolean;
121
+ config: LangGraphRunnableConfig;
122
+ }): Promise<void>;
113
123
  _streamIterator(input: PregelInputType | Command, options?: Partial<PregelOptions<Nn, Cc>>): AsyncGenerator<PregelOutputType>;
114
124
  /**
115
125
  * Run the graph with a single input and config.
@@ -820,6 +820,92 @@ export class Pregel extends Runnable {
820
820
  managed,
821
821
  };
822
822
  }
823
+ async _runLoop(params) {
824
+ const { loop, interruptAfter, interruptBefore, runManager, debug, config } = params;
825
+ let tickError;
826
+ try {
827
+ while (await loop.tick({
828
+ inputKeys: this.inputChannels,
829
+ interruptAfter,
830
+ interruptBefore,
831
+ manager: runManager,
832
+ })) {
833
+ if (debug) {
834
+ printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
835
+ }
836
+ if (debug) {
837
+ printStepTasks(loop.step, Object.values(loop.tasks));
838
+ }
839
+ // execute tasks, and wait for one to fail or all to finish.
840
+ // each task is independent from all other concurrent tasks
841
+ // yield updates/debug output as each task finishes
842
+ const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
843
+ stepTimeout: this.stepTimeout,
844
+ signal: config.signal,
845
+ retryPolicy: this.retryPolicy,
846
+ });
847
+ let graphInterrupt;
848
+ for await (const { task, error } of taskStream) {
849
+ if (error !== undefined) {
850
+ if (isGraphBubbleUp(error)) {
851
+ if (loop.isNested) {
852
+ throw error;
853
+ }
854
+ if (isGraphInterrupt(error)) {
855
+ graphInterrupt = error;
856
+ if (error.interrupts.length) {
857
+ const interrupts = error.interrupts.map((interrupt) => [INTERRUPT, interrupt]);
858
+ const resumes = task.writes.filter((w) => w[0] === RESUME);
859
+ if (resumes.length) {
860
+ interrupts.push(...resumes);
861
+ }
862
+ loop.putWrites(task.id, interrupts);
863
+ }
864
+ }
865
+ }
866
+ else {
867
+ loop.putWrites(task.id, [
868
+ [ERROR, { message: error.message, name: error.name }],
869
+ ]);
870
+ throw error;
871
+ }
872
+ }
873
+ else {
874
+ loop.putWrites(task.id, task.writes);
875
+ }
876
+ }
877
+ if (debug) {
878
+ printStepWrites(loop.step, Object.values(loop.tasks)
879
+ .map((task) => task.writes)
880
+ .flat(), this.streamChannelsList);
881
+ }
882
+ if (graphInterrupt !== undefined) {
883
+ throw graphInterrupt;
884
+ }
885
+ }
886
+ if (loop.status === "out_of_steps") {
887
+ throw new GraphRecursionError([
888
+ `Recursion limit of ${config.recursionLimit} reached`,
889
+ "without hitting a stop condition. You can increase the",
890
+ `limit by setting the "recursionLimit" config key.`,
891
+ ].join(" "), {
892
+ lc_error_code: "GRAPH_RECURSION_LIMIT",
893
+ });
894
+ }
895
+ }
896
+ catch (e) {
897
+ tickError = e;
898
+ const suppress = await loop.finishAndHandleError(tickError);
899
+ if (!suppress) {
900
+ throw e;
901
+ }
902
+ }
903
+ finally {
904
+ if (tickError === undefined) {
905
+ await loop.finishAndHandleError();
906
+ }
907
+ }
908
+ }
823
909
  async *_streamIterator(input, options) {
824
910
  const streamSubgraphs = options?.subgraphs;
825
911
  const inputConfig = ensureLangGraphConfig(this.config, options);
@@ -863,7 +949,15 @@ export class Pregel extends Runnable {
863
949
  const { channelSpecs, managed } = await this.prepareSpecs(config);
864
950
  let loop;
865
951
  let loopError;
866
- const runLoop = async () => {
952
+ /**
953
+ * The PregelLoop will yield events from concurrent tasks as soon as they are
954
+ * generated. Each task can push multiple events onto the stream in any order.
955
+ *
956
+ * We use a separate background method and stream here in order to yield events
957
+ * from the loop to the main stream and therefore back to the user as soon as
958
+ * they are available.
959
+ */
960
+ const createAndRunLoop = async () => {
867
961
  try {
868
962
  loop = await PregelLoop.initialize({
869
963
  input,
@@ -883,68 +977,14 @@ export class Pregel extends Runnable {
883
977
  [CONFIG_KEY_STREAM]: loop.stream,
884
978
  };
885
979
  }
886
- while (await loop.tick({
887
- inputKeys: this.inputChannels,
980
+ await this._runLoop({
981
+ loop,
888
982
  interruptAfter,
889
983
  interruptBefore,
890
- manager: runManager,
891
- })) {
892
- if (debug) {
893
- printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
894
- }
895
- if (debug) {
896
- printStepTasks(loop.step, Object.values(loop.tasks));
897
- }
898
- // execute tasks, and wait for one to fail or all to finish.
899
- // each task is independent from all other concurrent tasks
900
- // yield updates/debug output as each task finishes
901
- const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
902
- stepTimeout: this.stepTimeout,
903
- signal: config.signal,
904
- retryPolicy: this.retryPolicy,
905
- });
906
- // Timeouts will be thrown
907
- for await (const { task, error } of taskStream) {
908
- if (error !== undefined) {
909
- if (isGraphBubbleUp(error)) {
910
- if (loop.isNested) {
911
- throw error;
912
- }
913
- if (isGraphInterrupt(error) && error.interrupts.length) {
914
- const interrupts = error.interrupts.map((interrupt) => [INTERRUPT, interrupt]);
915
- const resumes = task.writes.filter((w) => w[0] === RESUME);
916
- if (resumes.length) {
917
- interrupts.push(...resumes);
918
- }
919
- loop.putWrites(task.id, interrupts);
920
- }
921
- }
922
- else {
923
- loop.putWrites(task.id, [
924
- [ERROR, { message: error.message, name: error.name }],
925
- ]);
926
- throw error;
927
- }
928
- }
929
- else {
930
- loop.putWrites(task.id, task.writes);
931
- }
932
- }
933
- if (debug) {
934
- printStepWrites(loop.step, Object.values(loop.tasks)
935
- .map((task) => task.writes)
936
- .flat(), this.streamChannelsList);
937
- }
938
- }
939
- if (loop.status === "out_of_steps") {
940
- throw new GraphRecursionError([
941
- `Recursion limit of ${config.recursionLimit} reached`,
942
- "without hitting a stop condition. You can increase the",
943
- `limit by setting the "recursionLimit" config key.`,
944
- ].join(" "), {
945
- lc_error_code: "GRAPH_RECURSION_LIMIT",
946
- });
947
- }
984
+ runManager,
985
+ debug,
986
+ config,
987
+ });
948
988
  }
949
989
  catch (e) {
950
990
  loopError = e;
@@ -978,7 +1018,7 @@ export class Pregel extends Runnable {
978
1018
  }
979
1019
  }
980
1020
  };
981
- const runLoopPromise = runLoop();
1021
+ const runLoopPromise = createAndRunLoop();
982
1022
  try {
983
1023
  for await (const chunk of stream) {
984
1024
  if (chunk === undefined) {
@@ -154,8 +154,10 @@ exports.mapOutputValues = mapOutputValues;
154
154
  function* mapOutputUpdates(outputChannels, tasks, cached
155
155
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
156
  ) {
157
- const outputTasks = tasks.filter(([task]) => {
158
- return task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN);
157
+ const outputTasks = tasks.filter(([task, ww]) => {
158
+ return ((task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN)) &&
159
+ ww[0][0] !== constants_js_1.ERROR &&
160
+ ww[0][0] !== constants_js_1.INTERRUPT);
159
161
  });
160
162
  if (!outputTasks.length) {
161
163
  return;
package/dist/pregel/io.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { validate } from "uuid";
2
- import { _isSend, Command, NULL_TASK_ID, RESUME, SELF, TAG_HIDDEN, TASKS, } from "../constants.js";
2
+ import { _isSend, Command, ERROR, INTERRUPT, NULL_TASK_ID, RESUME, SELF, TAG_HIDDEN, TASKS, } from "../constants.js";
3
3
  import { EmptyChannelError, InvalidUpdateError } from "../errors.js";
4
4
  export function readChannel(channels, chan, catchErrors = true, returnException = false) {
5
5
  try {
@@ -146,8 +146,10 @@ export function* mapOutputValues(outputChannels, pendingWrites, channels
146
146
  export function* mapOutputUpdates(outputChannels, tasks, cached
147
147
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
148
  ) {
149
- const outputTasks = tasks.filter(([task]) => {
150
- return task.config === undefined || !task.config.tags?.includes(TAG_HIDDEN);
149
+ const outputTasks = tasks.filter(([task, ww]) => {
150
+ return ((task.config === undefined || !task.config.tags?.includes(TAG_HIDDEN)) &&
151
+ ww[0][0] !== ERROR &&
152
+ ww[0][0] !== INTERRUPT);
151
153
  });
152
154
  if (!outputTasks.length) {
153
155
  return;
@@ -455,125 +455,125 @@ class PregelLoop {
455
455
  * @param params
456
456
  */
457
457
  async tick(params) {
458
- let tickError;
459
- try {
460
- if (this.store && !this.store.isRunning) {
461
- this.store?.start();
462
- }
463
- const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
464
- if (this.status !== "pending") {
465
- throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
466
- }
467
- if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
468
- await this._first(inputKeys);
469
- }
470
- else if (Object.values(this.tasks).every((task) => task.writes.filter(([c]) => !(c in langgraph_checkpoint_1.WRITES_IDX_MAP)).length > 0)) {
471
- const writes = Object.values(this.tasks).flatMap((t) => t.writes);
472
- // All tasks have finished
473
- const managedValueWrites = (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
474
- for (const [key, values] of Object.entries(managedValueWrites)) {
475
- await this.updateManagedValues(key, values);
476
- }
477
- // produce values output
478
- const valuesOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, writes, this.channels), "values"));
479
- this._emit(valuesOutput);
480
- // clear pending writes
481
- this.checkpointPendingWrites = [];
482
- await this._putCheckpoint({
483
- source: "loop",
484
- writes: (0, io_js_1.mapOutputUpdates)(this.outputKeys, Object.values(this.tasks).map((task) => [task, task.writes])).next().value ?? null,
485
- });
486
- // after execution, check if we should interrupt
487
- if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptAfter, Object.values(this.tasks))) {
488
- this.status = "interrupt_after";
489
- if (this.isNested) {
490
- throw new errors_js_1.GraphInterrupt();
491
- }
492
- else {
493
- return false;
494
- }
495
- }
496
- }
497
- else {
498
- return false;
499
- }
500
- if (this.step > this.stop) {
501
- this.status = "out_of_steps";
502
- return false;
458
+ if (this.store && !this.store.isRunning) {
459
+ this.store?.start();
460
+ }
461
+ const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
462
+ if (this.status !== "pending") {
463
+ throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
464
+ }
465
+ if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
466
+ await this._first(inputKeys);
467
+ }
468
+ else if (Object.values(this.tasks).every((task) => task.writes.filter(([c]) => !(c in langgraph_checkpoint_1.WRITES_IDX_MAP)).length > 0)) {
469
+ const writes = Object.values(this.tasks).flatMap((t) => t.writes);
470
+ // All tasks have finished
471
+ const managedValueWrites = (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
472
+ for (const [key, values] of Object.entries(managedValueWrites)) {
473
+ await this.updateManagedValues(key, values);
503
474
  }
504
- const nextTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, this.config, true, {
505
- step: this.step,
506
- checkpointer: this.checkpointer,
507
- isResuming: this.input === INPUT_RESUMING,
508
- manager,
509
- store: this.store,
475
+ // produce values output
476
+ const valuesOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, writes, this.channels), "values"));
477
+ this._emit(valuesOutput);
478
+ // clear pending writes
479
+ this.checkpointPendingWrites = [];
480
+ await this._putCheckpoint({
481
+ source: "loop",
482
+ writes: (0, io_js_1.mapOutputUpdates)(this.outputKeys, Object.values(this.tasks).map((task) => [task, task.writes])).next().value ?? null,
510
483
  });
511
- this.tasks = nextTasks;
512
- // Produce debug output
513
- if (this.checkpointer) {
514
- this._emit(await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugCheckpoint)(this.step - 1, // printing checkpoint for previous step
515
- this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, Object.values(this.tasks), this.checkpointPendingWrites, this.prevCheckpointConfig), "debug")));
516
- }
517
- if (Object.values(this.tasks).length === 0) {
518
- this.status = "done";
519
- return false;
484
+ // after execution, check if we should interrupt
485
+ if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptAfter, Object.values(this.tasks))) {
486
+ this.status = "interrupt_after";
487
+ throw new errors_js_1.GraphInterrupt();
520
488
  }
521
- // if there are pending writes from a previous loop, apply them
522
- if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
523
- for (const [tid, k, v] of this.checkpointPendingWrites) {
524
- if (k === constants_js_1.ERROR || k === constants_js_1.INTERRUPT || k === constants_js_1.RESUME) {
525
- continue;
526
- }
527
- const task = Object.values(this.tasks).find((t) => t.id === tid);
528
- if (task) {
529
- task.writes.push([k, v]);
530
- }
489
+ }
490
+ else {
491
+ return false;
492
+ }
493
+ if (this.step > this.stop) {
494
+ this.status = "out_of_steps";
495
+ return false;
496
+ }
497
+ const nextTasks = (0, algo_js_1._prepareNextTasks)(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, this.config, true, {
498
+ step: this.step,
499
+ checkpointer: this.checkpointer,
500
+ isResuming: this.input === INPUT_RESUMING,
501
+ manager,
502
+ store: this.store,
503
+ });
504
+ this.tasks = nextTasks;
505
+ // Produce debug output
506
+ if (this.checkpointer) {
507
+ this._emit(await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugCheckpoint)(this.step - 1, // printing checkpoint for previous step
508
+ this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, Object.values(this.tasks), this.checkpointPendingWrites, this.prevCheckpointConfig), "debug")));
509
+ }
510
+ if (Object.values(this.tasks).length === 0) {
511
+ this.status = "done";
512
+ return false;
513
+ }
514
+ // if there are pending writes from a previous loop, apply them
515
+ if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
516
+ for (const [tid, k, v] of this.checkpointPendingWrites) {
517
+ if (k === constants_js_1.ERROR || k === constants_js_1.INTERRUPT || k === constants_js_1.RESUME) {
518
+ continue;
531
519
  }
532
- for (const task of Object.values(this.tasks)) {
533
- if (task.writes.length > 0) {
534
- this._outputWrites(task.id, task.writes, true);
535
- }
520
+ const task = Object.values(this.tasks).find((t) => t.id === tid);
521
+ if (task) {
522
+ task.writes.push([k, v]);
536
523
  }
537
524
  }
538
- // if all tasks have finished, re-tick
539
- if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
540
- return this.tick({
541
- inputKeys,
542
- interruptAfter,
543
- interruptBefore,
544
- manager,
545
- });
546
- }
547
- // Before execution, check if we should interrupt
548
- if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptBefore, Object.values(this.tasks))) {
549
- this.status = "interrupt_before";
550
- if (this.isNested) {
551
- throw new errors_js_1.GraphInterrupt();
552
- }
553
- else {
554
- return false;
525
+ for (const task of Object.values(this.tasks)) {
526
+ if (task.writes.length > 0) {
527
+ this._outputWrites(task.id, task.writes, true);
555
528
  }
556
529
  }
557
- // Produce debug output
558
- const debugOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugTasks)(this.step, Object.values(this.tasks)), "debug"));
559
- this._emit(debugOutput);
560
- return true;
561
530
  }
562
- catch (e) {
563
- tickError = e;
564
- if (!this._suppressInterrupt(tickError)) {
565
- throw tickError;
566
- }
567
- else {
568
- this.output = (0, io_js_1.readChannels)(this.channels, this.outputKeys);
569
- }
570
- return false;
531
+ // if all tasks have finished, re-tick
532
+ if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
533
+ return this.tick({
534
+ inputKeys,
535
+ interruptAfter,
536
+ interruptBefore,
537
+ manager,
538
+ });
539
+ }
540
+ // Before execution, check if we should interrupt
541
+ if ((0, algo_js_1.shouldInterrupt)(this.checkpoint, interruptBefore, Object.values(this.tasks))) {
542
+ this.status = "interrupt_before";
543
+ throw new errors_js_1.GraphInterrupt();
571
544
  }
572
- finally {
573
- if (tickError === undefined) {
574
- this.output = (0, io_js_1.readChannels)(this.channels, this.outputKeys);
545
+ // Produce debug output
546
+ const debugOutput = await (0, utils_js_1.gatherIterator)((0, utils_js_1.prefixGenerator)((0, debug_js_1.mapDebugTasks)(this.step, Object.values(this.tasks)), "debug"));
547
+ this._emit(debugOutput);
548
+ return true;
549
+ }
550
+ async finishAndHandleError(error) {
551
+ const suppress = this._suppressInterrupt(error);
552
+ if (suppress || error === undefined) {
553
+ this.output = (0, io_js_1.readChannels)(this.channels, this.outputKeys);
554
+ }
555
+ if (suppress) {
556
+ // emit one last "values" event, with pending writes applied
557
+ if (this.tasks !== undefined &&
558
+ this.checkpointPendingWrites.length > 0 &&
559
+ Object.values(this.tasks).some((task) => task.writes.length > 0)) {
560
+ const managedValueWrites = (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
561
+ for (const [key, values] of Object.entries(managedValueWrites)) {
562
+ await this.updateManagedValues(key, values);
563
+ }
564
+ this._emit((0, utils_js_1.gatherIteratorSync)((0, utils_js_1.prefixGenerator)((0, io_js_1.mapOutputValues)(this.outputKeys, Object.values(this.tasks).flatMap((t) => t.writes), this.channels), "values")));
575
565
  }
566
+ // Emit INTERRUPT event
567
+ this._emit([
568
+ [
569
+ "updates",
570
+ {
571
+ [constants_js_1.INTERRUPT]: error.interrupts,
572
+ },
573
+ ],
574
+ ]);
576
575
  }
576
+ return suppress;
577
577
  }
578
578
  _suppressInterrupt(e) {
579
579
  return (0, errors_js_1.isGraphInterrupt)(e) && !this.isNested;
@@ -112,6 +112,7 @@ export declare class PregelLoop {
112
112
  interruptBefore: string[] | All;
113
113
  manager?: CallbackManagerForChainRun;
114
114
  }): Promise<boolean>;
115
+ finishAndHandleError(error?: Error): Promise<boolean>;
115
116
  protected _suppressInterrupt(e?: Error): boolean;
116
117
  /**
117
118
  * Resuming from previous checkpoint requires
@@ -451,125 +451,125 @@ export class PregelLoop {
451
451
  * @param params
452
452
  */
453
453
  async tick(params) {
454
- let tickError;
455
- try {
456
- if (this.store && !this.store.isRunning) {
457
- this.store?.start();
458
- }
459
- const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
460
- if (this.status !== "pending") {
461
- throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
462
- }
463
- if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
464
- await this._first(inputKeys);
465
- }
466
- else if (Object.values(this.tasks).every((task) => task.writes.filter(([c]) => !(c in WRITES_IDX_MAP)).length > 0)) {
467
- const writes = Object.values(this.tasks).flatMap((t) => t.writes);
468
- // All tasks have finished
469
- const managedValueWrites = _applyWrites(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
470
- for (const [key, values] of Object.entries(managedValueWrites)) {
471
- await this.updateManagedValues(key, values);
472
- }
473
- // produce values output
474
- const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, writes, this.channels), "values"));
475
- this._emit(valuesOutput);
476
- // clear pending writes
477
- this.checkpointPendingWrites = [];
478
- await this._putCheckpoint({
479
- source: "loop",
480
- writes: mapOutputUpdates(this.outputKeys, Object.values(this.tasks).map((task) => [task, task.writes])).next().value ?? null,
481
- });
482
- // after execution, check if we should interrupt
483
- if (shouldInterrupt(this.checkpoint, interruptAfter, Object.values(this.tasks))) {
484
- this.status = "interrupt_after";
485
- if (this.isNested) {
486
- throw new GraphInterrupt();
487
- }
488
- else {
489
- return false;
490
- }
491
- }
492
- }
493
- else {
494
- return false;
495
- }
496
- if (this.step > this.stop) {
497
- this.status = "out_of_steps";
498
- return false;
454
+ if (this.store && !this.store.isRunning) {
455
+ this.store?.start();
456
+ }
457
+ const { inputKeys = [], interruptAfter = [], interruptBefore = [], manager, } = params;
458
+ if (this.status !== "pending") {
459
+ throw new Error(`Cannot tick when status is no longer "pending". Current status: "${this.status}"`);
460
+ }
461
+ if (![INPUT_DONE, INPUT_RESUMING].includes(this.input)) {
462
+ await this._first(inputKeys);
463
+ }
464
+ else if (Object.values(this.tasks).every((task) => task.writes.filter(([c]) => !(c in WRITES_IDX_MAP)).length > 0)) {
465
+ const writes = Object.values(this.tasks).flatMap((t) => t.writes);
466
+ // All tasks have finished
467
+ const managedValueWrites = _applyWrites(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
468
+ for (const [key, values] of Object.entries(managedValueWrites)) {
469
+ await this.updateManagedValues(key, values);
499
470
  }
500
- const nextTasks = _prepareNextTasks(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, this.config, true, {
501
- step: this.step,
502
- checkpointer: this.checkpointer,
503
- isResuming: this.input === INPUT_RESUMING,
504
- manager,
505
- store: this.store,
471
+ // produce values output
472
+ const valuesOutput = await gatherIterator(prefixGenerator(mapOutputValues(this.outputKeys, writes, this.channels), "values"));
473
+ this._emit(valuesOutput);
474
+ // clear pending writes
475
+ this.checkpointPendingWrites = [];
476
+ await this._putCheckpoint({
477
+ source: "loop",
478
+ writes: mapOutputUpdates(this.outputKeys, Object.values(this.tasks).map((task) => [task, task.writes])).next().value ?? null,
506
479
  });
507
- this.tasks = nextTasks;
508
- // Produce debug output
509
- if (this.checkpointer) {
510
- this._emit(await gatherIterator(prefixGenerator(mapDebugCheckpoint(this.step - 1, // printing checkpoint for previous step
511
- this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, Object.values(this.tasks), this.checkpointPendingWrites, this.prevCheckpointConfig), "debug")));
512
- }
513
- if (Object.values(this.tasks).length === 0) {
514
- this.status = "done";
515
- return false;
480
+ // after execution, check if we should interrupt
481
+ if (shouldInterrupt(this.checkpoint, interruptAfter, Object.values(this.tasks))) {
482
+ this.status = "interrupt_after";
483
+ throw new GraphInterrupt();
516
484
  }
517
- // if there are pending writes from a previous loop, apply them
518
- if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
519
- for (const [tid, k, v] of this.checkpointPendingWrites) {
520
- if (k === ERROR || k === INTERRUPT || k === RESUME) {
521
- continue;
522
- }
523
- const task = Object.values(this.tasks).find((t) => t.id === tid);
524
- if (task) {
525
- task.writes.push([k, v]);
526
- }
485
+ }
486
+ else {
487
+ return false;
488
+ }
489
+ if (this.step > this.stop) {
490
+ this.status = "out_of_steps";
491
+ return false;
492
+ }
493
+ const nextTasks = _prepareNextTasks(this.checkpoint, this.checkpointPendingWrites, this.nodes, this.channels, this.managed, this.config, true, {
494
+ step: this.step,
495
+ checkpointer: this.checkpointer,
496
+ isResuming: this.input === INPUT_RESUMING,
497
+ manager,
498
+ store: this.store,
499
+ });
500
+ this.tasks = nextTasks;
501
+ // Produce debug output
502
+ if (this.checkpointer) {
503
+ this._emit(await gatherIterator(prefixGenerator(mapDebugCheckpoint(this.step - 1, // printing checkpoint for previous step
504
+ this.checkpointConfig, this.channels, this.streamKeys, this.checkpointMetadata, Object.values(this.tasks), this.checkpointPendingWrites, this.prevCheckpointConfig), "debug")));
505
+ }
506
+ if (Object.values(this.tasks).length === 0) {
507
+ this.status = "done";
508
+ return false;
509
+ }
510
+ // if there are pending writes from a previous loop, apply them
511
+ if (this.skipDoneTasks && this.checkpointPendingWrites.length > 0) {
512
+ for (const [tid, k, v] of this.checkpointPendingWrites) {
513
+ if (k === ERROR || k === INTERRUPT || k === RESUME) {
514
+ continue;
527
515
  }
528
- for (const task of Object.values(this.tasks)) {
529
- if (task.writes.length > 0) {
530
- this._outputWrites(task.id, task.writes, true);
531
- }
516
+ const task = Object.values(this.tasks).find((t) => t.id === tid);
517
+ if (task) {
518
+ task.writes.push([k, v]);
532
519
  }
533
520
  }
534
- // if all tasks have finished, re-tick
535
- if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
536
- return this.tick({
537
- inputKeys,
538
- interruptAfter,
539
- interruptBefore,
540
- manager,
541
- });
542
- }
543
- // Before execution, check if we should interrupt
544
- if (shouldInterrupt(this.checkpoint, interruptBefore, Object.values(this.tasks))) {
545
- this.status = "interrupt_before";
546
- if (this.isNested) {
547
- throw new GraphInterrupt();
548
- }
549
- else {
550
- return false;
521
+ for (const task of Object.values(this.tasks)) {
522
+ if (task.writes.length > 0) {
523
+ this._outputWrites(task.id, task.writes, true);
551
524
  }
552
525
  }
553
- // Produce debug output
554
- const debugOutput = await gatherIterator(prefixGenerator(mapDebugTasks(this.step, Object.values(this.tasks)), "debug"));
555
- this._emit(debugOutput);
556
- return true;
557
526
  }
558
- catch (e) {
559
- tickError = e;
560
- if (!this._suppressInterrupt(tickError)) {
561
- throw tickError;
562
- }
563
- else {
564
- this.output = readChannels(this.channels, this.outputKeys);
565
- }
566
- return false;
527
+ // if all tasks have finished, re-tick
528
+ if (Object.values(this.tasks).every((task) => task.writes.length > 0)) {
529
+ return this.tick({
530
+ inputKeys,
531
+ interruptAfter,
532
+ interruptBefore,
533
+ manager,
534
+ });
535
+ }
536
+ // Before execution, check if we should interrupt
537
+ if (shouldInterrupt(this.checkpoint, interruptBefore, Object.values(this.tasks))) {
538
+ this.status = "interrupt_before";
539
+ throw new GraphInterrupt();
567
540
  }
568
- finally {
569
- if (tickError === undefined) {
570
- this.output = readChannels(this.channels, this.outputKeys);
541
+ // Produce debug output
542
+ const debugOutput = await gatherIterator(prefixGenerator(mapDebugTasks(this.step, Object.values(this.tasks)), "debug"));
543
+ this._emit(debugOutput);
544
+ return true;
545
+ }
546
+ async finishAndHandleError(error) {
547
+ const suppress = this._suppressInterrupt(error);
548
+ if (suppress || error === undefined) {
549
+ this.output = readChannels(this.channels, this.outputKeys);
550
+ }
551
+ if (suppress) {
552
+ // emit one last "values" event, with pending writes applied
553
+ if (this.tasks !== undefined &&
554
+ this.checkpointPendingWrites.length > 0 &&
555
+ Object.values(this.tasks).some((task) => task.writes.length > 0)) {
556
+ const managedValueWrites = _applyWrites(this.checkpoint, this.channels, Object.values(this.tasks), this.checkpointerGetNextVersion);
557
+ for (const [key, values] of Object.entries(managedValueWrites)) {
558
+ await this.updateManagedValues(key, values);
559
+ }
560
+ this._emit(gatherIteratorSync(prefixGenerator(mapOutputValues(this.outputKeys, Object.values(this.tasks).flatMap((t) => t.writes), this.channels), "values")));
571
561
  }
562
+ // Emit INTERRUPT event
563
+ this._emit([
564
+ [
565
+ "updates",
566
+ {
567
+ [INTERRUPT]: error.interrupts,
568
+ },
569
+ ],
570
+ ]);
572
571
  }
572
+ return suppress;
573
573
  }
574
574
  _suppressInterrupt(e) {
575
575
  return isGraphInterrupt(e) && !this.isNested;
@@ -74,7 +74,7 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
74
74
  handleChatModelStart(_llm, _messages, runId, _parentRunId, _extraParams, tags, metadata, name) {
75
75
  if (metadata &&
76
76
  // Include legacy LangGraph SDK tag
77
- (!tags || !(tags.includes(constants_js_1.TAG_NOSTREAM) && tags.includes("nostream")))) {
77
+ (!tags || (!tags.includes(constants_js_1.TAG_NOSTREAM) && !tags.includes("nostream")))) {
78
78
  this.metadatas[runId] = [
79
79
  metadata.langgraph_checkpoint_ns.split("|"),
80
80
  { tags, name, ...metadata },
@@ -84,13 +84,15 @@ class StreamMessagesHandler extends base_1.BaseCallbackHandler {
84
84
  handleLLMNewToken(token, _idx, runId, _parentRunId, _tags, fields) {
85
85
  const chunk = fields?.chunk;
86
86
  this.emittedChatModelRunIds[runId] = true;
87
- if (isChatGenerationChunk(chunk) && this.metadatas[runId] !== undefined) {
88
- this._emit(this.metadatas[runId], chunk.message);
89
- }
90
- else {
91
- this._emit(this.metadatas[runId], new messages_1.AIMessageChunk({
92
- content: token,
93
- }));
87
+ if (this.metadatas[runId] !== undefined) {
88
+ if (isChatGenerationChunk(chunk)) {
89
+ this._emit(this.metadatas[runId], chunk.message);
90
+ }
91
+ else {
92
+ this._emit(this.metadatas[runId], new messages_1.AIMessageChunk({
93
+ content: token,
94
+ }));
95
+ }
94
96
  }
95
97
  }
96
98
  handleLLMEnd(output, runId) {
@@ -71,7 +71,7 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
71
71
  handleChatModelStart(_llm, _messages, runId, _parentRunId, _extraParams, tags, metadata, name) {
72
72
  if (metadata &&
73
73
  // Include legacy LangGraph SDK tag
74
- (!tags || !(tags.includes(TAG_NOSTREAM) && tags.includes("nostream")))) {
74
+ (!tags || (!tags.includes(TAG_NOSTREAM) && !tags.includes("nostream")))) {
75
75
  this.metadatas[runId] = [
76
76
  metadata.langgraph_checkpoint_ns.split("|"),
77
77
  { tags, name, ...metadata },
@@ -81,13 +81,15 @@ export class StreamMessagesHandler extends BaseCallbackHandler {
81
81
  handleLLMNewToken(token, _idx, runId, _parentRunId, _tags, fields) {
82
82
  const chunk = fields?.chunk;
83
83
  this.emittedChatModelRunIds[runId] = true;
84
- if (isChatGenerationChunk(chunk) && this.metadatas[runId] !== undefined) {
85
- this._emit(this.metadatas[runId], chunk.message);
86
- }
87
- else {
88
- this._emit(this.metadatas[runId], new AIMessageChunk({
89
- content: token,
90
- }));
84
+ if (this.metadatas[runId] !== undefined) {
85
+ if (isChatGenerationChunk(chunk)) {
86
+ this._emit(this.metadatas[runId], chunk.message);
87
+ }
88
+ else {
89
+ this._emit(this.metadatas[runId], new AIMessageChunk({
90
+ content: token,
91
+ }));
92
+ }
91
93
  }
92
94
  }
93
95
  handleLLMEnd(output, runId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph",
3
- "version": "0.2.37",
3
+ "version": "0.2.39",
4
4
  "description": "LangGraph",
5
5
  "type": "module",
6
6
  "engines": {