@langchain/langgraph 0.2.19 → 0.2.20-rc.0

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.
@@ -1,13 +1,9 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.Pregel = exports.Channel = void 0;
7
4
  /* eslint-disable no-param-reassign */
8
5
  const runnables_1 = require("@langchain/core/runnables");
9
6
  const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
10
- const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
11
7
  const base_js_1 = require("../channels/base.cjs");
12
8
  const read_js_1 = require("./read.cjs");
13
9
  const validate_js_1 = require("./validate.cjs");
@@ -711,15 +707,125 @@ class Pregel extends runnables_1.Runnable {
711
707
  // assign defaults
712
708
  const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store,] = this._defaults(inputConfig);
713
709
  const { channelSpecs, managed } = await this.prepareSpecs(config);
710
+ const stream = new loop_js_1.IterableReadableWritableStream({
711
+ modes: new Set(streamMode),
712
+ });
714
713
  let loop;
715
- const stream = new double_ended_queue_1.default();
716
- function* emitCurrentLoopOutputs() {
717
- while (loop !== undefined && stream.length > 0) {
718
- const nextItem = stream.shift();
719
- if (nextItem === undefined) {
714
+ let loopError;
715
+ const runLoop = async () => {
716
+ try {
717
+ loop = await loop_js_1.PregelLoop.initialize({
718
+ input,
719
+ config,
720
+ checkpointer,
721
+ nodes: this.nodes,
722
+ channelSpecs,
723
+ managed,
724
+ outputKeys,
725
+ streamKeys: this.streamChannelsAsIs,
726
+ store,
727
+ stream,
728
+ });
729
+ if (options?.subgraphs) {
730
+ loop.config.configurable = {
731
+ ...loop.config.configurable,
732
+ [constants_js_1.CONFIG_KEY_STREAM]: loop.stream,
733
+ };
734
+ }
735
+ while (await loop.tick({
736
+ inputKeys: this.inputChannels,
737
+ interruptAfter,
738
+ interruptBefore,
739
+ manager: runManager,
740
+ })) {
741
+ if (debug) {
742
+ (0, debug_js_1.printStepCheckpoint)(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
743
+ }
744
+ if (debug) {
745
+ (0, debug_js_1.printStepTasks)(loop.step, Object.values(loop.tasks));
746
+ }
747
+ // execute tasks, and wait for one to fail or all to finish.
748
+ // each task is independent from all other concurrent tasks
749
+ // yield updates/debug output as each task finishes
750
+ const taskStream = (0, retry_js_1.executeTasksWithRetry)(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
751
+ stepTimeout: this.stepTimeout,
752
+ signal: config.signal,
753
+ retryPolicy: this.retryPolicy,
754
+ });
755
+ // Timeouts will be thrown
756
+ for await (const { task, error } of taskStream) {
757
+ if (error !== undefined) {
758
+ if ((0, errors_js_1.isGraphInterrupt)(error)) {
759
+ if (loop.isNested) {
760
+ throw error;
761
+ }
762
+ if (error.interrupts.length) {
763
+ loop.putWrites(task.id, error.interrupts.map((interrupt) => [constants_js_1.INTERRUPT, interrupt]));
764
+ }
765
+ }
766
+ else {
767
+ loop.putWrites(task.id, [
768
+ [constants_js_1.ERROR, { message: error.message, name: error.name }],
769
+ ]);
770
+ }
771
+ }
772
+ else {
773
+ loop.putWrites(task.id, task.writes);
774
+ }
775
+ if (error !== undefined && !(0, errors_js_1.isGraphInterrupt)(error)) {
776
+ throw error;
777
+ }
778
+ }
779
+ if (debug) {
780
+ (0, debug_js_1.printStepWrites)(loop.step, Object.values(loop.tasks)
781
+ .map((task) => task.writes)
782
+ .flat(), this.streamChannelsList);
783
+ }
784
+ }
785
+ if (loop.status === "out_of_steps") {
786
+ throw new errors_js_1.GraphRecursionError([
787
+ `Recursion limit of ${config.recursionLimit} reached`,
788
+ "without hitting a stop condition. You can increase the",
789
+ `limit by setting the "recursionLimit" config key.`,
790
+ ].join(" "), {
791
+ lc_error_code: "GRAPH_RECURSION_LIMIT",
792
+ });
793
+ }
794
+ }
795
+ catch (e) {
796
+ loopError = e;
797
+ }
798
+ finally {
799
+ try {
800
+ // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
801
+ if (loop) {
802
+ await loop.store?.stop();
803
+ }
804
+ await Promise.all([
805
+ ...(loop?.checkpointerPromises ?? []),
806
+ ...Array.from(managed.values()).map((mv) => mv.promises()),
807
+ ]);
808
+ }
809
+ catch (e) {
810
+ loopError = loopError ?? e;
811
+ }
812
+ if (loopError) {
813
+ // Will throw an error outside of this method
814
+ stream.error(loopError);
815
+ }
816
+ else {
817
+ // Will end the iterator outside of this method
818
+ stream.close();
819
+ }
820
+ }
821
+ };
822
+ const runLoopPromise = runLoop();
823
+ try {
824
+ for await (const chunk of stream) {
825
+ if (chunk === undefined) {
720
826
  throw new Error("Data structure error.");
721
827
  }
722
- const [namespace, mode, payload] = nextItem;
828
+ const [namespace, mode, payload] = chunk;
723
829
  if (streamMode.includes(mode)) {
724
830
  if (streamSubgraphs && streamMode.length > 1) {
725
831
  yield [namespace, mode, payload];
@@ -736,104 +842,14 @@ class Pregel extends runnables_1.Runnable {
736
842
  }
737
843
  }
738
844
  }
739
- try {
740
- loop = await loop_js_1.PregelLoop.initialize({
741
- input,
742
- config,
743
- checkpointer,
744
- nodes: this.nodes,
745
- channelSpecs,
746
- managed,
747
- outputKeys,
748
- streamKeys: this.streamChannelsAsIs,
749
- store,
750
- stream: new loop_js_1.StreamProtocol((chunk) => stream.push(chunk), new Set(streamMode)),
751
- });
752
- if (options?.subgraphs) {
753
- loop.config.configurable = {
754
- ...loop.config.configurable,
755
- [constants_js_1.CONFIG_KEY_STREAM]: loop.stream,
756
- };
757
- }
758
- while (await loop.tick({
759
- inputKeys: this.inputChannels,
760
- interruptAfter,
761
- interruptBefore,
762
- manager: runManager,
763
- })) {
764
- if (debug) {
765
- (0, debug_js_1.printStepCheckpoint)(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
766
- }
767
- yield* emitCurrentLoopOutputs();
768
- if (debug) {
769
- (0, debug_js_1.printStepTasks)(loop.step, Object.values(loop.tasks));
770
- }
771
- // execute tasks, and wait for one to fail or all to finish.
772
- // each task is independent from all other concurrent tasks
773
- // yield updates/debug output as each task finishes
774
- const taskStream = (0, retry_js_1.executeTasksWithRetry)(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
775
- stepTimeout: this.stepTimeout,
776
- signal: config.signal,
777
- retryPolicy: this.retryPolicy,
778
- });
779
- // Timeouts will be thrown
780
- for await (const { task, error } of taskStream) {
781
- if (error !== undefined) {
782
- if ((0, errors_js_1.isGraphInterrupt)(error)) {
783
- if (loop.isNested) {
784
- throw error;
785
- }
786
- if (error.interrupts.length) {
787
- loop.putWrites(task.id, error.interrupts.map((interrupt) => [constants_js_1.INTERRUPT, interrupt]));
788
- }
789
- }
790
- else {
791
- loop.putWrites(task.id, [
792
- [constants_js_1.ERROR, { message: error.message, name: error.name }],
793
- ]);
794
- }
795
- }
796
- else {
797
- loop.putWrites(task.id, task.writes);
798
- }
799
- yield* emitCurrentLoopOutputs();
800
- if (error !== undefined && !(0, errors_js_1.isGraphInterrupt)(error)) {
801
- throw error;
802
- }
803
- }
804
- if (debug) {
805
- (0, debug_js_1.printStepWrites)(loop.step, Object.values(loop.tasks)
806
- .map((task) => task.writes)
807
- .flat(), this.streamChannelsList);
808
- }
809
- }
810
- yield* emitCurrentLoopOutputs();
811
- if (loop.status === "out_of_steps") {
812
- throw new errors_js_1.GraphRecursionError([
813
- `Recursion limit of ${config.recursionLimit} reached`,
814
- "without hitting a stop condition. You can increase the",
815
- `limit by setting the "recursionLimit" config key.`,
816
- ].join(" "), {
817
- lc_error_code: "GRAPH_RECURSION_LIMIT",
818
- });
819
- }
820
- await Promise.all(loop?.checkpointerPromises ?? []);
821
- await runManager?.handleChainEnd(loop.output);
822
- }
823
845
  catch (e) {
824
- await runManager?.handleChainError(e);
846
+ await runManager?.handleChainError(loopError);
825
847
  throw e;
826
848
  }
827
849
  finally {
828
- // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
829
- if (loop) {
830
- await loop.store?.stop();
831
- }
832
- await Promise.all([
833
- ...(loop?.checkpointerPromises ?? []),
834
- ...Array.from(managed.values()).map((mv) => mv.promises()),
835
- ]);
850
+ await runLoopPromise;
836
851
  }
852
+ await runManager?.handleChainEnd(loop?.output ?? {});
837
853
  }
838
854
  /**
839
855
  * Run the graph with a single input and config.
@@ -1,7 +1,6 @@
1
1
  /* eslint-disable no-param-reassign */
2
2
  import { Runnable, RunnableSequence, getCallbackManagerForConfig, mergeConfigs, patchConfig, _coerceToRunnable, } from "@langchain/core/runnables";
3
3
  import { compareChannelVersions, copyCheckpoint, emptyCheckpoint, uuid5, } from "@langchain/langgraph-checkpoint";
4
- import Deque from "double-ended-queue";
5
4
  import { createCheckpoint, emptyChannels, isBaseChannel, } from "../channels/base.js";
6
5
  import { PregelNode } from "./read.js";
7
6
  import { validateGraph, validateKeys } from "./validate.js";
@@ -13,7 +12,7 @@ import { GraphRecursionError, GraphValueError, InvalidUpdateError, isGraphInterr
13
12
  import { _prepareNextTasks, _localRead, _applyWrites, } from "./algo.js";
14
13
  import { _coerceToDict, getNewChannelVersions, patchCheckpointMap, } from "./utils/index.js";
15
14
  import { findSubgraphPregel } from "./utils/subgraph.js";
16
- import { PregelLoop, StreamProtocol } from "./loop.js";
15
+ import { PregelLoop, IterableReadableWritableStream } from "./loop.js";
17
16
  import { executeTasksWithRetry } from "./retry.js";
18
17
  import { ChannelKeyPlaceholder, isConfiguredManagedValue, ManagedValueMapping, NoopManagedValue, } from "../managed/base.js";
19
18
  import { gatherIterator, patchConfigurable } from "../utils.js";
@@ -704,15 +703,125 @@ export class Pregel extends Runnable {
704
703
  // assign defaults
705
704
  const [debug, streamMode, , outputKeys, config, interruptBefore, interruptAfter, checkpointer, store,] = this._defaults(inputConfig);
706
705
  const { channelSpecs, managed } = await this.prepareSpecs(config);
706
+ const stream = new IterableReadableWritableStream({
707
+ modes: new Set(streamMode),
708
+ });
707
709
  let loop;
708
- const stream = new Deque();
709
- function* emitCurrentLoopOutputs() {
710
- while (loop !== undefined && stream.length > 0) {
711
- const nextItem = stream.shift();
712
- if (nextItem === undefined) {
710
+ let loopError;
711
+ const runLoop = async () => {
712
+ try {
713
+ loop = await PregelLoop.initialize({
714
+ input,
715
+ config,
716
+ checkpointer,
717
+ nodes: this.nodes,
718
+ channelSpecs,
719
+ managed,
720
+ outputKeys,
721
+ streamKeys: this.streamChannelsAsIs,
722
+ store,
723
+ stream,
724
+ });
725
+ if (options?.subgraphs) {
726
+ loop.config.configurable = {
727
+ ...loop.config.configurable,
728
+ [CONFIG_KEY_STREAM]: loop.stream,
729
+ };
730
+ }
731
+ while (await loop.tick({
732
+ inputKeys: this.inputChannels,
733
+ interruptAfter,
734
+ interruptBefore,
735
+ manager: runManager,
736
+ })) {
737
+ if (debug) {
738
+ printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
739
+ }
740
+ if (debug) {
741
+ printStepTasks(loop.step, Object.values(loop.tasks));
742
+ }
743
+ // execute tasks, and wait for one to fail or all to finish.
744
+ // each task is independent from all other concurrent tasks
745
+ // yield updates/debug output as each task finishes
746
+ const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
747
+ stepTimeout: this.stepTimeout,
748
+ signal: config.signal,
749
+ retryPolicy: this.retryPolicy,
750
+ });
751
+ // Timeouts will be thrown
752
+ for await (const { task, error } of taskStream) {
753
+ if (error !== undefined) {
754
+ if (isGraphInterrupt(error)) {
755
+ if (loop.isNested) {
756
+ throw error;
757
+ }
758
+ if (error.interrupts.length) {
759
+ loop.putWrites(task.id, error.interrupts.map((interrupt) => [INTERRUPT, interrupt]));
760
+ }
761
+ }
762
+ else {
763
+ loop.putWrites(task.id, [
764
+ [ERROR, { message: error.message, name: error.name }],
765
+ ]);
766
+ }
767
+ }
768
+ else {
769
+ loop.putWrites(task.id, task.writes);
770
+ }
771
+ if (error !== undefined && !isGraphInterrupt(error)) {
772
+ throw error;
773
+ }
774
+ }
775
+ if (debug) {
776
+ printStepWrites(loop.step, Object.values(loop.tasks)
777
+ .map((task) => task.writes)
778
+ .flat(), this.streamChannelsList);
779
+ }
780
+ }
781
+ if (loop.status === "out_of_steps") {
782
+ throw new GraphRecursionError([
783
+ `Recursion limit of ${config.recursionLimit} reached`,
784
+ "without hitting a stop condition. You can increase the",
785
+ `limit by setting the "recursionLimit" config key.`,
786
+ ].join(" "), {
787
+ lc_error_code: "GRAPH_RECURSION_LIMIT",
788
+ });
789
+ }
790
+ }
791
+ catch (e) {
792
+ loopError = e;
793
+ }
794
+ finally {
795
+ try {
796
+ // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
797
+ if (loop) {
798
+ await loop.store?.stop();
799
+ }
800
+ await Promise.all([
801
+ ...(loop?.checkpointerPromises ?? []),
802
+ ...Array.from(managed.values()).map((mv) => mv.promises()),
803
+ ]);
804
+ }
805
+ catch (e) {
806
+ loopError = loopError ?? e;
807
+ }
808
+ if (loopError) {
809
+ // Will throw an error outside of this method
810
+ stream.error(loopError);
811
+ }
812
+ else {
813
+ // Will end the iterator outside of this method
814
+ stream.close();
815
+ }
816
+ }
817
+ };
818
+ const runLoopPromise = runLoop();
819
+ try {
820
+ for await (const chunk of stream) {
821
+ if (chunk === undefined) {
713
822
  throw new Error("Data structure error.");
714
823
  }
715
- const [namespace, mode, payload] = nextItem;
824
+ const [namespace, mode, payload] = chunk;
716
825
  if (streamMode.includes(mode)) {
717
826
  if (streamSubgraphs && streamMode.length > 1) {
718
827
  yield [namespace, mode, payload];
@@ -729,104 +838,14 @@ export class Pregel extends Runnable {
729
838
  }
730
839
  }
731
840
  }
732
- try {
733
- loop = await PregelLoop.initialize({
734
- input,
735
- config,
736
- checkpointer,
737
- nodes: this.nodes,
738
- channelSpecs,
739
- managed,
740
- outputKeys,
741
- streamKeys: this.streamChannelsAsIs,
742
- store,
743
- stream: new StreamProtocol((chunk) => stream.push(chunk), new Set(streamMode)),
744
- });
745
- if (options?.subgraphs) {
746
- loop.config.configurable = {
747
- ...loop.config.configurable,
748
- [CONFIG_KEY_STREAM]: loop.stream,
749
- };
750
- }
751
- while (await loop.tick({
752
- inputKeys: this.inputChannels,
753
- interruptAfter,
754
- interruptBefore,
755
- manager: runManager,
756
- })) {
757
- if (debug) {
758
- printStepCheckpoint(loop.checkpointMetadata.step, loop.channels, this.streamChannelsList);
759
- }
760
- yield* emitCurrentLoopOutputs();
761
- if (debug) {
762
- printStepTasks(loop.step, Object.values(loop.tasks));
763
- }
764
- // execute tasks, and wait for one to fail or all to finish.
765
- // each task is independent from all other concurrent tasks
766
- // yield updates/debug output as each task finishes
767
- const taskStream = executeTasksWithRetry(Object.values(loop.tasks).filter((task) => task.writes.length === 0), {
768
- stepTimeout: this.stepTimeout,
769
- signal: config.signal,
770
- retryPolicy: this.retryPolicy,
771
- });
772
- // Timeouts will be thrown
773
- for await (const { task, error } of taskStream) {
774
- if (error !== undefined) {
775
- if (isGraphInterrupt(error)) {
776
- if (loop.isNested) {
777
- throw error;
778
- }
779
- if (error.interrupts.length) {
780
- loop.putWrites(task.id, error.interrupts.map((interrupt) => [INTERRUPT, interrupt]));
781
- }
782
- }
783
- else {
784
- loop.putWrites(task.id, [
785
- [ERROR, { message: error.message, name: error.name }],
786
- ]);
787
- }
788
- }
789
- else {
790
- loop.putWrites(task.id, task.writes);
791
- }
792
- yield* emitCurrentLoopOutputs();
793
- if (error !== undefined && !isGraphInterrupt(error)) {
794
- throw error;
795
- }
796
- }
797
- if (debug) {
798
- printStepWrites(loop.step, Object.values(loop.tasks)
799
- .map((task) => task.writes)
800
- .flat(), this.streamChannelsList);
801
- }
802
- }
803
- yield* emitCurrentLoopOutputs();
804
- if (loop.status === "out_of_steps") {
805
- throw new GraphRecursionError([
806
- `Recursion limit of ${config.recursionLimit} reached`,
807
- "without hitting a stop condition. You can increase the",
808
- `limit by setting the "recursionLimit" config key.`,
809
- ].join(" "), {
810
- lc_error_code: "GRAPH_RECURSION_LIMIT",
811
- });
812
- }
813
- await Promise.all(loop?.checkpointerPromises ?? []);
814
- await runManager?.handleChainEnd(loop.output);
815
- }
816
841
  catch (e) {
817
- await runManager?.handleChainError(e);
842
+ await runManager?.handleChainError(loopError);
818
843
  throw e;
819
844
  }
820
845
  finally {
821
- // Call `.stop()` again incase it was not called in the loop, e.g due to an error.
822
- if (loop) {
823
- await loop.store?.stop();
824
- }
825
- await Promise.all([
826
- ...(loop?.checkpointerPromises ?? []),
827
- ...Array.from(managed.values()).map((mv) => mv.promises()),
828
- ]);
846
+ await runLoopPromise;
829
847
  }
848
+ await runManager?.handleChainEnd(loop?.output ?? {});
830
849
  }
831
850
  /**
832
851
  * Run the graph with a single input and config.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PregelLoop = exports.StreamProtocol = void 0;
3
+ exports.PregelLoop = exports.IterableReadableWritableStream = void 0;
4
+ const stream_1 = require("@langchain/core/utils/stream");
4
5
  const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
5
6
  const base_js_1 = require("../channels/base.cjs");
6
7
  const constants_js_1 = require("../constants.cjs");
@@ -14,33 +15,72 @@ const INPUT_DONE = Symbol.for("INPUT_DONE");
14
15
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
15
16
  const DEFAULT_LOOP_LIMIT = 25;
16
17
  const SPECIAL_CHANNELS = [constants_js_1.ERROR, constants_js_1.INTERRUPT];
17
- class StreamProtocol {
18
- constructor(pushFn, modes) {
19
- Object.defineProperty(this, "push", {
18
+ class IterableReadableWritableStream extends stream_1.IterableReadableStream {
19
+ constructor(params) {
20
+ let streamControllerPromiseResolver;
21
+ let streamControllerPromise = new Promise((resolve) => {
22
+ streamControllerPromiseResolver = resolve;
23
+ });
24
+ super({
25
+ start: (controller) => {
26
+ streamControllerPromiseResolver(controller);
27
+ },
28
+ });
29
+ Object.defineProperty(this, "modes", {
20
30
  enumerable: true,
21
31
  configurable: true,
22
32
  writable: true,
23
33
  value: void 0
24
34
  });
25
- Object.defineProperty(this, "modes", {
35
+ Object.defineProperty(this, "controller", {
36
+ enumerable: true,
37
+ configurable: true,
38
+ writable: true,
39
+ value: void 0
40
+ });
41
+ Object.defineProperty(this, "passthroughFn", {
26
42
  enumerable: true,
27
43
  configurable: true,
28
44
  writable: true,
29
45
  value: void 0
30
46
  });
31
- this.push = pushFn;
32
- this.modes = modes;
47
+ // .start() will always be called before the stream can be interacted
48
+ // with anyway
49
+ streamControllerPromise.then((controller) => {
50
+ this.controller = controller;
51
+ });
52
+ this.passthroughFn = params.passthroughFn;
53
+ this.modes = params.modes;
54
+ }
55
+ push(chunk) {
56
+ this.passthroughFn?.(chunk);
57
+ this.controller.enqueue(chunk);
58
+ }
59
+ close() {
60
+ try {
61
+ this.controller.close();
62
+ }
63
+ catch (e) {
64
+ // pass
65
+ }
66
+ }
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ error(e) {
69
+ this.controller.error(e);
33
70
  }
34
71
  }
35
- exports.StreamProtocol = StreamProtocol;
72
+ exports.IterableReadableWritableStream = IterableReadableWritableStream;
36
73
  function createDuplexStream(...streams) {
37
- return new StreamProtocol((value) => {
38
- for (const stream of streams) {
39
- if (stream.modes.has(value[1])) {
40
- stream.push(value);
74
+ return new IterableReadableWritableStream({
75
+ passthroughFn: (value) => {
76
+ for (const stream of streams) {
77
+ if (stream.modes.has(value[1])) {
78
+ stream.push(value);
79
+ }
41
80
  }
42
- }
43
- }, new Set(streams.flatMap((s) => Array.from(s.modes))));
81
+ },
82
+ modes: new Set(streams.flatMap((s) => Array.from(s.modes))),
83
+ });
44
84
  }
45
85
  class PregelLoop {
46
86
  constructor(params) {
@@ -1,5 +1,6 @@
1
1
  import type { RunnableConfig } from "@langchain/core/runnables";
2
2
  import type { CallbackManagerForChainRun } from "@langchain/core/callbacks/manager";
3
+ import { IterableReadableStream } from "@langchain/core/utils/stream";
3
4
  import { BaseCheckpointSaver, Checkpoint, PendingWrite, CheckpointPendingWrite, CheckpointMetadata, All, BaseStore, AsyncBatchedStore } from "@langchain/langgraph-checkpoint";
4
5
  import { BaseChannel } from "../channels/base.js";
5
6
  import { PregelExecutableTask, StreamMode } from "./types.js";
@@ -16,7 +17,7 @@ export type PregelLoopInitializeParams = {
16
17
  nodes: Record<string, PregelNode>;
17
18
  channelSpecs: Record<string, BaseChannel>;
18
19
  managed: ManagedValueMapping;
19
- stream: StreamProtocol;
20
+ stream: IterableReadableWritableStream;
20
21
  store?: BaseStore;
21
22
  checkSubgraphs?: boolean;
22
23
  };
@@ -39,14 +40,21 @@ type PregelLoopParams = {
39
40
  checkpointNamespace: string[];
40
41
  skipDoneTasks: boolean;
41
42
  isNested: boolean;
42
- stream: StreamProtocol;
43
+ stream: IterableReadableWritableStream;
43
44
  store?: AsyncBatchedStore;
44
45
  prevCheckpointConfig: RunnableConfig | undefined;
45
46
  };
46
- export declare class StreamProtocol {
47
- push: (chunk: StreamChunk) => void;
47
+ export declare class IterableReadableWritableStream extends IterableReadableStream<StreamChunk> {
48
48
  modes: Set<StreamMode>;
49
- constructor(pushFn: (chunk: StreamChunk) => void, modes: Set<StreamMode>);
49
+ private controller;
50
+ private passthroughFn?;
51
+ constructor(params: {
52
+ passthroughFn?: (chunk: StreamChunk) => void;
53
+ modes: Set<StreamMode>;
54
+ });
55
+ push(chunk: StreamChunk): void;
56
+ close(): void;
57
+ error(e: any): void;
50
58
  }
51
59
  export declare class PregelLoop {
52
60
  protected input?: any;
@@ -72,7 +80,7 @@ export declare class PregelLoop {
72
80
  protected prevCheckpointConfig: RunnableConfig | undefined;
73
81
  status: "pending" | "done" | "interrupt_before" | "interrupt_after" | "out_of_steps";
74
82
  tasks: Record<string, PregelExecutableTask<any, any>>;
75
- stream: StreamProtocol;
83
+ stream: IterableReadableWritableStream;
76
84
  checkpointerPromises: Promise<unknown>[];
77
85
  isNested: boolean;
78
86
  protected _checkpointerChainedPromise: Promise<unknown>;
@@ -1,3 +1,4 @@
1
+ import { IterableReadableStream } from "@langchain/core/utils/stream";
1
2
  import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, } from "@langchain/langgraph-checkpoint";
2
3
  import { createCheckpoint, emptyChannels, } from "../channels/base.js";
3
4
  import { CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, TAG_HIDDEN, TASKS, } from "../constants.js";
@@ -11,32 +12,71 @@ const INPUT_DONE = Symbol.for("INPUT_DONE");
11
12
  const INPUT_RESUMING = Symbol.for("INPUT_RESUMING");
12
13
  const DEFAULT_LOOP_LIMIT = 25;
13
14
  const SPECIAL_CHANNELS = [ERROR, INTERRUPT];
14
- export class StreamProtocol {
15
- constructor(pushFn, modes) {
16
- Object.defineProperty(this, "push", {
15
+ export class IterableReadableWritableStream extends IterableReadableStream {
16
+ constructor(params) {
17
+ let streamControllerPromiseResolver;
18
+ let streamControllerPromise = new Promise((resolve) => {
19
+ streamControllerPromiseResolver = resolve;
20
+ });
21
+ super({
22
+ start: (controller) => {
23
+ streamControllerPromiseResolver(controller);
24
+ },
25
+ });
26
+ Object.defineProperty(this, "modes", {
17
27
  enumerable: true,
18
28
  configurable: true,
19
29
  writable: true,
20
30
  value: void 0
21
31
  });
22
- Object.defineProperty(this, "modes", {
32
+ Object.defineProperty(this, "controller", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: void 0
37
+ });
38
+ Object.defineProperty(this, "passthroughFn", {
23
39
  enumerable: true,
24
40
  configurable: true,
25
41
  writable: true,
26
42
  value: void 0
27
43
  });
28
- this.push = pushFn;
29
- this.modes = modes;
44
+ // .start() will always be called before the stream can be interacted
45
+ // with anyway
46
+ streamControllerPromise.then((controller) => {
47
+ this.controller = controller;
48
+ });
49
+ this.passthroughFn = params.passthroughFn;
50
+ this.modes = params.modes;
51
+ }
52
+ push(chunk) {
53
+ this.passthroughFn?.(chunk);
54
+ this.controller.enqueue(chunk);
55
+ }
56
+ close() {
57
+ try {
58
+ this.controller.close();
59
+ }
60
+ catch (e) {
61
+ // pass
62
+ }
63
+ }
64
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
65
+ error(e) {
66
+ this.controller.error(e);
30
67
  }
31
68
  }
32
69
  function createDuplexStream(...streams) {
33
- return new StreamProtocol((value) => {
34
- for (const stream of streams) {
35
- if (stream.modes.has(value[1])) {
36
- stream.push(value);
70
+ return new IterableReadableWritableStream({
71
+ passthroughFn: (value) => {
72
+ for (const stream of streams) {
73
+ if (stream.modes.has(value[1])) {
74
+ stream.push(value);
75
+ }
37
76
  }
38
- }
39
- }, new Set(streams.flatMap((s) => Array.from(s.modes))));
77
+ },
78
+ modes: new Set(streams.flatMap((s) => Array.from(s.modes))),
79
+ });
40
80
  }
41
81
  export class PregelLoop {
42
82
  constructor(params) {
@@ -130,7 +130,7 @@ pregelTask, retryPolicy) {
130
130
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
131
  error.constructor.unminifiable_name ??
132
132
  error.constructor.name;
133
- console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)} seconds (attempt ${attempts}) after ${errorName}: ${error}`);
133
+ console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
134
134
  }
135
135
  finally {
136
136
  // Clear checkpoint_ns seen (for subgraph detection)
@@ -126,7 +126,7 @@ pregelTask, retryPolicy) {
126
126
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
127
127
  error.constructor.unminifiable_name ??
128
128
  error.constructor.name;
129
- console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)} seconds (attempt ${attempts}) after ${errorName}: ${error}`);
129
+ console.log(`Retrying task "${pregelTask.name}" after ${interval.toFixed(2)}ms (attempt ${attempts}) after ${errorName}: ${error}`);
130
130
  }
131
131
  finally {
132
132
  // Clear checkpoint_ns seen (for subgraph detection)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph",
3
- "version": "0.2.19",
3
+ "version": "0.2.20-rc.0",
4
4
  "description": "LangGraph",
5
5
  "type": "module",
6
6
  "engines": {
@@ -33,7 +33,6 @@
33
33
  "dependencies": {
34
34
  "@langchain/langgraph-checkpoint": "~0.0.10",
35
35
  "@langchain/langgraph-sdk": "~0.0.20",
36
- "double-ended-queue": "^2.1.0-0",
37
36
  "uuid": "^10.0.0",
38
37
  "zod": "^3.23.8"
39
38
  },
@@ -44,7 +43,7 @@
44
43
  "@jest/globals": "^29.5.0",
45
44
  "@langchain/anthropic": "^0.3.5",
46
45
  "@langchain/community": "^0.3.9",
47
- "@langchain/core": "^0.3.15",
46
+ "@langchain/core": "^0.3.16",
48
47
  "@langchain/langgraph-checkpoint-postgres": "workspace:*",
49
48
  "@langchain/langgraph-checkpoint-sqlite": "workspace:*",
50
49
  "@langchain/openai": "^0.3.11",
@@ -52,7 +51,6 @@
52
51
  "@swc/core": "^1.3.90",
53
52
  "@swc/jest": "^0.2.29",
54
53
  "@tsconfig/recommended": "^1.0.3",
55
- "@types/double-ended-queue": "^2",
56
54
  "@types/pg": "^8",
57
55
  "@types/uuid": "^10",
58
56
  "@typescript-eslint/eslint-plugin": "^6.12.0",