@langchain/langgraph 0.2.31 → 0.2.33

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.
@@ -2,12 +2,12 @@
2
2
  import { _coerceToRunnable, Runnable, } from "@langchain/core/runnables";
3
3
  import { isBaseChannel } from "../channels/base.js";
4
4
  import { END, CompiledGraph, Graph, START, Branch, } from "./graph.js";
5
- import { ChannelWrite, PASSTHROUGH, SKIP_WRITE, } from "../pregel/write.js";
5
+ import { ChannelWrite, PASSTHROUGH, } from "../pregel/write.js";
6
6
  import { ChannelRead, PregelNode } from "../pregel/read.js";
7
7
  import { NamedBarrierValue } from "../channels/named_barrier_value.js";
8
8
  import { EphemeralValue } from "../channels/ephemeral_value.js";
9
9
  import { RunnableCallable } from "../utils.js";
10
- import { _isCommand, _isSend, CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, Command, SELF, TAG_HIDDEN, } from "../constants.js";
10
+ import { isCommand, _isSend, CHECKPOINT_NAMESPACE_END, CHECKPOINT_NAMESPACE_SEPARATOR, Command, SELF, TAG_HIDDEN, } from "../constants.js";
11
11
  import { InvalidUpdateError, ParentCommand } from "../errors.js";
12
12
  import { getChannel, } from "./annotation.js";
13
13
  import { isConfiguredManagedValue } from "../managed/base.js";
@@ -343,59 +343,101 @@ function _getChannels(schema) {
343
343
  */
344
344
  export class CompiledStateGraph extends CompiledGraph {
345
345
  attachNode(key, node) {
346
- const stateKeys = Object.keys(this.builder.channels);
346
+ let outputKeys;
347
+ if (key === START) {
348
+ // Get input schema keys excluding managed values
349
+ outputKeys = Object.entries(this.builder._schemaDefinitions.get(this.builder._inputDefinition))
350
+ .filter(([_, v]) => !isConfiguredManagedValue(v))
351
+ .map(([k]) => k);
352
+ }
353
+ else {
354
+ outputKeys = Object.keys(this.builder.channels);
355
+ }
356
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
347
357
  function _getRoot(input) {
348
- if (_isCommand(input)) {
358
+ if (isCommand(input)) {
349
359
  if (input.graph === Command.PARENT) {
350
- return SKIP_WRITE;
360
+ return null;
361
+ }
362
+ return input._updateAsTuples();
363
+ }
364
+ else if (Array.isArray(input) &&
365
+ input.length > 0 &&
366
+ input.some((i) => isCommand(i))) {
367
+ const updates = [];
368
+ for (const i of input) {
369
+ if (isCommand(i)) {
370
+ if (i.graph === Command.PARENT) {
371
+ continue;
372
+ }
373
+ updates.push(...i._updateAsTuples());
374
+ }
375
+ else {
376
+ updates.push([ROOT, i]);
377
+ }
351
378
  }
352
- return input.update;
379
+ return updates;
353
380
  }
354
- return input;
381
+ else if (input != null) {
382
+ return [[ROOT, input]];
383
+ }
384
+ return null;
355
385
  }
356
386
  // to avoid name collision below
357
387
  const nodeKey = key;
358
- function getStateKey(key, input) {
388
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
389
+ function _getUpdates(input) {
359
390
  if (!input) {
360
- return SKIP_WRITE;
391
+ return null;
361
392
  }
362
- else if (_isCommand(input)) {
393
+ else if (isCommand(input)) {
363
394
  if (input.graph === Command.PARENT) {
364
- return SKIP_WRITE;
395
+ return null;
365
396
  }
366
- return getStateKey(key, input.update);
397
+ return input._updateAsTuples();
367
398
  }
368
- else if (typeof input !== "object" || Array.isArray(input)) {
399
+ else if (Array.isArray(input) &&
400
+ input.length > 0 &&
401
+ input.some(isCommand)) {
402
+ const updates = [];
403
+ for (const item of input) {
404
+ if (isCommand(item)) {
405
+ if (item.graph === Command.PARENT) {
406
+ continue;
407
+ }
408
+ updates.push(...item._updateAsTuples());
409
+ }
410
+ else {
411
+ const itemUpdates = _getUpdates(item);
412
+ if (itemUpdates) {
413
+ updates.push(...(itemUpdates ?? []));
414
+ }
415
+ }
416
+ }
417
+ return updates;
418
+ }
419
+ else if (typeof input === "object" && !Array.isArray(input)) {
420
+ return Object.entries(input).filter(([k]) => outputKeys.includes(k));
421
+ }
422
+ else {
369
423
  const typeofInput = Array.isArray(input) ? "array" : typeof input;
370
424
  throw new InvalidUpdateError(`Expected node "${nodeKey.toString()}" to return an object, received ${typeofInput}`, {
371
425
  lc_error_code: "INVALID_GRAPH_NODE_RETURN_VALUE",
372
426
  });
373
427
  }
374
- else {
375
- return key in input ? input[key] : SKIP_WRITE;
376
- }
377
428
  }
378
- // state updaters
379
- const stateWriteEntries = stateKeys.map((key) => key === ROOT
380
- ? {
381
- channel: key,
429
+ const stateWriteEntries = [
430
+ {
382
431
  value: PASSTHROUGH,
383
- skipNone: true,
384
432
  mapper: new RunnableCallable({
385
- func: _getRoot,
433
+ func: outputKeys.length && outputKeys[0] === ROOT
434
+ ? _getRoot
435
+ : _getUpdates,
386
436
  trace: false,
387
437
  recurse: false,
388
438
  }),
389
- }
390
- : {
391
- channel: key,
392
- value: PASSTHROUGH,
393
- mapper: new RunnableCallable({
394
- func: getStateKey.bind(null, key),
395
- trace: false,
396
- recurse: false,
397
- }),
398
- });
439
+ },
440
+ ];
399
441
  // add node and output channel
400
442
  if (key === START) {
401
443
  this.nodes[key] = new PregelNode({
@@ -536,7 +578,7 @@ function _controlBranch(value) {
536
578
  if (_isSend(value)) {
537
579
  return [value];
538
580
  }
539
- if (!_isCommand(value)) {
581
+ if (!isCommand(value)) {
540
582
  return [];
541
583
  }
542
584
  if (value.graph === Command.PARENT) {
@@ -108,7 +108,7 @@ function _getModelPreprocessingRunnable(stateModifier, messageModifier) {
108
108
  * ```
109
109
  */
110
110
  function createReactAgent(params) {
111
- const { llm, tools, messageModifier, stateModifier, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
111
+ const { llm, tools, messageModifier, stateModifier, stateSchema, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
112
112
  let toolClasses;
113
113
  if (!Array.isArray(tools)) {
114
114
  toolClasses = tools.tools;
@@ -138,7 +138,7 @@ function createReactAgent(params) {
138
138
  // TODO: Auto-promote streaming.
139
139
  return { messages: [await modelRunnable.invoke(state, config)] };
140
140
  };
141
- const workflow = new index_js_1.StateGraph(messages_annotation_js_1.MessagesAnnotation)
141
+ const workflow = new index_js_1.StateGraph(stateSchema ?? messages_annotation_js_1.MessagesAnnotation)
142
142
  .addNode("agent", callModel)
143
143
  .addNode("tools", new tool_node_js_1.ToolNode(toolClasses))
144
144
  .addEdge(index_js_1.START, "agent")
@@ -3,7 +3,7 @@ import { BaseMessage, BaseMessageLike, SystemMessage } from "@langchain/core/mes
3
3
  import { Runnable, RunnableToolLike } from "@langchain/core/runnables";
4
4
  import { StructuredToolInterface } from "@langchain/core/tools";
5
5
  import { All, BaseCheckpointSaver, BaseStore } from "@langchain/langgraph-checkpoint";
6
- import { START, CompiledStateGraph } from "../graph/index.js";
6
+ import { START, CompiledStateGraph, AnnotationRoot } from "../graph/index.js";
7
7
  import { MessagesAnnotation } from "../graph/messages_annotation.js";
8
8
  import { ToolNode } from "./tool_node.js";
9
9
  import { LangGraphRunnableConfig } from "../pregel/runnable_types.js";
@@ -14,11 +14,11 @@ export type N = typeof START | "agent" | "tools";
14
14
  export type StateModifier = SystemMessage | string | ((state: typeof MessagesAnnotation.State, config: LangGraphRunnableConfig) => BaseMessageLike[]) | ((state: typeof MessagesAnnotation.State, config: LangGraphRunnableConfig) => Promise<BaseMessageLike[]>) | Runnable;
15
15
  /** @deprecated Use StateModifier instead. */
16
16
  export type MessageModifier = SystemMessage | string | ((messages: BaseMessage[]) => BaseMessage[]) | ((messages: BaseMessage[]) => Promise<BaseMessage[]>) | Runnable;
17
- export type CreateReactAgentParams = {
17
+ export type CreateReactAgentParams<A extends AnnotationRoot<any> = AnnotationRoot<any>> = {
18
18
  /** The chat model that can utilize OpenAI-style tool calling. */
19
19
  llm: BaseChatModel;
20
20
  /** A list of tools or a ToolNode. */
21
- tools: ToolNode<typeof MessagesAnnotation.State> | (StructuredToolInterface | RunnableToolLike)[];
21
+ tools: ToolNode | (StructuredToolInterface | RunnableToolLike)[];
22
22
  /**
23
23
  * @deprecated
24
24
  * Use stateModifier instead. stateModifier works the same as
@@ -73,6 +73,7 @@ export type CreateReactAgentParams = {
73
73
  * - Runnable: This runnable should take in full graph state and the output is then passed to the language model.
74
74
  */
75
75
  stateModifier?: StateModifier;
76
+ stateSchema?: A;
76
77
  /** An optional checkpoint saver to persist the agent's state. */
77
78
  checkpointSaver?: BaseCheckpointSaver;
78
79
  /** An optional list of node names to interrupt before running. */
@@ -123,4 +124,4 @@ export type CreateReactAgentParams = {
123
124
  * // Returns the messages in the state at each step of execution
124
125
  * ```
125
126
  */
126
- export declare function createReactAgent(params: CreateReactAgentParams): CompiledStateGraph<(typeof MessagesAnnotation)["State"], (typeof MessagesAnnotation)["Update"], typeof START | "agent" | "tools">;
127
+ export declare function createReactAgent<A extends AnnotationRoot<any> = AnnotationRoot<any>>(params: CreateReactAgentParams<A>): CompiledStateGraph<(typeof MessagesAnnotation)["State"], (typeof MessagesAnnotation)["Update"], typeof START | "agent" | "tools", typeof MessagesAnnotation.spec & A["spec"], typeof MessagesAnnotation.spec & A["spec"]>;
@@ -1,6 +1,6 @@
1
1
  import { isAIMessage, isBaseMessage, SystemMessage, } from "@langchain/core/messages";
2
2
  import { Runnable, RunnableLambda, } from "@langchain/core/runnables";
3
- import { END, START, StateGraph } from "../graph/index.js";
3
+ import { END, START, StateGraph, } from "../graph/index.js";
4
4
  import { MessagesAnnotation } from "../graph/messages_annotation.js";
5
5
  import { ToolNode } from "./tool_node.js";
6
6
  function _convertMessageModifierToStateModifier(messageModifier) {
@@ -105,7 +105,7 @@ function _getModelPreprocessingRunnable(stateModifier, messageModifier) {
105
105
  * ```
106
106
  */
107
107
  export function createReactAgent(params) {
108
- const { llm, tools, messageModifier, stateModifier, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
108
+ const { llm, tools, messageModifier, stateModifier, stateSchema, checkpointSaver, interruptBefore, interruptAfter, store, } = params;
109
109
  let toolClasses;
110
110
  if (!Array.isArray(tools)) {
111
111
  toolClasses = tools.tools;
@@ -135,7 +135,7 @@ export function createReactAgent(params) {
135
135
  // TODO: Auto-promote streaming.
136
136
  return { messages: [await modelRunnable.invoke(state, config)] };
137
137
  };
138
- const workflow = new StateGraph(MessagesAnnotation)
138
+ const workflow = new StateGraph(stateSchema ?? MessagesAnnotation)
139
139
  .addNode("agent", callModel)
140
140
  .addNode("tools", new ToolNode(toolClasses))
141
141
  .addEdge(START, "agent")
@@ -5,6 +5,7 @@ const messages_1 = require("@langchain/core/messages");
5
5
  const utils_js_1 = require("../utils.cjs");
6
6
  const graph_js_1 = require("../graph/graph.cjs");
7
7
  const errors_js_1 = require("../errors.cjs");
8
+ const constants_js_1 = require("../constants.cjs");
8
9
  /**
9
10
  * A node that runs the tools requested in the last AIMessage. It can be used
10
11
  * either in StateGraph with a "messages" key or in MessageGraph. If multiple
@@ -163,7 +164,8 @@ class ToolNode extends utils_js_1.RunnableCallable {
163
164
  throw new Error(`Tool "${call.name}" not found.`);
164
165
  }
165
166
  const output = await tool.invoke({ ...call, type: "tool_call" }, config);
166
- if ((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") {
167
+ if (((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") ||
168
+ (0, constants_js_1.isCommand)(output)) {
167
169
  return output;
168
170
  }
169
171
  else {
@@ -192,7 +194,18 @@ class ToolNode extends utils_js_1.RunnableCallable {
192
194
  });
193
195
  }
194
196
  }) ?? []);
195
- return (Array.isArray(input) ? outputs : { messages: outputs });
197
+ // Preserve existing behavior for non-command tool outputs for backwards compatibility
198
+ if (!outputs.some(constants_js_1.isCommand)) {
199
+ return (Array.isArray(input) ? outputs : { messages: outputs });
200
+ }
201
+ // Handle mixed Command and non-Command outputs
202
+ const combinedOutputs = outputs.map((output) => {
203
+ if ((0, constants_js_1.isCommand)(output)) {
204
+ return output;
205
+ }
206
+ return Array.isArray(input) ? [output] : { messages: [output] };
207
+ });
208
+ return combinedOutputs;
196
209
  }
197
210
  }
198
211
  exports.ToolNode = ToolNode;
@@ -2,6 +2,7 @@ import { ToolMessage, isBaseMessage, } from "@langchain/core/messages";
2
2
  import { RunnableCallable } from "../utils.js";
3
3
  import { END } from "../graph/graph.js";
4
4
  import { isGraphInterrupt } from "../errors.js";
5
+ import { isCommand } from "../constants.js";
5
6
  /**
6
7
  * A node that runs the tools requested in the last AIMessage. It can be used
7
8
  * either in StateGraph with a "messages" key or in MessageGraph. If multiple
@@ -160,7 +161,8 @@ export class ToolNode extends RunnableCallable {
160
161
  throw new Error(`Tool "${call.name}" not found.`);
161
162
  }
162
163
  const output = await tool.invoke({ ...call, type: "tool_call" }, config);
163
- if (isBaseMessage(output) && output._getType() === "tool") {
164
+ if ((isBaseMessage(output) && output._getType() === "tool") ||
165
+ isCommand(output)) {
164
166
  return output;
165
167
  }
166
168
  else {
@@ -189,7 +191,18 @@ export class ToolNode extends RunnableCallable {
189
191
  });
190
192
  }
191
193
  }) ?? []);
192
- return (Array.isArray(input) ? outputs : { messages: outputs });
194
+ // Preserve existing behavior for non-command tool outputs for backwards compatibility
195
+ if (!outputs.some(isCommand)) {
196
+ return (Array.isArray(input) ? outputs : { messages: outputs });
197
+ }
198
+ // Handle mixed Command and non-Command outputs
199
+ const combinedOutputs = outputs.map((output) => {
200
+ if (isCommand(output)) {
201
+ return output;
202
+ }
203
+ return Array.isArray(input) ? [output] : { messages: [output] };
204
+ });
205
+ return combinedOutputs;
193
206
  }
194
207
  }
195
208
  export function toolsCondition(state) {
@@ -345,6 +345,19 @@ class Pregel extends runnables_1.Runnable {
345
345
  });
346
346
  }
347
347
  }
348
+ // apply pending writes
349
+ const nullWrites = (saved.pendingWrites ?? [])
350
+ .filter((w) => w[0] === constants_js_1.NULL_TASK_ID)
351
+ .map((w) => w.slice(1));
352
+ if (nullWrites.length > 0) {
353
+ (0, algo_js_1._applyWrites)(saved.checkpoint, channels, [
354
+ {
355
+ name: constants_js_1.INPUT,
356
+ writes: nullWrites,
357
+ triggers: [],
358
+ },
359
+ ]);
360
+ }
348
361
  // assemble the state snapshot
349
362
  return {
350
363
  values: (0, io_js_1.readChannels)(channels, this.streamChannelsAsIs),
@@ -505,7 +518,7 @@ class Pregel extends runnables_1.Runnable {
505
518
  // apply null writes
506
519
  const nullWrites = (saved.pendingWrites || [])
507
520
  .filter((w) => w[0] === constants_js_1.NULL_TASK_ID)
508
- .flatMap((w) => w.slice(1));
521
+ .map((w) => w.slice(1));
509
522
  if (nullWrites.length > 0) {
510
523
  (0, algo_js_1._applyWrites)(saved.checkpoint, channels, [
511
524
  {
@@ -547,6 +560,45 @@ class Pregel extends runnables_1.Runnable {
547
560
  }, {});
548
561
  return (0, index_js_1.patchCheckpointMap)(nextConfig, saved ? saved.metadata : undefined);
549
562
  }
563
+ // apply pending writes, if not on specific checkpoint
564
+ if (config.configurable?.checkpoint_id === undefined &&
565
+ saved?.pendingWrites !== undefined &&
566
+ saved.pendingWrites.length > 0) {
567
+ // tasks for this checkpoint
568
+ const nextTasks = (0, algo_js_1._prepareNextTasks)(checkpoint, saved.pendingWrites, this.nodes, channels, managed, saved.config, true, {
569
+ store: this.store,
570
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
571
+ checkpointer: this.checkpointer,
572
+ step: (saved.metadata?.step ?? -1) + 1,
573
+ });
574
+ // apply null writes
575
+ const nullWrites = (saved.pendingWrites ?? [])
576
+ .filter((w) => w[0] === constants_js_1.NULL_TASK_ID)
577
+ .map((w) => w.slice(1));
578
+ if (nullWrites.length > 0) {
579
+ (0, algo_js_1._applyWrites)(saved.checkpoint, channels, [
580
+ {
581
+ name: constants_js_1.INPUT,
582
+ writes: nullWrites,
583
+ triggers: [],
584
+ },
585
+ ]);
586
+ }
587
+ // apply writes
588
+ for (const [tid, k, v] of saved.pendingWrites) {
589
+ if ([constants_js_1.ERROR, constants_js_1.INTERRUPT, langgraph_checkpoint_1.SCHEDULED].includes(k) ||
590
+ nextTasks[tid] === undefined) {
591
+ continue;
592
+ }
593
+ nextTasks[tid].writes.push([k, v]);
594
+ }
595
+ const tasks = Object.values(nextTasks).filter((task) => {
596
+ return task.writes.length > 0;
597
+ });
598
+ if (tasks.length > 0) {
599
+ (0, algo_js_1._applyWrites)(checkpoint, channels, tasks);
600
+ }
601
+ }
550
602
  const nonNullVersion = Object.values(checkpoint.versions_seen)
551
603
  .map((seenVersions) => {
552
604
  return Object.values(seenVersions);
@@ -341,6 +341,19 @@ export class Pregel extends Runnable {
341
341
  });
342
342
  }
343
343
  }
344
+ // apply pending writes
345
+ const nullWrites = (saved.pendingWrites ?? [])
346
+ .filter((w) => w[0] === NULL_TASK_ID)
347
+ .map((w) => w.slice(1));
348
+ if (nullWrites.length > 0) {
349
+ _applyWrites(saved.checkpoint, channels, [
350
+ {
351
+ name: INPUT,
352
+ writes: nullWrites,
353
+ triggers: [],
354
+ },
355
+ ]);
356
+ }
344
357
  // assemble the state snapshot
345
358
  return {
346
359
  values: readChannels(channels, this.streamChannelsAsIs),
@@ -501,7 +514,7 @@ export class Pregel extends Runnable {
501
514
  // apply null writes
502
515
  const nullWrites = (saved.pendingWrites || [])
503
516
  .filter((w) => w[0] === NULL_TASK_ID)
504
- .flatMap((w) => w.slice(1));
517
+ .map((w) => w.slice(1));
505
518
  if (nullWrites.length > 0) {
506
519
  _applyWrites(saved.checkpoint, channels, [
507
520
  {
@@ -543,6 +556,45 @@ export class Pregel extends Runnable {
543
556
  }, {});
544
557
  return patchCheckpointMap(nextConfig, saved ? saved.metadata : undefined);
545
558
  }
559
+ // apply pending writes, if not on specific checkpoint
560
+ if (config.configurable?.checkpoint_id === undefined &&
561
+ saved?.pendingWrites !== undefined &&
562
+ saved.pendingWrites.length > 0) {
563
+ // tasks for this checkpoint
564
+ const nextTasks = _prepareNextTasks(checkpoint, saved.pendingWrites, this.nodes, channels, managed, saved.config, true, {
565
+ store: this.store,
566
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
567
+ checkpointer: this.checkpointer,
568
+ step: (saved.metadata?.step ?? -1) + 1,
569
+ });
570
+ // apply null writes
571
+ const nullWrites = (saved.pendingWrites ?? [])
572
+ .filter((w) => w[0] === NULL_TASK_ID)
573
+ .map((w) => w.slice(1));
574
+ if (nullWrites.length > 0) {
575
+ _applyWrites(saved.checkpoint, channels, [
576
+ {
577
+ name: INPUT,
578
+ writes: nullWrites,
579
+ triggers: [],
580
+ },
581
+ ]);
582
+ }
583
+ // apply writes
584
+ for (const [tid, k, v] of saved.pendingWrites) {
585
+ if ([ERROR, INTERRUPT, SCHEDULED].includes(k) ||
586
+ nextTasks[tid] === undefined) {
587
+ continue;
588
+ }
589
+ nextTasks[tid].writes.push([k, v]);
590
+ }
591
+ const tasks = Object.values(nextTasks).filter((task) => {
592
+ return task.writes.length > 0;
593
+ });
594
+ if (tasks.length > 0) {
595
+ _applyWrites(checkpoint, channels, tasks);
596
+ }
597
+ }
546
598
  const nonNullVersion = Object.values(checkpoint.versions_seen)
547
599
  .map((seenVersions) => {
548
600
  return Object.values(seenVersions);
@@ -72,17 +72,16 @@ function* mapCommand(cmd, pendingWrites) {
72
72
  throw new Error(`In Command.send, expected Send or string, got ${typeof send}`);
73
73
  }
74
74
  }
75
- // TODO: handle goto str for state graph
76
75
  }
77
76
  if (cmd.resume) {
78
77
  if (typeof cmd.resume === "object" &&
79
- !!cmd.resume &&
80
78
  Object.keys(cmd.resume).length &&
81
79
  Object.keys(cmd.resume).every(uuid_1.validate)) {
82
80
  for (const [tid, resume] of Object.entries(cmd.resume)) {
83
- // Find existing resume values for this task ID
84
- const existing = (pendingWrites.find(([id, type]) => id === tid && type === constants_js_1.RESUME)?.[2] ?? []);
85
- // Ensure we have an array and append the resume value
81
+ const existing = pendingWrites
82
+ .filter((w) => w[0] === tid && w[1] === constants_js_1.RESUME)
83
+ .map((w) => w[2])
84
+ .slice(0, 1) ?? [];
86
85
  existing.push(resume);
87
86
  yield [tid, constants_js_1.RESUME, existing];
88
87
  }
@@ -7,7 +7,7 @@ export declare function readChannels<C extends PropertyKey>(channels: Record<C,
7
7
  /**
8
8
  * Map input chunk to a sequence of pending writes in the form (channel, value).
9
9
  */
10
- export declare function mapCommand(cmd: Command, pendingWrites: CheckpointPendingWrite<string>[]): Generator<[string, string, unknown]>;
10
+ export declare function mapCommand(cmd: Command, pendingWrites: CheckpointPendingWrite[]): Generator<[string, string, unknown]>;
11
11
  /**
12
12
  * Map input chunk to a sequence of pending writes in the form [channel, value].
13
13
  */
package/dist/pregel/io.js CHANGED
@@ -67,17 +67,16 @@ export function* mapCommand(cmd, pendingWrites) {
67
67
  throw new Error(`In Command.send, expected Send or string, got ${typeof send}`);
68
68
  }
69
69
  }
70
- // TODO: handle goto str for state graph
71
70
  }
72
71
  if (cmd.resume) {
73
72
  if (typeof cmd.resume === "object" &&
74
- !!cmd.resume &&
75
73
  Object.keys(cmd.resume).length &&
76
74
  Object.keys(cmd.resume).every(validate)) {
77
75
  for (const [tid, resume] of Object.entries(cmd.resume)) {
78
- // Find existing resume values for this task ID
79
- const existing = (pendingWrites.find(([id, type]) => id === tid && type === RESUME)?.[2] ?? []);
80
- // Ensure we have an array and append the resume value
76
+ const existing = pendingWrites
77
+ .filter((w) => w[0] === tid && w[1] === RESUME)
78
+ .map((w) => w[2])
79
+ .slice(0, 1) ?? [];
81
80
  existing.push(resume);
82
81
  yield [tid, RESUME, existing];
83
82
  }
@@ -409,14 +409,9 @@ class PregelLoop {
409
409
  }
410
410
  // save writes
411
411
  for (const [c, v] of writesCopy) {
412
- if (c in langgraph_checkpoint_1.WRITES_IDX_MAP) {
413
- const idx = this.checkpointPendingWrites.findIndex((w) => w[0] === taskId && w[1] === c);
414
- if (idx !== -1) {
415
- this.checkpointPendingWrites[idx] = [taskId, c, v];
416
- }
417
- else {
418
- this.checkpointPendingWrites.push([taskId, c, v]);
419
- }
412
+ const idx = this.checkpointPendingWrites.findIndex((w) => w[0] === taskId && w[1] === c);
413
+ if (c in langgraph_checkpoint_1.WRITES_IDX_MAP && idx !== -1) {
414
+ this.checkpointPendingWrites[idx] = [taskId, c, v];
420
415
  }
421
416
  else {
422
417
  this.checkpointPendingWrites.push([taskId, c, v]);
@@ -586,14 +581,14 @@ class PregelLoop {
586
581
  /**
587
582
  * Resuming from previous checkpoint requires
588
583
  * - finding a previous checkpoint
589
- * - receiving None input (outer graph) or RESUMING flag (subgraph)
584
+ * - receiving null input (outer graph) or RESUMING flag (subgraph)
590
585
  */
591
586
  async _first(inputKeys) {
592
587
  const isResuming = Object.keys(this.checkpoint.channel_versions).length !== 0 &&
593
588
  (this.config.configurable?.[constants_js_1.CONFIG_KEY_RESUMING] !== undefined ||
594
589
  this.input === null ||
595
- (0, constants_js_1._isCommand)(this.input));
596
- if ((0, constants_js_1._isCommand)(this.input)) {
590
+ (0, constants_js_1.isCommand)(this.input));
591
+ if ((0, constants_js_1.isCommand)(this.input)) {
597
592
  const writes = {};
598
593
  // group writes by task id
599
594
  for (const [tid, key, value] of (0, io_js_1.mapCommand)(this.input, this.checkpointPendingWrites)) {
@@ -610,6 +605,19 @@ class PregelLoop {
610
605
  this.putWrites(tid, ws);
611
606
  }
612
607
  }
608
+ // apply null writes
609
+ const nullWrites = (this.checkpointPendingWrites ?? [])
610
+ .filter((w) => w[0] === constants_js_1.NULL_TASK_ID)
611
+ .map((w) => w.slice(1));
612
+ if (nullWrites.length > 0) {
613
+ (0, algo_js_1._applyWrites)(this.checkpoint, this.channels, [
614
+ {
615
+ name: constants_js_1.INPUT,
616
+ writes: nullWrites,
617
+ triggers: [],
618
+ },
619
+ ], this.checkpointerGetNextVersion);
620
+ }
613
621
  if (isResuming) {
614
622
  for (const channelName of Object.keys(this.channels)) {
615
623
  if (this.checkpoint.channel_versions[channelName] !== undefined) {
@@ -116,7 +116,7 @@ export declare class PregelLoop {
116
116
  /**
117
117
  * Resuming from previous checkpoint requires
118
118
  * - finding a previous checkpoint
119
- * - receiving None input (outer graph) or RESUMING flag (subgraph)
119
+ * - receiving null input (outer graph) or RESUMING flag (subgraph)
120
120
  */
121
121
  protected _first(inputKeys: string | string[]): Promise<void>;
122
122
  protected _emit(values: [StreamMode, unknown][]): void;
@@ -1,7 +1,7 @@
1
1
  import { IterableReadableStream } from "@langchain/core/utils/stream";
2
2
  import { copyCheckpoint, emptyCheckpoint, AsyncBatchedStore, WRITES_IDX_MAP, } from "@langchain/langgraph-checkpoint";
3
3
  import { createCheckpoint, emptyChannels, } from "../channels/base.js";
4
- import { _isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, RESUME, TAG_HIDDEN, } from "../constants.js";
4
+ import { isCommand, CHECKPOINT_NAMESPACE_SEPARATOR, CONFIG_KEY_CHECKPOINT_MAP, CONFIG_KEY_READ, CONFIG_KEY_RESUMING, CONFIG_KEY_STREAM, ERROR, INPUT, INTERRUPT, NULL_TASK_ID, RESUME, TAG_HIDDEN, } from "../constants.js";
5
5
  import { _applyWrites, _prepareNextTasks, increment, shouldInterrupt, } from "./algo.js";
6
6
  import { gatherIterator, gatherIteratorSync, prefixGenerator, } from "../utils.js";
7
7
  import { mapCommand, mapInput, mapOutputUpdates, mapOutputValues, readChannels, } from "./io.js";
@@ -405,14 +405,9 @@ export class PregelLoop {
405
405
  }
406
406
  // save writes
407
407
  for (const [c, v] of writesCopy) {
408
- if (c in WRITES_IDX_MAP) {
409
- const idx = this.checkpointPendingWrites.findIndex((w) => w[0] === taskId && w[1] === c);
410
- if (idx !== -1) {
411
- this.checkpointPendingWrites[idx] = [taskId, c, v];
412
- }
413
- else {
414
- this.checkpointPendingWrites.push([taskId, c, v]);
415
- }
408
+ const idx = this.checkpointPendingWrites.findIndex((w) => w[0] === taskId && w[1] === c);
409
+ if (c in WRITES_IDX_MAP && idx !== -1) {
410
+ this.checkpointPendingWrites[idx] = [taskId, c, v];
416
411
  }
417
412
  else {
418
413
  this.checkpointPendingWrites.push([taskId, c, v]);
@@ -582,14 +577,14 @@ export class PregelLoop {
582
577
  /**
583
578
  * Resuming from previous checkpoint requires
584
579
  * - finding a previous checkpoint
585
- * - receiving None input (outer graph) or RESUMING flag (subgraph)
580
+ * - receiving null input (outer graph) or RESUMING flag (subgraph)
586
581
  */
587
582
  async _first(inputKeys) {
588
583
  const isResuming = Object.keys(this.checkpoint.channel_versions).length !== 0 &&
589
584
  (this.config.configurable?.[CONFIG_KEY_RESUMING] !== undefined ||
590
585
  this.input === null ||
591
- _isCommand(this.input));
592
- if (_isCommand(this.input)) {
586
+ isCommand(this.input));
587
+ if (isCommand(this.input)) {
593
588
  const writes = {};
594
589
  // group writes by task id
595
590
  for (const [tid, key, value] of mapCommand(this.input, this.checkpointPendingWrites)) {
@@ -606,6 +601,19 @@ export class PregelLoop {
606
601
  this.putWrites(tid, ws);
607
602
  }
608
603
  }
604
+ // apply null writes
605
+ const nullWrites = (this.checkpointPendingWrites ?? [])
606
+ .filter((w) => w[0] === NULL_TASK_ID)
607
+ .map((w) => w.slice(1));
608
+ if (nullWrites.length > 0) {
609
+ _applyWrites(this.checkpoint, this.channels, [
610
+ {
611
+ name: INPUT,
612
+ writes: nullWrites,
613
+ triggers: [],
614
+ },
615
+ ], this.checkpointerGetNextVersion);
616
+ }
609
617
  if (isResuming) {
610
618
  for (const channelName of Object.keys(this.channels)) {
611
619
  if (this.checkpoint.channel_versions[channelName] !== undefined) {