From 5b4d6e70c80621c1a0e37d67f5f38124a4ddbb89 Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Thu, 20 Jun 2024 22:33:09 -0700 Subject: [PATCH 1/4] [Docs] Update ToolNode API Ref --- langgraph/src/prebuilt/tool_node.ts | 69 ++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/langgraph/src/prebuilt/tool_node.ts b/langgraph/src/prebuilt/tool_node.ts index eac3fb6f9..4e2e110b1 100644 --- a/langgraph/src/prebuilt/tool_node.ts +++ b/langgraph/src/prebuilt/tool_node.ts @@ -5,18 +5,56 @@ import { RunnableCallable } from "../utils.js"; import { END } from "../graph/graph.js"; import { MessagesState } from "../graph/message.js"; +/** + * ToolNode executes the provided functions when requested by an LLM as tool_calls. + * + * Key expectations: + * 1. Input: Expects either a BaseMessage[] or an object with a messages key containing a list of BaseMessages. + * The last message **must** be an AIMessage containing `tool_call`'s. + * 2. Tool Execution: Processes all tool calls found in the last AIMessage, executing them in parallel. + * 3. Output: Returns either an array of `ToolMessage`'s or an object with a messages key containing the `ToolMessage`'s, depending on the input type. + * 4. Error Handling: Throws errors for invalid inputs (non-AIMessage) or if a requested tool is not found. + * + * Typical usage: + * - Construct the ToolNode with the same list of tools (functions) provided to the LLM for tool calling. + * - Ensure the AI model is aware of and can request the tools available to the ToolNode (e.g., by calling .llm.bind_tools(tools)) + * - Route to the tool node only if the last message contains tool calls. + * + * @typeparam T - The type of input, either an array of `BaseMessage` or `MessagesState`. + * + * @example + * ```typescript + * const tools = [new TavilySearchResults({ maxResults: 1 })]; + * const toolNode = new ToolNode(tools); + * + * const workflow = new StateGraph({ channels: graphState }) + * .addNode("agent", callModel) // contains an LLM that will emit an AIMessage with tool_calls + * .addNode("tools", toolNode) + * .addConditionalEdges("agent", toolsCondition) + * .addEdge("tools", "agent"); // After tools are executed, return to the agent to summarize results. + * ``` + */ export class ToolNode< T extends BaseMessage[] | MessagesState > extends RunnableCallable { - /** - A node that runs the tools requested in the last AIMessage. It can be used - either in StateGraph with a "messages" key or in MessageGraph. If multiple - tool calls are requested, they will be run in parallel. The output will be - a list of ToolMessages, one for each tool call. - */ - + /** The array of tools available for execution. */ tools: StructuredTool[]; + /** + * Creates a new ToolNode instance. + * + * @param tools - An array of `StructuredTool` objects available for execution. + * @param name - The name of the node. Defaults to "tools". + * @param tags - An array of tags for the node. Defaults to an empty array. + * + * @example + * ```typescript + * import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; + * + * const tools = [new TavilySearchResults({ maxResults: 1 })]; + * const toolNode = new ToolNode(tools, "search_tools", ["search"]); + * ``` + */ constructor( tools: StructuredTool[], name: string = "tools", @@ -57,6 +95,23 @@ export class ToolNode< } } +/** + * Determines whether to route to the `tools` node or to end the graph execution. + * This function is typically used in conjunction with ToolNode to control the flow in a graph. + * + * @param state - Either an array of BaseMessage or a MessagesState object. + * @returns "tools" if there are tool calls in the last message, otherwise returns END. + * + * @example + * ```typescript + * const state = [new AIMessage({ + * content: "We need to search for information.", + * tool_calls: [{ name: "search", args: { query: "LangChain usage" }, id: "tc_1" }] + * })]; + * const result = toolsCondition(state); + * console.log(result); // "tools" + * ``` + */ export function toolsCondition( state: BaseMessage[] | MessagesState ): "tools" | typeof END { From ab2aa2e0dd62db0d320842ef1f7fd2a2e4a773aa Mon Sep 17 00:00:00 2001 From: William Fu-Hinthorn <13333726+hinthornw@users.noreply.github.com> Date: Thu, 20 Jun 2024 22:39:02 -0700 Subject: [PATCH 2/4] And for invoke --- langgraph/src/prebuilt/tool_node.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/langgraph/src/prebuilt/tool_node.ts b/langgraph/src/prebuilt/tool_node.ts index 4e2e110b1..b89f2f2f6 100644 --- a/langgraph/src/prebuilt/tool_node.ts +++ b/langgraph/src/prebuilt/tool_node.ts @@ -1,5 +1,5 @@ import { BaseMessage, ToolMessage, AIMessage } from "@langchain/core/messages"; -import { RunnableConfig } from "@langchain/core/runnables"; +import { Runnable, RunnableConfig } from "@langchain/core/runnables"; import { StructuredTool } from "@langchain/core/tools"; import { RunnableCallable } from "../utils.js"; import { END } from "../graph/graph.js"; @@ -93,6 +93,30 @@ export class ToolNode< return Array.isArray(input) ? outputs : { messages: outputs }; } + + /** + * @inheritdoc + * + * @remarks + * Eexecutes all requested tool calls found in the last AIMessage. + * It processes all tool calls in parallel and returns the results. + * + * @param input - Either an array of BaseMessage or a MessagesState object. Must contain an AIMessage with tool_calls. + * @param config - The RunnableConfig object containing the runtime configuration. + * @returns An array of ToolMessage objects or a MessagesState object containing the ToolMessage objects. + * + * @throws Error if the last message is not an AIMessage or if a requested tool is not found. + * + * @example + * ```typescript + * const result = await toolNode.invoke([new AIMessage({ + * content: "Search for the weather", + * tool_calls: [{ name: "tavily_search_results", args: { query: "weather in New York" } }] + * })]); + * console.log(result); // Array of ToolMessage objects or MessagesState + * ``` + */ + invoke: Runnable["invoke"]; } /** From 3d7c0bc63de4f846fc5955411276984aefa28165 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 30 Aug 2024 11:31:56 -0700 Subject: [PATCH 3/4] Update --- langgraph/src/prebuilt/tool_node.ts | 154 ----------------------- libs/langgraph/src/prebuilt/tool_node.ts | 51 +++++++- 2 files changed, 47 insertions(+), 158 deletions(-) delete mode 100644 langgraph/src/prebuilt/tool_node.ts diff --git a/langgraph/src/prebuilt/tool_node.ts b/langgraph/src/prebuilt/tool_node.ts deleted file mode 100644 index b89f2f2f6..000000000 --- a/langgraph/src/prebuilt/tool_node.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { BaseMessage, ToolMessage, AIMessage } from "@langchain/core/messages"; -import { Runnable, RunnableConfig } from "@langchain/core/runnables"; -import { StructuredTool } from "@langchain/core/tools"; -import { RunnableCallable } from "../utils.js"; -import { END } from "../graph/graph.js"; -import { MessagesState } from "../graph/message.js"; - -/** - * ToolNode executes the provided functions when requested by an LLM as tool_calls. - * - * Key expectations: - * 1. Input: Expects either a BaseMessage[] or an object with a messages key containing a list of BaseMessages. - * The last message **must** be an AIMessage containing `tool_call`'s. - * 2. Tool Execution: Processes all tool calls found in the last AIMessage, executing them in parallel. - * 3. Output: Returns either an array of `ToolMessage`'s or an object with a messages key containing the `ToolMessage`'s, depending on the input type. - * 4. Error Handling: Throws errors for invalid inputs (non-AIMessage) or if a requested tool is not found. - * - * Typical usage: - * - Construct the ToolNode with the same list of tools (functions) provided to the LLM for tool calling. - * - Ensure the AI model is aware of and can request the tools available to the ToolNode (e.g., by calling .llm.bind_tools(tools)) - * - Route to the tool node only if the last message contains tool calls. - * - * @typeparam T - The type of input, either an array of `BaseMessage` or `MessagesState`. - * - * @example - * ```typescript - * const tools = [new TavilySearchResults({ maxResults: 1 })]; - * const toolNode = new ToolNode(tools); - * - * const workflow = new StateGraph({ channels: graphState }) - * .addNode("agent", callModel) // contains an LLM that will emit an AIMessage with tool_calls - * .addNode("tools", toolNode) - * .addConditionalEdges("agent", toolsCondition) - * .addEdge("tools", "agent"); // After tools are executed, return to the agent to summarize results. - * ``` - */ -export class ToolNode< - T extends BaseMessage[] | MessagesState -> extends RunnableCallable { - /** The array of tools available for execution. */ - tools: StructuredTool[]; - - /** - * Creates a new ToolNode instance. - * - * @param tools - An array of `StructuredTool` objects available for execution. - * @param name - The name of the node. Defaults to "tools". - * @param tags - An array of tags for the node. Defaults to an empty array. - * - * @example - * ```typescript - * import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; - * - * const tools = [new TavilySearchResults({ maxResults: 1 })]; - * const toolNode = new ToolNode(tools, "search_tools", ["search"]); - * ``` - */ - constructor( - tools: StructuredTool[], - name: string = "tools", - tags: string[] = [] - ) { - super({ name, tags, func: (input, config) => this.run(input, config) }); - this.tools = tools; - } - - private async run( - input: BaseMessage[] | MessagesState, - config: RunnableConfig - ): Promise { - const message = Array.isArray(input) - ? input[input.length - 1] - : input.messages[input.messages.length - 1]; - - if (message._getType() !== "ai") { - throw new Error("ToolNode only accepts AIMessages as input."); - } - - const outputs = await Promise.all( - (message as AIMessage).tool_calls?.map(async (call) => { - const tool = this.tools.find((tool) => tool.name === call.name); - if (tool === undefined) { - throw new Error(`Tool ${call.name} not found.`); - } - const output = await tool.invoke(call.args, config); - return new ToolMessage({ - name: tool.name, - content: typeof output === "string" ? output : JSON.stringify(output), - tool_call_id: call.id!, - }); - }) ?? [] - ); - - return Array.isArray(input) ? outputs : { messages: outputs }; - } - - /** - * @inheritdoc - * - * @remarks - * Eexecutes all requested tool calls found in the last AIMessage. - * It processes all tool calls in parallel and returns the results. - * - * @param input - Either an array of BaseMessage or a MessagesState object. Must contain an AIMessage with tool_calls. - * @param config - The RunnableConfig object containing the runtime configuration. - * @returns An array of ToolMessage objects or a MessagesState object containing the ToolMessage objects. - * - * @throws Error if the last message is not an AIMessage or if a requested tool is not found. - * - * @example - * ```typescript - * const result = await toolNode.invoke([new AIMessage({ - * content: "Search for the weather", - * tool_calls: [{ name: "tavily_search_results", args: { query: "weather in New York" } }] - * })]); - * console.log(result); // Array of ToolMessage objects or MessagesState - * ``` - */ - invoke: Runnable["invoke"]; -} - -/** - * Determines whether to route to the `tools` node or to end the graph execution. - * This function is typically used in conjunction with ToolNode to control the flow in a graph. - * - * @param state - Either an array of BaseMessage or a MessagesState object. - * @returns "tools" if there are tool calls in the last message, otherwise returns END. - * - * @example - * ```typescript - * const state = [new AIMessage({ - * content: "We need to search for information.", - * tool_calls: [{ name: "search", args: { query: "LangChain usage" }, id: "tc_1" }] - * })]; - * const result = toolsCondition(state); - * console.log(result); // "tools" - * ``` - */ -export function toolsCondition( - state: BaseMessage[] | MessagesState -): "tools" | typeof END { - const message = Array.isArray(state) - ? state[state.length - 1] - : state.messages[state.messages.length - 1]; - - if ( - "tool_calls" in message && - ((message as AIMessage).tool_calls?.length ?? 0) > 0 - ) { - return "tools"; - } else { - return END; - } -} diff --git a/libs/langgraph/src/prebuilt/tool_node.ts b/libs/langgraph/src/prebuilt/tool_node.ts index 5b1f14809..dc6c4552b 100644 --- a/libs/langgraph/src/prebuilt/tool_node.ts +++ b/libs/langgraph/src/prebuilt/tool_node.ts @@ -17,13 +17,39 @@ export type ToolNodeOptions = { }; /** - * A node that runs the tools requested in the last AIMessage. It can be used - * either in StateGraph with a "messages" key or in MessageGraph. If multiple - * tool calls are requested, they will be run in parallel. The output will be - * a list of ToolMessages, one for each tool call. + * ToolNode executes the provided functions when requested by an LLM as tool_calls. + * + * Key expectations: + * 1. Input: Expects either a BaseMessage[] or an object with a messages key containing a list of BaseMessages. + * The last message **must** be an AIMessage containing `tool_call`'s. + * 2. Tool Execution: Processes all tool calls found in the last AIMessage, executing them in parallel. + * 3. Output: Returns either an array of `ToolMessage`'s or an object with a messages key containing the `ToolMessage`'s, depending on the input type. + * 4. Error Handling: Throws errors for invalid inputs (non-AIMessage) or if a requested tool is not found. + * + * Typical usage: + * - Construct the ToolNode with the same list of tools (functions) provided to the LLM for tool calling. + * - Ensure the AI model is aware of and can request the tools available to the ToolNode (e.g., by calling .llm.bind_tools(tools)) + * - Route to the tool node only if the last message contains tool calls. + * + * @typeparam T - The type of input, either an array of `BaseMessage` or `MessagesState`. + * + * @example + * ```typescript + * import { MessagesAnnotation } from "@langchain/langgraph"; + * + * const tools = [new TavilySearchResults({ maxResults: 1 })]; + * const toolNode = new ToolNode(tools); + * + * const workflow = new StateGraph(MessagesAnnotation) + * .addNode("agent", callModel) // contains an LLM that will emit an AIMessage with tool_calls + * .addNode("tools", toolNode) + * .addConditionalEdges("agent", toolsCondition) + * .addEdge("tools", "agent"); // After tools are executed, return to the agent to summarize results. + * ``` */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export class ToolNode extends RunnableCallable { + /** The array of tools available for execution. */ tools: (StructuredToolInterface | RunnableToolLike)[]; handleToolErrors = true; @@ -87,6 +113,23 @@ export class ToolNode extends RunnableCallable { } } +/** + * Determines whether to route to the `tools` node or to end the graph execution. + * This function is typically used in conjunction with ToolNode to control the flow in a graph. + * + * @param state - Either an array of BaseMessage or a MessagesState object. + * @returns "tools" if there are tool calls in the last message, otherwise returns END. + * + * @example + * ```typescript + * const state = [new AIMessage({ + * content: "We need to search for information.", + * tool_calls: [{ name: "search", args: { query: "LangChain usage" }, id: "tc_1" }] + * })]; + * const result = toolsCondition(state); + * console.log(result); // "tools" + * ``` + */ export function toolsCondition( state: BaseMessage[] | typeof MessagesAnnotation.State ): "tools" | typeof END { From fd26c5be05e83d535e8c6643c9304fc30c589253 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 30 Aug 2024 11:35:12 -0700 Subject: [PATCH 4/4] Copy --- libs/langgraph/src/prebuilt/tool_node.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libs/langgraph/src/prebuilt/tool_node.ts b/libs/langgraph/src/prebuilt/tool_node.ts index dc6c4552b..9e9dc8d79 100644 --- a/libs/langgraph/src/prebuilt/tool_node.ts +++ b/libs/langgraph/src/prebuilt/tool_node.ts @@ -17,10 +17,10 @@ export type ToolNodeOptions = { }; /** - * ToolNode executes the provided functions when requested by an LLM as tool_calls. + * The prebuilt ToolNode executes the provided functions when requested by an LLM as tool_calls. * * Key expectations: - * 1. Input: Expects either a BaseMessage[] or an object with a messages key containing a list of BaseMessages. + * 1. Input: Expects either a state object with a messages key containing a list of BaseMessages, or a list of messages directly. * The last message **must** be an AIMessage containing `tool_call`'s. * 2. Tool Execution: Processes all tool calls found in the last AIMessage, executing them in parallel. * 3. Output: Returns either an array of `ToolMessage`'s or an object with a messages key containing the `ToolMessage`'s, depending on the input type. @@ -31,17 +31,19 @@ export type ToolNodeOptions = { * - Ensure the AI model is aware of and can request the tools available to the ToolNode (e.g., by calling .llm.bind_tools(tools)) * - Route to the tool node only if the last message contains tool calls. * - * @typeparam T - The type of input, either an array of `BaseMessage` or `MessagesState`. + * @typeparam T - Optional: the type of input, either an array of `BaseMessage` or `MessagesState`. * * @example * ```typescript - * import { MessagesAnnotation } from "@langchain/langgraph"; + * import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt"; + * import { StateGraph, MessagesAnnotation } from "@langchain/langgraph"; + * import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; * * const tools = [new TavilySearchResults({ maxResults: 1 })]; * const toolNode = new ToolNode(tools); * * const workflow = new StateGraph(MessagesAnnotation) - * .addNode("agent", callModel) // contains an LLM that will emit an AIMessage with tool_calls + * .addNode("agent", callModel) // contains an LLM call that will emit an AIMessage with tool_calls * .addNode("tools", toolNode) * .addConditionalEdges("agent", toolsCondition) * .addEdge("tools", "agent"); // After tools are executed, return to the agent to summarize results.