@langchain/langgraph 0.2.38 → 0.2.40

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.
@@ -3,3 +3,4 @@ export { type FunctionCallingExecutorState, createFunctionCallingExecutor, } fro
3
3
  export { type AgentState, type CreateReactAgentParams, createReactAgent, } from "./react_agent_executor.js";
4
4
  export { type ToolExecutorArgs, type ToolInvocationInterface, ToolExecutor, } from "./tool_executor.js";
5
5
  export { ToolNode, toolsCondition, type ToolNodeOptions } from "./tool_node.js";
6
+ export type { HumanInterruptConfig, ActionRequest, HumanInterrupt, HumanResponse, } from "./interrupt.js";
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Configuration interface that defines what actions are allowed for a human interrupt.
3
+ * This controls the available interaction options when the graph is paused for human input.
4
+ *
5
+ * @property {boolean} allow_ignore - Whether the human can choose to ignore/skip the current step
6
+ * @property {boolean} allow_respond - Whether the human can provide a text response/feedback
7
+ * @property {boolean} allow_edit - Whether the human can edit the provided content/state
8
+ * @property {boolean} allow_accept - Whether the human can accept/approve the current state
9
+ */
10
+ export interface HumanInterruptConfig {
11
+ allow_ignore: boolean;
12
+ allow_respond: boolean;
13
+ allow_edit: boolean;
14
+ allow_accept: boolean;
15
+ }
16
+ /**
17
+ * Represents a request for human action within the graph execution.
18
+ * Contains the action type and any associated arguments needed for the action.
19
+ *
20
+ * @property {string} action - The type or name of action being requested (e.g., "Approve XYZ action")
21
+ * @property {Record<string, any>} args - Key-value pairs of arguments needed for the action
22
+ */
23
+ export interface ActionRequest {
24
+ action: string;
25
+ args: Record<string, any>;
26
+ }
27
+ /**
28
+ * Represents an interrupt triggered by the graph that requires human intervention.
29
+ * This is passed to the `interrupt` function when execution is paused for human input.
30
+ *
31
+ * @property {ActionRequest} action_request - The specific action being requested from the human
32
+ * @property {HumanInterruptConfig} config - Configuration defining what actions are allowed
33
+ * @property {string} [description] - Optional detailed description of what input is needed
34
+ */
35
+ export interface HumanInterrupt {
36
+ action_request: ActionRequest;
37
+ config: HumanInterruptConfig;
38
+ description?: string;
39
+ }
40
+ /**
41
+ * The response provided by a human to an interrupt, which is returned when graph execution resumes.
42
+ *
43
+ * @property {("accept"|"ignore"|"response"|"edit")} type - The type of response:
44
+ * - "accept": Approves the current state without changes
45
+ * - "ignore": Skips/ignores the current step
46
+ * - "response": Provides text feedback or instructions
47
+ * - "edit": Modifies the current state/content
48
+ * @property {null|string|ActionRequest} args - The response payload:
49
+ * - null: For ignore/accept actions
50
+ * - string: For text responses
51
+ * - ActionRequest: For edit actions with updated content
52
+ */
53
+ export type HumanResponse = {
54
+ type: "accept" | "ignore" | "response" | "edit";
55
+ args: null | string | ActionRequest;
56
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -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) {
@@ -94,8 +94,15 @@ function* mapCommand(cmd, pendingWrites) {
94
94
  if (typeof cmd.update !== "object" || !cmd.update) {
95
95
  throw new Error("Expected cmd.update to be a dict mapping channel names to update values");
96
96
  }
97
- for (const [k, v] of Object.entries(cmd.update)) {
98
- yield [constants_js_1.NULL_TASK_ID, k, v];
97
+ if (Array.isArray(cmd.update)) {
98
+ for (const [k, v] of cmd.update) {
99
+ yield [constants_js_1.NULL_TASK_ID, k, v];
100
+ }
101
+ }
102
+ else {
103
+ for (const [k, v] of Object.entries(cmd.update)) {
104
+ yield [constants_js_1.NULL_TASK_ID, k, v];
105
+ }
99
106
  }
100
107
  }
101
108
  }
@@ -154,8 +161,10 @@ exports.mapOutputValues = mapOutputValues;
154
161
  function* mapOutputUpdates(outputChannels, tasks, cached
155
162
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
163
  ) {
157
- const outputTasks = tasks.filter(([task]) => {
158
- return task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN);
164
+ const outputTasks = tasks.filter(([task, ww]) => {
165
+ return ((task.config === undefined || !task.config.tags?.includes(constants_js_1.TAG_HIDDEN)) &&
166
+ ww[0][0] !== constants_js_1.ERROR &&
167
+ ww[0][0] !== constants_js_1.INTERRUPT);
159
168
  });
160
169
  if (!outputTasks.length) {
161
170
  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 {
@@ -89,8 +89,15 @@ export function* mapCommand(cmd, pendingWrites) {
89
89
  if (typeof cmd.update !== "object" || !cmd.update) {
90
90
  throw new Error("Expected cmd.update to be a dict mapping channel names to update values");
91
91
  }
92
- for (const [k, v] of Object.entries(cmd.update)) {
93
- yield [NULL_TASK_ID, k, v];
92
+ if (Array.isArray(cmd.update)) {
93
+ for (const [k, v] of cmd.update) {
94
+ yield [NULL_TASK_ID, k, v];
95
+ }
96
+ }
97
+ else {
98
+ for (const [k, v] of Object.entries(cmd.update)) {
99
+ yield [NULL_TASK_ID, k, v];
100
+ }
94
101
  }
95
102
  }
96
103
  }
@@ -146,8 +153,10 @@ export function* mapOutputValues(outputChannels, pendingWrites, channels
146
153
  export function* mapOutputUpdates(outputChannels, tasks, cached
147
154
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
148
155
  ) {
149
- const outputTasks = tasks.filter(([task]) => {
150
- return task.config === undefined || !task.config.tags?.includes(TAG_HIDDEN);
156
+ const outputTasks = tasks.filter(([task, ww]) => {
157
+ return ((task.config === undefined || !task.config.tags?.includes(TAG_HIDDEN)) &&
158
+ ww[0][0] !== ERROR &&
159
+ ww[0][0] !== INTERRUPT);
151
160
  });
152
161
  if (!outputTasks.length) {
153
162
  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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph",
3
- "version": "0.2.38",
3
+ "version": "0.2.40",
4
4
  "description": "LangGraph",
5
5
  "type": "module",
6
6
  "engines": {
@@ -72,7 +72,7 @@
72
72
  "pg": "^8.13.0",
73
73
  "prettier": "^2.8.3",
74
74
  "release-it": "^17.6.0",
75
- "rollup": "^4.24.1",
75
+ "rollup": "^4.29.0",
76
76
  "ts-jest": "^29.1.0",
77
77
  "tsx": "^4.7.0",
78
78
  "typescript": "^4.9.5 || ^5.4.5",