UNPKG

langchain

Version:
1 lines 19.4 kB
{"version":3,"file":"ToolNode.cjs","names":["input: unknown","BaseMessage","error: unknown","toolCall: ToolCall","ToolInvocationError","ToolMessage","RunnableCallable","tools: (StructuredToolInterface | DynamicTool | RunnableToolLike)[]","options?: ToolNodeOptions","call: ToolCall","isMiddlewareError: boolean","config: RunnableConfig","state: AgentBuiltInState","request: ToolCallRequest","request","tool","mergeAbortSignals","e: unknown","ToolInputParsingException","#handleError","state: ToAnnotationRoot<StateSchema>[\"State\"] & AgentBuiltInState","outputs: (ToolMessage | Command)[]","messages: BaseMessage[]","toolMessageIds: Set<string>","aiMessage: AIMessage | undefined","AIMessage","isCommand","combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[]","parentCommand: Command | null","Command","x: unknown","Send"],"sources":["../../../src/agents/nodes/ToolNode.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n/* eslint-disable no-instanceof/no-instanceof */\nimport { BaseMessage, ToolMessage, AIMessage } from \"@langchain/core/messages\";\nimport { RunnableConfig, RunnableToolLike } from \"@langchain/core/runnables\";\nimport {\n DynamicTool,\n StructuredToolInterface,\n ToolInputParsingException,\n} from \"@langchain/core/tools\";\nimport type { ToolCall } from \"@langchain/core/messages/tool\";\nimport type { InteropZodObject } from \"@langchain/core/utils/types\";\nimport {\n isCommand,\n Command,\n Send,\n isGraphInterrupt,\n type LangGraphRunnableConfig,\n} from \"@langchain/langgraph\";\n\nimport { RunnableCallable } from \"../RunnableCallable.js\";\nimport { mergeAbortSignals } from \"./utils.js\";\nimport { ToolInvocationError } from \"../errors.js\";\nimport type {\n WrapToolCallHook,\n ToolCallRequest,\n ToAnnotationRoot,\n} from \"../middleware/types.js\";\nimport type { AgentBuiltInState } from \"../runtime.js\";\n\n/**\n * The name of the tool node in the state graph.\n */\nexport const TOOLS_NODE_NAME = \"tools\";\n\nexport interface ToolNodeOptions {\n /**\n * The name of the tool node.\n */\n name?: string;\n /**\n * The tags to add to the tool call.\n */\n tags?: string[];\n /**\n * The abort signal to cancel the tool call.\n */\n signal?: AbortSignal;\n /**\n * Whether to throw the error immediately if the tool fails or handle it by the `onToolError` function or via ToolMessage.\n *\n * **Default behavior** (matches Python):\n * - Catches only `ToolInvocationError` (invalid arguments from model) and converts to ToolMessage\n * - Re-raises all other errors including errors from `wrapToolCall` middleware\n *\n * If `true`:\n * - Catches all errors and returns a ToolMessage with the error\n *\n * If `false`:\n * - All errors are thrown immediately\n *\n * If a function is provided:\n * - If function returns a `ToolMessage`, use it as the result\n * - If function returns `undefined`, re-raise the error\n *\n * @default A function that only catches ToolInvocationError\n */\n handleToolErrors?:\n | boolean\n | ((error: unknown, toolCall: ToolCall) => ToolMessage | undefined);\n /**\n * Optional wrapper function for tool execution.\n * Allows middleware to intercept and modify tool calls before execution.\n * The wrapper receives the tool call request and a handler function to execute the tool.\n */\n wrapToolCall?: WrapToolCallHook;\n}\n\nconst isBaseMessageArray = (input: unknown): input is BaseMessage[] =>\n Array.isArray(input) && input.every(BaseMessage.isInstance);\n\nconst isMessagesState = (\n input: unknown\n): input is { messages: BaseMessage[] } =>\n typeof input === \"object\" &&\n input != null &&\n \"messages\" in input &&\n isBaseMessageArray(input.messages);\n\nconst isSendInput = (input: unknown): input is { lg_tool_call: ToolCall } =>\n typeof input === \"object\" && input != null && \"lg_tool_call\" in input;\n\n/**\n * Default error handler for tool errors.\n *\n * This is applied to errors from baseHandler (tool execution).\n * For errors from wrapToolCall middleware, those are handled separately\n * and will bubble up by default.\n *\n * Catches all tool execution errors and converts them to ToolMessage.\n * This allows the LLM to see the error and potentially retry with different arguments.\n */\nfunction defaultHandleToolErrors(\n error: unknown,\n toolCall: ToolCall\n): ToolMessage | undefined {\n if (error instanceof ToolInvocationError) {\n return new ToolMessage({\n content: error.message,\n tool_call_id: toolCall.id!,\n name: toolCall.name,\n });\n }\n /**\n * Catch all other tool errors and convert to ToolMessage\n */\n return new ToolMessage({\n content: `${error}\\n Please fix your mistakes.`,\n tool_call_id: toolCall.id!,\n name: toolCall.name,\n });\n}\n\n/**\n * `ToolNode` is a built-in LangGraph component that handles tool calls within an agent's workflow.\n * It works seamlessly with `createAgent`, offering advanced tool execution control, built\n * in parallelism, and error handling.\n *\n * @example\n * ```ts\n * import { ToolNode, tool, AIMessage } from \"langchain\";\n * import { z } from \"zod/v3\";\n *\n * const getWeather = tool((input) => {\n * if ([\"sf\", \"san francisco\"].includes(input.location.toLowerCase())) {\n * return \"It's 60 degrees and foggy.\";\n * } else {\n * return \"It's 90 degrees and sunny.\";\n * }\n * }, {\n * name: \"get_weather\",\n * description: \"Call to get the current weather.\",\n * schema: z.object({\n * location: z.string().describe(\"Location to get the weather for.\"),\n * }),\n * });\n *\n * const tools = [getWeather];\n * const toolNode = new ToolNode(tools);\n *\n * const messageWithSingleToolCall = new AIMessage({\n * content: \"\",\n * tool_calls: [\n * {\n * name: \"get_weather\",\n * args: { location: \"sf\" },\n * id: \"tool_call_id\",\n * type: \"tool_call\",\n * }\n * ]\n * })\n *\n * await toolNode.invoke({ messages: [messageWithSingleToolCall] });\n * // Returns tool invocation responses as:\n * // { messages: ToolMessage[] }\n * ```\n */\nexport class ToolNode<\n StateSchema extends InteropZodObject = any,\n ContextSchema extends InteropZodObject = any,\n> extends RunnableCallable<StateSchema, ContextSchema> {\n tools: (StructuredToolInterface | DynamicTool | RunnableToolLike)[];\n\n trace = false;\n\n signal?: AbortSignal;\n\n handleToolErrors:\n | boolean\n | ((error: unknown, toolCall: ToolCall) => ToolMessage | undefined) =\n defaultHandleToolErrors;\n\n wrapToolCall: WrapToolCallHook | undefined;\n\n constructor(\n tools: (StructuredToolInterface | DynamicTool | RunnableToolLike)[],\n public options?: ToolNodeOptions\n ) {\n const { name, tags, handleToolErrors, signal, wrapToolCall } =\n options ?? {};\n super({\n name,\n tags,\n func: (state, config) =>\n this.run(\n state as ToAnnotationRoot<StateSchema>[\"State\"] & AgentBuiltInState,\n config as RunnableConfig\n ),\n });\n this.tools = tools;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.signal = signal;\n this.wrapToolCall = wrapToolCall;\n }\n\n /**\n * Handle errors from tool execution or middleware.\n * @param error - The error to handle\n * @param call - The tool call that caused the error\n * @param isMiddlewareError - Whether the error came from wrapToolCall middleware\n * @returns ToolMessage if error is handled, otherwise re-throws\n */\n #handleError(\n error: unknown,\n call: ToolCall,\n isMiddlewareError: boolean\n ): ToolMessage {\n /**\n * {@link NodeInterrupt} errors are a breakpoint to bring a human into the loop.\n * As such, they are not recoverable by the agent and shouldn't be fed\n * back. Instead, re-throw these errors even when `handleToolErrors = true`.\n */\n if (isGraphInterrupt(error)) {\n throw error;\n }\n\n /**\n * If the signal is aborted, we want to bubble up the error to the invoke caller.\n */\n if (this.signal?.aborted) {\n throw error;\n }\n\n /**\n * If error is from middleware and handleToolErrors is not true, bubble up\n * (default handler and false both re-raise middleware errors)\n */\n if (isMiddlewareError && this.handleToolErrors !== true) {\n throw error;\n }\n\n /**\n * If handleToolErrors is false, throw all errors\n */\n if (!this.handleToolErrors) {\n throw error;\n }\n\n /**\n * Apply handleToolErrors to the error\n */\n if (typeof this.handleToolErrors === \"function\") {\n const result = this.handleToolErrors(error, call);\n if (result && ToolMessage.isInstance(result)) {\n return result;\n }\n\n /**\n * `handleToolErrors` returned undefined - re-raise\n */\n throw error;\n } else if (this.handleToolErrors) {\n return new ToolMessage({\n name: call.name,\n content: `${error}\\n Please fix your mistakes.`,\n tool_call_id: call.id!,\n });\n }\n\n /**\n * Shouldn't reach here, but throw as fallback\n */\n throw error;\n }\n\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig,\n state: AgentBuiltInState\n ): Promise<ToolMessage | Command> {\n /**\n * Define the base handler that executes the tool.\n * When wrapToolCall middleware is present, this handler does NOT catch errors\n * so the middleware can handle them.\n * When no middleware, errors are caught and handled here.\n */\n const baseHandler = async (\n request: ToolCallRequest\n ): Promise<ToolMessage | Command> => {\n const { toolCall } = request;\n const tool = this.tools.find((tool) => tool.name === toolCall.name);\n if (tool === undefined) {\n throw new Error(`Tool \"${toolCall.name}\" not found.`);\n }\n\n try {\n const output = await tool.invoke(\n { ...toolCall, type: \"tool_call\" },\n {\n ...config,\n /**\n * extend to match ToolRuntime\n */\n config,\n toolCallId: toolCall.id!,\n state: config.configurable?.__pregel_scratchpad?.currentTaskInput,\n signal: mergeAbortSignals(this.signal, config.signal),\n }\n );\n\n if (ToolMessage.isInstance(output) || isCommand(output)) {\n return output as ToolMessage | Command;\n }\n\n return new ToolMessage({\n name: tool.name,\n content: typeof output === \"string\" ? output : JSON.stringify(output),\n tool_call_id: toolCall.id!,\n });\n } catch (e: unknown) {\n /**\n * Handle errors from tool execution (not from wrapToolCall)\n * If tool invocation fails due to input parsing error, throw a {@link ToolInvocationError}\n */\n if (e instanceof ToolInputParsingException) {\n throw new ToolInvocationError(e, toolCall);\n }\n /**\n * Re-throw to be handled by caller\n */\n throw e;\n }\n };\n\n /**\n * Build runtime from LangGraph config\n */\n const lgConfig = config as LangGraphRunnableConfig;\n const runtime = {\n context: lgConfig?.context,\n writer: lgConfig?.writer,\n interrupt: lgConfig?.interrupt,\n signal: lgConfig?.signal,\n };\n\n /**\n * Find the tool instance to include in the request\n */\n const tool = this.tools.find((t) => t.name === call.name);\n if (!tool) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n\n const request = {\n toolCall: call,\n tool,\n state,\n runtime,\n };\n\n /**\n * If wrapToolCall is provided, use it to wrap the tool execution\n */\n if (this.wrapToolCall) {\n try {\n return await this.wrapToolCall(request, baseHandler);\n } catch (e: unknown) {\n /**\n * Handle middleware errors\n */\n return this.#handleError(e, call, true);\n }\n }\n\n /**\n * No wrapToolCall - execute tool directly and handle errors here\n */\n try {\n return await baseHandler(request);\n } catch (e: unknown) {\n /**\n * Handle tool errors when no middleware provided\n */\n return this.#handleError(e, call, false);\n }\n }\n\n protected async run(\n state: ToAnnotationRoot<StateSchema>[\"State\"] & AgentBuiltInState,\n config: RunnableConfig\n ): Promise<ContextSchema> {\n let outputs: (ToolMessage | Command)[];\n\n if (isSendInput(state)) {\n const { lg_tool_call, jumpTo, ...newState } = state;\n outputs = [await this.runTool(state.lg_tool_call, config, newState)];\n } else {\n let messages: BaseMessage[];\n if (isBaseMessageArray(state)) {\n messages = state;\n } else if (isMessagesState(state)) {\n messages = state.messages;\n } else {\n throw new Error(\n \"ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.\"\n );\n }\n\n const toolMessageIds: Set<string> = new Set(\n messages\n .filter((msg) => msg.getType() === \"tool\")\n .map((msg) => (msg as ToolMessage).tool_call_id)\n );\n\n let aiMessage: AIMessage | undefined;\n for (let i = messages.length - 1; i >= 0; i -= 1) {\n const message = messages[i];\n if (AIMessage.isInstance(message)) {\n aiMessage = message;\n break;\n }\n }\n\n if (!AIMessage.isInstance(aiMessage)) {\n throw new Error(\"ToolNode only accepts AIMessages as input.\");\n }\n\n outputs = await Promise.all(\n aiMessage.tool_calls\n ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))\n .map((call) => this.runTool(call, config, state)) ?? []\n );\n }\n\n // Preserve existing behavior for non-command tool outputs for backwards compatibility\n if (!outputs.some(isCommand)) {\n return (Array.isArray(state)\n ? outputs\n : { messages: outputs }) as unknown as ContextSchema;\n }\n\n // Handle mixed Command and non-Command outputs\n const combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[] = [];\n let parentCommand: Command | null = null;\n\n for (const output of outputs) {\n if (isCommand(output)) {\n if (\n output.graph === Command.PARENT &&\n Array.isArray(output.goto) &&\n output.goto.every((send) => isSend(send))\n ) {\n if (parentCommand) {\n (parentCommand.goto as Send[]).push(...(output.goto as Send[]));\n } else {\n parentCommand = new Command({\n graph: Command.PARENT,\n goto: output.goto,\n });\n }\n } else {\n combinedOutputs.push(output);\n }\n } else {\n combinedOutputs.push(\n Array.isArray(state) ? [output] : { messages: [output] }\n );\n }\n }\n\n if (parentCommand) {\n combinedOutputs.push(parentCommand);\n }\n\n return combinedOutputs as unknown as ContextSchema;\n }\n}\n\nexport function isSend(x: unknown): x is Send {\n return x instanceof Send;\n}\n"],"mappings":"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"}