@langchain/langgraph 0.0.3 → 0.0.4

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.
@@ -24,7 +24,13 @@ class Branch {
24
24
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
25
  runnable(input, options) {
26
26
  const result = this.condition(input, options?.config);
27
- const destination = this.ends[result];
27
+ let destination;
28
+ if (this.ends) {
29
+ destination = this.ends[result];
30
+ }
31
+ else {
32
+ destination = result;
33
+ }
28
34
  return index_js_1.Channel.writeTo(destination !== exports.END ? `${destination}:inbox` : exports.END);
29
35
  }
30
36
  }
@@ -90,9 +96,13 @@ class Graph {
90
96
  if (condition.constructor.name === "AsyncFunction") {
91
97
  throw new Error("Condition cannot be an async function");
92
98
  }
93
- for (const destination of Object.values(conditionalEdgeMapping)) {
94
- if (!this.nodes[destination] && destination !== exports.END) {
95
- throw new Error(`Need to addNode \`${destination}\` first`);
99
+ if (conditionalEdgeMapping) {
100
+ const mappingValues = Array.from(Object.values(conditionalEdgeMapping));
101
+ const nodesValues = Object.keys(this.nodes);
102
+ const endExcluded = mappingValues.filter((value) => value !== exports.END);
103
+ const difference = endExcluded.filter((value) => !nodesValues.some((nv) => nv === value));
104
+ if (difference.length > 0) {
105
+ throw new Error(`Missing nodes which are in conditional edge mapping.\nMapping contains possible destinations: ${mappingValues.join(", ")}.\nPossible nodes are ${nodesValues.join(", ")}.`);
96
106
  }
97
107
  }
98
108
  if (!this.branches[startKey]) {
@@ -158,18 +168,23 @@ class Graph {
158
168
  }
159
169
  validate() {
160
170
  const allStarts = new Set([...this.edges].map(([src, _]) => src).concat(Object.keys(this.branches)));
161
- const allEnds = new Set([...this.edges]
162
- .map(([_, end]) => end)
163
- .concat(...Object.values(this.branches).flatMap((branchList) => branchList.flatMap((branch) => Object.values(branch.ends))))
164
- .concat(this.entryPoint ? [this.entryPoint] : []));
165
171
  for (const node of Object.keys(this.nodes)) {
166
- if (!allEnds.has(node)) {
167
- throw new Error(`Node \`${node}\` is not reachable`);
168
- }
169
172
  if (!allStarts.has(node)) {
170
173
  throw new Error(`Node \`${node}\` is a dead-end`);
171
174
  }
172
175
  }
176
+ const allEndsAreDefined = Object.values(this.branches).every((branchList) => branchList.every((branch) => branch.ends !== null));
177
+ if (allEndsAreDefined) {
178
+ const allEnds = new Set([...this.edges]
179
+ .map(([_, end]) => end)
180
+ .concat(...Object.values(this.branches).flatMap((branchList) => branchList.flatMap((branch) => Object.values(branch.ends ?? {}))))
181
+ .concat(this.entryPoint ? [this.entryPoint] : []));
182
+ for (const node of Object.keys(this.nodes)) {
183
+ if (!allEnds.has(node)) {
184
+ throw new Error(`Node \`${node}\` is not reachable`);
185
+ }
186
+ }
187
+ }
173
188
  }
174
189
  }
175
190
  exports.Graph = Graph;
@@ -7,8 +7,8 @@ type EndsMap = {
7
7
  };
8
8
  declare class Branch {
9
9
  condition: CallableFunction;
10
- ends: EndsMap;
11
- constructor(condition: CallableFunction, ends: EndsMap);
10
+ ends?: EndsMap;
11
+ constructor(condition: CallableFunction, ends?: EndsMap);
12
12
  runnable(input: any, options?: {
13
13
  config?: RunnableConfig;
14
14
  }): Runnable;
@@ -21,7 +21,7 @@ export declare class Graph<RunInput = any, RunOutput = any> {
21
21
  constructor();
22
22
  addNode(key: string, action: RunnableLike<RunInput, RunOutput>): void;
23
23
  addEdge(startKey: string, endKey: string): void;
24
- addConditionalEdges(startKey: string, condition: CallableFunction, conditionalEdgeMapping: Record<string, string>): void;
24
+ addConditionalEdges(startKey: string, condition: CallableFunction, conditionalEdgeMapping?: Record<string, string>): void;
25
25
  setEntryPoint(key: string): void;
26
26
  setFinishPoint(key: string): void;
27
27
  compile(checkpointer?: BaseCheckpointSaver): Pregel;
@@ -21,7 +21,13 @@ class Branch {
21
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
22
  runnable(input, options) {
23
23
  const result = this.condition(input, options?.config);
24
- const destination = this.ends[result];
24
+ let destination;
25
+ if (this.ends) {
26
+ destination = this.ends[result];
27
+ }
28
+ else {
29
+ destination = result;
30
+ }
25
31
  return Channel.writeTo(destination !== END ? `${destination}:inbox` : END);
26
32
  }
27
33
  }
@@ -87,9 +93,13 @@ export class Graph {
87
93
  if (condition.constructor.name === "AsyncFunction") {
88
94
  throw new Error("Condition cannot be an async function");
89
95
  }
90
- for (const destination of Object.values(conditionalEdgeMapping)) {
91
- if (!this.nodes[destination] && destination !== END) {
92
- throw new Error(`Need to addNode \`${destination}\` first`);
96
+ if (conditionalEdgeMapping) {
97
+ const mappingValues = Array.from(Object.values(conditionalEdgeMapping));
98
+ const nodesValues = Object.keys(this.nodes);
99
+ const endExcluded = mappingValues.filter((value) => value !== END);
100
+ const difference = endExcluded.filter((value) => !nodesValues.some((nv) => nv === value));
101
+ if (difference.length > 0) {
102
+ throw new Error(`Missing nodes which are in conditional edge mapping.\nMapping contains possible destinations: ${mappingValues.join(", ")}.\nPossible nodes are ${nodesValues.join(", ")}.`);
93
103
  }
94
104
  }
95
105
  if (!this.branches[startKey]) {
@@ -155,17 +165,22 @@ export class Graph {
155
165
  }
156
166
  validate() {
157
167
  const allStarts = new Set([...this.edges].map(([src, _]) => src).concat(Object.keys(this.branches)));
158
- const allEnds = new Set([...this.edges]
159
- .map(([_, end]) => end)
160
- .concat(...Object.values(this.branches).flatMap((branchList) => branchList.flatMap((branch) => Object.values(branch.ends))))
161
- .concat(this.entryPoint ? [this.entryPoint] : []));
162
168
  for (const node of Object.keys(this.nodes)) {
163
- if (!allEnds.has(node)) {
164
- throw new Error(`Node \`${node}\` is not reachable`);
165
- }
166
169
  if (!allStarts.has(node)) {
167
170
  throw new Error(`Node \`${node}\` is a dead-end`);
168
171
  }
169
172
  }
173
+ const allEndsAreDefined = Object.values(this.branches).every((branchList) => branchList.every((branch) => branch.ends !== null));
174
+ if (allEndsAreDefined) {
175
+ const allEnds = new Set([...this.edges]
176
+ .map(([_, end]) => end)
177
+ .concat(...Object.values(this.branches).flatMap((branchList) => branchList.flatMap((branch) => Object.values(branch.ends ?? {}))))
178
+ .concat(this.entryPoint ? [this.entryPoint] : []));
179
+ for (const node of Object.keys(this.nodes)) {
180
+ if (!allEnds.has(node)) {
181
+ throw new Error(`Node \`${node}\` is not reachable`);
182
+ }
183
+ }
184
+ }
170
185
  }
171
186
  }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessageGraph = void 0;
4
+ const state_js_1 = require("./state.cjs");
5
+ function addMessages(left, right) {
6
+ const leftArray = Array.isArray(left) ? left : [left];
7
+ const rightArray = Array.isArray(right) ? right : [right];
8
+ return [...leftArray, ...rightArray];
9
+ }
10
+ class MessageGraph extends state_js_1.StateGraph {
11
+ constructor() {
12
+ super({
13
+ channels: {
14
+ value: (a, b) => addMessages(a, b),
15
+ default: () => [],
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ },
18
+ });
19
+ }
20
+ }
21
+ exports.MessageGraph = MessageGraph;
@@ -0,0 +1,7 @@
1
+ import { BaseMessage } from "@langchain/core/messages";
2
+ import { StateGraph } from "./state.js";
3
+ type Messages = Array<BaseMessage> | BaseMessage;
4
+ export declare class MessageGraph<T extends Messages> extends StateGraph<T> {
5
+ constructor();
6
+ }
7
+ export {};
@@ -0,0 +1,17 @@
1
+ import { StateGraph } from "./state.js";
2
+ function addMessages(left, right) {
3
+ const leftArray = Array.isArray(left) ? left : [left];
4
+ const rightArray = Array.isArray(right) ? right : [right];
5
+ return [...leftArray, ...rightArray];
6
+ }
7
+ export class MessageGraph extends StateGraph {
8
+ constructor() {
9
+ super({
10
+ channels: {
11
+ value: (a, b) => addMessages(a, b),
12
+ default: () => [],
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ },
15
+ });
16
+ }
17
+ }
@@ -20,12 +20,26 @@ class StateGraph extends graph_js_1.Graph {
20
20
  });
21
21
  this.channels = _getChannels(fields.channels);
22
22
  }
23
+ addNode(key, action) {
24
+ if (Object.keys(this.nodes).some((key) => key in this.channels)) {
25
+ throw new Error(`${key} is already being used as a state attribute (a.k.a. a channel), cannot also be used as a node name.`);
26
+ }
27
+ super.addNode(key, action);
28
+ }
23
29
  compile(checkpointer) {
24
30
  this.validate();
25
31
  if (Object.keys(this.nodes).some((key) => key in this.channels)) {
26
32
  throw new Error("Cannot use channel names as node names");
27
33
  }
28
34
  const stateKeys = Object.keys(this.channels);
35
+ const stateKeysRead = stateKeys.length === 1 && stateKeys[0] === "__root__"
36
+ ? stateKeys[0]
37
+ : stateKeys;
38
+ const updateState = Array.isArray(stateKeysRead)
39
+ ? (nodeName,
40
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
+ input, options) => _updateStateObject(stateKeys, nodeName, input, options)
42
+ : _updateStateRoot;
29
43
  const outgoingEdges = {};
30
44
  for (const [start, end] of this.edges) {
31
45
  if (!outgoingEdges[start]) {
@@ -38,7 +52,9 @@ class StateGraph extends graph_js_1.Graph {
38
52
  for (const [key, node] of Object.entries(this.nodes)) {
39
53
  nodes[key] = index_js_1.Channel.subscribeTo(`${key}:inbox`)
40
54
  .pipe(node)
41
- .pipe(_updateState)
55
+ .pipe((
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ input, options) => updateState(key, input, options))
42
58
  .pipe(index_js_1.Channel.writeTo(key));
43
59
  }
44
60
  for (const key of Object.keys(this.nodes)) {
@@ -47,7 +63,7 @@ class StateGraph extends graph_js_1.Graph {
47
63
  if (outgoing || this.branches[key]) {
48
64
  nodes[edgesKey] = index_js_1.Channel.subscribeTo(key, {
49
65
  tags: ["langsmith:hidden"],
50
- }).pipe(new read_js_1.ChannelRead(stateKeys));
66
+ }).pipe(new read_js_1.ChannelRead(stateKeysRead));
51
67
  }
52
68
  if (outgoing) {
53
69
  nodes[edgesKey] = nodes[edgesKey].pipe(index_js_1.Channel.writeTo(...outgoing));
@@ -63,12 +79,14 @@ class StateGraph extends graph_js_1.Graph {
63
79
  nodes[exports.START] = index_js_1.Channel.subscribeTo(`${exports.START}:inbox`, {
64
80
  tags: ["langsmith:hidden"],
65
81
  })
66
- .pipe(_updateState)
82
+ .pipe((
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ input, options) => updateState(exports.START, input, options))
67
85
  .pipe(index_js_1.Channel.writeTo(exports.START));
68
86
  nodes[`${exports.START}:edges`] = index_js_1.Channel.subscribeTo(exports.START, {
69
87
  tags: ["langsmith:hidden"],
70
88
  })
71
- .pipe(new read_js_1.ChannelRead(stateKeys))
89
+ .pipe(new read_js_1.ChannelRead(stateKeysRead))
72
90
  .pipe(index_js_1.Channel.writeTo(`${this.entryPoint}:inbox`));
73
91
  return new index_js_1.Pregel({
74
92
  nodes,
@@ -83,7 +101,7 @@ class StateGraph extends graph_js_1.Graph {
83
101
  }
84
102
  }
85
103
  exports.StateGraph = StateGraph;
86
- function _updateState(
104
+ function _updateStateObject(stateKeys, nodeName,
87
105
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
106
  input, options
89
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -91,10 +109,30 @@ input, options
91
109
  if (!options?.config) {
92
110
  throw new Error("Config not found when updating state.");
93
111
  }
112
+ if (Object.keys(input).some((key) => !stateKeys.some((sk) => sk === key))) {
113
+ throw new Error(`Invalid state update from node ${nodeName}, expected object with one or more of ${stateKeys.join(", ")}, got ${Object.keys(input).join(",")}`);
114
+ }
94
115
  write_js_1.ChannelWrite.doWrite(options.config, input);
95
116
  return input;
96
117
  }
118
+ function _updateStateRoot(_nodeName, input, options) {
119
+ if (!options?.config) {
120
+ throw new Error("Config not found when updating state.");
121
+ }
122
+ write_js_1.ChannelWrite.doWrite(options.config, {
123
+ __root__: input,
124
+ });
125
+ return input;
126
+ }
97
127
  function _getChannels(schema) {
128
+ if ("value" in schema && "default" in schema) {
129
+ if (!schema.value) {
130
+ throw new Error("Value is required for channels");
131
+ }
132
+ return {
133
+ __root__: new binop_js_1.BinaryOperatorAggregate(schema.value, schema.default),
134
+ };
135
+ }
98
136
  const channels = {};
99
137
  for (const [name, values] of Object.entries(schema)) {
100
138
  if (values.value) {
@@ -1,3 +1,4 @@
1
+ import { RunnableLike } from "@langchain/core/runnables";
1
2
  import { BaseChannel } from "../channels/base.js";
2
3
  import { BinaryOperator } from "../channels/binop.js";
3
4
  import { Graph } from "./graph.js";
@@ -10,10 +11,14 @@ export interface StateGraphArgs<Channels extends Record<string, any>> {
10
11
  value: BinaryOperator<Channels[K]> | null;
11
12
  default?: () => Channels[K];
12
13
  };
14
+ } | {
15
+ value: BinaryOperator<unknown> | null;
16
+ default?: () => unknown;
13
17
  };
14
18
  }
15
19
  export declare class StateGraph<Channels extends Record<string, any>> extends Graph<Channels> {
16
20
  channels: Record<string, BaseChannel>;
17
21
  constructor(fields: StateGraphArgs<Channels>);
22
+ addNode(key: string, action: RunnableLike): void;
18
23
  compile(checkpointer?: BaseCheckpointSaver): Pregel;
19
24
  }
@@ -1,4 +1,4 @@
1
- import { RunnableLambda } from "@langchain/core/runnables";
1
+ import { RunnableLambda, } from "@langchain/core/runnables";
2
2
  import { BinaryOperatorAggregate } from "../channels/binop.js";
3
3
  import { END, Graph } from "./graph.js";
4
4
  import { LastValue } from "../channels/last_value.js";
@@ -17,12 +17,26 @@ export class StateGraph extends Graph {
17
17
  });
18
18
  this.channels = _getChannels(fields.channels);
19
19
  }
20
+ addNode(key, action) {
21
+ if (Object.keys(this.nodes).some((key) => key in this.channels)) {
22
+ throw new Error(`${key} is already being used as a state attribute (a.k.a. a channel), cannot also be used as a node name.`);
23
+ }
24
+ super.addNode(key, action);
25
+ }
20
26
  compile(checkpointer) {
21
27
  this.validate();
22
28
  if (Object.keys(this.nodes).some((key) => key in this.channels)) {
23
29
  throw new Error("Cannot use channel names as node names");
24
30
  }
25
31
  const stateKeys = Object.keys(this.channels);
32
+ const stateKeysRead = stateKeys.length === 1 && stateKeys[0] === "__root__"
33
+ ? stateKeys[0]
34
+ : stateKeys;
35
+ const updateState = Array.isArray(stateKeysRead)
36
+ ? (nodeName,
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ input, options) => _updateStateObject(stateKeys, nodeName, input, options)
39
+ : _updateStateRoot;
26
40
  const outgoingEdges = {};
27
41
  for (const [start, end] of this.edges) {
28
42
  if (!outgoingEdges[start]) {
@@ -35,7 +49,9 @@ export class StateGraph extends Graph {
35
49
  for (const [key, node] of Object.entries(this.nodes)) {
36
50
  nodes[key] = Channel.subscribeTo(`${key}:inbox`)
37
51
  .pipe(node)
38
- .pipe(_updateState)
52
+ .pipe((
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ input, options) => updateState(key, input, options))
39
55
  .pipe(Channel.writeTo(key));
40
56
  }
41
57
  for (const key of Object.keys(this.nodes)) {
@@ -44,7 +60,7 @@ export class StateGraph extends Graph {
44
60
  if (outgoing || this.branches[key]) {
45
61
  nodes[edgesKey] = Channel.subscribeTo(key, {
46
62
  tags: ["langsmith:hidden"],
47
- }).pipe(new ChannelRead(stateKeys));
63
+ }).pipe(new ChannelRead(stateKeysRead));
48
64
  }
49
65
  if (outgoing) {
50
66
  nodes[edgesKey] = nodes[edgesKey].pipe(Channel.writeTo(...outgoing));
@@ -60,12 +76,14 @@ export class StateGraph extends Graph {
60
76
  nodes[START] = Channel.subscribeTo(`${START}:inbox`, {
61
77
  tags: ["langsmith:hidden"],
62
78
  })
63
- .pipe(_updateState)
79
+ .pipe((
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ input, options) => updateState(START, input, options))
64
82
  .pipe(Channel.writeTo(START));
65
83
  nodes[`${START}:edges`] = Channel.subscribeTo(START, {
66
84
  tags: ["langsmith:hidden"],
67
85
  })
68
- .pipe(new ChannelRead(stateKeys))
86
+ .pipe(new ChannelRead(stateKeysRead))
69
87
  .pipe(Channel.writeTo(`${this.entryPoint}:inbox`));
70
88
  return new Pregel({
71
89
  nodes,
@@ -79,7 +97,7 @@ export class StateGraph extends Graph {
79
97
  });
80
98
  }
81
99
  }
82
- function _updateState(
100
+ function _updateStateObject(stateKeys, nodeName,
83
101
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
102
  input, options
85
103
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -87,10 +105,30 @@ input, options
87
105
  if (!options?.config) {
88
106
  throw new Error("Config not found when updating state.");
89
107
  }
108
+ if (Object.keys(input).some((key) => !stateKeys.some((sk) => sk === key))) {
109
+ throw new Error(`Invalid state update from node ${nodeName}, expected object with one or more of ${stateKeys.join(", ")}, got ${Object.keys(input).join(",")}`);
110
+ }
90
111
  ChannelWrite.doWrite(options.config, input);
91
112
  return input;
92
113
  }
114
+ function _updateStateRoot(_nodeName, input, options) {
115
+ if (!options?.config) {
116
+ throw new Error("Config not found when updating state.");
117
+ }
118
+ ChannelWrite.doWrite(options.config, {
119
+ __root__: input,
120
+ });
121
+ return input;
122
+ }
93
123
  function _getChannels(schema) {
124
+ if ("value" in schema && "default" in schema) {
125
+ if (!schema.value) {
126
+ throw new Error("Value is required for channels");
127
+ }
128
+ return {
129
+ __root__: new BinaryOperatorAggregate(schema.value, schema.default),
130
+ };
131
+ }
94
132
  const channels = {};
95
133
  for (const [name, values] of Object.entries(schema)) {
96
134
  if (values.value) {
@@ -1,4 +1,4 @@
1
- import { Tool } from "@langchain/core/tools";
1
+ import { StructuredTool } from "@langchain/core/tools";
2
2
  import { BaseMessage } from "@langchain/core/messages";
3
3
  import { ToolExecutor } from "./tool_executor.js";
4
4
  export type FunctionCallingExecutorState = {
@@ -6,5 +6,5 @@ export type FunctionCallingExecutorState = {
6
6
  };
7
7
  export declare function createFunctionCallingExecutor<Model extends object>({ model, tools, }: {
8
8
  model: Model;
9
- tools: Array<Tool> | ToolExecutor;
9
+ tools: Array<StructuredTool> | ToolExecutor;
10
10
  }): import("../pregel/index.js").Pregel;
@@ -1,7 +1,7 @@
1
1
  import { RunnableBinding, RunnableConfig } from "@langchain/core/runnables";
2
- import { Tool } from "@langchain/core/tools";
2
+ import { StructuredTool } from "@langchain/core/tools";
3
3
  export interface ToolExecutorArgs {
4
- tools: Array<Tool>;
4
+ tools: Array<StructuredTool>;
5
5
  /**
6
6
  * @default {INVALID_TOOL_MSG_TEMPLATE}
7
7
  */
@@ -18,8 +18,8 @@ type ToolExecutorInputType = any;
18
18
  type ToolExecutorOutputType = any;
19
19
  export declare class ToolExecutor extends RunnableBinding<ToolExecutorInputType, ToolExecutorOutputType> {
20
20
  lc_graph_name: string;
21
- tools: Array<Tool>;
22
- toolMap: Record<string, Tool>;
21
+ tools: Array<StructuredTool>;
22
+ toolMap: Record<string, StructuredTool>;
23
23
  invalidToolMsgTemplate: string;
24
24
  constructor(fields: ToolExecutorArgs);
25
25
  _execute(toolInvocation: ToolInvocationInterface, config?: RunnableConfig): Promise<string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "LangGraph",
5
5
  "type": "module",
6
6
  "engines": {