@langchain/langgraph 0.0.18 → 0.0.20

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.
@@ -13,7 +13,7 @@ export interface CheckpointMetadata {
13
13
  * -1 for the first "input" checkpoint.
14
14
  * 0 for the first "loop" checkpoint.
15
15
  * ... for the nth checkpoint afterwards. */
16
- writes?: Record<string, unknown>;
16
+ writes: Record<string, unknown> | null;
17
17
  }
18
18
  export interface Checkpoint<N extends string = string, C extends string = string> {
19
19
  /**
@@ -25,12 +25,6 @@ class Branch {
25
25
  writable: true,
26
26
  value: void 0
27
27
  });
28
- Object.defineProperty(this, "then", {
29
- enumerable: true,
30
- configurable: true,
31
- writable: true,
32
- value: void 0
33
- });
34
28
  this.condition = options.path;
35
29
  this.ends = Array.isArray(options.pathMap)
36
30
  ? options.pathMap.reduce((acc, n) => {
@@ -38,7 +32,6 @@ class Branch {
38
32
  return acc;
39
33
  }, {})
40
34
  : options.pathMap;
41
- this.then = options.then;
42
35
  }
43
36
  compile(writer, reader) {
44
37
  return write_js_1.ChannelWrite.registerWriter(new utils_js_1.RunnableCallable({
@@ -57,6 +50,9 @@ class Branch {
57
50
  else {
58
51
  destinations = result;
59
52
  }
53
+ if (destinations.some((dest) => !dest)) {
54
+ throw new Error("Branch condition returned unknown or null destination");
55
+ }
60
56
  return writer(destinations);
61
57
  }
62
58
  }
@@ -207,26 +203,8 @@ class Graph {
207
203
  validate(interrupt) {
208
204
  // assemble sources
209
205
  const allSources = new Set([...this.allEdges].map(([src, _]) => src));
210
- for (const [start, branches] of Object.entries(this.branches)) {
206
+ for (const [start] of Object.entries(this.branches)) {
211
207
  allSources.add(start);
212
- for (const branch of Object.values(branches)) {
213
- if (branch.then) {
214
- if (branch.ends) {
215
- for (const end of Object.values(branch.ends)) {
216
- if (end !== exports.END) {
217
- allSources.add(end);
218
- }
219
- }
220
- }
221
- else {
222
- for (const node of Object.keys(this.nodes)) {
223
- if (node !== start) {
224
- allSources.add(node);
225
- }
226
- }
227
- }
228
- }
229
- }
230
208
  }
231
209
  // validate sources
232
210
  for (const node of Object.keys(this.nodes)) {
@@ -243,9 +221,6 @@ class Graph {
243
221
  const allTargets = new Set([...this.allEdges].map(([_, target]) => target));
244
222
  for (const [start, branches] of Object.entries(this.branches)) {
245
223
  for (const branch of Object.values(branches)) {
246
- if (branch.then) {
247
- allTargets.add(branch.then);
248
- }
249
224
  if (branch.ends) {
250
225
  for (const end of Object.values(branch.ends)) {
251
226
  allTargets.add(end);
@@ -254,7 +229,7 @@ class Graph {
254
229
  else {
255
230
  allTargets.add(exports.END);
256
231
  for (const node of Object.keys(this.nodes)) {
257
- if (node !== start && node !== branch.then) {
232
+ if (node !== start) {
258
233
  allTargets.add(node);
259
234
  }
260
235
  }
@@ -11,12 +11,10 @@ export interface BranchOptions<IO, N extends string> {
11
11
  source: N;
12
12
  path: Branch<IO, N>["condition"];
13
13
  pathMap?: Record<string, N | typeof END> | N[];
14
- then?: N | typeof END;
15
14
  }
16
15
  export declare class Branch<IO, N extends string> {
17
16
  condition: (input: IO, config?: RunnableConfig) => string | string[] | Promise<string> | Promise<string[]>;
18
17
  ends?: Record<string, N | typeof END>;
19
- then?: BranchOptions<IO, N>["then"];
20
18
  constructor(options: Omit<BranchOptions<IO, N>, "source">);
21
19
  compile(writer: (dests: string[]) => Runnable | undefined, reader?: (config: RunnableConfig) => IO): RunnableCallable<unknown, unknown>;
22
20
  _route(input: IO, config: RunnableConfig, writer: (dests: string[]) => Runnable | undefined, reader?: (config: RunnableConfig) => IO): Promise<Runnable | undefined>;
@@ -22,12 +22,6 @@ export class Branch {
22
22
  writable: true,
23
23
  value: void 0
24
24
  });
25
- Object.defineProperty(this, "then", {
26
- enumerable: true,
27
- configurable: true,
28
- writable: true,
29
- value: void 0
30
- });
31
25
  this.condition = options.path;
32
26
  this.ends = Array.isArray(options.pathMap)
33
27
  ? options.pathMap.reduce((acc, n) => {
@@ -35,7 +29,6 @@ export class Branch {
35
29
  return acc;
36
30
  }, {})
37
31
  : options.pathMap;
38
- this.then = options.then;
39
32
  }
40
33
  compile(writer, reader) {
41
34
  return ChannelWrite.registerWriter(new RunnableCallable({
@@ -54,6 +47,9 @@ export class Branch {
54
47
  else {
55
48
  destinations = result;
56
49
  }
50
+ if (destinations.some((dest) => !dest)) {
51
+ throw new Error("Branch condition returned unknown or null destination");
52
+ }
57
53
  return writer(destinations);
58
54
  }
59
55
  }
@@ -203,26 +199,8 @@ export class Graph {
203
199
  validate(interrupt) {
204
200
  // assemble sources
205
201
  const allSources = new Set([...this.allEdges].map(([src, _]) => src));
206
- for (const [start, branches] of Object.entries(this.branches)) {
202
+ for (const [start] of Object.entries(this.branches)) {
207
203
  allSources.add(start);
208
- for (const branch of Object.values(branches)) {
209
- if (branch.then) {
210
- if (branch.ends) {
211
- for (const end of Object.values(branch.ends)) {
212
- if (end !== END) {
213
- allSources.add(end);
214
- }
215
- }
216
- }
217
- else {
218
- for (const node of Object.keys(this.nodes)) {
219
- if (node !== start) {
220
- allSources.add(node);
221
- }
222
- }
223
- }
224
- }
225
- }
226
204
  }
227
205
  // validate sources
228
206
  for (const node of Object.keys(this.nodes)) {
@@ -239,9 +217,6 @@ export class Graph {
239
217
  const allTargets = new Set([...this.allEdges].map(([_, target]) => target));
240
218
  for (const [start, branches] of Object.entries(this.branches)) {
241
219
  for (const branch of Object.values(branches)) {
242
- if (branch.then) {
243
- allTargets.add(branch.then);
244
- }
245
220
  if (branch.ends) {
246
221
  for (const end of Object.values(branch.ends)) {
247
222
  allTargets.add(end);
@@ -250,7 +225,7 @@ export class Graph {
250
225
  else {
251
226
  allTargets.add(END);
252
227
  for (const node of Object.keys(this.nodes)) {
253
- if (node !== start && node !== branch.then) {
228
+ if (node !== start) {
254
229
  allTargets.add(node);
255
230
  }
256
231
  }
@@ -11,7 +11,6 @@ const ephemeral_value_js_1 = require("../channels/ephemeral_value.cjs");
11
11
  const utils_js_1 = require("../utils.cjs");
12
12
  const constants_js_1 = require("../constants.cjs");
13
13
  const errors_js_1 = require("../errors.cjs");
14
- const dynamic_barrier_value_js_1 = require("../channels/dynamic_barrier_value.cjs");
15
14
  const ROOT = "__root__";
16
15
  class StateGraph extends graph_js_1.Graph {
17
16
  constructor(fields) {
@@ -241,12 +240,6 @@ class CompiledStateGraph extends graph_js_1.CompiledGraph {
241
240
  channel: `branch:${start}:${name}:${dest}`,
242
241
  value: start,
243
242
  }));
244
- if (branch.then && branch.then !== graph_js_1.END) {
245
- writes.push({
246
- channel: `branch:${start}:${name}:then`,
247
- value: { __names: filteredDests },
248
- });
249
- }
250
243
  return new write_js_1.ChannelWrite(writes, [constants_js_1.TAG_HIDDEN]);
251
244
  },
252
245
  // reader
@@ -254,7 +247,7 @@ class CompiledStateGraph extends graph_js_1.CompiledGraph {
254
247
  // attach branch subscribers
255
248
  const ends = branch.ends
256
249
  ? Object.values(branch.ends)
257
- : Object.keys(this.builder.nodes).filter((n) => n !== branch.then);
250
+ : Object.keys(this.builder.nodes);
258
251
  for (const end of ends) {
259
252
  if (end === graph_js_1.END) {
260
253
  continue;
@@ -264,18 +257,6 @@ class CompiledStateGraph extends graph_js_1.CompiledGraph {
264
257
  new ephemeral_value_js_1.EphemeralValue();
265
258
  this.nodes[end].triggers.push(channelName);
266
259
  }
267
- if (branch.then && branch.then !== graph_js_1.END) {
268
- const channelName = `branch:${start}:${name}:then`;
269
- this.channels[channelName] =
270
- new dynamic_barrier_value_js_1.DynamicBarrierValue();
271
- this.nodes[branch.then].triggers.push(channelName);
272
- for (const end of ends) {
273
- if (end === graph_js_1.END) {
274
- continue;
275
- }
276
- this.nodes[end].writers.push(new write_js_1.ChannelWrite([{ channel: channelName, value: end }], [constants_js_1.TAG_HIDDEN]));
277
- }
278
- }
279
260
  }
280
261
  }
281
262
  exports.CompiledStateGraph = CompiledStateGraph;
@@ -8,7 +8,6 @@ import { EphemeralValue } from "../channels/ephemeral_value.js";
8
8
  import { RunnableCallable } from "../utils.js";
9
9
  import { TAG_HIDDEN } from "../constants.js";
10
10
  import { InvalidUpdateError } from "../errors.js";
11
- import { DynamicBarrierValue } from "../channels/dynamic_barrier_value.js";
12
11
  const ROOT = "__root__";
13
12
  export class StateGraph extends Graph {
14
13
  constructor(fields) {
@@ -237,12 +236,6 @@ export class CompiledStateGraph extends CompiledGraph {
237
236
  channel: `branch:${start}:${name}:${dest}`,
238
237
  value: start,
239
238
  }));
240
- if (branch.then && branch.then !== END) {
241
- writes.push({
242
- channel: `branch:${start}:${name}:then`,
243
- value: { __names: filteredDests },
244
- });
245
- }
246
239
  return new ChannelWrite(writes, [TAG_HIDDEN]);
247
240
  },
248
241
  // reader
@@ -250,7 +243,7 @@ export class CompiledStateGraph extends CompiledGraph {
250
243
  // attach branch subscribers
251
244
  const ends = branch.ends
252
245
  ? Object.values(branch.ends)
253
- : Object.keys(this.builder.nodes).filter((n) => n !== branch.then);
246
+ : Object.keys(this.builder.nodes);
254
247
  for (const end of ends) {
255
248
  if (end === END) {
256
249
  continue;
@@ -260,17 +253,5 @@ export class CompiledStateGraph extends CompiledGraph {
260
253
  new EphemeralValue();
261
254
  this.nodes[end].triggers.push(channelName);
262
255
  }
263
- if (branch.then && branch.then !== END) {
264
- const channelName = `branch:${start}:${name}:then`;
265
- this.channels[channelName] =
266
- new DynamicBarrierValue();
267
- this.nodes[branch.then].triggers.push(channelName);
268
- for (const end of ends) {
269
- if (end === END) {
270
- continue;
271
- }
272
- this.nodes[end].writers.push(new ChannelWrite([{ channel: channelName, value: end }], [TAG_HIDDEN]));
273
- }
274
- }
275
256
  }
276
257
  }
@@ -7,8 +7,8 @@ const prompts_1 = require("@langchain/core/prompts");
7
7
  const index_js_1 = require("../graph/index.cjs");
8
8
  const tool_node_js_1 = require("./tool_node.cjs");
9
9
  /**
10
- * Creates a StateGraph agent that relies on a chat model utilizing tool calling.
11
- * @param model The chat model that can utilize OpenAI-style function calling.
10
+ * Creates a StateGraph agent that relies on a chat llm utilizing tool calling.
11
+ * @param llm The chat llm that can utilize OpenAI-style function calling.
12
12
  * @param tools A list of tools or a ToolNode.
13
13
  * @param messageModifier An optional message modifier to apply to messages before being passed to the LLM.
14
14
  * Can be a SystemMessage, string, function that takes and returns a list of messages, or a Runnable.
@@ -17,7 +17,8 @@ const tool_node_js_1 = require("./tool_node.cjs");
17
17
  * @param interruptAfter An optional list of node names to interrupt after running.
18
18
  * @returns A compiled agent as a LangChain Runnable.
19
19
  */
20
- function createReactAgent(model, tools, messageModifier, checkpointSaver, interruptBefore, interruptAfter) {
20
+ function createReactAgent(props) {
21
+ const { llm, tools, messageModifier, checkpointSaver, interruptBefore, interruptAfter, } = props;
21
22
  const schema = {
22
23
  messages: {
23
24
  value: (left, right) => left.concat(right),
@@ -31,10 +32,10 @@ function createReactAgent(model, tools, messageModifier, checkpointSaver, interr
31
32
  else {
32
33
  toolClasses = tools;
33
34
  }
34
- if (!("bindTools" in model) || typeof model.bindTools !== "function") {
35
- throw new Error(`Model ${model} must define bindTools method.`);
35
+ if (!("bindTools" in llm) || typeof llm.bindTools !== "function") {
36
+ throw new Error(`llm ${llm} must define bindTools method.`);
36
37
  }
37
- const modelWithTools = model.bindTools(toolClasses);
38
+ const modelWithTools = llm.bindTools(toolClasses);
38
39
  const modelRunnable = _createModelWrapper(modelWithTools, messageModifier);
39
40
  const shouldContinue = (state) => {
40
41
  const { messages } = state;
@@ -60,7 +61,7 @@ function createReactAgent(model, tools, messageModifier, checkpointSaver, interr
60
61
  .addEdge(index_js_1.START, "agent")
61
62
  .addConditionalEdges("agent", shouldContinue, {
62
63
  continue: "tools",
63
- end: index_js_1.END,
64
+ [index_js_1.END]: index_js_1.END,
64
65
  })
65
66
  .addEdge("tools", "agent");
66
67
  return workflow.compile({
@@ -12,9 +12,17 @@ export interface AgentState {
12
12
  messages: BaseMessage[];
13
13
  }
14
14
  export type N = typeof START | "agent" | "tools";
15
+ export type CreateReactAgentParams = {
16
+ llm: BaseChatModel;
17
+ tools: ToolNode<MessagesState> | StructuredTool[];
18
+ messageModifier?: SystemMessage | string | ((messages: BaseMessage[]) => BaseMessage[]) | ((messages: BaseMessage[]) => Promise<BaseMessage[]>) | Runnable;
19
+ checkpointSaver?: BaseCheckpointSaver;
20
+ interruptBefore?: N[] | All;
21
+ interruptAfter?: N[] | All;
22
+ };
15
23
  /**
16
- * Creates a StateGraph agent that relies on a chat model utilizing tool calling.
17
- * @param model The chat model that can utilize OpenAI-style function calling.
24
+ * Creates a StateGraph agent that relies on a chat llm utilizing tool calling.
25
+ * @param llm The chat llm that can utilize OpenAI-style function calling.
18
26
  * @param tools A list of tools or a ToolNode.
19
27
  * @param messageModifier An optional message modifier to apply to messages before being passed to the LLM.
20
28
  * Can be a SystemMessage, string, function that takes and returns a list of messages, or a Runnable.
@@ -23,4 +31,4 @@ export type N = typeof START | "agent" | "tools";
23
31
  * @param interruptAfter An optional list of node names to interrupt after running.
24
32
  * @returns A compiled agent as a LangChain Runnable.
25
33
  */
26
- export declare function createReactAgent(model: BaseChatModel, tools: ToolNode<MessagesState> | StructuredTool[], messageModifier?: SystemMessage | string | ((messages: BaseMessage[]) => BaseMessage[]) | Runnable, checkpointSaver?: BaseCheckpointSaver, interruptBefore?: N[] | All, interruptAfter?: N[] | All): CompiledStateGraph<AgentState, Partial<AgentState>, typeof START | "agent" | "tools">;
34
+ export declare function createReactAgent(props: CreateReactAgentParams): CompiledStateGraph<AgentState, Partial<AgentState>, typeof START | "agent" | "tools">;
@@ -4,8 +4,8 @@ import { ChatPromptTemplate } from "@langchain/core/prompts";
4
4
  import { END, START, StateGraph } from "../graph/index.js";
5
5
  import { ToolNode } from "./tool_node.js";
6
6
  /**
7
- * Creates a StateGraph agent that relies on a chat model utilizing tool calling.
8
- * @param model The chat model that can utilize OpenAI-style function calling.
7
+ * Creates a StateGraph agent that relies on a chat llm utilizing tool calling.
8
+ * @param llm The chat llm that can utilize OpenAI-style function calling.
9
9
  * @param tools A list of tools or a ToolNode.
10
10
  * @param messageModifier An optional message modifier to apply to messages before being passed to the LLM.
11
11
  * Can be a SystemMessage, string, function that takes and returns a list of messages, or a Runnable.
@@ -14,7 +14,8 @@ import { ToolNode } from "./tool_node.js";
14
14
  * @param interruptAfter An optional list of node names to interrupt after running.
15
15
  * @returns A compiled agent as a LangChain Runnable.
16
16
  */
17
- export function createReactAgent(model, tools, messageModifier, checkpointSaver, interruptBefore, interruptAfter) {
17
+ export function createReactAgent(props) {
18
+ const { llm, tools, messageModifier, checkpointSaver, interruptBefore, interruptAfter, } = props;
18
19
  const schema = {
19
20
  messages: {
20
21
  value: (left, right) => left.concat(right),
@@ -28,10 +29,10 @@ export function createReactAgent(model, tools, messageModifier, checkpointSaver,
28
29
  else {
29
30
  toolClasses = tools;
30
31
  }
31
- if (!("bindTools" in model) || typeof model.bindTools !== "function") {
32
- throw new Error(`Model ${model} must define bindTools method.`);
32
+ if (!("bindTools" in llm) || typeof llm.bindTools !== "function") {
33
+ throw new Error(`llm ${llm} must define bindTools method.`);
33
34
  }
34
- const modelWithTools = model.bindTools(toolClasses);
35
+ const modelWithTools = llm.bindTools(toolClasses);
35
36
  const modelRunnable = _createModelWrapper(modelWithTools, messageModifier);
36
37
  const shouldContinue = (state) => {
37
38
  const { messages } = state;
@@ -57,7 +58,7 @@ export function createReactAgent(model, tools, messageModifier, checkpointSaver,
57
58
  .addEdge(START, "agent")
58
59
  .addConditionalEdges("agent", shouldContinue, {
59
60
  continue: "tools",
60
- end: END,
61
+ [END]: END,
61
62
  })
62
63
  .addEdge("tools", "agent");
63
64
  return workflow.compile({
@@ -222,6 +222,7 @@ class Pregel extends runnables_1.Runnable {
222
222
  next: nextTasks.map((task) => task.name),
223
223
  metadata: saved?.metadata,
224
224
  config: saved ? saved.config : config,
225
+ createdAt: saved?.checkpoint.ts,
225
226
  parentConfig: saved?.parentConfig,
226
227
  };
227
228
  }
@@ -238,6 +239,7 @@ class Pregel extends runnables_1.Runnable {
238
239
  next: nextTasks.map((task) => task.name),
239
240
  metadata: saved.metadata,
240
241
  config: saved.config,
242
+ createdAt: saved.checkpoint.ts,
241
243
  parentConfig: saved.parentConfig,
242
244
  };
243
245
  }
@@ -480,7 +482,7 @@ class Pregel extends runnables_1.Runnable {
480
482
  bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
481
483
  source: "loop",
482
484
  step,
483
- writes: (0, io_js_1.single)(streamMode === "values"
485
+ writes: (0, io_js_1.single)(this.streamMode === "values"
484
486
  ? (0, io_js_1.mapOutputValues)(outputKeys, pendingWrites, channels)
485
487
  : (0, io_js_1.mapOutputUpdates)(outputKeys, nextTasks)),
486
488
  }));
@@ -218,6 +218,7 @@ export class Pregel extends Runnable {
218
218
  next: nextTasks.map((task) => task.name),
219
219
  metadata: saved?.metadata,
220
220
  config: saved ? saved.config : config,
221
+ createdAt: saved?.checkpoint.ts,
221
222
  parentConfig: saved?.parentConfig,
222
223
  };
223
224
  }
@@ -234,6 +235,7 @@ export class Pregel extends Runnable {
234
235
  next: nextTasks.map((task) => task.name),
235
236
  metadata: saved.metadata,
236
237
  config: saved.config,
238
+ createdAt: saved.checkpoint.ts,
237
239
  parentConfig: saved.parentConfig,
238
240
  };
239
241
  }
@@ -476,7 +478,7 @@ export class Pregel extends Runnable {
476
478
  bg.push(this.checkpointer.put(checkpointConfig, checkpoint, {
477
479
  source: "loop",
478
480
  step,
479
- writes: single(streamMode === "values"
481
+ writes: single(this.streamMode === "values"
480
482
  ? mapOutputValues(outputKeys, pendingWrites, channels)
481
483
  : mapOutputUpdates(outputKeys, nextTasks)),
482
484
  }));
@@ -138,6 +138,6 @@ function single(iter) {
138
138
  for (const value of iter) {
139
139
  return value;
140
140
  }
141
- return undefined;
141
+ return null;
142
142
  }
143
143
  exports.single = single;
@@ -14,4 +14,4 @@ export declare function mapOutputValues<C extends PropertyKey>(outputChannels: C
14
14
  * Map pending writes (a sequence of tuples (channel, value)) to output chunk.
15
15
  */
16
16
  export declare function mapOutputUpdates<N extends PropertyKey, C extends PropertyKey>(outputChannels: C | Array<C>, tasks: readonly PregelExecutableTask<N, C>[]): Generator<Record<N, any | Record<string, any>>>;
17
- export declare function single<T>(iter: IterableIterator<T>): T | undefined;
17
+ export declare function single<T>(iter: IterableIterator<T>): T | null;
package/dist/pregel/io.js CHANGED
@@ -130,5 +130,5 @@ export function single(iter) {
130
130
  for (const value of iter) {
131
131
  return value;
132
132
  }
133
- return undefined;
133
+ return null;
134
134
  }
@@ -28,6 +28,10 @@ export interface StateSnapshot {
28
28
  * Metadata about the checkpoint
29
29
  */
30
30
  readonly metadata?: CheckpointMetadata;
31
+ /**
32
+ * Time when the snapshot was created
33
+ */
34
+ readonly createdAt?: string;
31
35
  /**
32
36
  * Config used to fetch the parent snapshot, if any
33
37
  * @default undefined
@@ -1,7 +1,7 @@
1
1
  import { describe, it } from "@jest/globals";
2
2
  import { ChatOpenAI } from "@langchain/openai";
3
3
  import { HumanMessage, ToolMessage, } from "@langchain/core/messages";
4
- import { Calculator } from "langchain/tools/calculator";
4
+ import { Calculator } from "@langchain/community/tools/calculator";
5
5
  import { convertToOpenAITool } from "@langchain/core/utils/function_calling";
6
6
  import { END, MessageGraph, START } from "../index.js";
7
7
  describe("Chatbot", () => {
@@ -69,7 +69,7 @@ describe("MemorySaver", () => {
69
69
  it("should save and retrieve checkpoints correctly", async () => {
70
70
  const memorySaver = new MemorySaver();
71
71
  // save checkpoint
72
- const runnableConfig = await memorySaver.put({ configurable: { thread_id: "1" } }, checkpoint1, { source: "update", step: -1 });
72
+ const runnableConfig = await memorySaver.put({ configurable: { thread_id: "1" } }, checkpoint1, { source: "update", step: -1, writes: null });
73
73
  expect(runnableConfig).toEqual({
74
74
  configurable: {
75
75
  thread_id: "1",
@@ -91,6 +91,7 @@ describe("MemorySaver", () => {
91
91
  await memorySaver.put({ configurable: { thread_id: "1" } }, checkpoint2, {
92
92
  source: "update",
93
93
  step: -1,
94
+ writes: null,
94
95
  });
95
96
  // list checkpoints
96
97
  const checkpointTupleGenerator = await memorySaver.list({
@@ -116,7 +117,7 @@ describe("SqliteSaver", () => {
116
117
  });
117
118
  expect(undefinedCheckpoint).toBeUndefined();
118
119
  // save first checkpoint
119
- const runnableConfig = await sqliteSaver.put({ configurable: { thread_id: "1" } }, checkpoint1, { source: "update", step: -1 });
120
+ const runnableConfig = await sqliteSaver.put({ configurable: { thread_id: "1" } }, checkpoint1, { source: "update", step: -1, writes: null });
120
121
  expect(runnableConfig).toEqual({
121
122
  configurable: {
122
123
  thread_id: "1",
@@ -141,7 +142,7 @@ describe("SqliteSaver", () => {
141
142
  thread_id: "1",
142
143
  checkpoint_id: "2024-04-18T17:19:07.952Z",
143
144
  },
144
- }, checkpoint2, { source: "update", step: -1 });
145
+ }, checkpoint2, { source: "update", step: -1, writes: null });
145
146
  // verify that parentTs is set and retrieved correctly for second checkpoint
146
147
  const secondCheckpointTuple = await sqliteSaver.getTuple({
147
148
  configurable: { thread_id: "1" },
@@ -120,7 +120,7 @@ describe("createReactAgent", () => {
120
120
  }
121
121
  }
122
122
  const tools = [new SanFranciscoWeatherTool()];
123
- const reactAgent = createReactAgent(model, tools);
123
+ const reactAgent = createReactAgent({ llm: model, tools });
124
124
  const response = await reactAgent.invoke({
125
125
  messages: [new HumanMessage("What's the weather like in SF?")],
126
126
  });
@@ -156,7 +156,7 @@ describe("createReactAgent", () => {
156
156
  }
157
157
  }
158
158
  const tools = [new SanFranciscoWeatherTool()];
159
- const reactAgent = createReactAgent(model, tools);
159
+ const reactAgent = createReactAgent({ llm: model, tools });
160
160
  const stream = await reactAgent.stream({
161
161
  messages: [new HumanMessage("What's the weather like in SF?")],
162
162
  }, { streamMode: "values" });
@@ -299,7 +299,11 @@ describe("createReactAgent", () => {
299
299
  new AIMessage("result2"),
300
300
  ],
301
301
  });
302
- const agent = createReactAgent(llm, tools, "You are a helpful assistant");
302
+ const agent = createReactAgent({
303
+ llm,
304
+ tools,
305
+ messageModifier: "You are a helpful assistant",
306
+ });
303
307
  const result = await agent.invoke({
304
308
  messages: [new HumanMessage("Hello Input!")],
305
309
  });
@@ -331,11 +335,14 @@ describe("createReactAgent", () => {
331
335
  new AIMessage("result2"),
332
336
  ],
333
337
  });
334
- const agent = createReactAgent(llm, tools, new SystemMessage("You are a helpful assistant"));
338
+ const agent = createReactAgent({
339
+ llm,
340
+ tools,
341
+ messageModifier: new SystemMessage("You are a helpful assistant"),
342
+ });
335
343
  const result = await agent.invoke({
336
344
  messages: [],
337
345
  });
338
- console.log("RESULT THING", result);
339
346
  expect(result.messages).toEqual([
340
347
  new AIMessage({
341
348
  content: "result1",
@@ -366,7 +373,37 @@ describe("createReactAgent", () => {
366
373
  new SystemMessage("You are a helpful assistant"),
367
374
  ...messages,
368
375
  ];
369
- const agent = createReactAgent(llm, tools, messageModifier);
376
+ const agent = createReactAgent({ llm, tools, messageModifier });
377
+ const result = await agent.invoke({
378
+ messages: [new HumanMessage("Hello Input!")],
379
+ });
380
+ expect(result.messages).toEqual([
381
+ new HumanMessage("Hello Input!"),
382
+ aiM1,
383
+ new ToolMessage({
384
+ name: "search_api",
385
+ content: "result for foo",
386
+ tool_call_id: "tool_abcd123",
387
+ }),
388
+ aiM2,
389
+ ]);
390
+ });
391
+ it("Can use async custom function message modifier", async () => {
392
+ const aiM1 = new AIMessage({
393
+ content: "result1",
394
+ tool_calls: [
395
+ { name: "search_api", id: "tool_abcd123", args: { query: "foo" } },
396
+ ],
397
+ });
398
+ const aiM2 = new AIMessage("result2");
399
+ const llm = new FakeToolCallingChatModel({
400
+ responses: [aiM1, aiM2],
401
+ });
402
+ const messageModifier = async (messages) => [
403
+ new SystemMessage("You are a helpful assistant"),
404
+ ...messages,
405
+ ];
406
+ const agent = createReactAgent({ llm, tools, messageModifier });
370
407
  const result = await agent.invoke({
371
408
  messages: [new HumanMessage("Hello Input!")],
372
409
  });
@@ -398,7 +435,7 @@ describe("createReactAgent", () => {
398
435
  ...messages,
399
436
  ],
400
437
  });
401
- const agent = createReactAgent(llm, tools, messageModifier);
438
+ const agent = createReactAgent({ llm, tools, messageModifier });
402
439
  const result = await agent.invoke({
403
440
  messages: [
404
441
  new HumanMessage("Hello Input!"),
@@ -1777,8 +1777,9 @@ it("StateGraph start branch then end", async () => {
1777
1777
  .addConditionalEdges({
1778
1778
  source: START,
1779
1779
  path: (state) => state.market === "DE" ? "tool_two_slow" : "tool_two_fast",
1780
- then: END,
1781
- });
1780
+ })
1781
+ .addEdge("tool_two_fast", END)
1782
+ .addEdge("tool_two_slow", END);
1782
1783
  const toolTwo = toolTwoBuilder.compile();
1783
1784
  expect(await toolTwo.invoke({ my_key: "value", market: "DE" })).toEqual({
1784
1785
  my_key: "value slow",
@@ -1793,71 +1794,45 @@ it("StateGraph start branch then end", async () => {
1793
1794
  interruptBefore: ["tool_two_fast", "tool_two_slow"],
1794
1795
  });
1795
1796
  await expect(() => toolTwoWithCheckpointer.invoke({ my_key: "value", market: "DE" })).rejects.toThrowError("thread_id");
1796
- // const thread1 = { configurable: { thread_id: "1" } }
1797
- // expect(toolTwoWithCheckpointer.invoke({ my_key: "value", market: "DE" }, thread1)).toEqual({ my_key: "value", market: "DE" })
1798
- // expect(toolTwoWithCheckpointer.getState(thread1)).toEqual({
1799
- // values: { my_key: "value", market: "DE" },
1800
- // next: ["tool_two_slow"],
1801
- // config: toolTwoWithCheckpointer.checkpointer.getTuple(thread1).config,
1802
- // metadata: { source: "loop", step: 0, writes: null },
1803
- // parentConfig: [...toolTwoWithCheckpointer.checkpointer.list(thread1, { limit: 2 })].pop().config
1804
- // })
1805
- // expect(toolTwoWithCheckpointer.invoke(null, thread1, { debug: 1 })).toEqual({ my_key: "value slow", market: "DE" })
1806
- // expect(toolTwoWithCheckpointer.getState(thread1)).toEqual({
1807
- // values: { my_key
1808
- // : "value slow", market: "DE" },
1809
- // next: [],
1810
- // config: (await toolTwoWithCheckpointer.checkpointer!.getTuple(thread1))!.config,
1811
- // metadata: { source: "loop", step: 1, writes: { tool_two_slow: { my_key: " slow" } } },
1812
- // parentConfig: [...toolTwoWithCheckpointer.checkpointer!.list(thread1, { limit: 2 })].pop().config
1813
- });
1814
- /**
1815
- * def test_branch_then_node(snapshot: SnapshotAssertion) -> None:
1816
- class State(TypedDict):
1817
- my_key: Annotated[str, operator.add]
1818
- market: str
1819
-
1820
- # this graph is invalid because there is no path to "finish"
1821
- invalid_graph = StateGraph(State)
1822
- invalid_graph.set_entry_point("prepare")
1823
- invalid_graph.set_finish_point("finish")
1824
- invalid_graph.add_conditional_edges(
1825
- source="prepare",
1826
- path=lambda s: "tool_two_slow" if s["market"] == "DE" else "tool_two_fast",
1827
- path_map=["tool_two_slow", "tool_two_fast"],
1828
- )
1829
- invalid_graph.add_node("prepare", lambda s: {"my_key": " prepared"})
1830
- invalid_graph.add_node("tool_two_slow", lambda s: {"my_key": " slow"})
1831
- invalid_graph.add_node("tool_two_fast", lambda s: {"my_key": " fast"})
1832
- invalid_graph.add_node("finish", lambda s: {"my_key": " finished"})
1833
- with pytest.raises(ValueError):
1834
- invalid_graph.compile()
1835
-
1836
- tool_two_graph = StateGraph(State)
1837
- tool_two_graph.set_entry_point("prepare")
1838
- tool_two_graph.set_finish_point("finish")
1839
- tool_two_graph.add_conditional_edges(
1840
- source="prepare",
1841
- path=lambda s: "tool_two_slow" if s["market"] == "DE" else "tool_two_fast",
1842
- then="finish",
1843
- )
1844
- tool_two_graph.add_node("prepare", lambda s: {"my_key": " prepared"})
1845
- tool_two_graph.add_node("tool_two_slow", lambda s: {"my_key": " slow"})
1846
- tool_two_graph.add_node("tool_two_fast", lambda s: {"my_key": " fast"})
1847
- tool_two_graph.add_node("finish", lambda s: {"my_key": " finished"})
1848
- tool_two = tool_two_graph.compile()
1849
- assert tool_two.get_graph().draw_mermaid(with_styles=False) == snapshot
1850
- assert tool_two.get_graph().draw_mermaid() == snapshot
1851
-
1852
- assert tool_two.invoke({"my_key": "value", "market": "DE"}, debug=1) == {
1853
- "my_key": "value prepared slow finished",
1854
- "market": "DE",
1855
- }
1856
- assert tool_two.invoke({"my_key": "value", "market": "US"}) == {
1857
- "my_key": "value prepared fast finished",
1858
- "market": "US",
1797
+ async function last(iter) {
1798
+ // eslint-disable-next-line no-undef-init
1799
+ let value = undefined;
1800
+ for await (value of iter) {
1801
+ // do nothing
1802
+ }
1803
+ return value;
1859
1804
  }
1860
- */
1805
+ const thread1 = { configurable: { thread_id: "1" } };
1806
+ expect(await toolTwoWithCheckpointer.invoke({ my_key: "value", market: "DE" }, thread1)).toEqual({ my_key: "value", market: "DE" });
1807
+ expect(await toolTwoWithCheckpointer.getState(thread1)).toEqual({
1808
+ values: { my_key: "value", market: "DE" },
1809
+ next: ["tool_two_slow"],
1810
+ config: (await toolTwoWithCheckpointer.checkpointer.getTuple(thread1))
1811
+ .config,
1812
+ createdAt: (await toolTwoWithCheckpointer.checkpointer.getTuple(thread1))
1813
+ .checkpoint.ts,
1814
+ metadata: { source: "loop", step: 0, writes: null },
1815
+ parentConfig: (await last(toolTwoWithCheckpointer.checkpointer.list(thread1, 2))).config,
1816
+ });
1817
+ expect(await toolTwoWithCheckpointer.invoke(null, thread1)).toEqual({
1818
+ my_key: "value slow",
1819
+ market: "DE",
1820
+ });
1821
+ expect(await toolTwoWithCheckpointer.getState(thread1)).toEqual({
1822
+ values: { my_key: "value slow", market: "DE" },
1823
+ next: [],
1824
+ config: (await toolTwoWithCheckpointer.checkpointer.getTuple(thread1))
1825
+ .config,
1826
+ createdAt: (await toolTwoWithCheckpointer.checkpointer.getTuple(thread1))
1827
+ .checkpoint.ts,
1828
+ metadata: {
1829
+ source: "loop",
1830
+ step: 1,
1831
+ writes: { tool_two_slow: { my_key: " slow" } },
1832
+ },
1833
+ parentConfig: (await last(toolTwoWithCheckpointer.checkpointer.list(thread1, 2))).config,
1834
+ });
1835
+ });
1861
1836
  it("StateGraph branch then node", async () => {
1862
1837
  const invalidBuilder = new StateGraph({
1863
1838
  channels: {
@@ -1891,8 +1866,9 @@ it("StateGraph branch then node", async () => {
1891
1866
  .addConditionalEdges({
1892
1867
  source: "prepare",
1893
1868
  path: (state) => state.market === "DE" ? "tool_two_slow" : "tool_two_fast",
1894
- then: "finish",
1895
1869
  })
1870
+ .addEdge("tool_two_fast", "finish")
1871
+ .addEdge("tool_two_slow", "finish")
1896
1872
  .addEdge("finish", END);
1897
1873
  const tool = toolBuilder.compile();
1898
1874
  expect(await tool.invoke({ my_key: "value", market: "DE" })).toEqual({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph",
3
- "version": "0.0.18",
3
+ "version": "0.0.20",
4
4
  "description": "LangGraph",
5
5
  "type": "module",
6
6
  "engines": {
@@ -37,13 +37,14 @@
37
37
  "author": "LangChain",
38
38
  "license": "MIT",
39
39
  "dependencies": {
40
- "@langchain/core": "^0.1.61",
40
+ "@langchain/core": ">0.1.61 <0.3.0",
41
41
  "uuid": "^9.0.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@jest/globals": "^29.5.0",
45
- "@langchain/community": "^0.0.43",
46
- "@langchain/openai": "latest",
45
+ "@langchain/anthropic": "^0.1.21",
46
+ "@langchain/community": "^0.2.2",
47
+ "@langchain/openai": "^0.0.33",
47
48
  "@langchain/scripts": "^0.0.13",
48
49
  "@swc/core": "^1.3.90",
49
50
  "@swc/jest": "^0.2.29",
@@ -63,7 +64,7 @@
63
64
  "eslint-plugin-prettier": "^4.2.1",
64
65
  "jest": "^29.5.0",
65
66
  "jest-environment-node": "^29.6.4",
66
- "langchain": "^0.1.29",
67
+ "langchain": "^0.2.1",
67
68
  "prettier": "^2.8.3",
68
69
  "release-it": "^15.10.1",
69
70
  "rollup": "^4.5.2",