langchain 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/agents/ReactAgent.cjs +21 -41
- package/dist/agents/ReactAgent.cjs.map +1 -1
- package/dist/agents/ReactAgent.js +21 -41
- package/dist/agents/ReactAgent.js.map +1 -1
- package/dist/agents/annotation.cjs +7 -4
- package/dist/agents/annotation.cjs.map +1 -1
- package/dist/agents/annotation.js +8 -5
- package/dist/agents/annotation.js.map +1 -1
- package/dist/agents/middleware/contextEditing.d.cts.map +1 -1
- package/dist/agents/middleware/contextEditing.d.ts.map +1 -1
- package/dist/agents/middleware/dynamicSystemPrompt.d.cts.map +1 -1
- package/dist/agents/middleware/dynamicSystemPrompt.d.ts.map +1 -1
- package/dist/agents/middleware/hitl.cjs +7 -2
- package/dist/agents/middleware/hitl.cjs.map +1 -1
- package/dist/agents/middleware/hitl.d.cts.map +1 -1
- package/dist/agents/middleware/hitl.d.ts.map +1 -1
- package/dist/agents/middleware/hitl.js +7 -2
- package/dist/agents/middleware/hitl.js.map +1 -1
- package/dist/agents/middleware/index.cjs +2 -1
- package/dist/agents/middleware/index.js +2 -1
- package/dist/agents/middleware/llmToolSelector.d.cts +4 -4
- package/dist/agents/middleware/llmToolSelector.d.cts.map +1 -1
- package/dist/agents/middleware/{callLimit.cjs → modelCallLimit.cjs} +22 -7
- package/dist/agents/middleware/modelCallLimit.cjs.map +1 -0
- package/dist/agents/middleware/{callLimit.d.cts → modelCallLimit.d.cts} +12 -3
- package/dist/agents/middleware/modelCallLimit.d.cts.map +1 -0
- package/dist/agents/middleware/{callLimit.d.ts → modelCallLimit.d.ts} +12 -3
- package/dist/agents/middleware/modelCallLimit.d.ts.map +1 -0
- package/dist/agents/middleware/{callLimit.js → modelCallLimit.js} +22 -7
- package/dist/agents/middleware/modelCallLimit.js.map +1 -0
- package/dist/agents/middleware/promptCaching.d.cts.map +1 -1
- package/dist/agents/middleware/promptCaching.d.ts.map +1 -1
- package/dist/agents/middleware/summarization.cjs +274 -52
- package/dist/agents/middleware/summarization.cjs.map +1 -1
- package/dist/agents/middleware/summarization.d.cts +326 -30
- package/dist/agents/middleware/summarization.d.cts.map +1 -1
- package/dist/agents/middleware/summarization.d.ts +325 -29
- package/dist/agents/middleware/summarization.d.ts.map +1 -1
- package/dist/agents/middleware/summarization.js +276 -54
- package/dist/agents/middleware/summarization.js.map +1 -1
- package/dist/agents/middleware/todoListMiddleware.d.cts.map +1 -1
- package/dist/agents/middleware/todoListMiddleware.d.ts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.cjs +215 -93
- package/dist/agents/middleware/toolCallLimit.cjs.map +1 -1
- package/dist/agents/middleware/toolCallLimit.d.cts +48 -37
- package/dist/agents/middleware/toolCallLimit.d.cts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.d.ts +48 -37
- package/dist/agents/middleware/toolCallLimit.d.ts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.js +216 -94
- package/dist/agents/middleware/toolCallLimit.js.map +1 -1
- package/dist/agents/middleware/toolRetry.cjs +224 -0
- package/dist/agents/middleware/toolRetry.cjs.map +1 -0
- package/dist/agents/middleware/toolRetry.d.cts +179 -0
- package/dist/agents/middleware/toolRetry.d.cts.map +1 -0
- package/dist/agents/middleware/toolRetry.d.ts +179 -0
- package/dist/agents/middleware/toolRetry.d.ts.map +1 -0
- package/dist/agents/middleware/toolRetry.js +223 -0
- package/dist/agents/middleware/toolRetry.js.map +1 -0
- package/dist/agents/middleware/types.d.cts +21 -19
- package/dist/agents/middleware/types.d.cts.map +1 -1
- package/dist/agents/middleware/types.d.ts +21 -19
- package/dist/agents/middleware/types.d.ts.map +1 -1
- package/dist/agents/middleware/utils.cjs +7 -0
- package/dist/agents/middleware/utils.cjs.map +1 -1
- package/dist/agents/middleware/utils.d.cts.map +1 -1
- package/dist/agents/middleware/utils.d.ts.map +1 -1
- package/dist/agents/middleware/utils.js +7 -1
- package/dist/agents/middleware/utils.js.map +1 -1
- package/dist/agents/middleware.cjs.map +1 -1
- package/dist/agents/middleware.d.cts +4 -4
- package/dist/agents/middleware.d.cts.map +1 -1
- package/dist/agents/middleware.d.ts +4 -4
- package/dist/agents/middleware.d.ts.map +1 -1
- package/dist/agents/middleware.js.map +1 -1
- package/dist/agents/nodes/AgentNode.cjs +21 -51
- package/dist/agents/nodes/AgentNode.cjs.map +1 -1
- package/dist/agents/nodes/AgentNode.js +23 -53
- package/dist/agents/nodes/AgentNode.js.map +1 -1
- package/dist/agents/nodes/ToolNode.cjs +12 -18
- package/dist/agents/nodes/ToolNode.cjs.map +1 -1
- package/dist/agents/nodes/ToolNode.js +12 -18
- package/dist/agents/nodes/ToolNode.js.map +1 -1
- package/dist/agents/nodes/middleware.cjs +9 -7
- package/dist/agents/nodes/middleware.cjs.map +1 -1
- package/dist/agents/nodes/middleware.js +10 -8
- package/dist/agents/nodes/middleware.js.map +1 -1
- package/dist/agents/nodes/types.d.cts +1 -1
- package/dist/agents/nodes/types.d.cts.map +1 -1
- package/dist/agents/nodes/types.d.ts +1 -1
- package/dist/agents/nodes/types.d.ts.map +1 -1
- package/dist/agents/nodes/utils.cjs +5 -1
- package/dist/agents/nodes/utils.cjs.map +1 -1
- package/dist/agents/nodes/utils.js +5 -1
- package/dist/agents/nodes/utils.js.map +1 -1
- package/dist/agents/responses.cjs +0 -1
- package/dist/agents/responses.js +1 -1
- package/dist/agents/runtime.d.cts +11 -27
- package/dist/agents/runtime.d.cts.map +1 -1
- package/dist/agents/runtime.d.ts +11 -27
- package/dist/agents/runtime.d.ts.map +1 -1
- package/dist/agents/state.cjs +45 -0
- package/dist/agents/state.cjs.map +1 -0
- package/dist/agents/state.js +44 -0
- package/dist/agents/state.js.map +1 -0
- package/dist/agents/types.d.cts +1 -1
- package/dist/agents/types.d.cts.map +1 -1
- package/dist/agents/types.d.ts +1 -1
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/agents/utils.cjs +10 -2
- package/dist/agents/utils.cjs.map +1 -1
- package/dist/agents/utils.js +10 -2
- package/dist/agents/utils.js.map +1 -1
- package/dist/chat_models/universal.cjs +2 -2
- package/dist/chat_models/universal.cjs.map +1 -1
- package/dist/chat_models/universal.js +2 -2
- package/dist/chat_models/universal.js.map +1 -1
- package/dist/index.cjs +6 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/dist/agents/middleware/callLimit.cjs.map +0 -1
- package/dist/agents/middleware/callLimit.d.cts.map +0 -1
- package/dist/agents/middleware/callLimit.d.ts.map +0 -1
- package/dist/agents/middleware/callLimit.js.map +0 -1
- package/dist/agents/tools.d.cts +0 -9
- package/dist/agents/tools.d.cts.map +0 -1
- package/dist/agents/tools.d.ts +0 -9
- package/dist/agents/tools.d.ts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolCallLimit.js","names":["messages: BaseMessage[]","toolName?: string","threadCount: number","runCount: number","threadLimit: number | undefined","runLimit: number | undefined","toolName: string | undefined","exceededLimits: string[]","options: ToolCallLimitConfig"],"sources":["../../../src/agents/middleware/toolCallLimit.ts"],"sourcesContent":["/**\n * Tool call limit middleware for agents.\n */\n\nimport { AIMessage, BaseMessage, HumanMessage } from \"@langchain/core/messages\";\nimport { z } from \"zod/v3\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\n\nimport { createMiddleware } from \"../middleware.js\";\n\n/**\n * Count tool calls in a list of messages.\n *\n * @param messages - List of messages to count tool calls in.\n * @param toolName - If specified, only count calls to this specific tool.\n * If undefined, count all tool calls.\n * @returns The total number of tool calls (optionally filtered by toolName).\n */\nfunction countToolCallsInMessages(\n messages: BaseMessage[],\n toolName?: string\n): number {\n let count = 0;\n for (const message of messages) {\n if (AIMessage.isInstance(message) && message.tool_calls) {\n if (toolName === undefined) {\n // Count all tool calls\n count += message.tool_calls.length;\n } else {\n // Count only calls to the specified tool\n count += message.tool_calls.filter((tc) => tc.name === toolName).length;\n }\n }\n }\n return count;\n}\n\n/**\n * Get messages from the current run (after the last HumanMessage).\n *\n * @param messages - Full list of messages.\n * @returns Messages from the current run (after last HumanMessage).\n */\nfunction getRunMessages(messages: BaseMessage[]): BaseMessage[] {\n /**\n * Find the last HumanMessage\n */\n let lastHumanIndex = -1;\n for (let i = messages.length - 1; i >= 0; i--) {\n if (HumanMessage.isInstance(messages[i])) {\n lastHumanIndex = i;\n break;\n }\n }\n\n /**\n * If no HumanMessage found, return all messages\n */\n if (lastHumanIndex === -1) {\n return messages;\n }\n\n /**\n * Return messages after the last HumanMessage\n */\n return messages.slice(lastHumanIndex + 1);\n}\n\n/**\n * Build a message indicating which tool call limits were reached.\n *\n * @param threadCount - Current thread tool call count.\n * @param runCount - Current run tool call count.\n * @param threadLimit - Thread tool call limit (if set).\n * @param runLimit - Run tool call limit (if set).\n * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.\n * @returns A formatted message describing which limits were reached.\n */\nfunction buildToolLimitExceededMessage(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined\n): string {\n const toolDesc = toolName ? `'${toolName}' tool call` : \"Tool call\";\n const exceededLimits: string[] = [];\n\n if (threadLimit !== undefined && threadCount >= threadLimit) {\n exceededLimits.push(`thread limit reached (${threadCount}/${threadLimit})`);\n }\n if (runLimit !== undefined && runCount >= runLimit) {\n exceededLimits.push(`run limit reached (${runCount}/${runLimit})`);\n }\n\n return `${toolDesc} limit${\n exceededLimits.length > 1 ? \"s\" : \"\"\n }: ${exceededLimits.join(\", \")}. Stopping to prevent further tool calls.`;\n}\n\n/**\n * Exception raised when tool call limits are exceeded.\n *\n * This exception is raised when the configured exit behavior is 'error'\n * and either the thread or run tool call limit has been exceeded.\n */\nexport class ToolCallLimitExceededError extends Error {\n /**\n * Current thread tool call count.\n */\n threadCount: number;\n /**\n * Current run tool call count.\n */\n runCount: number;\n /**\n * Thread tool call limit (if set).\n */\n threadLimit: number | undefined;\n /**\n * Run tool call limit (if set).\n */\n runLimit: number | undefined;\n /**\n * Tool name being limited (if specific tool), or undefined for all tools.\n */\n toolName: string | undefined;\n\n constructor(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined = undefined\n ) {\n const message = buildToolLimitExceededMessage(\n threadCount,\n runCount,\n threadLimit,\n runLimit,\n toolName\n );\n super(message);\n\n this.name = \"ToolCallLimitExceededError\";\n this.threadCount = threadCount;\n this.runCount = runCount;\n this.threadLimit = threadLimit;\n this.runLimit = runLimit;\n this.toolName = toolName;\n }\n}\n\n/**\n * Options for configuring the Tool Call Limit middleware.\n */\nexport const ToolCallLimitOptionsSchema = z.object({\n /**\n * Name of the specific tool to limit. If undefined, limits apply to all tools.\n */\n toolName: z.string().optional(),\n /**\n * Maximum number of tool calls allowed per thread.\n * undefined means no limit.\n */\n threadLimit: z.number().optional(),\n /**\n * Maximum number of tool calls allowed per run.\n * undefined means no limit.\n */\n runLimit: z.number().optional(),\n /**\n * What to do when limits are exceeded.\n * - \"end\": Jump to the end of the agent execution and inject an artificial\n * AI message indicating that the limit was exceeded.\n * - \"error\": throws a ToolCallLimitExceededError\n */\n exitBehavior: z.enum([\"end\", \"error\"]).default(\"end\"),\n});\n\nexport type ToolCallLimitConfig = InferInteropZodInput<\n typeof ToolCallLimitOptionsSchema\n>;\n\n/**\n * Middleware that tracks tool call counts and enforces limits.\n *\n * This middleware monitors the number of tool calls made during agent execution\n * and can terminate the agent when specified limits are reached. It supports\n * both thread-level and run-level call counting with configurable exit behaviors.\n *\n * Thread-level: The middleware counts all tool calls in the entire message history\n * and persists this count across multiple runs (invocations) of the agent.\n *\n * Run-level: The middleware counts tool calls made after the last HumanMessage,\n * representing the current run (invocation) of the agent.\n *\n * @param options - Configuration options for the middleware\n * @param options.toolName - Name of the specific tool to limit. If undefined, limits apply to all tools.\n * @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.\n * @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.\n * @param options.exitBehavior - What to do when limits are exceeded.\n * - \"end\": Jump to the end of the agent execution and inject an artificial AI message indicating that the limit was exceeded.\n * - \"error\": throws a ToolCallLimitExceededError\n *\n * @throws {Error} If both limits are undefined or if exitBehavior is invalid.\n *\n * @example Limit all tool calls globally\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * const globalLimiter = toolCallLimitMiddleware({\n * threadLimit: 20,\n * runLimit: 10,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [globalLimiter]\n * });\n * ```\n *\n * @example Limit a specific tool\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * const searchLimiter = toolCallLimitMiddleware({\n * toolName: \"search\",\n * threadLimit: 5,\n * runLimit: 3,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [searchLimiter]\n * });\n * ```\n *\n * @example Use both in the same agent\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * const globalLimiter = toolCallLimitMiddleware({\n * threadLimit: 20,\n * runLimit: 10,\n * exitBehavior: \"end\"\n * });\n *\n * const searchLimiter = toolCallLimitMiddleware({\n * toolName: \"search\",\n * threadLimit: 5,\n * runLimit: 3,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [globalLimiter, searchLimiter]\n * });\n * ```\n */\nexport function toolCallLimitMiddleware(options: ToolCallLimitConfig) {\n /**\n * Validate that at least one limit is specified\n */\n if (options.threadLimit === undefined && options.runLimit === undefined) {\n throw new Error(\n \"At least one limit must be specified (threadLimit or runLimit)\"\n );\n }\n\n /**\n * Apply default for exitBehavior and validate\n */\n const exitBehavior = options.exitBehavior ?? \"end\";\n if (exitBehavior !== \"end\" && exitBehavior !== \"error\") {\n throw new Error(\n `Invalid exit behavior: ${exitBehavior}. Must be 'end' or 'error'`\n );\n }\n\n /**\n * Generate the middleware name based on the tool name\n */\n const middlewareName = options.toolName\n ? `ToolCallLimitMiddleware[${options.toolName}]`\n : \"ToolCallLimitMiddleware\";\n\n return createMiddleware({\n name: middlewareName,\n beforeModel: {\n canJumpTo: [\"end\"],\n hook: (state) => {\n const messages = state.messages;\n\n /**\n * Count tool calls in entire thread\n */\n const threadCount = countToolCallsInMessages(\n messages,\n options.toolName\n );\n\n /**\n * Count tool calls in current run (after last HumanMessage)\n */\n const runMessages = getRunMessages(messages);\n const runCount = countToolCallsInMessages(\n runMessages,\n options.toolName\n );\n\n /**\n * Check if any limits are exceeded\n */\n const threadLimitExceeded =\n options.threadLimit !== undefined &&\n threadCount >= options.threadLimit;\n const runLimitExceeded =\n options.runLimit !== undefined && runCount >= options.runLimit;\n\n if (!threadLimitExceeded && !runLimitExceeded) {\n return undefined;\n }\n\n if (exitBehavior === \"error\") {\n throw new ToolCallLimitExceededError(\n threadCount,\n runCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n }\n\n /**\n * Create a message indicating the limit was exceeded\n */\n const limitMessage = buildToolLimitExceededMessage(\n threadCount,\n runCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n const limitAiMessage = new AIMessage(limitMessage);\n\n return {\n jumpTo: \"end\",\n messages: [limitAiMessage],\n };\n },\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"toolCallLimit.js","names":["toolName: string | undefined","threadCount: number","runCount: number","threadLimit: number | undefined","runLimit: number | undefined","exceededLimits: string[]","options: ToolCallLimitConfig","z4","toolCall: { name?: string }","toolCalls: ToolCall[]","allowed: ToolCall[]","blocked: ToolCall[]","blocked","allowed","artificialMessages: Array<ToolMessage | AIMessage>","otherTools: ToolCall[]"],"sources":["../../../src/agents/middleware/toolCallLimit.ts"],"sourcesContent":["import { AIMessage, ToolMessage } from \"@langchain/core/messages\";\nimport { z as z4 } from \"zod/v4\";\nimport { z } from \"zod/v3\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\nimport type { ToolCall } from \"@langchain/core/messages/tool\";\n\nimport { createMiddleware } from \"../middleware.js\";\n\n/**\n * Build the error message content for ToolMessage when limit is exceeded.\n *\n * This message is sent to the model, so it should not reference thread/run concepts\n * that the model has no notion of.\n *\n * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.\n * @returns A concise message instructing the model not to call the tool again.\n */\nfunction buildToolMessageContent(toolName: string | undefined): string {\n // Always instruct the model not to call again, regardless of which limit was hit\n if (toolName) {\n return `Tool call limit exceeded. Do not call '${toolName}' again.`;\n }\n return \"Tool call limit exceeded. Do not make additional tool calls.\";\n}\n\nconst VALID_EXIT_BEHAVIORS = [\"continue\", \"error\", \"end\"] as const;\nconst DEFAULT_EXIT_BEHAVIOR = \"continue\";\n\n/**\n * Build the final AI message content for 'end' behavior.\n *\n * This message is displayed to the user, so it should include detailed information\n * about which limits were exceeded.\n *\n * @param threadCount - Current thread tool call count.\n * @param runCount - Current run tool call count.\n * @param threadLimit - Thread tool call limit (if set).\n * @param runLimit - Run tool call limit (if set).\n * @param toolName - Tool name being limited (if specific tool), or undefined for all tools.\n * @returns A formatted message describing which limits were exceeded.\n */\nfunction buildFinalAIMessageContent(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined\n): string {\n const toolDesc = toolName ? `'${toolName}' tool` : \"Tool\";\n const exceededLimits: string[] = [];\n\n if (threadLimit !== undefined && threadCount > threadLimit) {\n exceededLimits.push(\n `thread limit exceeded (${threadCount}/${threadLimit} calls)`\n );\n }\n if (runLimit !== undefined && runCount > runLimit) {\n exceededLimits.push(`run limit exceeded (${runCount}/${runLimit} calls)`);\n }\n\n const limitsText = exceededLimits.join(\" and \");\n return `${toolDesc} call limit reached: ${limitsText}.`;\n}\n\n/**\n * Schema for the exit behavior.\n */\nconst exitBehaviorSchema = z\n .enum(VALID_EXIT_BEHAVIORS)\n .default(DEFAULT_EXIT_BEHAVIOR);\n\n/**\n * Exception raised when tool call limits are exceeded.\n *\n * This exception is raised when the configured exit behavior is 'error'\n * and either the thread or run tool call limit has been exceeded.\n */\nexport class ToolCallLimitExceededError extends Error {\n /**\n * Current thread tool call count.\n */\n threadCount: number;\n /**\n * Current run tool call count.\n */\n runCount: number;\n /**\n * Thread tool call limit (if set).\n */\n threadLimit: number | undefined;\n /**\n * Run tool call limit (if set).\n */\n runLimit: number | undefined;\n /**\n * Tool name being limited (if specific tool), or undefined for all tools.\n */\n toolName: string | undefined;\n\n constructor(\n threadCount: number,\n runCount: number,\n threadLimit: number | undefined,\n runLimit: number | undefined,\n toolName: string | undefined = undefined\n ) {\n const message = buildFinalAIMessageContent(\n threadCount,\n runCount,\n threadLimit,\n runLimit,\n toolName\n );\n super(message);\n\n this.name = \"ToolCallLimitExceededError\";\n this.threadCount = threadCount;\n this.runCount = runCount;\n this.threadLimit = threadLimit;\n this.runLimit = runLimit;\n this.toolName = toolName;\n }\n}\n\n/**\n * Options for configuring the Tool Call Limit middleware.\n */\nexport const ToolCallLimitOptionsSchema = z.object({\n /**\n * Name of the specific tool to limit. If undefined, limits apply to all tools.\n */\n toolName: z.string().optional(),\n /**\n * Maximum number of tool calls allowed per thread.\n * undefined means no limit.\n */\n threadLimit: z.number().optional(),\n /**\n * Maximum number of tool calls allowed per run.\n * undefined means no limit.\n */\n runLimit: z.number().optional(),\n /**\n * What to do when limits are exceeded.\n * - \"continue\": Block exceeded tools with error messages, let other tools continue (default)\n * - \"error\": Raise a ToolCallLimitExceededError exception\n * - \"end\": Stop execution immediately, injecting a ToolMessage and an AI message\n * for the single tool call that exceeded the limit. Raises NotImplementedError\n * if there are multiple tool calls.\n *\n * @default \"continue\"\n */\n exitBehavior: exitBehaviorSchema,\n});\n\nexport type ToolCallLimitConfig = InferInteropZodInput<\n typeof ToolCallLimitOptionsSchema\n>;\n\n/**\n * Middleware state schema to track the number of model calls made at the thread and run level.\n */\nconst stateSchema = z.object({\n threadToolCallCount: z.record(z.string(), z.number()).default({}),\n runToolCallCount: z.record(z.string(), z.number()).default({}),\n});\n\nconst DEFAULT_TOOL_COUNT_KEY = \"__all__\";\n\n/**\n * Middleware that tracks tool call counts and enforces limits.\n *\n * This middleware monitors the number of tool calls made during agent execution\n * and can terminate the agent when specified limits are reached. It supports\n * both thread-level and run-level call counting with configurable exit behaviors.\n *\n * Thread-level: The middleware counts all tool calls in the entire message history\n * and persists this count across multiple runs (invocations) of the agent.\n *\n * Run-level: The middleware counts tool calls made after the last HumanMessage,\n * representing the current run (invocation) of the agent.\n *\n * @param options - Configuration options for the middleware\n * @param options.toolName - Name of the specific tool to limit. If undefined, limits apply to all tools.\n * @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.\n * @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.\n * @param options.exitBehavior - What to do when limits are exceeded.\n * - \"continue\": Block exceeded tools with error messages, let other tools continue. Model decides when to end. (default)\n * - \"error\": Raise a ToolCallLimitExceededError exception\n * - \"end\": Stop execution immediately with a ToolMessage + AI message for the single tool call that exceeded the limit. Raises NotImplementedError if there are multiple tool calls.\n *\n * @throws {Error} If both limits are undefined, if exitBehavior is invalid, or if runLimit exceeds threadLimit.\n * @throws {NotImplementedError} If exitBehavior is \"end\" and there are multiple tool calls.\n *\n * @example Continue execution with blocked tools (default)\n * ```ts\n * import { toolCallLimitMiddleware } from \"@langchain/langchain/agents/middleware\";\n * import { createAgent } from \"@langchain/langchain/agents\";\n *\n * // Block exceeded tools but let other tools and model continue\n * const limiter = toolCallLimitMiddleware({\n * threadLimit: 20,\n * runLimit: 10,\n * exitBehavior: \"continue\", // default\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [limiter]\n * });\n * ```\n *\n * @example Stop immediately when limit exceeded\n * ```ts\n * // End execution immediately with an AI message\n * const limiter = toolCallLimitMiddleware({\n * runLimit: 5,\n * exitBehavior: \"end\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [limiter]\n * });\n * ```\n *\n * @example Raise exception on limit\n * ```ts\n * // Strict limit with exception handling\n * const limiter = toolCallLimitMiddleware({\n * toolName: \"search\",\n * threadLimit: 5,\n * exitBehavior: \"error\"\n * });\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * middleware: [limiter]\n * });\n *\n * try {\n * const result = await agent.invoke({ messages: [new HumanMessage(\"Task\")] });\n * } catch (error) {\n * if (error instanceof ToolCallLimitExceededError) {\n * console.log(`Search limit exceeded: ${error}`);\n * }\n * }\n * ```\n */\nexport function toolCallLimitMiddleware(options: ToolCallLimitConfig) {\n /**\n * Validate that at least one limit is specified\n */\n if (options.threadLimit === undefined && options.runLimit === undefined) {\n throw new Error(\n \"At least one limit must be specified (threadLimit or runLimit)\"\n );\n }\n\n /**\n * Validate exitBehavior (Zod schema already validates, but provide helpful error)\n */\n const exitBehavior = options.exitBehavior ?? DEFAULT_EXIT_BEHAVIOR;\n const parseResult = exitBehaviorSchema.safeParse(exitBehavior);\n if (!parseResult.success) {\n throw new Error(z4.prettifyError(parseResult.error).slice(2));\n }\n\n /**\n * Validate that runLimit does not exceed threadLimit\n */\n if (\n options.threadLimit !== undefined &&\n options.runLimit !== undefined &&\n options.runLimit > options.threadLimit\n ) {\n throw new Error(\n `runLimit (${options.runLimit}) cannot exceed threadLimit (${options.threadLimit}). ` +\n \"The run limit should be less than or equal to the thread limit.\"\n );\n }\n\n /**\n * Generate the middleware name based on the tool name\n */\n const middlewareName = options.toolName\n ? `ToolCallLimitMiddleware[${options.toolName}]`\n : \"ToolCallLimitMiddleware\";\n\n return createMiddleware({\n name: middlewareName,\n stateSchema,\n afterModel: {\n canJumpTo: [\"end\"],\n hook: (state) => {\n /**\n * Get the last AI message to check for tool calls\n */\n const lastAIMessage = [...state.messages]\n .reverse()\n .find(AIMessage.isInstance);\n\n if (!lastAIMessage || !lastAIMessage.tool_calls) {\n return undefined;\n }\n\n /**\n * Helper to check if limit would be exceeded by one more call\n */\n const wouldExceedLimit = (\n threadCount: number,\n runCount: number\n ): boolean => {\n return (\n (options.threadLimit !== undefined &&\n threadCount + 1 > options.threadLimit) ||\n (options.runLimit !== undefined && runCount + 1 > options.runLimit)\n );\n };\n\n /**\n * Helper to check if a tool call matches our filter\n */\n const matchesToolFilter = (toolCall: { name?: string }): boolean => {\n return (\n options.toolName === undefined || toolCall.name === options.toolName\n );\n };\n\n /**\n * Separate tool calls into allowed and blocked based on limits\n */\n const separateToolCalls = (\n toolCalls: ToolCall[],\n threadCount: number,\n runCount: number\n ): {\n allowed: ToolCall[];\n blocked: ToolCall[];\n finalThreadCount: number;\n finalRunCount: number;\n } => {\n const allowed: ToolCall[] = [];\n const blocked: ToolCall[] = [];\n let tempThreadCount = threadCount;\n let tempRunCount = runCount;\n\n for (const toolCall of toolCalls) {\n if (!matchesToolFilter(toolCall)) {\n // Tool call doesn't match our filter, skip it\n continue;\n }\n\n if (wouldExceedLimit(tempThreadCount, tempRunCount)) {\n blocked.push(toolCall);\n } else {\n allowed.push(toolCall);\n tempThreadCount += 1;\n tempRunCount += 1;\n }\n }\n\n return {\n allowed,\n blocked,\n finalThreadCount: tempThreadCount,\n finalRunCount: tempRunCount + blocked.length,\n };\n };\n\n /**\n * Get the count key for this middleware instance\n */\n const countKey = options.toolName ?? DEFAULT_TOOL_COUNT_KEY;\n\n /**\n * Get current counts\n */\n const threadCounts = { ...(state.threadToolCallCount ?? {}) };\n const runCounts = { ...(state.runToolCallCount ?? {}) };\n const currentThreadCount = threadCounts[countKey] ?? 0;\n const currentRunCount = runCounts[countKey] ?? 0;\n\n /**\n * Separate tool calls into allowed and blocked\n */\n const { allowed, blocked, finalThreadCount, finalRunCount } =\n separateToolCalls(\n lastAIMessage.tool_calls,\n currentThreadCount,\n currentRunCount\n );\n\n /**\n * Update counts:\n * - Thread count includes only allowed calls (blocked calls don't count towards thread-level tracking)\n * - Run count includes blocked calls since they were attempted in this run\n */\n threadCounts[countKey] = finalThreadCount;\n runCounts[countKey] = finalRunCount;\n\n /**\n * If no tool calls are blocked, just update counts\n */\n if (blocked.length === 0) {\n if (allowed.length > 0) {\n return {\n threadToolCallCount: threadCounts,\n runToolCallCount: runCounts,\n };\n }\n return undefined;\n }\n\n /**\n * Handle different exit behaviors\n */\n if (exitBehavior === \"error\") {\n // Use hypothetical thread count to show which limit was exceeded\n const hypotheticalThreadCount = finalThreadCount + blocked.length;\n throw new ToolCallLimitExceededError(\n hypotheticalThreadCount,\n finalRunCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n }\n\n /**\n * Build tool message content (sent to model - no thread/run details)\n */\n const toolMsgContent = buildToolMessageContent(options.toolName);\n\n /**\n * Inject artificial error ToolMessages for blocked tool calls\n */\n const artificialMessages: Array<ToolMessage | AIMessage> = blocked.map(\n (toolCall) =>\n new ToolMessage({\n content: toolMsgContent,\n tool_call_id: toolCall.id!,\n name: toolCall.name,\n status: \"error\",\n })\n );\n\n if (exitBehavior === \"end\") {\n /**\n * Check if there are tool calls to other tools that would continue executing\n * For tool-specific limiters: check for calls to other tools\n * For global limiters: check if there are multiple different tool types\n */\n let otherTools: ToolCall[] = [];\n if (options.toolName !== undefined) {\n /**\n * Tool-specific limiter: check for calls to other tools\n */\n otherTools = lastAIMessage.tool_calls.filter(\n (tc) => tc.name !== options.toolName\n );\n } else {\n /**\n * Global limiter: check if there are multiple different tool types\n * If there are allowed calls, those would execute\n * But even if all are blocked, we can't handle multiple tool types with \"end\"\n */\n const uniqueToolNames = new Set(\n lastAIMessage.tool_calls.map((tc) => tc.name).filter(Boolean)\n );\n if (uniqueToolNames.size > 1) {\n /**\n * Multiple different tool types - use allowed calls to show which ones\n */\n otherTools =\n allowed.length > 0 ? allowed : lastAIMessage.tool_calls;\n }\n }\n\n if (otherTools.length > 0) {\n const toolNames = Array.from(\n new Set(otherTools.map((tc) => tc.name).filter(Boolean))\n ).join(\", \");\n throw new Error(\n `Cannot end execution with other tool calls pending. Found calls to: ${toolNames}. Use 'continue' or 'error' behavior instead.`\n );\n }\n\n /**\n * Build final AI message content (displayed to user - includes thread/run details)\n * Use hypothetical thread count (what it would have been if call wasn't blocked)\n * to show which limit was actually exceeded\n */\n const hypotheticalThreadCount = finalThreadCount + blocked.length;\n const finalMsgContent = buildFinalAIMessageContent(\n hypotheticalThreadCount,\n finalRunCount,\n options.threadLimit,\n options.runLimit,\n options.toolName\n );\n artificialMessages.push(new AIMessage(finalMsgContent));\n\n return {\n threadToolCallCount: threadCounts,\n runToolCallCount: runCounts,\n jumpTo: \"end\" as const,\n messages: artificialMessages,\n };\n }\n\n /**\n * For exit_behavior=\"continue\", return error messages to block exceeded tools\n */\n return {\n threadToolCallCount: threadCounts,\n runToolCallCount: runCounts,\n messages: artificialMessages,\n };\n },\n },\n /**\n * reset the run tool call count after the agent execution completes\n */\n afterAgent: () => ({\n runToolCallCount: {},\n }),\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_utils = require('./utils.cjs');
|
|
3
|
+
const require_middleware = require('../middleware.cjs');
|
|
4
|
+
const __langchain_core_messages = require_rolldown_runtime.__toESM(require("@langchain/core/messages"));
|
|
5
|
+
const zod_v3 = require_rolldown_runtime.__toESM(require("zod/v3"));
|
|
6
|
+
|
|
7
|
+
//#region src/agents/middleware/toolRetry.ts
|
|
8
|
+
/**
|
|
9
|
+
* Calculate delay for a retry attempt with exponential backoff and jitter.
|
|
10
|
+
*
|
|
11
|
+
* @param retryNumber - The retry attempt number (0-indexed)
|
|
12
|
+
* @param config - Configuration for backoff calculation
|
|
13
|
+
* @returns Delay in milliseconds before next retry
|
|
14
|
+
*
|
|
15
|
+
* @internal Exported for testing purposes
|
|
16
|
+
*/
|
|
17
|
+
function calculateRetryDelay(config, retryNumber) {
|
|
18
|
+
const { backoffFactor, initialDelayMs, maxDelayMs, jitter } = config;
|
|
19
|
+
let delay;
|
|
20
|
+
if (backoffFactor === 0) delay = initialDelayMs;
|
|
21
|
+
else delay = initialDelayMs * backoffFactor ** retryNumber;
|
|
22
|
+
delay = Math.min(delay, maxDelayMs);
|
|
23
|
+
if (jitter && delay > 0) {
|
|
24
|
+
const jitterAmount = delay * .25;
|
|
25
|
+
delay = delay + (Math.random() * 2 - 1) * jitterAmount;
|
|
26
|
+
delay = Math.max(0, delay);
|
|
27
|
+
}
|
|
28
|
+
return delay;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Configuration options for the Tool Retry Middleware.
|
|
32
|
+
*/
|
|
33
|
+
const ToolRetryMiddlewareOptionsSchema = zod_v3.z.object({
|
|
34
|
+
maxRetries: zod_v3.z.number().min(0).default(2),
|
|
35
|
+
tools: zod_v3.z.array(zod_v3.z.union([
|
|
36
|
+
zod_v3.z.custom(),
|
|
37
|
+
zod_v3.z.custom(),
|
|
38
|
+
zod_v3.z.string()
|
|
39
|
+
])).optional(),
|
|
40
|
+
retryOn: zod_v3.z.union([zod_v3.z.function().args(zod_v3.z.instanceof(Error)).returns(zod_v3.z.boolean()), zod_v3.z.array(zod_v3.z.custom())]).default(() => () => true),
|
|
41
|
+
onFailure: zod_v3.z.union([
|
|
42
|
+
zod_v3.z.literal("raise"),
|
|
43
|
+
zod_v3.z.literal("return_message"),
|
|
44
|
+
zod_v3.z.function().args(zod_v3.z.instanceof(Error)).returns(zod_v3.z.string())
|
|
45
|
+
]).default("return_message"),
|
|
46
|
+
backoffFactor: zod_v3.z.number().min(0).default(2),
|
|
47
|
+
initialDelayMs: zod_v3.z.number().min(0).default(1e3),
|
|
48
|
+
maxDelayMs: zod_v3.z.number().min(0).default(6e4),
|
|
49
|
+
jitter: zod_v3.z.boolean().default(true)
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Middleware that automatically retries failed tool calls with configurable backoff.
|
|
53
|
+
*
|
|
54
|
+
* Supports retrying on specific exceptions and exponential backoff.
|
|
55
|
+
*
|
|
56
|
+
* @example Basic usage with default settings (2 retries, exponential backoff)
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { createAgent, toolRetryMiddleware } from "langchain";
|
|
59
|
+
*
|
|
60
|
+
* const agent = createAgent({
|
|
61
|
+
* model: "openai:gpt-4o",
|
|
62
|
+
* tools: [searchTool],
|
|
63
|
+
* middleware: [toolRetryMiddleware()],
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*
|
|
67
|
+
* @example Retry specific exceptions only
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { toolRetryMiddleware } from "langchain";
|
|
70
|
+
*
|
|
71
|
+
* const retry = toolRetryMiddleware({
|
|
72
|
+
* maxRetries: 4,
|
|
73
|
+
* retryOn: [TimeoutError, NetworkError],
|
|
74
|
+
* backoffFactor: 1.5,
|
|
75
|
+
* });
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @example Custom exception filtering
|
|
79
|
+
* ```ts
|
|
80
|
+
* function shouldRetry(error: Error): boolean {
|
|
81
|
+
* // Only retry on 5xx errors
|
|
82
|
+
* if (error.name === "HTTPError" && "statusCode" in error) {
|
|
83
|
+
* const statusCode = (error as any).statusCode;
|
|
84
|
+
* return 500 <= statusCode && statusCode < 600;
|
|
85
|
+
* }
|
|
86
|
+
* return false;
|
|
87
|
+
* }
|
|
88
|
+
*
|
|
89
|
+
* const retry = toolRetryMiddleware({
|
|
90
|
+
* maxRetries: 3,
|
|
91
|
+
* retryOn: shouldRetry,
|
|
92
|
+
* });
|
|
93
|
+
* ```
|
|
94
|
+
*
|
|
95
|
+
* @example Apply to specific tools with custom error handling
|
|
96
|
+
* ```ts
|
|
97
|
+
* const formatError = (error: Error) =>
|
|
98
|
+
* "Database temporarily unavailable. Please try again later.";
|
|
99
|
+
*
|
|
100
|
+
* const retry = toolRetryMiddleware({
|
|
101
|
+
* maxRetries: 4,
|
|
102
|
+
* tools: ["search_database"],
|
|
103
|
+
* onFailure: formatError,
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example Apply to specific tools using BaseTool instances
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { tool } from "@langchain/core/tools";
|
|
110
|
+
* import { z } from "zod";
|
|
111
|
+
*
|
|
112
|
+
* const searchDatabase = tool(
|
|
113
|
+
* async ({ query }) => {
|
|
114
|
+
* // Search implementation
|
|
115
|
+
* return results;
|
|
116
|
+
* },
|
|
117
|
+
* {
|
|
118
|
+
* name: "search_database",
|
|
119
|
+
* description: "Search the database",
|
|
120
|
+
* schema: z.object({ query: z.string() }),
|
|
121
|
+
* }
|
|
122
|
+
* );
|
|
123
|
+
*
|
|
124
|
+
* const retry = toolRetryMiddleware({
|
|
125
|
+
* maxRetries: 4,
|
|
126
|
+
* tools: [searchDatabase], // Pass BaseTool instance
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example Constant backoff (no exponential growth)
|
|
131
|
+
* ```ts
|
|
132
|
+
* const retry = toolRetryMiddleware({
|
|
133
|
+
* maxRetries: 5,
|
|
134
|
+
* backoffFactor: 0.0, // No exponential growth
|
|
135
|
+
* initialDelayMs: 2000, // Always wait 2 seconds
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @example Raise exception on failure
|
|
140
|
+
* ```ts
|
|
141
|
+
* const retry = toolRetryMiddleware({
|
|
142
|
+
* maxRetries: 2,
|
|
143
|
+
* onFailure: "raise", // Re-raise exception instead of returning message
|
|
144
|
+
* });
|
|
145
|
+
* ```
|
|
146
|
+
*
|
|
147
|
+
* @param config - Configuration options for the retry middleware
|
|
148
|
+
* @returns A middleware instance that handles tool failures with retries
|
|
149
|
+
*/
|
|
150
|
+
function toolRetryMiddleware(config = {}) {
|
|
151
|
+
const { maxRetries, tools, retryOn, onFailure, backoffFactor, initialDelayMs, maxDelayMs, jitter } = ToolRetryMiddlewareOptionsSchema.parse(config);
|
|
152
|
+
const toolFilter = [];
|
|
153
|
+
for (const tool of tools ?? []) if (typeof tool === "string") toolFilter.push(tool);
|
|
154
|
+
else if ("name" in tool && typeof tool.name === "string") toolFilter.push(tool.name);
|
|
155
|
+
else throw new TypeError("Expected a tool name string or tool instance to be passed to toolRetryMiddleware");
|
|
156
|
+
/**
|
|
157
|
+
* Check if retry logic should apply to this tool.
|
|
158
|
+
*/
|
|
159
|
+
const shouldRetryTool = (toolName) => {
|
|
160
|
+
if (toolFilter.length === 0) return true;
|
|
161
|
+
return toolFilter.includes(toolName);
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Check if the exception should trigger a retry.
|
|
165
|
+
*/
|
|
166
|
+
const shouldRetryException = (error) => {
|
|
167
|
+
if (typeof retryOn === "function") return retryOn(error);
|
|
168
|
+
return retryOn.some((ErrorConstructor) => error.constructor === ErrorConstructor);
|
|
169
|
+
};
|
|
170
|
+
const delayConfig = {
|
|
171
|
+
backoffFactor,
|
|
172
|
+
initialDelayMs,
|
|
173
|
+
maxDelayMs,
|
|
174
|
+
jitter
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Format the failure message when retries are exhausted.
|
|
178
|
+
*/
|
|
179
|
+
const formatFailureMessage = (toolName, error, attemptsMade) => {
|
|
180
|
+
const errorType = error.constructor.name;
|
|
181
|
+
const attemptWord = attemptsMade === 1 ? "attempt" : "attempts";
|
|
182
|
+
return `Tool '${toolName}' failed after ${attemptsMade} ${attemptWord} with ${errorType}`;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Handle failure when all retries are exhausted.
|
|
186
|
+
*/
|
|
187
|
+
const handleFailure = (toolName, toolCallId, error, attemptsMade) => {
|
|
188
|
+
if (onFailure === "raise") throw error;
|
|
189
|
+
let content;
|
|
190
|
+
if (typeof onFailure === "function") content = onFailure(error);
|
|
191
|
+
else content = formatFailureMessage(toolName, error, attemptsMade);
|
|
192
|
+
return new __langchain_core_messages.ToolMessage({
|
|
193
|
+
content,
|
|
194
|
+
tool_call_id: toolCallId,
|
|
195
|
+
name: toolName,
|
|
196
|
+
status: "error"
|
|
197
|
+
});
|
|
198
|
+
};
|
|
199
|
+
return require_middleware.createMiddleware({
|
|
200
|
+
name: "toolRetryMiddleware",
|
|
201
|
+
contextSchema: ToolRetryMiddlewareOptionsSchema,
|
|
202
|
+
wrapToolCall: async (request, handler) => {
|
|
203
|
+
const toolName = request.tool.name;
|
|
204
|
+
if (!shouldRetryTool(toolName)) return handler(request);
|
|
205
|
+
const toolCallId = request.toolCall.id ?? "";
|
|
206
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
207
|
+
return await handler(request);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const attemptsMade = attempt + 1;
|
|
210
|
+
const err = error && typeof error === "object" && "message" in error ? error : new Error(String(error));
|
|
211
|
+
if (!shouldRetryException(err)) return handleFailure(toolName, toolCallId, err, attemptsMade);
|
|
212
|
+
if (attempt < maxRetries) {
|
|
213
|
+
const delay = calculateRetryDelay(delayConfig, attempt);
|
|
214
|
+
if (delay > 0) await require_utils.sleep(delay);
|
|
215
|
+
} else return handleFailure(toolName, toolCallId, err, attemptsMade);
|
|
216
|
+
}
|
|
217
|
+
throw new Error("Unexpected: retry loop completed without returning");
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
exports.toolRetryMiddleware = toolRetryMiddleware;
|
|
224
|
+
//# sourceMappingURL=toolRetry.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolRetry.cjs","names":["config: {\n backoffFactor: number;\n initialDelayMs: number;\n maxDelayMs: number;\n jitter: boolean;\n }","retryNumber: number","delay: number","z","config: ToolRetryMiddlewareConfig","toolFilter: string[]","toolName: string","error: Error","attemptsMade: number","toolCallId: string","content: string","ToolMessage","createMiddleware","sleep"],"sources":["../../../src/agents/middleware/toolRetry.ts"],"sourcesContent":["/**\n * Tool retry middleware for agents.\n */\nimport { z } from \"zod/v3\";\nimport { ToolMessage } from \"@langchain/core/messages\";\nimport type { ClientTool, ServerTool } from \"@langchain/core/tools\";\n\nimport { createMiddleware } from \"../middleware.js\";\nimport type { AgentMiddleware } from \"./types.js\";\nimport { sleep } from \"./utils.js\";\n\n/**\n * Calculate delay for a retry attempt with exponential backoff and jitter.\n *\n * @param retryNumber - The retry attempt number (0-indexed)\n * @param config - Configuration for backoff calculation\n * @returns Delay in milliseconds before next retry\n *\n * @internal Exported for testing purposes\n */\nexport function calculateRetryDelay(\n config: {\n backoffFactor: number;\n initialDelayMs: number;\n maxDelayMs: number;\n jitter: boolean;\n },\n retryNumber: number\n): number {\n const { backoffFactor, initialDelayMs, maxDelayMs, jitter } = config;\n\n let delay: number;\n if (backoffFactor === 0.0) {\n delay = initialDelayMs;\n } else {\n delay = initialDelayMs * backoffFactor ** retryNumber;\n }\n\n // Cap at maxDelayMs\n delay = Math.min(delay, maxDelayMs);\n\n if (jitter && delay > 0) {\n const jitterAmount = delay * 0.25;\n delay = delay + (Math.random() * 2 - 1) * jitterAmount;\n // Ensure delay is not negative after jitter\n delay = Math.max(0, delay);\n }\n\n return delay;\n}\n\n/**\n * Configuration options for the Tool Retry Middleware.\n */\nexport const ToolRetryMiddlewareOptionsSchema = z.object({\n /**\n * Maximum number of retry attempts after the initial call.\n * Default is 2 retries (3 total attempts). Must be >= 0.\n */\n maxRetries: z.number().min(0).default(2),\n\n /**\n * Optional list of tools or tool names to apply retry logic to.\n * Can be a list of `BaseTool` instances or tool name strings.\n * If `undefined`, applies to all tools. Default is `undefined`.\n */\n tools: z\n .array(\n z.union([z.custom<ClientTool>(), z.custom<ServerTool>(), z.string()])\n )\n .optional(),\n\n /**\n * Either an array of error constructors to retry on, or a function\n * that takes an error and returns `true` if it should be retried.\n * Default is to retry on all errors.\n */\n retryOn: z\n .union([\n z.function().args(z.instanceof(Error)).returns(z.boolean()),\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n z.array(z.custom<new (...args: any[]) => Error>()),\n ])\n .default(() => () => true),\n\n /**\n * Behavior when all retries are exhausted. Options:\n * - `\"return_message\"` (default): Return a ToolMessage with error details,\n * allowing the LLM to handle the failure and potentially recover.\n * - `\"raise\"`: Re-raise the exception, stopping agent execution.\n * - Custom function: Function that takes the exception and returns a string\n * for the ToolMessage content, allowing custom error formatting.\n */\n onFailure: z\n .union([\n z.literal(\"raise\"),\n z.literal(\"return_message\"),\n z.function().args(z.instanceof(Error)).returns(z.string()),\n ])\n .default(\"return_message\"),\n\n /**\n * Multiplier for exponential backoff. Each retry waits\n * `initialDelayMs * (backoffFactor ** retryNumber)` milliseconds.\n * Set to 0.0 for constant delay. Default is 2.0.\n */\n backoffFactor: z.number().min(0).default(2.0),\n\n /**\n * Initial delay in milliseconds before first retry. Default is 1000 (1 second).\n */\n initialDelayMs: z.number().min(0).default(1000),\n\n /**\n * Maximum delay in milliseconds between retries. Caps exponential\n * backoff growth. Default is 60000 (60 seconds).\n */\n maxDelayMs: z.number().min(0).default(60000),\n\n /**\n * Whether to add random jitter (±25%) to delay to avoid thundering herd.\n * Default is `true`.\n */\n jitter: z.boolean().default(true),\n});\n\nexport type ToolRetryMiddlewareConfig = z.input<\n typeof ToolRetryMiddlewareOptionsSchema\n>;\n\n/**\n * Middleware that automatically retries failed tool calls with configurable backoff.\n *\n * Supports retrying on specific exceptions and exponential backoff.\n *\n * @example Basic usage with default settings (2 retries, exponential backoff)\n * ```ts\n * import { createAgent, toolRetryMiddleware } from \"langchain\";\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [searchTool],\n * middleware: [toolRetryMiddleware()],\n * });\n * ```\n *\n * @example Retry specific exceptions only\n * ```ts\n * import { toolRetryMiddleware } from \"langchain\";\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 4,\n * retryOn: [TimeoutError, NetworkError],\n * backoffFactor: 1.5,\n * });\n * ```\n *\n * @example Custom exception filtering\n * ```ts\n * function shouldRetry(error: Error): boolean {\n * // Only retry on 5xx errors\n * if (error.name === \"HTTPError\" && \"statusCode\" in error) {\n * const statusCode = (error as any).statusCode;\n * return 500 <= statusCode && statusCode < 600;\n * }\n * return false;\n * }\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 3,\n * retryOn: shouldRetry,\n * });\n * ```\n *\n * @example Apply to specific tools with custom error handling\n * ```ts\n * const formatError = (error: Error) =>\n * \"Database temporarily unavailable. Please try again later.\";\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 4,\n * tools: [\"search_database\"],\n * onFailure: formatError,\n * });\n * ```\n *\n * @example Apply to specific tools using BaseTool instances\n * ```ts\n * import { tool } from \"@langchain/core/tools\";\n * import { z } from \"zod\";\n *\n * const searchDatabase = tool(\n * async ({ query }) => {\n * // Search implementation\n * return results;\n * },\n * {\n * name: \"search_database\",\n * description: \"Search the database\",\n * schema: z.object({ query: z.string() }),\n * }\n * );\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 4,\n * tools: [searchDatabase], // Pass BaseTool instance\n * });\n * ```\n *\n * @example Constant backoff (no exponential growth)\n * ```ts\n * const retry = toolRetryMiddleware({\n * maxRetries: 5,\n * backoffFactor: 0.0, // No exponential growth\n * initialDelayMs: 2000, // Always wait 2 seconds\n * });\n * ```\n *\n * @example Raise exception on failure\n * ```ts\n * const retry = toolRetryMiddleware({\n * maxRetries: 2,\n * onFailure: \"raise\", // Re-raise exception instead of returning message\n * });\n * ```\n *\n * @param config - Configuration options for the retry middleware\n * @returns A middleware instance that handles tool failures with retries\n */\nexport function toolRetryMiddleware(\n config: ToolRetryMiddlewareConfig = {}\n): AgentMiddleware {\n const {\n maxRetries,\n tools,\n retryOn,\n onFailure,\n backoffFactor,\n initialDelayMs,\n maxDelayMs,\n jitter,\n } = ToolRetryMiddlewareOptionsSchema.parse(config);\n\n // Extract tool names from BaseTool instances or strings\n const toolFilter: string[] = [];\n for (const tool of tools ?? []) {\n if (typeof tool === \"string\") {\n toolFilter.push(tool);\n } else if (\"name\" in tool && typeof tool.name === \"string\") {\n toolFilter.push(tool.name);\n } else {\n throw new TypeError(\n \"Expected a tool name string or tool instance to be passed to toolRetryMiddleware\"\n );\n }\n }\n\n /**\n * Check if retry logic should apply to this tool.\n */\n const shouldRetryTool = (toolName: string): boolean => {\n if (toolFilter.length === 0) {\n return true;\n }\n return toolFilter.includes(toolName);\n };\n\n /**\n * Check if the exception should trigger a retry.\n */\n const shouldRetryException = (error: Error): boolean => {\n if (typeof retryOn === \"function\") {\n return retryOn(error);\n }\n // retryOn is an array of error constructors\n return retryOn.some(\n (ErrorConstructor) => error.constructor === ErrorConstructor\n );\n };\n\n // Use the exported calculateRetryDelay function with our config\n const delayConfig = { backoffFactor, initialDelayMs, maxDelayMs, jitter };\n\n /**\n * Format the failure message when retries are exhausted.\n */\n const formatFailureMessage = (\n toolName: string,\n error: Error,\n attemptsMade: number\n ): string => {\n const errorType = error.constructor.name;\n const attemptWord = attemptsMade === 1 ? \"attempt\" : \"attempts\";\n return `Tool '${toolName}' failed after ${attemptsMade} ${attemptWord} with ${errorType}`;\n };\n\n /**\n * Handle failure when all retries are exhausted.\n */\n const handleFailure = (\n toolName: string,\n toolCallId: string,\n error: Error,\n attemptsMade: number\n ): ToolMessage => {\n if (onFailure === \"raise\") {\n throw error;\n }\n\n let content: string;\n if (typeof onFailure === \"function\") {\n content = onFailure(error);\n } else {\n content = formatFailureMessage(toolName, error, attemptsMade);\n }\n\n return new ToolMessage({\n content,\n tool_call_id: toolCallId,\n name: toolName,\n status: \"error\",\n });\n };\n\n return createMiddleware({\n name: \"toolRetryMiddleware\",\n contextSchema: ToolRetryMiddlewareOptionsSchema,\n wrapToolCall: async (request, handler) => {\n const toolName = request.tool.name as string;\n\n // Check if retry should apply to this tool\n if (!shouldRetryTool(toolName)) {\n return handler(request);\n }\n\n const toolCallId = request.toolCall.id ?? \"\";\n\n // Initial attempt + retries\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await handler(request);\n } catch (error) {\n const attemptsMade = attempt + 1; // attempt is 0-indexed\n\n // Ensure error is an Error instance\n const err =\n error && typeof error === \"object\" && \"message\" in error\n ? (error as Error)\n : new Error(String(error));\n\n // Check if we should retry this exception\n if (!shouldRetryException(err)) {\n // Exception is not retryable, handle failure immediately\n return handleFailure(toolName, toolCallId, err, attemptsMade);\n }\n\n // Check if we have more retries left\n if (attempt < maxRetries) {\n // Calculate and apply backoff delay\n const delay = calculateRetryDelay(delayConfig, attempt);\n if (delay > 0) {\n await sleep(delay);\n }\n // Continue to next retry\n } else {\n // No more retries, handle failure\n return handleFailure(toolName, toolCallId, err, attemptsMade);\n }\n }\n }\n\n // Unreachable: loop always returns via handler success or handleFailure\n throw new Error(\"Unexpected: retry loop completed without returning\");\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { AgentMiddleware } from "./types.cjs";
|
|
2
|
+
import { ClientTool, ServerTool } from "@langchain/core/tools";
|
|
3
|
+
import { z } from "zod/v3";
|
|
4
|
+
|
|
5
|
+
//#region src/agents/middleware/toolRetry.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration options for the Tool Retry Middleware.
|
|
9
|
+
*/
|
|
10
|
+
declare const ToolRetryMiddlewareOptionsSchema: z.ZodObject<{
|
|
11
|
+
/**
|
|
12
|
+
* Maximum number of retry attempts after the initial call.
|
|
13
|
+
* Default is 2 retries (3 total attempts). Must be >= 0.
|
|
14
|
+
*/
|
|
15
|
+
maxRetries: z.ZodDefault<z.ZodNumber>;
|
|
16
|
+
/**
|
|
17
|
+
* Optional list of tools or tool names to apply retry logic to.
|
|
18
|
+
* Can be a list of `BaseTool` instances or tool name strings.
|
|
19
|
+
* If `undefined`, applies to all tools. Default is `undefined`.
|
|
20
|
+
*/
|
|
21
|
+
tools: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodType<ClientTool, z.ZodTypeDef, ClientTool>, z.ZodType<ServerTool, z.ZodTypeDef, ServerTool>, z.ZodString]>, "many">>;
|
|
22
|
+
/**
|
|
23
|
+
* Either an array of error constructors to retry on, or a function
|
|
24
|
+
* that takes an error and returns `true` if it should be retried.
|
|
25
|
+
* Default is to retry on all errors.
|
|
26
|
+
*/
|
|
27
|
+
retryOn: z.ZodDefault<z.ZodUnion<[z.ZodFunction<z.ZodTuple<[z.ZodType<Error, z.ZodTypeDef, Error>], z.ZodUnknown>, z.ZodBoolean>, z.ZodArray<z.ZodType<new (...args: any[]) => Error, z.ZodTypeDef, new (...args: any[]) => Error>, "many">]>>;
|
|
28
|
+
/**
|
|
29
|
+
* Behavior when all retries are exhausted. Options:
|
|
30
|
+
* - `"return_message"` (default): Return a ToolMessage with error details,
|
|
31
|
+
* allowing the LLM to handle the failure and potentially recover.
|
|
32
|
+
* - `"raise"`: Re-raise the exception, stopping agent execution.
|
|
33
|
+
* - Custom function: Function that takes the exception and returns a string
|
|
34
|
+
* for the ToolMessage content, allowing custom error formatting.
|
|
35
|
+
*/
|
|
36
|
+
onFailure: z.ZodDefault<z.ZodUnion<[z.ZodLiteral<"raise">, z.ZodLiteral<"return_message">, z.ZodFunction<z.ZodTuple<[z.ZodType<Error, z.ZodTypeDef, Error>], z.ZodUnknown>, z.ZodString>]>>;
|
|
37
|
+
/**
|
|
38
|
+
* Multiplier for exponential backoff. Each retry waits
|
|
39
|
+
* `initialDelayMs * (backoffFactor ** retryNumber)` milliseconds.
|
|
40
|
+
* Set to 0.0 for constant delay. Default is 2.0.
|
|
41
|
+
*/
|
|
42
|
+
backoffFactor: z.ZodDefault<z.ZodNumber>;
|
|
43
|
+
/**
|
|
44
|
+
* Initial delay in milliseconds before first retry. Default is 1000 (1 second).
|
|
45
|
+
*/
|
|
46
|
+
initialDelayMs: z.ZodDefault<z.ZodNumber>;
|
|
47
|
+
/**
|
|
48
|
+
* Maximum delay in milliseconds between retries. Caps exponential
|
|
49
|
+
* backoff growth. Default is 60000 (60 seconds).
|
|
50
|
+
*/
|
|
51
|
+
maxDelayMs: z.ZodDefault<z.ZodNumber>;
|
|
52
|
+
/**
|
|
53
|
+
* Whether to add random jitter (±25%) to delay to avoid thundering herd.
|
|
54
|
+
* Default is `true`.
|
|
55
|
+
*/
|
|
56
|
+
jitter: z.ZodDefault<z.ZodBoolean>;
|
|
57
|
+
}, "strip", z.ZodTypeAny, {
|
|
58
|
+
maxRetries: number;
|
|
59
|
+
tools?: (string | ServerTool | ClientTool)[] | undefined;
|
|
60
|
+
retryOn: (new (...args: any[]) => Error)[] | ((args_0: Error, ...args: unknown[]) => boolean);
|
|
61
|
+
onFailure: "raise" | "return_message" | ((args_0: Error, ...args: unknown[]) => string);
|
|
62
|
+
backoffFactor: number;
|
|
63
|
+
initialDelayMs: number;
|
|
64
|
+
maxDelayMs: number;
|
|
65
|
+
jitter: boolean;
|
|
66
|
+
}, {
|
|
67
|
+
maxRetries?: number | undefined;
|
|
68
|
+
tools?: (string | ServerTool | ClientTool)[] | undefined;
|
|
69
|
+
retryOn?: (new (...args: any[]) => Error)[] | ((args_0: Error, ...args: unknown[]) => boolean) | undefined;
|
|
70
|
+
onFailure?: "raise" | "return_message" | ((args_0: Error, ...args: unknown[]) => string) | undefined;
|
|
71
|
+
backoffFactor?: number | undefined;
|
|
72
|
+
initialDelayMs?: number | undefined;
|
|
73
|
+
maxDelayMs?: number | undefined;
|
|
74
|
+
jitter?: boolean | undefined;
|
|
75
|
+
}>;
|
|
76
|
+
type ToolRetryMiddlewareConfig = z.input<typeof ToolRetryMiddlewareOptionsSchema>;
|
|
77
|
+
/**
|
|
78
|
+
* Middleware that automatically retries failed tool calls with configurable backoff.
|
|
79
|
+
*
|
|
80
|
+
* Supports retrying on specific exceptions and exponential backoff.
|
|
81
|
+
*
|
|
82
|
+
* @example Basic usage with default settings (2 retries, exponential backoff)
|
|
83
|
+
* ```ts
|
|
84
|
+
* import { createAgent, toolRetryMiddleware } from "langchain";
|
|
85
|
+
*
|
|
86
|
+
* const agent = createAgent({
|
|
87
|
+
* model: "openai:gpt-4o",
|
|
88
|
+
* tools: [searchTool],
|
|
89
|
+
* middleware: [toolRetryMiddleware()],
|
|
90
|
+
* });
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example Retry specific exceptions only
|
|
94
|
+
* ```ts
|
|
95
|
+
* import { toolRetryMiddleware } from "langchain";
|
|
96
|
+
*
|
|
97
|
+
* const retry = toolRetryMiddleware({
|
|
98
|
+
* maxRetries: 4,
|
|
99
|
+
* retryOn: [TimeoutError, NetworkError],
|
|
100
|
+
* backoffFactor: 1.5,
|
|
101
|
+
* });
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* @example Custom exception filtering
|
|
105
|
+
* ```ts
|
|
106
|
+
* function shouldRetry(error: Error): boolean {
|
|
107
|
+
* // Only retry on 5xx errors
|
|
108
|
+
* if (error.name === "HTTPError" && "statusCode" in error) {
|
|
109
|
+
* const statusCode = (error as any).statusCode;
|
|
110
|
+
* return 500 <= statusCode && statusCode < 600;
|
|
111
|
+
* }
|
|
112
|
+
* return false;
|
|
113
|
+
* }
|
|
114
|
+
*
|
|
115
|
+
* const retry = toolRetryMiddleware({
|
|
116
|
+
* maxRetries: 3,
|
|
117
|
+
* retryOn: shouldRetry,
|
|
118
|
+
* });
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @example Apply to specific tools with custom error handling
|
|
122
|
+
* ```ts
|
|
123
|
+
* const formatError = (error: Error) =>
|
|
124
|
+
* "Database temporarily unavailable. Please try again later.";
|
|
125
|
+
*
|
|
126
|
+
* const retry = toolRetryMiddleware({
|
|
127
|
+
* maxRetries: 4,
|
|
128
|
+
* tools: ["search_database"],
|
|
129
|
+
* onFailure: formatError,
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* @example Apply to specific tools using BaseTool instances
|
|
134
|
+
* ```ts
|
|
135
|
+
* import { tool } from "@langchain/core/tools";
|
|
136
|
+
* import { z } from "zod";
|
|
137
|
+
*
|
|
138
|
+
* const searchDatabase = tool(
|
|
139
|
+
* async ({ query }) => {
|
|
140
|
+
* // Search implementation
|
|
141
|
+
* return results;
|
|
142
|
+
* },
|
|
143
|
+
* {
|
|
144
|
+
* name: "search_database",
|
|
145
|
+
* description: "Search the database",
|
|
146
|
+
* schema: z.object({ query: z.string() }),
|
|
147
|
+
* }
|
|
148
|
+
* );
|
|
149
|
+
*
|
|
150
|
+
* const retry = toolRetryMiddleware({
|
|
151
|
+
* maxRetries: 4,
|
|
152
|
+
* tools: [searchDatabase], // Pass BaseTool instance
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
155
|
+
*
|
|
156
|
+
* @example Constant backoff (no exponential growth)
|
|
157
|
+
* ```ts
|
|
158
|
+
* const retry = toolRetryMiddleware({
|
|
159
|
+
* maxRetries: 5,
|
|
160
|
+
* backoffFactor: 0.0, // No exponential growth
|
|
161
|
+
* initialDelayMs: 2000, // Always wait 2 seconds
|
|
162
|
+
* });
|
|
163
|
+
* ```
|
|
164
|
+
*
|
|
165
|
+
* @example Raise exception on failure
|
|
166
|
+
* ```ts
|
|
167
|
+
* const retry = toolRetryMiddleware({
|
|
168
|
+
* maxRetries: 2,
|
|
169
|
+
* onFailure: "raise", // Re-raise exception instead of returning message
|
|
170
|
+
* });
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* @param config - Configuration options for the retry middleware
|
|
174
|
+
* @returns A middleware instance that handles tool failures with retries
|
|
175
|
+
*/
|
|
176
|
+
declare function toolRetryMiddleware(config?: ToolRetryMiddlewareConfig): AgentMiddleware;
|
|
177
|
+
//#endregion
|
|
178
|
+
export { ToolRetryMiddlewareConfig, toolRetryMiddleware };
|
|
179
|
+
//# sourceMappingURL=toolRetry.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolRetry.d.cts","names":["z","ClientTool","ServerTool","AgentMiddleware","calculateRetryDelay","ToolRetryMiddlewareOptionsSchema","ZodNumber","ZodDefault","ZodTypeDef","ZodType","ZodString","ZodUnion","ZodArray","ZodOptional","Error","ZodUnknown","ZodTuple","ZodBoolean","ZodFunction","ZodLiteral","ZodTypeAny","ZodObject","ToolRetryMiddlewareConfig","input","toolRetryMiddleware"],"sources":["../../../src/agents/middleware/toolRetry.d.ts"],"sourcesContent":["/**\n * Tool retry middleware for agents.\n */\nimport { z } from \"zod/v3\";\nimport type { ClientTool, ServerTool } from \"@langchain/core/tools\";\nimport type { AgentMiddleware } from \"./types.js\";\n/**\n * Calculate delay for a retry attempt with exponential backoff and jitter.\n *\n * @param retryNumber - The retry attempt number (0-indexed)\n * @param config - Configuration for backoff calculation\n * @returns Delay in milliseconds before next retry\n *\n * @internal Exported for testing purposes\n */\nexport declare function calculateRetryDelay(config: {\n backoffFactor: number;\n initialDelayMs: number;\n maxDelayMs: number;\n jitter: boolean;\n}, retryNumber: number): number;\n/**\n * Configuration options for the Tool Retry Middleware.\n */\nexport declare const ToolRetryMiddlewareOptionsSchema: z.ZodObject<{\n /**\n * Maximum number of retry attempts after the initial call.\n * Default is 2 retries (3 total attempts). Must be >= 0.\n */\n maxRetries: z.ZodDefault<z.ZodNumber>;\n /**\n * Optional list of tools or tool names to apply retry logic to.\n * Can be a list of `BaseTool` instances or tool name strings.\n * If `undefined`, applies to all tools. Default is `undefined`.\n */\n tools: z.ZodOptional<z.ZodArray<z.ZodUnion<[z.ZodType<ClientTool, z.ZodTypeDef, ClientTool>, z.ZodType<ServerTool, z.ZodTypeDef, ServerTool>, z.ZodString]>, \"many\">>;\n /**\n * Either an array of error constructors to retry on, or a function\n * that takes an error and returns `true` if it should be retried.\n * Default is to retry on all errors.\n */\n retryOn: z.ZodDefault<z.ZodUnion<[z.ZodFunction<z.ZodTuple<[z.ZodType<Error, z.ZodTypeDef, Error>], z.ZodUnknown>, z.ZodBoolean>, z.ZodArray<z.ZodType<new (...args: any[]) => Error, z.ZodTypeDef, new (...args: any[]) => Error>, \"many\">]>>;\n /**\n * Behavior when all retries are exhausted. Options:\n * - `\"return_message\"` (default): Return a ToolMessage with error details,\n * allowing the LLM to handle the failure and potentially recover.\n * - `\"raise\"`: Re-raise the exception, stopping agent execution.\n * - Custom function: Function that takes the exception and returns a string\n * for the ToolMessage content, allowing custom error formatting.\n */\n onFailure: z.ZodDefault<z.ZodUnion<[z.ZodLiteral<\"raise\">, z.ZodLiteral<\"return_message\">, z.ZodFunction<z.ZodTuple<[z.ZodType<Error, z.ZodTypeDef, Error>], z.ZodUnknown>, z.ZodString>]>>;\n /**\n * Multiplier for exponential backoff. Each retry waits\n * `initialDelayMs * (backoffFactor ** retryNumber)` milliseconds.\n * Set to 0.0 for constant delay. Default is 2.0.\n */\n backoffFactor: z.ZodDefault<z.ZodNumber>;\n /**\n * Initial delay in milliseconds before first retry. Default is 1000 (1 second).\n */\n initialDelayMs: z.ZodDefault<z.ZodNumber>;\n /**\n * Maximum delay in milliseconds between retries. Caps exponential\n * backoff growth. Default is 60000 (60 seconds).\n */\n maxDelayMs: z.ZodDefault<z.ZodNumber>;\n /**\n * Whether to add random jitter (±25%) to delay to avoid thundering herd.\n * Default is `true`.\n */\n jitter: z.ZodDefault<z.ZodBoolean>;\n}, \"strip\", z.ZodTypeAny, {\n maxRetries: number;\n tools?: (string | ServerTool | ClientTool)[] | undefined;\n retryOn: (new (...args: any[]) => Error)[] | ((args_0: Error, ...args: unknown[]) => boolean);\n onFailure: \"raise\" | \"return_message\" | ((args_0: Error, ...args: unknown[]) => string);\n backoffFactor: number;\n initialDelayMs: number;\n maxDelayMs: number;\n jitter: boolean;\n}, {\n maxRetries?: number | undefined;\n tools?: (string | ServerTool | ClientTool)[] | undefined;\n retryOn?: (new (...args: any[]) => Error)[] | ((args_0: Error, ...args: unknown[]) => boolean) | undefined;\n onFailure?: \"raise\" | \"return_message\" | ((args_0: Error, ...args: unknown[]) => string) | undefined;\n backoffFactor?: number | undefined;\n initialDelayMs?: number | undefined;\n maxDelayMs?: number | undefined;\n jitter?: boolean | undefined;\n}>;\nexport type ToolRetryMiddlewareConfig = z.input<typeof ToolRetryMiddlewareOptionsSchema>;\n/**\n * Middleware that automatically retries failed tool calls with configurable backoff.\n *\n * Supports retrying on specific exceptions and exponential backoff.\n *\n * @example Basic usage with default settings (2 retries, exponential backoff)\n * ```ts\n * import { createAgent, toolRetryMiddleware } from \"langchain\";\n *\n * const agent = createAgent({\n * model: \"openai:gpt-4o\",\n * tools: [searchTool],\n * middleware: [toolRetryMiddleware()],\n * });\n * ```\n *\n * @example Retry specific exceptions only\n * ```ts\n * import { toolRetryMiddleware } from \"langchain\";\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 4,\n * retryOn: [TimeoutError, NetworkError],\n * backoffFactor: 1.5,\n * });\n * ```\n *\n * @example Custom exception filtering\n * ```ts\n * function shouldRetry(error: Error): boolean {\n * // Only retry on 5xx errors\n * if (error.name === \"HTTPError\" && \"statusCode\" in error) {\n * const statusCode = (error as any).statusCode;\n * return 500 <= statusCode && statusCode < 600;\n * }\n * return false;\n * }\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 3,\n * retryOn: shouldRetry,\n * });\n * ```\n *\n * @example Apply to specific tools with custom error handling\n * ```ts\n * const formatError = (error: Error) =>\n * \"Database temporarily unavailable. Please try again later.\";\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 4,\n * tools: [\"search_database\"],\n * onFailure: formatError,\n * });\n * ```\n *\n * @example Apply to specific tools using BaseTool instances\n * ```ts\n * import { tool } from \"@langchain/core/tools\";\n * import { z } from \"zod\";\n *\n * const searchDatabase = tool(\n * async ({ query }) => {\n * // Search implementation\n * return results;\n * },\n * {\n * name: \"search_database\",\n * description: \"Search the database\",\n * schema: z.object({ query: z.string() }),\n * }\n * );\n *\n * const retry = toolRetryMiddleware({\n * maxRetries: 4,\n * tools: [searchDatabase], // Pass BaseTool instance\n * });\n * ```\n *\n * @example Constant backoff (no exponential growth)\n * ```ts\n * const retry = toolRetryMiddleware({\n * maxRetries: 5,\n * backoffFactor: 0.0, // No exponential growth\n * initialDelayMs: 2000, // Always wait 2 seconds\n * });\n * ```\n *\n * @example Raise exception on failure\n * ```ts\n * const retry = toolRetryMiddleware({\n * maxRetries: 2,\n * onFailure: \"raise\", // Re-raise exception instead of returning message\n * });\n * ```\n *\n * @param config - Configuration options for the retry middleware\n * @returns A middleware instance that handles tool failures with retries\n */\nexport declare function toolRetryMiddleware(config?: ToolRetryMiddlewareConfig): AgentMiddleware;\n"],"mappings":"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"}
|