@langchain/langgraph 0.0.27 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MessageGraph = exports.StateGraph = exports.Graph = exports.START = exports.END = void 0;
3
+ exports.messagesStateReducer = exports.MessageGraph = exports.StateGraph = exports.Graph = exports.START = exports.END = void 0;
4
4
  var graph_js_1 = require("./graph.cjs");
5
5
  Object.defineProperty(exports, "END", { enumerable: true, get: function () { return graph_js_1.END; } });
6
6
  Object.defineProperty(exports, "START", { enumerable: true, get: function () { return graph_js_1.START; } });
@@ -9,3 +9,4 @@ var state_js_1 = require("./state.cjs");
9
9
  Object.defineProperty(exports, "StateGraph", { enumerable: true, get: function () { return state_js_1.StateGraph; } });
10
10
  var message_js_1 = require("./message.cjs");
11
11
  Object.defineProperty(exports, "MessageGraph", { enumerable: true, get: function () { return message_js_1.MessageGraph; } });
12
+ Object.defineProperty(exports, "messagesStateReducer", { enumerable: true, get: function () { return message_js_1.messagesStateReducer; } });
@@ -1,3 +1,3 @@
1
1
  export { END, START, Graph } from "./graph.js";
2
2
  export { type StateGraphArgs, StateGraph, type CompiledStateGraph, } from "./state.js";
3
- export { MessageGraph } from "./message.js";
3
+ export { MessageGraph, messagesStateReducer } from "./message.js";
@@ -1,3 +1,3 @@
1
1
  export { END, START, Graph } from "./graph.js";
2
2
  export { StateGraph, } from "./state.js";
3
- export { MessageGraph } from "./message.js";
3
+ export { MessageGraph, messagesStateReducer } from "./message.js";
@@ -1,18 +1,56 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MessageGraph = void 0;
3
+ exports.MessageGraph = exports.messagesStateReducer = void 0;
4
+ const messages_1 = require("@langchain/core/messages");
5
+ const uuid_1 = require("uuid");
4
6
  const state_js_1 = require("./state.cjs");
5
- function addMessages(left, right) {
7
+ function messagesStateReducer(left, right) {
6
8
  const leftArray = Array.isArray(left) ? left : [left];
7
9
  const rightArray = Array.isArray(right) ? right : [right];
8
- return [...leftArray, ...rightArray];
10
+ // coerce to message
11
+ const leftMessages = leftArray.map(messages_1.coerceMessageLikeToMessage);
12
+ const rightMessages = rightArray.map(messages_1.coerceMessageLikeToMessage);
13
+ // assign missing ids
14
+ for (const m of leftMessages) {
15
+ if (m.id === null || m.id === undefined) {
16
+ m.id = (0, uuid_1.v4)();
17
+ }
18
+ }
19
+ for (const m of rightMessages) {
20
+ if (m.id === null || m.id === undefined) {
21
+ m.id = (0, uuid_1.v4)();
22
+ }
23
+ }
24
+ // merge
25
+ const leftIdxById = new Map(leftMessages.map((m, i) => [m.id, i]));
26
+ const merged = [...leftMessages];
27
+ const idsToRemove = new Set();
28
+ for (const m of rightMessages) {
29
+ const existingIdx = leftIdxById.get(m.id);
30
+ if (existingIdx !== undefined) {
31
+ if (m._getType() === "remove") {
32
+ idsToRemove.add(m.id);
33
+ }
34
+ else {
35
+ merged[existingIdx] = m;
36
+ }
37
+ }
38
+ else {
39
+ if (m._getType() === "remove") {
40
+ throw new Error(`Attempting to delete a message with an ID that doesn't exist ('${m.id}')`);
41
+ }
42
+ merged.push(m);
43
+ }
44
+ }
45
+ return merged.filter((m) => !idsToRemove.has(m.id));
9
46
  }
47
+ exports.messagesStateReducer = messagesStateReducer;
10
48
  class MessageGraph extends state_js_1.StateGraph {
11
49
  constructor() {
12
50
  super({
13
51
  channels: {
14
52
  __root__: {
15
- reducer: addMessages,
53
+ reducer: messagesStateReducer,
16
54
  default: () => [],
17
55
  },
18
56
  },
@@ -1,6 +1,7 @@
1
- import { BaseMessage } from "@langchain/core/messages";
1
+ import { BaseMessage, BaseMessageLike } from "@langchain/core/messages";
2
2
  import { StateGraph } from "./state.js";
3
- type Messages = Array<BaseMessage> | BaseMessage;
3
+ type Messages = Array<BaseMessage | BaseMessageLike> | BaseMessage | BaseMessageLike;
4
+ export declare function messagesStateReducer(left: Messages, right: Messages): BaseMessage[];
4
5
  export declare class MessageGraph extends StateGraph<BaseMessage[], Messages> {
5
6
  constructor();
6
7
  }
@@ -1,15 +1,52 @@
1
+ import { coerceMessageLikeToMessage, } from "@langchain/core/messages";
2
+ import { v4 } from "uuid";
1
3
  import { StateGraph } from "./state.js";
2
- function addMessages(left, right) {
4
+ export function messagesStateReducer(left, right) {
3
5
  const leftArray = Array.isArray(left) ? left : [left];
4
6
  const rightArray = Array.isArray(right) ? right : [right];
5
- return [...leftArray, ...rightArray];
7
+ // coerce to message
8
+ const leftMessages = leftArray.map(coerceMessageLikeToMessage);
9
+ const rightMessages = rightArray.map(coerceMessageLikeToMessage);
10
+ // assign missing ids
11
+ for (const m of leftMessages) {
12
+ if (m.id === null || m.id === undefined) {
13
+ m.id = v4();
14
+ }
15
+ }
16
+ for (const m of rightMessages) {
17
+ if (m.id === null || m.id === undefined) {
18
+ m.id = v4();
19
+ }
20
+ }
21
+ // merge
22
+ const leftIdxById = new Map(leftMessages.map((m, i) => [m.id, i]));
23
+ const merged = [...leftMessages];
24
+ const idsToRemove = new Set();
25
+ for (const m of rightMessages) {
26
+ const existingIdx = leftIdxById.get(m.id);
27
+ if (existingIdx !== undefined) {
28
+ if (m._getType() === "remove") {
29
+ idsToRemove.add(m.id);
30
+ }
31
+ else {
32
+ merged[existingIdx] = m;
33
+ }
34
+ }
35
+ else {
36
+ if (m._getType() === "remove") {
37
+ throw new Error(`Attempting to delete a message with an ID that doesn't exist ('${m.id}')`);
38
+ }
39
+ merged.push(m);
40
+ }
41
+ }
42
+ return merged.filter((m) => !idsToRemove.has(m.id));
6
43
  }
7
44
  export class MessageGraph extends StateGraph {
8
45
  constructor() {
9
46
  super({
10
47
  channels: {
11
48
  __root__: {
12
- reducer: addMessages,
49
+ reducer: messagesStateReducer,
13
50
  default: () => [],
14
51
  },
15
52
  },
@@ -1,5 +1,6 @@
1
- import { StructuredTool } from "@langchain/core/tools";
1
+ import { StructuredToolInterface } from "@langchain/core/tools";
2
2
  import { BaseMessage } from "@langchain/core/messages";
3
+ import { RunnableToolLike } from "@langchain/core/runnables";
3
4
  import { ToolExecutor } from "./tool_executor.js";
4
5
  import { CompiledStateGraph } from "../graph/state.js";
5
6
  import { START } from "../graph/index.js";
@@ -8,5 +9,5 @@ export type FunctionCallingExecutorState = {
8
9
  };
9
10
  export declare function createFunctionCallingExecutor<Model extends object>({ model, tools, }: {
10
11
  model: Model;
11
- tools: Array<StructuredTool> | ToolExecutor;
12
+ tools: Array<StructuredToolInterface | RunnableToolLike> | ToolExecutor;
12
13
  }): CompiledStateGraph<FunctionCallingExecutorState, Partial<FunctionCallingExecutorState>, typeof START | "agent" | "action">;
@@ -1,6 +1,6 @@
1
1
  import { convertToOpenAIFunction } from "@langchain/core/utils/function_calling";
2
2
  import { FunctionMessage } from "@langchain/core/messages";
3
- import { RunnableLambda } from "@langchain/core/runnables";
3
+ import { RunnableLambda, } from "@langchain/core/runnables";
4
4
  import { ToolExecutor } from "./tool_executor.js";
5
5
  import { StateGraph, } from "../graph/state.js";
6
6
  import { END, START } from "../graph/index.js";
@@ -1,7 +1,7 @@
1
1
  import { BaseChatModel } from "@langchain/core/language_models/chat_models";
2
2
  import { BaseMessage, SystemMessage } from "@langchain/core/messages";
3
- import { Runnable } from "@langchain/core/runnables";
4
- import { StructuredTool } from "@langchain/core/tools";
3
+ import { Runnable, RunnableToolLike } from "@langchain/core/runnables";
4
+ import { StructuredToolInterface } from "@langchain/core/tools";
5
5
  import { BaseCheckpointSaver } from "../checkpoint/base.js";
6
6
  import { START } from "../graph/index.js";
7
7
  import { MessagesState } from "../graph/message.js";
@@ -14,7 +14,7 @@ export interface AgentState {
14
14
  export type N = typeof START | "agent" | "tools";
15
15
  export type CreateReactAgentParams = {
16
16
  llm: BaseChatModel;
17
- tools: ToolNode<MessagesState> | StructuredTool[];
17
+ tools: ToolNode<MessagesState> | (StructuredToolInterface | RunnableToolLike)[];
18
18
  messageModifier?: SystemMessage | string | ((messages: BaseMessage[]) => BaseMessage[]) | ((messages: BaseMessage[]) => Promise<BaseMessage[]>) | Runnable;
19
19
  checkpointSaver?: BaseCheckpointSaver;
20
20
  interruptBefore?: N[] | All;
@@ -1,7 +1,7 @@
1
- import { RunnableBinding, RunnableConfig } from "@langchain/core/runnables";
2
- import { StructuredTool } from "@langchain/core/tools";
1
+ import { RunnableBinding, RunnableConfig, RunnableToolLike } from "@langchain/core/runnables";
2
+ import { StructuredToolInterface } from "@langchain/core/tools";
3
3
  export interface ToolExecutorArgs {
4
- tools: Array<StructuredTool>;
4
+ tools: Array<StructuredToolInterface | RunnableToolLike>;
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<StructuredTool>;
22
- toolMap: Record<string, StructuredTool>;
21
+ tools: Array<StructuredToolInterface | RunnableToolLike>;
22
+ toolMap: Record<string, StructuredToolInterface | RunnableToolLike>;
23
23
  invalidToolMsgTemplate: string;
24
24
  constructor(fields: ToolExecutorArgs);
25
25
  /**
@@ -5,7 +5,8 @@ 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
  class ToolNode extends utils_js_1.RunnableCallable {
8
- constructor(tools, name = "tools", tags = []) {
8
+ constructor(tools, options) {
9
+ const { name, tags, handleToolErrors } = options ?? {};
9
10
  super({ name, tags, func: (input, config) => this.run(input, config) });
10
11
  /**
11
12
  A node that runs the tools requested in the last AIMessage. It can be used
@@ -19,7 +20,14 @@ class ToolNode extends utils_js_1.RunnableCallable {
19
20
  writable: true,
20
21
  value: void 0
21
22
  });
23
+ Object.defineProperty(this, "handleToolErrors", {
24
+ enumerable: true,
25
+ configurable: true,
26
+ writable: true,
27
+ value: true
28
+ });
22
29
  this.tools = tools;
30
+ this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
23
31
  }
24
32
  async run(input, config) {
25
33
  const message = Array.isArray(input)
@@ -30,18 +38,31 @@ class ToolNode extends utils_js_1.RunnableCallable {
30
38
  }
31
39
  const outputs = await Promise.all(message.tool_calls?.map(async (call) => {
32
40
  const tool = this.tools.find((tool) => tool.name === call.name);
33
- if (tool === undefined) {
34
- throw new Error(`Tool ${call.name} not found.`);
35
- }
36
- const output = await tool.invoke({ ...call, type: "tool_call" }, config);
37
- if ((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") {
38
- return output;
41
+ try {
42
+ if (tool === undefined) {
43
+ throw new Error(`Tool "${call.name}" not found.`);
44
+ }
45
+ const output = await tool.invoke({ ...call, type: "tool_call" }, config);
46
+ if ((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") {
47
+ return output;
48
+ }
49
+ else {
50
+ return new messages_1.ToolMessage({
51
+ name: tool.name,
52
+ content: typeof output === "string" ? output : JSON.stringify(output),
53
+ tool_call_id: call.id,
54
+ });
55
+ }
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
57
  }
40
- else {
58
+ catch (e) {
59
+ if (!this.handleToolErrors) {
60
+ throw e;
61
+ }
41
62
  return new messages_1.ToolMessage({
42
- name: tool.name,
43
- content: typeof output === "string" ? output : JSON.stringify(output),
44
- tool_call_id: call.id,
63
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
64
+ name: call.name,
65
+ tool_call_id: call.id ?? "",
45
66
  });
46
67
  }
47
68
  }) ?? []);
@@ -1,8 +1,14 @@
1
1
  import { BaseMessage } from "@langchain/core/messages";
2
- import { StructuredTool } from "@langchain/core/tools";
2
+ import { RunnableToolLike } from "@langchain/core/runnables";
3
+ import { StructuredToolInterface } from "@langchain/core/tools";
3
4
  import { RunnableCallable } from "../utils.js";
4
5
  import { END } from "../graph/graph.js";
5
6
  import { MessagesState } from "../graph/message.js";
7
+ export type ToolNodeOptions = {
8
+ name?: string;
9
+ tags?: string[];
10
+ handleToolErrors?: boolean;
11
+ };
6
12
  export declare class ToolNode<T extends BaseMessage[] | MessagesState> extends RunnableCallable<T, T> {
7
13
  /**
8
14
  A node that runs the tools requested in the last AIMessage. It can be used
@@ -10,8 +16,9 @@ export declare class ToolNode<T extends BaseMessage[] | MessagesState> extends R
10
16
  tool calls are requested, they will be run in parallel. The output will be
11
17
  a list of ToolMessages, one for each tool call.
12
18
  */
13
- tools: StructuredTool[];
14
- constructor(tools: StructuredTool[], name?: string, tags?: string[]);
19
+ tools: (StructuredToolInterface | RunnableToolLike)[];
20
+ handleToolErrors: boolean;
21
+ constructor(tools: (StructuredToolInterface | RunnableToolLike)[], options?: ToolNodeOptions);
15
22
  private run;
16
23
  }
17
24
  export declare function toolsCondition(state: BaseMessage[] | MessagesState): "tools" | typeof END;
@@ -2,7 +2,8 @@ import { ToolMessage, isBaseMessage, } from "@langchain/core/messages";
2
2
  import { RunnableCallable } from "../utils.js";
3
3
  import { END } from "../graph/graph.js";
4
4
  export class ToolNode extends RunnableCallable {
5
- constructor(tools, name = "tools", tags = []) {
5
+ constructor(tools, options) {
6
+ const { name, tags, handleToolErrors } = options ?? {};
6
7
  super({ name, tags, func: (input, config) => this.run(input, config) });
7
8
  /**
8
9
  A node that runs the tools requested in the last AIMessage. It can be used
@@ -16,7 +17,14 @@ export class ToolNode extends RunnableCallable {
16
17
  writable: true,
17
18
  value: void 0
18
19
  });
20
+ Object.defineProperty(this, "handleToolErrors", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: true
25
+ });
19
26
  this.tools = tools;
27
+ this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
20
28
  }
21
29
  async run(input, config) {
22
30
  const message = Array.isArray(input)
@@ -27,18 +35,31 @@ export class ToolNode extends RunnableCallable {
27
35
  }
28
36
  const outputs = await Promise.all(message.tool_calls?.map(async (call) => {
29
37
  const tool = this.tools.find((tool) => tool.name === call.name);
30
- if (tool === undefined) {
31
- throw new Error(`Tool ${call.name} not found.`);
32
- }
33
- const output = await tool.invoke({ ...call, type: "tool_call" }, config);
34
- if (isBaseMessage(output) && output._getType() === "tool") {
35
- return output;
38
+ try {
39
+ if (tool === undefined) {
40
+ throw new Error(`Tool "${call.name}" not found.`);
41
+ }
42
+ const output = await tool.invoke({ ...call, type: "tool_call" }, config);
43
+ if (isBaseMessage(output) && output._getType() === "tool") {
44
+ return output;
45
+ }
46
+ else {
47
+ return new ToolMessage({
48
+ name: tool.name,
49
+ content: typeof output === "string" ? output : JSON.stringify(output),
50
+ tool_call_id: call.id,
51
+ });
52
+ }
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
54
  }
37
- else {
55
+ catch (e) {
56
+ if (!this.handleToolErrors) {
57
+ throw e;
58
+ }
38
59
  return new ToolMessage({
39
- name: tool.name,
40
- content: typeof output === "string" ? output : JSON.stringify(output),
41
- tool_call_id: call.id,
60
+ content: `Error: ${e.message}\n Please fix your mistakes.`,
61
+ name: call.name,
62
+ tool_call_id: call.id ?? "",
42
63
  });
43
64
  }
44
65
  }) ?? []);
@@ -3,6 +3,8 @@ import { it, beforeAll, describe, expect } from "@jest/globals";
3
3
  import { Tool } from "@langchain/core/tools";
4
4
  import { ChatOpenAI } from "@langchain/openai";
5
5
  import { HumanMessage } from "@langchain/core/messages";
6
+ import { RunnableLambda } from "@langchain/core/runnables";
7
+ import { z } from "zod";
6
8
  import { createReactAgent, createFunctionCallingExecutor, } from "../prebuilt/index.js";
7
9
  import { initializeAsyncLocalStorageSingleton } from "../setup/async_local_storage.js";
8
10
  // Tracing slows down the tests
@@ -97,6 +99,32 @@ describe("createFunctionCallingExecutor", () => {
97
99
  const functionCall = endState.messages.find((message) => message._getType() === "function");
98
100
  expect(functionCall.content).toBe(weatherResponse);
99
101
  });
102
+ it("can accept RunnableToolLike tools", async () => {
103
+ const weatherResponse = `Not too cold, not too hot 😎`;
104
+ const model = new ChatOpenAI();
105
+ const sfWeatherTool = RunnableLambda.from(async (_) => weatherResponse);
106
+ const tools = [
107
+ sfWeatherTool.asTool({
108
+ name: "current_weather",
109
+ description: "Get the current weather report for San Francisco, CA",
110
+ schema: z.object({
111
+ location: z.string(),
112
+ }),
113
+ }),
114
+ ];
115
+ const functionsAgentExecutor = createFunctionCallingExecutor({
116
+ model,
117
+ tools,
118
+ });
119
+ const response = await functionsAgentExecutor.invoke({
120
+ messages: [new HumanMessage("What's the weather like in SF?")],
121
+ });
122
+ // It needs at least one human message, one AI and one function message.
123
+ expect(response.messages.length > 3).toBe(true);
124
+ const firstFunctionMessage = response.messages.find((message) => message._getType() === "function");
125
+ expect(firstFunctionMessage).toBeDefined();
126
+ expect(firstFunctionMessage?.content).toBe(weatherResponse);
127
+ });
100
128
  });
101
129
  describe("createReactAgent", () => {
102
130
  it("can call a tool", async () => {
@@ -5,8 +5,9 @@ import { StructuredTool, Tool } from "@langchain/core/tools";
5
5
  import { FakeStreamingLLM } from "@langchain/core/utils/testing";
6
6
  import { AIMessage, HumanMessage, SystemMessage, ToolMessage, } from "@langchain/core/messages";
7
7
  import { z } from "zod";
8
+ import { RunnableLambda } from "@langchain/core/runnables";
8
9
  import { FakeToolCallingChatModel } from "./utils.js";
9
- import { createAgentExecutor, createReactAgent } from "../prebuilt/index.js";
10
+ import { ToolNode, createAgentExecutor, createReactAgent, } from "../prebuilt/index.js";
10
11
  // Tracing slows down the tests
11
12
  beforeAll(() => {
12
13
  process.env.LANGCHAIN_TRACING_V2 = "false";
@@ -15,6 +16,67 @@ beforeAll(() => {
15
16
  process.env.LANGCHAIN_API_KEY = "";
16
17
  process.env.LANGCHAIN_PROJECT = "";
17
18
  });
19
+ const searchSchema = z.object({
20
+ query: z.string().describe("The query to search for."),
21
+ });
22
+ class SearchAPI extends StructuredTool {
23
+ constructor() {
24
+ super(...arguments);
25
+ Object.defineProperty(this, "name", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: "search_api"
30
+ });
31
+ Object.defineProperty(this, "description", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: "A simple API that returns the input string."
36
+ });
37
+ Object.defineProperty(this, "schema", {
38
+ enumerable: true,
39
+ configurable: true,
40
+ writable: true,
41
+ value: searchSchema
42
+ });
43
+ }
44
+ async _call(input) {
45
+ return `result for ${input?.query}`;
46
+ }
47
+ }
48
+ class SearchAPIWithArtifact extends StructuredTool {
49
+ constructor() {
50
+ super(...arguments);
51
+ Object.defineProperty(this, "name", {
52
+ enumerable: true,
53
+ configurable: true,
54
+ writable: true,
55
+ value: "search_api"
56
+ });
57
+ Object.defineProperty(this, "description", {
58
+ enumerable: true,
59
+ configurable: true,
60
+ writable: true,
61
+ value: "A simple API that returns the input string."
62
+ });
63
+ Object.defineProperty(this, "schema", {
64
+ enumerable: true,
65
+ configurable: true,
66
+ writable: true,
67
+ value: searchSchema
68
+ });
69
+ Object.defineProperty(this, "responseFormat", {
70
+ enumerable: true,
71
+ configurable: true,
72
+ writable: true,
73
+ value: "content_and_artifact"
74
+ });
75
+ }
76
+ async _call(_) {
77
+ return ["some response format", Buffer.from("123")];
78
+ }
79
+ }
18
80
  describe("PreBuilt", () => {
19
81
  class SearchAPI extends Tool {
20
82
  constructor() {
@@ -197,67 +259,6 @@ describe("PreBuilt", () => {
197
259
  });
198
260
  });
199
261
  describe("createReactAgent", () => {
200
- const searchSchema = z.object({
201
- query: z.string().describe("The query to search for."),
202
- });
203
- class SearchAPI extends StructuredTool {
204
- constructor() {
205
- super(...arguments);
206
- Object.defineProperty(this, "name", {
207
- enumerable: true,
208
- configurable: true,
209
- writable: true,
210
- value: "search_api"
211
- });
212
- Object.defineProperty(this, "description", {
213
- enumerable: true,
214
- configurable: true,
215
- writable: true,
216
- value: "A simple API that returns the input string."
217
- });
218
- Object.defineProperty(this, "schema", {
219
- enumerable: true,
220
- configurable: true,
221
- writable: true,
222
- value: searchSchema
223
- });
224
- }
225
- async _call(input) {
226
- return `result for ${input?.query}`;
227
- }
228
- }
229
- class SearchAPIWithArtifact extends StructuredTool {
230
- constructor() {
231
- super(...arguments);
232
- Object.defineProperty(this, "name", {
233
- enumerable: true,
234
- configurable: true,
235
- writable: true,
236
- value: "search_api"
237
- });
238
- Object.defineProperty(this, "description", {
239
- enumerable: true,
240
- configurable: true,
241
- writable: true,
242
- value: "A simple API that returns the input string."
243
- });
244
- Object.defineProperty(this, "schema", {
245
- enumerable: true,
246
- configurable: true,
247
- writable: true,
248
- value: searchSchema
249
- });
250
- Object.defineProperty(this, "responseFormat", {
251
- enumerable: true,
252
- configurable: true,
253
- writable: true,
254
- value: "content_and_artifact"
255
- });
256
- }
257
- async _call(_) {
258
- return ["some response format", Buffer.from("123")];
259
- }
260
- }
261
262
  const tools = [new SearchAPI()];
262
263
  it("Can use string message modifier", async () => {
263
264
  const llm = new FakeToolCallingChatModel({
@@ -367,4 +368,60 @@ describe("createReactAgent", () => {
367
368
  new AIMessage("result2"),
368
369
  ]);
369
370
  });
371
+ it("Can accept RunnableToolLike", async () => {
372
+ const llm = new FakeToolCallingChatModel({
373
+ responses: [
374
+ new AIMessage({
375
+ content: "result1",
376
+ tool_calls: [
377
+ { name: "search_api", id: "tool_abcd123", args: { query: "foo" } },
378
+ ],
379
+ }),
380
+ new AIMessage("result2"),
381
+ ],
382
+ });
383
+ // Instead of re-implementing the tool, wrap it in a RunnableLambda and
384
+ // call `asTool` to create a RunnableToolLike.
385
+ const searchApiTool = new SearchAPI();
386
+ const runnableToolLikeTool = RunnableLambda.from(async (input, config) => searchApiTool.invoke(input, config)).asTool({
387
+ name: searchApiTool.name,
388
+ description: searchApiTool.description,
389
+ schema: searchApiTool.schema,
390
+ });
391
+ const agent = createReactAgent({
392
+ llm,
393
+ tools: [runnableToolLikeTool],
394
+ messageModifier: "You are a helpful assistant",
395
+ });
396
+ const result = await agent.invoke({
397
+ messages: [new HumanMessage("Hello Input!")],
398
+ });
399
+ expect(result.messages).toEqual([
400
+ new HumanMessage("Hello Input!"),
401
+ new AIMessage({
402
+ content: "result1",
403
+ tool_calls: [
404
+ { name: "search_api", id: "tool_abcd123", args: { query: "foo" } },
405
+ ],
406
+ }),
407
+ new ToolMessage({
408
+ name: "search_api",
409
+ content: "result for foo",
410
+ tool_call_id: "tool_abcd123",
411
+ }),
412
+ new AIMessage("result2"),
413
+ ]);
414
+ });
415
+ });
416
+ describe("ToolNode", () => {
417
+ it("Should support graceful error handling", async () => {
418
+ const toolNode = new ToolNode([new SearchAPI()]);
419
+ const res = await toolNode.invoke([
420
+ new AIMessage({
421
+ content: "",
422
+ tool_calls: [{ name: "badtool", args: {}, id: "testid" }],
423
+ }),
424
+ ]);
425
+ expect(res[0].content).toEqual(`Error: Tool "badtool" not found.\n Please fix your mistakes.`);
426
+ });
370
427
  });
@@ -1652,7 +1652,7 @@ describe("MessageGraph", () => {
1652
1652
  .compile();
1653
1653
  const result = await app.invoke(new HumanMessage("what is the weather in sf?"));
1654
1654
  expect(result).toHaveLength(6);
1655
- expect(result).toStrictEqual([
1655
+ expect(JSON.stringify(result)).toEqual(JSON.stringify([
1656
1656
  new HumanMessage("what is the weather in sf?"),
1657
1657
  new AIMessage({
1658
1658
  content: "",
@@ -1681,7 +1681,7 @@ describe("MessageGraph", () => {
1681
1681
  name: "search_api",
1682
1682
  }),
1683
1683
  new AIMessage("answer"),
1684
- ]);
1684
+ ]));
1685
1685
  });
1686
1686
  it("can stream a list of messages", async () => {
1687
1687
  const model = new FakeChatModel({
@@ -1752,7 +1752,7 @@ describe("MessageGraph", () => {
1752
1752
  }
1753
1753
  const lastItem = streamItems[streamItems.length - 1];
1754
1754
  expect(Object.keys(lastItem)).toEqual(["agent"]);
1755
- expect(Object.values(lastItem)[0]).toEqual(new AIMessage("answer"));
1755
+ expect(JSON.stringify(Object.values(lastItem)[0])).toEqual(JSON.stringify(new AIMessage("answer")));
1756
1756
  });
1757
1757
  });
1758
1758
  it("StateGraph start branch then end", async () => {
package/dist/web.cjs CHANGED
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EmptyChannelError = exports.InvalidUpdateError = exports.GraphValueError = exports.GraphRecursionError = exports.BaseCheckpointSaver = exports.emptyCheckpoint = exports.copyCheckpoint = exports.MemorySaver = exports.MessageGraph = exports.StateGraph = exports.START = exports.Graph = exports.END = void 0;
3
+ exports.EmptyChannelError = exports.InvalidUpdateError = exports.GraphValueError = exports.GraphRecursionError = exports.BaseCheckpointSaver = exports.emptyCheckpoint = exports.copyCheckpoint = exports.MemorySaver = exports.messagesStateReducer = exports.MessageGraph = exports.StateGraph = exports.START = exports.Graph = exports.END = void 0;
4
4
  var index_js_1 = require("./graph/index.cjs");
5
5
  Object.defineProperty(exports, "END", { enumerable: true, get: function () { return index_js_1.END; } });
6
6
  Object.defineProperty(exports, "Graph", { enumerable: true, get: function () { return index_js_1.Graph; } });
7
7
  Object.defineProperty(exports, "START", { enumerable: true, get: function () { return index_js_1.START; } });
8
8
  Object.defineProperty(exports, "StateGraph", { enumerable: true, get: function () { return index_js_1.StateGraph; } });
9
9
  Object.defineProperty(exports, "MessageGraph", { enumerable: true, get: function () { return index_js_1.MessageGraph; } });
10
+ Object.defineProperty(exports, "messagesStateReducer", { enumerable: true, get: function () { return index_js_1.messagesStateReducer; } });
10
11
  var memory_js_1 = require("./checkpoint/memory.cjs");
11
12
  Object.defineProperty(exports, "MemorySaver", { enumerable: true, get: function () { return memory_js_1.MemorySaver; } });
12
13
  var base_js_1 = require("./checkpoint/base.cjs");
package/dist/web.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { END, Graph, type StateGraphArgs, START, StateGraph, type CompiledStateGraph, MessageGraph, } from "./graph/index.js";
1
+ export { END, Graph, type StateGraphArgs, START, StateGraph, type CompiledStateGraph, MessageGraph, messagesStateReducer, } from "./graph/index.js";
2
2
  export { MemorySaver } from "./checkpoint/memory.js";
3
3
  export { type Checkpoint, type CheckpointMetadata, type CheckpointTuple, copyCheckpoint, emptyCheckpoint, BaseCheckpointSaver, } from "./checkpoint/base.js";
4
4
  export { GraphRecursionError, GraphValueError, InvalidUpdateError, EmptyChannelError, } from "./errors.js";
package/dist/web.js CHANGED
@@ -1,4 +1,4 @@
1
- export { END, Graph, START, StateGraph, MessageGraph, } from "./graph/index.js";
1
+ export { END, Graph, START, StateGraph, MessageGraph, messagesStateReducer, } from "./graph/index.js";
2
2
  export { MemorySaver } from "./checkpoint/memory.js";
3
3
  export { copyCheckpoint, emptyCheckpoint, BaseCheckpointSaver, } from "./checkpoint/base.js";
4
4
  export { GraphRecursionError, GraphValueError, InvalidUpdateError, EmptyChannelError, } from "./errors.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/langgraph",
3
- "version": "0.0.27",
3
+ "version": "0.0.29",
4
4
  "description": "LangGraph",
5
5
  "type": "module",
6
6
  "engines": {
@@ -43,9 +43,9 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@jest/globals": "^29.5.0",
46
- "@langchain/anthropic": "^0.2.3",
47
- "@langchain/community": "^0.2.12",
48
- "@langchain/openai": "^0.1.3",
46
+ "@langchain/anthropic": "^0.2.6",
47
+ "@langchain/community": "^0.2.19",
48
+ "@langchain/openai": "^0.2.4",
49
49
  "@langchain/scripts": "^0.0.13",
50
50
  "@swc/core": "^1.3.90",
51
51
  "@swc/jest": "^0.2.29",
@@ -67,7 +67,7 @@
67
67
  "eslint-plugin-prettier": "^4.2.1",
68
68
  "jest": "^29.5.0",
69
69
  "jest-environment-node": "^29.6.4",
70
- "langchain": "^0.2.1",
70
+ "langchain": "^0.2.10",
71
71
  "prettier": "^2.8.3",
72
72
  "release-it": "^15.10.1",
73
73
  "rollup": "^4.5.2",