langchain 1.5.14 → 1.5.15
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 +6 -0
- package/dist/agents/middleware/toolRetry.cjs +2 -0
- package/dist/agents/middleware/toolRetry.cjs.map +1 -1
- package/dist/agents/middleware/toolRetry.d.cts.map +1 -1
- package/dist/agents/middleware/toolRetry.d.ts.map +1 -1
- package/dist/agents/middleware/toolRetry.js +2 -0
- package/dist/agents/middleware/toolRetry.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# langchain
|
|
2
2
|
|
|
3
|
+
## 1.5.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#11649](https://github.com/langchain-ai/langchainjs/pull/11649) [`17b6bba`](https://github.com/langchain-ai/langchainjs/commit/17b6bba8343bdd3f3dfc5c59578e0b72337ec740) Thanks [@byhow](https://github.com/byhow)! - Preserve LangGraph interrupts in `toolRetryMiddleware` instead of retrying them or converting them into tool errors. Interrupted tool calls can pause and resume through their checkpointer without being treated as failures.
|
|
8
|
+
|
|
3
9
|
## 1.5.14
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
|
@@ -3,6 +3,7 @@ const require_middleware = require("../middleware.cjs");
|
|
|
3
3
|
const require_constants = require("./constants.cjs");
|
|
4
4
|
const require_error = require("./error.cjs");
|
|
5
5
|
let _langchain_core_messages = require("@langchain/core/messages");
|
|
6
|
+
let _langchain_langgraph = require("@langchain/langgraph");
|
|
6
7
|
let zod_v3 = require("zod/v3");
|
|
7
8
|
//#region src/agents/middleware/toolRetry.ts
|
|
8
9
|
/**
|
|
@@ -211,6 +212,7 @@ function toolRetryMiddleware(config = {}) {
|
|
|
211
212
|
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
212
213
|
return await handler(request);
|
|
213
214
|
} catch (error) {
|
|
215
|
+
if ((0, _langchain_langgraph.isGraphBubbleUp)(error)) throw error;
|
|
214
216
|
const attemptsMade = attempt + 1;
|
|
215
217
|
const err = error && typeof error === "object" && "message" in error ? error : new Error(String(error));
|
|
216
218
|
if (!shouldRetryException(err)) return handleFailure(toolName, toolCallId, err, attemptsMade);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolRetry.cjs","names":["z","RetrySchema","InvalidRetryConfigError","ToolMessage","createMiddleware","calculateRetryDelay","getRetryAfterMs","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 { sleep, calculateRetryDelay, getRetryAfterMs } from \"./utils.js\";\nimport { RetrySchema } from \"./constants.js\";\nimport { InvalidRetryConfigError } from \"./error.js\";\n\n/**\n * Configuration options for the Tool Retry Middleware.\n */\nexport const ToolRetryMiddlewareOptionsSchema = z\n .object({\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 * Behavior when all retries are exhausted. Options:\n * - `\"continue\"` (default): Return an AIMessage with error details, allowing\n * the agent to potentially handle the failure gracefully.\n * - `\"error\"`: Re-raise the exception, stopping agent execution.\n * - Custom function: Function that takes the exception and returns a string\n * for the AIMessage content, allowing custom error formatting.\n *\n * Deprecated values:\n * - `\"raise\"`: use `\"error\"` instead.\n * - `\"return_message\"`: use `\"continue\"` instead.\n */\n onFailure: z\n .union([\n z.literal(\"error\"),\n z.literal(\"continue\"),\n /**\n * @deprecated Use `\"error\"` instead.\n */\n z.literal(\"raise\"),\n /**\n * @deprecated Use `\"continue\"` instead.\n */\n z.literal(\"return_message\"),\n z.function().args(z.instanceof(Error)).returns(z.string()),\n ])\n .default(\"continue\"),\n })\n .merge(RetrySchema);\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(config: ToolRetryMiddlewareConfig = {}) {\n const { success, error, data } =\n ToolRetryMiddlewareOptionsSchema.safeParse(config);\n if (!success) {\n throw new InvalidRetryConfigError(error);\n }\n const {\n maxRetries,\n tools,\n retryOn,\n onFailure: onFailureConfig,\n backoffFactor,\n initialDelayMs,\n maxDelayMs,\n jitter,\n } = data;\n\n let onFailure = onFailureConfig;\n if (onFailureConfig === \"raise\") {\n console.warn(\n \"⚠️ `onFailure: 'raise'` is deprecated. Use `onFailure: 'error'` instead.\"\n );\n onFailure = \"error\";\n } else if (onFailureConfig === \"return_message\") {\n console.warn(\n \"⚠️ `onFailure: 'return_message'` is deprecated. Use `onFailure: 'continue'` instead.\"\n );\n onFailure = \"continue\";\n }\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((ErrorConstructor) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n return error instanceof 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 === \"error\") {\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 ?? request.toolCall.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(\n delayConfig,\n attempt,\n getRetryAfterMs(error)\n );\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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"}
|
|
1
|
+
{"version":3,"file":"toolRetry.cjs","names":["z","RetrySchema","InvalidRetryConfigError","ToolMessage","createMiddleware","isGraphBubbleUp","calculateRetryDelay","getRetryAfterMs","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\";\nimport { isGraphBubbleUp } from \"@langchain/langgraph\";\nimport { createMiddleware } from \"../middleware.js\";\nimport { sleep, calculateRetryDelay, getRetryAfterMs } from \"./utils.js\";\nimport { RetrySchema } from \"./constants.js\";\nimport { InvalidRetryConfigError } from \"./error.js\";\n\n/**\n * Configuration options for the Tool Retry Middleware.\n */\nexport const ToolRetryMiddlewareOptionsSchema = z\n .object({\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 * Behavior when all retries are exhausted. Options:\n * - `\"continue\"` (default): Return an AIMessage with error details, allowing\n * the agent to potentially handle the failure gracefully.\n * - `\"error\"`: Re-raise the exception, stopping agent execution.\n * - Custom function: Function that takes the exception and returns a string\n * for the AIMessage content, allowing custom error formatting.\n *\n * Deprecated values:\n * - `\"raise\"`: use `\"error\"` instead.\n * - `\"return_message\"`: use `\"continue\"` instead.\n */\n onFailure: z\n .union([\n z.literal(\"error\"),\n z.literal(\"continue\"),\n /**\n * @deprecated Use `\"error\"` instead.\n */\n z.literal(\"raise\"),\n /**\n * @deprecated Use `\"continue\"` instead.\n */\n z.literal(\"return_message\"),\n z.function().args(z.instanceof(Error)).returns(z.string()),\n ])\n .default(\"continue\"),\n })\n .merge(RetrySchema);\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(config: ToolRetryMiddlewareConfig = {}) {\n const { success, error, data } =\n ToolRetryMiddlewareOptionsSchema.safeParse(config);\n if (!success) {\n throw new InvalidRetryConfigError(error);\n }\n const {\n maxRetries,\n tools,\n retryOn,\n onFailure: onFailureConfig,\n backoffFactor,\n initialDelayMs,\n maxDelayMs,\n jitter,\n } = data;\n\n let onFailure = onFailureConfig;\n if (onFailureConfig === \"raise\") {\n console.warn(\n \"⚠️ `onFailure: 'raise'` is deprecated. Use `onFailure: 'error'` instead.\"\n );\n onFailure = \"error\";\n } else if (onFailureConfig === \"return_message\") {\n console.warn(\n \"⚠️ `onFailure: 'return_message'` is deprecated. Use `onFailure: 'continue'` instead.\"\n );\n onFailure = \"continue\";\n }\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((ErrorConstructor) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n return error instanceof 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 === \"error\") {\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 ?? request.toolCall.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 if (isGraphBubbleUp(error)) {\n throw error;\n }\n\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(\n delayConfig,\n attempt,\n getRetryAfterMs(error)\n );\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolRetry.d.cts","names":[],"sources":["../../../src/agents/middleware/toolRetry.ts"],"mappings":";;;;;;;cAea,kCAAgC,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2CjC,4BAA4B,EAAE,aACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsGO,oBAAoB,SAAQ,
|
|
1
|
+
{"version":3,"file":"toolRetry.d.cts","names":[],"sources":["../../../src/agents/middleware/toolRetry.ts"],"mappings":";;;;;;;cAea,kCAAgC,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2CjC,4BAA4B,EAAE,aACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsGO,oBAAoB,SAAQ,4BA2K3C,2BA3KyE,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA2KzE,aAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolRetry.d.ts","names":[],"sources":["../../../src/agents/middleware/toolRetry.ts"],"mappings":";;;;;;;cAea,kCAAgC,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2CjC,4BAA4B,EAAE,aACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsGO,oBAAoB,SAAQ,
|
|
1
|
+
{"version":3,"file":"toolRetry.d.ts","names":[],"sources":["../../../src/agents/middleware/toolRetry.ts"],"mappings":";;;;;;;cAea,kCAAgC,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2CjC,4BAA4B,EAAE,aACjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAsGO,oBAAoB,SAAQ,4BA2K3C,2BA3KyE,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;aA2KzE,aAAA"}
|
|
@@ -3,6 +3,7 @@ import { createMiddleware } from "../middleware.js";
|
|
|
3
3
|
import { RetrySchema } from "./constants.js";
|
|
4
4
|
import { InvalidRetryConfigError } from "./error.js";
|
|
5
5
|
import { ToolMessage } from "@langchain/core/messages";
|
|
6
|
+
import { isGraphBubbleUp } from "@langchain/langgraph";
|
|
6
7
|
import { z } from "zod/v3";
|
|
7
8
|
//#region src/agents/middleware/toolRetry.ts
|
|
8
9
|
/**
|
|
@@ -211,6 +212,7 @@ function toolRetryMiddleware(config = {}) {
|
|
|
211
212
|
for (let attempt = 0; attempt <= maxRetries; attempt++) try {
|
|
212
213
|
return await handler(request);
|
|
213
214
|
} catch (error) {
|
|
215
|
+
if (isGraphBubbleUp(error)) throw error;
|
|
214
216
|
const attemptsMade = attempt + 1;
|
|
215
217
|
const err = error && typeof error === "object" && "message" in error ? error : new Error(String(error));
|
|
216
218
|
if (!shouldRetryException(err)) return handleFailure(toolName, toolCallId, err, attemptsMade);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolRetry.js","names":[],"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 { sleep, calculateRetryDelay, getRetryAfterMs } from \"./utils.js\";\nimport { RetrySchema } from \"./constants.js\";\nimport { InvalidRetryConfigError } from \"./error.js\";\n\n/**\n * Configuration options for the Tool Retry Middleware.\n */\nexport const ToolRetryMiddlewareOptionsSchema = z\n .object({\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 * Behavior when all retries are exhausted. Options:\n * - `\"continue\"` (default): Return an AIMessage with error details, allowing\n * the agent to potentially handle the failure gracefully.\n * - `\"error\"`: Re-raise the exception, stopping agent execution.\n * - Custom function: Function that takes the exception and returns a string\n * for the AIMessage content, allowing custom error formatting.\n *\n * Deprecated values:\n * - `\"raise\"`: use `\"error\"` instead.\n * - `\"return_message\"`: use `\"continue\"` instead.\n */\n onFailure: z\n .union([\n z.literal(\"error\"),\n z.literal(\"continue\"),\n /**\n * @deprecated Use `\"error\"` instead.\n */\n z.literal(\"raise\"),\n /**\n * @deprecated Use `\"continue\"` instead.\n */\n z.literal(\"return_message\"),\n z.function().args(z.instanceof(Error)).returns(z.string()),\n ])\n .default(\"continue\"),\n })\n .merge(RetrySchema);\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(config: ToolRetryMiddlewareConfig = {}) {\n const { success, error, data } =\n ToolRetryMiddlewareOptionsSchema.safeParse(config);\n if (!success) {\n throw new InvalidRetryConfigError(error);\n }\n const {\n maxRetries,\n tools,\n retryOn,\n onFailure: onFailureConfig,\n backoffFactor,\n initialDelayMs,\n maxDelayMs,\n jitter,\n } = data;\n\n let onFailure = onFailureConfig;\n if (onFailureConfig === \"raise\") {\n console.warn(\n \"⚠️ `onFailure: 'raise'` is deprecated. Use `onFailure: 'error'` instead.\"\n );\n onFailure = \"error\";\n } else if (onFailureConfig === \"return_message\") {\n console.warn(\n \"⚠️ `onFailure: 'return_message'` is deprecated. Use `onFailure: 'continue'` instead.\"\n );\n onFailure = \"continue\";\n }\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((ErrorConstructor) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n return error instanceof 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 === \"error\") {\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 ?? request.toolCall.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(\n delayConfig,\n attempt,\n getRetryAfterMs(error)\n );\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":";;;;;;;;;;;;;AAeA,MAAa,mCAAmC,EAC7C,OAAO;;;;;;CAMN,OAAO,EACJ,MACC,EAAE,MAAM;EAAC,EAAE,OAAmB;EAAG,EAAE,OAAmB;EAAG,EAAE,OAAO;CAAC,CAAC,CACtE,CAAC,CACA,SAAS;;;;;;;;;;;;;CAcZ,WAAW,EACR,MAAM;EACL,EAAE,QAAQ,OAAO;EACjB,EAAE,QAAQ,UAAU;EAIpB,EAAE,QAAQ,OAAO;EAIjB,EAAE,QAAQ,gBAAgB;EAC1B,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC3D,CAAC,CAAC,CACD,QAAQ,UAAU;AACvB,CAAC,CAAC,CACD,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyGpB,SAAgB,oBAAoB,SAAoC,CAAC,GAAG;CAC1E,MAAM,EAAE,SAAS,OAAO,SACtB,iCAAiC,UAAU,MAAM;CACnD,IAAI,CAAC,SACH,MAAM,IAAI,wBAAwB,KAAK;CAEzC,MAAM,EACJ,YACA,OACA,SACA,WAAW,iBACX,eACA,gBACA,YACA,WACE;CAEJ,IAAI,YAAY;CAChB,IAAI,oBAAoB,SAAS;EAC/B,QAAQ,KACN,0EACF;EACA,YAAY;CACd,OAAO,IAAI,oBAAoB,kBAAkB;EAC/C,QAAQ,KACN,sFACF;EACA,YAAY;CACd;CAGA,MAAM,aAAuB,CAAC;CAC9B,KAAK,MAAM,QAAQ,SAAS,CAAC,GAC3B,IAAI,OAAO,SAAS,UAClB,WAAW,KAAK,IAAI;MACf,IAAI,UAAU,QAAQ,OAAO,KAAK,SAAS,UAChD,WAAW,KAAK,KAAK,IAAI;MAEzB,MAAM,IAAI,UACR,kFACF;;;;CAOJ,MAAM,mBAAmB,aAA8B;EACrD,IAAI,WAAW,WAAW,GACxB,OAAO;EAET,OAAO,WAAW,SAAS,QAAQ;CACrC;;;;CAKA,MAAM,wBAAwB,UAA0B;EACtD,IAAI,OAAO,YAAY,YACrB,OAAO,QAAQ,KAAK;EAGtB,OAAO,QAAQ,MAAM,qBAAqB;GAExC,OAAO,iBAAiB;EAC1B,CAAC;CACH;CAGA,MAAM,cAAc;EAAE;EAAe;EAAgB;EAAY;CAAO;;;;CAKxE,MAAM,wBACJ,UACA,OACA,iBACW;EACX,MAAM,YAAY,MAAM,YAAY;EAEpC,OAAO,SAAS,SAAS,iBAAiB,aAAa,GADnC,iBAAiB,IAAI,YAAY,WACiB,QAAQ;CAChF;;;;CAKA,MAAM,iBACJ,UACA,YACA,OACA,iBACgB;EAChB,IAAI,cAAc,SAChB,MAAM;EAGR,IAAI;EACJ,IAAI,OAAO,cAAc,YACvB,UAAU,UAAU,KAAK;OAEzB,UAAU,qBAAqB,UAAU,OAAO,YAAY;EAG9D,OAAO,IAAI,YAAY;GACrB;GACA,cAAc;GACd,MAAM;GACN,QAAQ;EACV,CAAC;CACH;CAEA,OAAO,iBAAiB;EACtB,MAAM;EACN,eAAe;EACf,cAAc,OAAO,SAAS,YAAY;GACxC,MAAM,WAAY,QAAQ,MAAM,QAAQ,QAAQ,SAAS;GAGzD,IAAI,CAAC,gBAAgB,QAAQ,GAC3B,OAAO,QAAQ,OAAO;GAGxB,MAAM,aAAa,QAAQ,SAAS,MAAM;GAG1C,KAAK,IAAI,UAAU,GAAG,WAAW,YAAY,WAC3C,IAAI;IACF,OAAO,MAAM,QAAQ,OAAO;GAC9B,SAAS,OAAO;IACd,MAAM,eAAe,UAAU;IAG/B,MAAM,MACJ,SAAS,OAAO,UAAU,YAAY,aAAa,QAC9C,QACD,IAAI,MAAM,OAAO,KAAK,CAAC;IAG7B,IAAI,CAAC,qBAAqB,GAAG,GAE3B,OAAO,cAAc,UAAU,YAAY,KAAK,YAAY;IAI9D,IAAI,UAAU,YAAY;KAExB,MAAM,QAAQ,oBACZ,aACA,SACA,gBAAgB,KAAK,CACvB;KACA,IAAI,QAAQ,GACV,MAAM,MAAM,KAAK;IAGrB,OAEE,OAAO,cAAc,UAAU,YAAY,KAAK,YAAY;GAEhE;GAIF,MAAM,IAAI,MAAM,oDAAoD;EACtE;CACF,CAAC;AACH"}
|
|
1
|
+
{"version":3,"file":"toolRetry.js","names":[],"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\";\nimport { isGraphBubbleUp } from \"@langchain/langgraph\";\nimport { createMiddleware } from \"../middleware.js\";\nimport { sleep, calculateRetryDelay, getRetryAfterMs } from \"./utils.js\";\nimport { RetrySchema } from \"./constants.js\";\nimport { InvalidRetryConfigError } from \"./error.js\";\n\n/**\n * Configuration options for the Tool Retry Middleware.\n */\nexport const ToolRetryMiddlewareOptionsSchema = z\n .object({\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 * Behavior when all retries are exhausted. Options:\n * - `\"continue\"` (default): Return an AIMessage with error details, allowing\n * the agent to potentially handle the failure gracefully.\n * - `\"error\"`: Re-raise the exception, stopping agent execution.\n * - Custom function: Function that takes the exception and returns a string\n * for the AIMessage content, allowing custom error formatting.\n *\n * Deprecated values:\n * - `\"raise\"`: use `\"error\"` instead.\n * - `\"return_message\"`: use `\"continue\"` instead.\n */\n onFailure: z\n .union([\n z.literal(\"error\"),\n z.literal(\"continue\"),\n /**\n * @deprecated Use `\"error\"` instead.\n */\n z.literal(\"raise\"),\n /**\n * @deprecated Use `\"continue\"` instead.\n */\n z.literal(\"return_message\"),\n z.function().args(z.instanceof(Error)).returns(z.string()),\n ])\n .default(\"continue\"),\n })\n .merge(RetrySchema);\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(config: ToolRetryMiddlewareConfig = {}) {\n const { success, error, data } =\n ToolRetryMiddlewareOptionsSchema.safeParse(config);\n if (!success) {\n throw new InvalidRetryConfigError(error);\n }\n const {\n maxRetries,\n tools,\n retryOn,\n onFailure: onFailureConfig,\n backoffFactor,\n initialDelayMs,\n maxDelayMs,\n jitter,\n } = data;\n\n let onFailure = onFailureConfig;\n if (onFailureConfig === \"raise\") {\n console.warn(\n \"⚠️ `onFailure: 'raise'` is deprecated. Use `onFailure: 'error'` instead.\"\n );\n onFailure = \"error\";\n } else if (onFailureConfig === \"return_message\") {\n console.warn(\n \"⚠️ `onFailure: 'return_message'` is deprecated. Use `onFailure: 'continue'` instead.\"\n );\n onFailure = \"continue\";\n }\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((ErrorConstructor) => {\n // oxlint-disable-next-line no-instanceof/no-instanceof\n return error instanceof 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 === \"error\") {\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 ?? request.toolCall.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 if (isGraphBubbleUp(error)) {\n throw error;\n }\n\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(\n delayConfig,\n attempt,\n getRetryAfterMs(error)\n );\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "langchain",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.15",
|
|
4
4
|
"description": "Typescript bindings for langchain",
|
|
5
5
|
"author": "LangChain",
|
|
6
6
|
"license": "MIT",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
"yaml": "^2.9.0",
|
|
56
56
|
"@langchain/anthropic": "1.5.11",
|
|
57
57
|
"@langchain/aws": "1.4.6",
|
|
58
|
-
"@langchain/core": "^1.2.
|
|
59
|
-
"@langchain/fireworks": "0.2.
|
|
60
|
-
"@langchain/
|
|
61
|
-
"@langchain/
|
|
58
|
+
"@langchain/core": "^1.2.14",
|
|
59
|
+
"@langchain/fireworks": "0.2.16",
|
|
60
|
+
"@langchain/tsconfig": "0.0.1",
|
|
61
|
+
"@langchain/openai": "1.6.1"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@langchain/core": "^1.2.
|
|
64
|
+
"@langchain/core": "^1.2.14"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@langchain/langgraph": "^1.4.13",
|