langchain 1.0.1 → 1.0.3
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/_virtual/rolldown_runtime.cjs +0 -4
- package/dist/_virtual/rolldown_runtime.js +1 -10
- 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 +1 -0
- package/dist/agents/annotation.cjs.map +1 -1
- package/dist/agents/annotation.js +1 -0
- package/dist/agents/annotation.js.map +1 -1
- package/dist/agents/index.cjs.map +1 -1
- package/dist/agents/index.d.cts +11 -11
- package/dist/agents/index.d.cts.map +1 -1
- package/dist/agents/index.d.ts +11 -11
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/middleware/callLimit.d.cts.map +1 -1
- package/dist/agents/middleware/callLimit.d.ts.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.d.cts +15 -0
- package/dist/agents/middleware/index.d.ts +14 -0
- package/dist/agents/middleware/index.js +2 -1
- package/dist/agents/middleware/modelCallLimit.cjs +145 -0
- package/dist/agents/middleware/modelCallLimit.cjs.map +1 -0
- package/dist/agents/middleware/modelCallLimit.d.cts +128 -0
- package/dist/agents/middleware/modelCallLimit.d.cts.map +1 -0
- package/dist/agents/middleware/modelCallLimit.d.ts +128 -0
- package/dist/agents/middleware/modelCallLimit.d.ts.map +1 -0
- package/dist/agents/middleware/modelCallLimit.js +144 -0
- package/dist/agents/middleware/modelCallLimit.js.map +1 -0
- package/dist/agents/middleware/promptCaching.d.cts.map +1 -1
- package/dist/agents/middleware/summarization.cjs +4 -2
- package/dist/agents/middleware/summarization.cjs.map +1 -1
- package/dist/agents/middleware/summarization.d.cts +7 -6
- package/dist/agents/middleware/summarization.d.cts.map +1 -1
- package/dist/agents/middleware/summarization.d.ts +7 -6
- package/dist/agents/middleware/summarization.d.ts.map +1 -1
- package/dist/agents/middleware/summarization.js +4 -2
- package/dist/agents/middleware/summarization.js.map +1 -1
- package/dist/agents/middleware/todoListMiddleware.d.cts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.cjs +205 -92
- package/dist/agents/middleware/toolCallLimit.cjs.map +1 -1
- package/dist/agents/middleware/toolCallLimit.d.cts +46 -37
- package/dist/agents/middleware/toolCallLimit.d.cts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.d.ts +46 -37
- package/dist/agents/middleware/toolCallLimit.d.ts.map +1 -1
- package/dist/agents/middleware/toolCallLimit.js +206 -93
- 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 -47
- package/dist/agents/nodes/AgentNode.cjs.map +1 -1
- package/dist/agents/nodes/AgentNode.js +22 -48
- 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/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 +4 -3
- package/dist/chat_models/universal.cjs.map +1 -1
- package/dist/chat_models/universal.js +4 -3
- package/dist/chat_models/universal.js.map +1 -1
- package/dist/embeddings/cache_backed.cjs +140 -0
- package/dist/embeddings/cache_backed.cjs.map +1 -0
- package/dist/embeddings/cache_backed.d.cts +107 -0
- package/dist/embeddings/cache_backed.d.cts.map +1 -0
- package/dist/embeddings/cache_backed.d.ts +107 -0
- package/dist/embeddings/cache_backed.d.ts.map +1 -0
- package/dist/embeddings/cache_backed.js +134 -0
- package/dist/embeddings/cache_backed.js.map +1 -0
- package/dist/embeddings/fake.cjs +22 -0
- package/dist/embeddings/fake.cjs.map +1 -0
- package/dist/embeddings/fake.d.cts +1 -0
- package/dist/embeddings/fake.d.ts +1 -0
- package/dist/embeddings/fake.js +12 -0
- package/dist/embeddings/fake.js.map +1 -0
- package/dist/hub/base.cjs +4 -17
- package/dist/hub/base.cjs.map +1 -1
- package/dist/hub/base.js +2 -15
- package/dist/hub/base.js.map +1 -1
- package/dist/index.cjs +6 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/package.json +6 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolCallLimit.d.cts","names":["z","InferInteropZodInput","ToolCallLimitExceededError","Error","ToolCallLimitOptionsSchema","ZodString","ZodOptional","ZodNumber","ZodEnum","ZodDefault","ZodTypeAny","ZodObject","ToolCallLimitConfig","toolCallLimitMiddleware","__types_js3","AgentMiddleware"],"sources":["../../../src/agents/middleware/toolCallLimit.d.ts"],"sourcesContent":["import { z } from \"zod/v3\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\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 declare 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 constructor(threadCount: number, runCount: number, threadLimit: number | undefined, runLimit: number | undefined, toolName?: string | undefined);\n}\n/**\n * Options for configuring the Tool Call Limit middleware.\n */\nexport declare const ToolCallLimitOptionsSchema: z.ZodObject<{\n /**\n * Name of the specific tool to limit. If undefined, limits apply to all tools.\n */\n toolName: z.ZodOptional<z.ZodString>;\n /**\n * Maximum number of tool calls allowed per thread.\n * undefined means no limit.\n */\n threadLimit: z.ZodOptional<z.ZodNumber>;\n /**\n * Maximum number of tool calls allowed per run.\n * undefined means no limit.\n */\n runLimit: z.ZodOptional<z.ZodNumber>;\n /**\n * What to do when limits are exceeded.\n * - \"
|
|
1
|
+
{"version":3,"file":"toolCallLimit.d.cts","names":["z","InferInteropZodInput","ToolCallLimitExceededError","Error","ToolCallLimitOptionsSchema","ZodString","ZodOptional","ZodNumber","ZodEnum","ZodDefault","ZodTypeAny","ZodObject","ToolCallLimitConfig","toolCallLimitMiddleware","ZodRecord","Record","__types_js3","AgentMiddleware"],"sources":["../../../src/agents/middleware/toolCallLimit.d.ts"],"sourcesContent":["import { z } from \"zod/v3\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\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 declare 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 constructor(threadCount: number, runCount: number, threadLimit: number | undefined, runLimit: number | undefined, toolName?: string | undefined);\n}\n/**\n * Options for configuring the Tool Call Limit middleware.\n */\nexport declare const ToolCallLimitOptionsSchema: z.ZodObject<{\n /**\n * Name of the specific tool to limit. If undefined, limits apply to all tools.\n */\n toolName: z.ZodOptional<z.ZodString>;\n /**\n * Maximum number of tool calls allowed per thread.\n * undefined means no limit.\n */\n threadLimit: z.ZodOptional<z.ZodNumber>;\n /**\n * Maximum number of tool calls allowed per run.\n * undefined means no limit.\n */\n runLimit: z.ZodOptional<z.ZodNumber>;\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 exitBehavior: z.ZodDefault<z.ZodEnum<[\"continue\", \"error\", \"end\"]>>;\n}, \"strip\", z.ZodTypeAny, {\n toolName?: string | undefined;\n threadLimit?: number | undefined;\n runLimit?: number | undefined;\n exitBehavior: \"continue\" | \"end\" | \"error\";\n}, {\n toolName?: string | undefined;\n threadLimit?: number | undefined;\n runLimit?: number | undefined;\n exitBehavior?: \"continue\" | \"end\" | \"error\" | undefined;\n}>;\nexport type ToolCallLimitConfig = InferInteropZodInput<typeof ToolCallLimitOptionsSchema>;\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.\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 declare function toolCallLimitMiddleware(options: ToolCallLimitConfig): import(\"./types.js\").AgentMiddleware<z.ZodObject<{\n threadToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;\n runToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;\n}, \"strip\", z.ZodTypeAny, {\n threadToolCallCount: Record<string, number>;\n runToolCallCount: Record<string, number>;\n}, {\n threadToolCallCount?: Record<string, number> | undefined;\n runToolCallCount?: Record<string, number> | undefined;\n}>, undefined, any>;\n"],"mappings":";;;;;;;;;AAQA;AA0BA;;AAI4BA,cA9BPE,0BAAAA,SAAmCC,KAAK,CA8B/BE;EAAS;;;EAKT,WAKAE,EAAAA,MAAAA;EAAS;;;EAST,QAChBG,EAAAA,MAAAA;EAAU;AAxBoC;AAmC5D;EAA+B,WAAA,EAAA,MAAA,GAAA,SAAA;EAAA;;AAAuB;EAiF9BG,QAAAA,EAAAA,MAAAA,GAAAA,SAAuB;EAAA;;;EACc,QAAIN,EAAAA,MAAAA,GAAAA,SAAAA;EAAS,WAAlCO,CAAAA,WAAAA,EAAAA,MAAAA,EAAAA,QAAAA,EAAAA,MAAAA,EAAAA,WAAAA,EAAAA,MAAAA,GAAAA,SAAAA,EAAAA,QAAAA,EAAAA,MAAAA,GAAAA,SAAAA,EAAAA,QAAAA,CAAAA,EAAAA,MAAAA,GAAAA,SAAAA;;;;;AAClBd,cAtHDI,0BAsHGK,EAtHyBT,CAAAA,CAAEW,SAsH3BF,CAAAA;EAAU;;;EAGN,QAEFM,EAvHZf,CAAAA,CAAEM,WAuHUS,CAvHEf,CAAAA,CAAEK,SAuHJU,CAAAA;EAAM;;;AAPmF;eA3GlGf,CAAAA,CAAEM,YAAYN,CAAAA,CAAEO;;;;;YAKnBP,CAAAA,CAAEM,YAAYN,CAAAA,CAAEO;;;;;;;;;gBASZP,CAAAA,CAAES,WAAWT,CAAAA,CAAEQ;YACrBR,CAAAA,CAAEU;;;;;;;;;;;KAWFE,mBAAAA,GAAsBX,4BAA4BG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiFtCS,uBAAAA,UAAiCD,sBAAsE,gBAAXZ,CAAAA,CAAEW;uBAC7FX,CAAAA,CAAES,WAAWT,CAAAA,CAAEc,UAAUd,CAAAA,CAAEK,WAAWL,CAAAA,CAAEO;oBAC3CP,CAAAA,CAAES,WAAWT,CAAAA,CAAEc,UAAUd,CAAAA,CAAEK,WAAWL,CAAAA,CAAEO;YAClDP,CAAAA,CAAEU;uBACWK;oBACHA;;wBAEIA;qBACHA"}
|
|
@@ -53,21 +53,23 @@ declare const ToolCallLimitOptionsSchema: z.ZodObject<{
|
|
|
53
53
|
runLimit: z.ZodOptional<z.ZodNumber>;
|
|
54
54
|
/**
|
|
55
55
|
* What to do when limits are exceeded.
|
|
56
|
-
* - "
|
|
57
|
-
*
|
|
58
|
-
* - "
|
|
56
|
+
* - "continue": Block exceeded tools with error messages, let other tools continue (default)
|
|
57
|
+
* - "error": Raise a ToolCallLimitExceededError exception
|
|
58
|
+
* - "end": Stop execution immediately, injecting a ToolMessage and an AI message
|
|
59
|
+
* for the single tool call that exceeded the limit. Raises NotImplementedError
|
|
60
|
+
* if there are multiple tool calls.
|
|
59
61
|
*/
|
|
60
|
-
exitBehavior: z.ZodDefault<z.ZodEnum<["
|
|
62
|
+
exitBehavior: z.ZodDefault<z.ZodEnum<["continue", "error", "end"]>>;
|
|
61
63
|
}, "strip", z.ZodTypeAny, {
|
|
62
64
|
toolName?: string | undefined;
|
|
63
65
|
threadLimit?: number | undefined;
|
|
64
66
|
runLimit?: number | undefined;
|
|
65
|
-
exitBehavior: "end" | "error";
|
|
67
|
+
exitBehavior: "continue" | "end" | "error";
|
|
66
68
|
}, {
|
|
67
69
|
toolName?: string | undefined;
|
|
68
70
|
threadLimit?: number | undefined;
|
|
69
71
|
runLimit?: number | undefined;
|
|
70
|
-
exitBehavior?: "end" | "error" | undefined;
|
|
72
|
+
exitBehavior?: "continue" | "end" | "error" | undefined;
|
|
71
73
|
}>;
|
|
72
74
|
type ToolCallLimitConfig = InferInteropZodInput<typeof ToolCallLimitOptionsSchema>;
|
|
73
75
|
/**
|
|
@@ -88,71 +90,78 @@ type ToolCallLimitConfig = InferInteropZodInput<typeof ToolCallLimitOptionsSchem
|
|
|
88
90
|
* @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.
|
|
89
91
|
* @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.
|
|
90
92
|
* @param options.exitBehavior - What to do when limits are exceeded.
|
|
91
|
-
* - "
|
|
92
|
-
* - "error":
|
|
93
|
+
* - "continue": Block exceeded tools with error messages, let other tools continue. Model decides when to end. (default)
|
|
94
|
+
* - "error": Raise a ToolCallLimitExceededError exception
|
|
95
|
+
* - "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.
|
|
93
96
|
*
|
|
94
|
-
* @throws {Error} If both limits are undefined
|
|
97
|
+
* @throws {Error} If both limits are undefined.
|
|
98
|
+
* @throws {NotImplementedError} If exitBehavior is "end" and there are multiple tool calls.
|
|
95
99
|
*
|
|
96
|
-
* @example
|
|
100
|
+
* @example Continue execution with blocked tools (default)
|
|
97
101
|
* ```ts
|
|
98
102
|
* import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
|
|
99
103
|
* import { createAgent } from "@langchain/langchain/agents";
|
|
100
104
|
*
|
|
101
|
-
*
|
|
105
|
+
* // Block exceeded tools but let other tools and model continue
|
|
106
|
+
* const limiter = toolCallLimitMiddleware({
|
|
102
107
|
* threadLimit: 20,
|
|
103
108
|
* runLimit: 10,
|
|
104
|
-
* exitBehavior: "
|
|
109
|
+
* exitBehavior: "continue", // default
|
|
105
110
|
* });
|
|
106
111
|
*
|
|
107
112
|
* const agent = createAgent({
|
|
108
113
|
* model: "openai:gpt-4o",
|
|
109
|
-
* middleware: [
|
|
114
|
+
* middleware: [limiter]
|
|
110
115
|
* });
|
|
111
116
|
* ```
|
|
112
117
|
*
|
|
113
|
-
* @example
|
|
118
|
+
* @example Stop immediately when limit exceeded
|
|
114
119
|
* ```ts
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* const searchLimiter = toolCallLimitMiddleware({
|
|
119
|
-
* toolName: "search",
|
|
120
|
-
* threadLimit: 5,
|
|
121
|
-
* runLimit: 3,
|
|
120
|
+
* // End execution immediately with an AI message
|
|
121
|
+
* const limiter = toolCallLimitMiddleware({
|
|
122
|
+
* runLimit: 5,
|
|
122
123
|
* exitBehavior: "end"
|
|
123
124
|
* });
|
|
124
125
|
*
|
|
125
126
|
* const agent = createAgent({
|
|
126
127
|
* model: "openai:gpt-4o",
|
|
127
|
-
* middleware: [
|
|
128
|
+
* middleware: [limiter]
|
|
128
129
|
* });
|
|
129
130
|
* ```
|
|
130
131
|
*
|
|
131
|
-
* @example
|
|
132
|
+
* @example Raise exception on limit
|
|
132
133
|
* ```ts
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* const globalLimiter = toolCallLimitMiddleware({
|
|
137
|
-
* threadLimit: 20,
|
|
138
|
-
* runLimit: 10,
|
|
139
|
-
* exitBehavior: "end"
|
|
140
|
-
* });
|
|
141
|
-
*
|
|
142
|
-
* const searchLimiter = toolCallLimitMiddleware({
|
|
134
|
+
* // Strict limit with exception handling
|
|
135
|
+
* const limiter = toolCallLimitMiddleware({
|
|
143
136
|
* toolName: "search",
|
|
144
137
|
* threadLimit: 5,
|
|
145
|
-
*
|
|
146
|
-
* exitBehavior: "end"
|
|
138
|
+
* exitBehavior: "error"
|
|
147
139
|
* });
|
|
148
140
|
*
|
|
149
141
|
* const agent = createAgent({
|
|
150
142
|
* model: "openai:gpt-4o",
|
|
151
|
-
* middleware: [
|
|
143
|
+
* middleware: [limiter]
|
|
152
144
|
* });
|
|
145
|
+
*
|
|
146
|
+
* try {
|
|
147
|
+
* const result = await agent.invoke({ messages: [new HumanMessage("Task")] });
|
|
148
|
+
* } catch (error) {
|
|
149
|
+
* if (error instanceof ToolCallLimitExceededError) {
|
|
150
|
+
* console.log(`Search limit exceeded: ${error}`);
|
|
151
|
+
* }
|
|
152
|
+
* }
|
|
153
153
|
* ```
|
|
154
154
|
*/
|
|
155
|
-
declare function toolCallLimitMiddleware(options: ToolCallLimitConfig): AgentMiddleware<
|
|
155
|
+
declare function toolCallLimitMiddleware(options: ToolCallLimitConfig): AgentMiddleware<z.ZodObject<{
|
|
156
|
+
threadToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
157
|
+
runToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
158
|
+
}, "strip", z.ZodTypeAny, {
|
|
159
|
+
threadToolCallCount: Record<string, number>;
|
|
160
|
+
runToolCallCount: Record<string, number>;
|
|
161
|
+
}, {
|
|
162
|
+
threadToolCallCount?: Record<string, number> | undefined;
|
|
163
|
+
runToolCallCount?: Record<string, number> | undefined;
|
|
164
|
+
}>, undefined, any>;
|
|
156
165
|
//#endregion
|
|
157
166
|
export { ToolCallLimitConfig, ToolCallLimitExceededError, toolCallLimitMiddleware };
|
|
158
167
|
//# sourceMappingURL=toolCallLimit.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolCallLimit.d.ts","names":["z","InferInteropZodInput","ToolCallLimitExceededError","Error","ToolCallLimitOptionsSchema","ZodString","ZodOptional","ZodNumber","ZodEnum","ZodDefault","ZodTypeAny","ZodObject","ToolCallLimitConfig","toolCallLimitMiddleware","
|
|
1
|
+
{"version":3,"file":"toolCallLimit.d.ts","names":["z","InferInteropZodInput","ToolCallLimitExceededError","Error","ToolCallLimitOptionsSchema","ZodString","ZodOptional","ZodNumber","ZodEnum","ZodDefault","ZodTypeAny","ZodObject","ToolCallLimitConfig","toolCallLimitMiddleware","ZodRecord","Record","__types_js14","AgentMiddleware"],"sources":["../../../src/agents/middleware/toolCallLimit.d.ts"],"sourcesContent":["import { z } from \"zod/v3\";\nimport type { InferInteropZodInput } from \"@langchain/core/utils/types\";\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 declare 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 constructor(threadCount: number, runCount: number, threadLimit: number | undefined, runLimit: number | undefined, toolName?: string | undefined);\n}\n/**\n * Options for configuring the Tool Call Limit middleware.\n */\nexport declare const ToolCallLimitOptionsSchema: z.ZodObject<{\n /**\n * Name of the specific tool to limit. If undefined, limits apply to all tools.\n */\n toolName: z.ZodOptional<z.ZodString>;\n /**\n * Maximum number of tool calls allowed per thread.\n * undefined means no limit.\n */\n threadLimit: z.ZodOptional<z.ZodNumber>;\n /**\n * Maximum number of tool calls allowed per run.\n * undefined means no limit.\n */\n runLimit: z.ZodOptional<z.ZodNumber>;\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 exitBehavior: z.ZodDefault<z.ZodEnum<[\"continue\", \"error\", \"end\"]>>;\n}, \"strip\", z.ZodTypeAny, {\n toolName?: string | undefined;\n threadLimit?: number | undefined;\n runLimit?: number | undefined;\n exitBehavior: \"continue\" | \"end\" | \"error\";\n}, {\n toolName?: string | undefined;\n threadLimit?: number | undefined;\n runLimit?: number | undefined;\n exitBehavior?: \"continue\" | \"end\" | \"error\" | undefined;\n}>;\nexport type ToolCallLimitConfig = InferInteropZodInput<typeof ToolCallLimitOptionsSchema>;\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.\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 declare function toolCallLimitMiddleware(options: ToolCallLimitConfig): import(\"./types.js\").AgentMiddleware<z.ZodObject<{\n threadToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;\n runToolCallCount: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodNumber>>;\n}, \"strip\", z.ZodTypeAny, {\n threadToolCallCount: Record<string, number>;\n runToolCallCount: Record<string, number>;\n}, {\n threadToolCallCount?: Record<string, number> | undefined;\n runToolCallCount?: Record<string, number> | undefined;\n}>, undefined, any>;\n"],"mappings":";;;;;;;;;AAQA;AA0BA;;AAI4BA,cA9BPE,0BAAAA,SAAmCC,KAAK,CA8B/BE;EAAS;;;EAKT,WAKAE,EAAAA,MAAAA;EAAS;;;EAST,QAChBG,EAAAA,MAAAA;EAAU;AAxBoC;AAmC5D;EAA+B,WAAA,EAAA,MAAA,GAAA,SAAA;EAAA;;AAAuB;EAiF9BG,QAAAA,EAAAA,MAAAA,GAAAA,SAAuB;EAAA;;;EACc,QAAIN,EAAAA,MAAAA,GAAAA,SAAAA;EAAS,WAAlCO,CAAAA,WAAAA,EAAAA,MAAAA,EAAAA,QAAAA,EAAAA,MAAAA,EAAAA,WAAAA,EAAAA,MAAAA,GAAAA,SAAAA,EAAAA,QAAAA,EAAAA,MAAAA,GAAAA,SAAAA,EAAAA,QAAAA,CAAAA,EAAAA,MAAAA,GAAAA,SAAAA;;;;;AAClBd,cAtHDI,0BAsHGK,EAtHyBT,CAAAA,CAAEW,SAsH3BF,CAAAA;EAAU;;;EAGN,QAEFM,EAvHZf,CAAAA,CAAEM,WAuHUS,CAvHEf,CAAAA,CAAEK,SAuHJU,CAAAA;EAAM;;;AAPmF;eA3GlGf,CAAAA,CAAEM,YAAYN,CAAAA,CAAEO;;;;;YAKnBP,CAAAA,CAAEM,YAAYN,CAAAA,CAAEO;;;;;;;;;gBASZP,CAAAA,CAAES,WAAWT,CAAAA,CAAEQ;YACrBR,CAAAA,CAAEU;;;;;;;;;;;KAWFE,mBAAAA,GAAsBX,4BAA4BG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiFtCS,uBAAAA,UAAiCD,sBAAsE,gBAAXZ,CAAAA,CAAEW;uBAC7FX,CAAAA,CAAES,WAAWT,CAAAA,CAAEc,UAAUd,CAAAA,CAAEK,WAAWL,CAAAA,CAAEO;oBAC3CP,CAAAA,CAAES,WAAWT,CAAAA,CAAEc,UAAUd,CAAAA,CAAEK,WAAWL,CAAAA,CAAEO;YAClDP,CAAAA,CAAEU;uBACWK;oBACHA;;wBAEIA;qBACHA"}
|
|
@@ -1,62 +1,53 @@
|
|
|
1
1
|
import { createMiddleware } from "../middleware.js";
|
|
2
|
-
import { AIMessage,
|
|
2
|
+
import { AIMessage, ToolMessage } from "@langchain/core/messages";
|
|
3
3
|
import { z } from "zod/v3";
|
|
4
4
|
|
|
5
5
|
//#region src/agents/middleware/toolCallLimit.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Build the error message content for ToolMessage when limit is exceeded.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* If undefined, count all tool calls.
|
|
12
|
-
* @returns The total number of tool calls (optionally filtered by toolName).
|
|
13
|
-
*/
|
|
14
|
-
function countToolCallsInMessages(messages, toolName) {
|
|
15
|
-
let count = 0;
|
|
16
|
-
for (const message of messages) if (AIMessage.isInstance(message) && message.tool_calls) if (toolName === void 0) count += message.tool_calls.length;
|
|
17
|
-
else count += message.tool_calls.filter((tc) => tc.name === toolName).length;
|
|
18
|
-
return count;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Get messages from the current run (after the last HumanMessage).
|
|
9
|
+
* This message is sent to the model, so it should not reference thread/run concepts
|
|
10
|
+
* that the model has no notion of.
|
|
22
11
|
*
|
|
23
|
-
* @param
|
|
24
|
-
* @
|
|
12
|
+
* @param toolName - Tool name being limited (if specific tool), or undefined for all tools.
|
|
13
|
+
* @param threadCount - Current thread tool call count.
|
|
14
|
+
* @param runCount - Current run tool call count.
|
|
15
|
+
* @param threadLimit - Thread tool call limit (if set).
|
|
16
|
+
* @param runLimit - Run tool call limit (if set).
|
|
17
|
+
* @returns A concise message. If only run limit is exceeded (not thread limit),
|
|
18
|
+
* returns a simple "limit exceeded" message without instructing model to stop.
|
|
19
|
+
* If thread limit is exceeded, includes instruction not to call again.
|
|
25
20
|
*/
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
lastHumanIndex = i;
|
|
33
|
-
break;
|
|
21
|
+
function buildToolMessageContent(toolName, threadCount, runCount, threadLimit, runLimit) {
|
|
22
|
+
const threadExceeded = threadLimit !== void 0 && threadCount > threadLimit;
|
|
23
|
+
const onlyRunExceeded = runLimit !== void 0 && runCount > runLimit && !threadExceeded;
|
|
24
|
+
if (onlyRunExceeded) {
|
|
25
|
+
if (toolName) return `Tool call limit exceeded for '${toolName}'.`;
|
|
26
|
+
return "Tool call limit exceeded.";
|
|
34
27
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
*/
|
|
38
|
-
if (lastHumanIndex === -1) return messages;
|
|
39
|
-
/**
|
|
40
|
-
* Return messages after the last HumanMessage
|
|
41
|
-
*/
|
|
42
|
-
return messages.slice(lastHumanIndex + 1);
|
|
28
|
+
if (toolName) return `Tool call limit exceeded. Do not call '${toolName}' again.`;
|
|
29
|
+
return "Tool call limit exceeded. Do not make additional tool calls.";
|
|
43
30
|
}
|
|
44
31
|
/**
|
|
45
|
-
* Build
|
|
32
|
+
* Build the final AI message content for 'end' behavior.
|
|
33
|
+
*
|
|
34
|
+
* This message is displayed to the user, so it should include detailed information
|
|
35
|
+
* about which limits were exceeded.
|
|
46
36
|
*
|
|
47
37
|
* @param threadCount - Current thread tool call count.
|
|
48
38
|
* @param runCount - Current run tool call count.
|
|
49
39
|
* @param threadLimit - Thread tool call limit (if set).
|
|
50
40
|
* @param runLimit - Run tool call limit (if set).
|
|
51
41
|
* @param toolName - Tool name being limited (if specific tool), or undefined for all tools.
|
|
52
|
-
* @returns A formatted message describing which limits were
|
|
42
|
+
* @returns A formatted message describing which limits were exceeded.
|
|
53
43
|
*/
|
|
54
|
-
function
|
|
55
|
-
const toolDesc = toolName ? `'${toolName}' tool
|
|
44
|
+
function buildFinalAIMessageContent(threadCount, runCount, threadLimit, runLimit, toolName) {
|
|
45
|
+
const toolDesc = toolName ? `'${toolName}' tool` : "Tool";
|
|
56
46
|
const exceededLimits = [];
|
|
57
|
-
if (threadLimit !== void 0 && threadCount
|
|
58
|
-
if (runLimit !== void 0 && runCount
|
|
59
|
-
|
|
47
|
+
if (threadLimit !== void 0 && threadCount > threadLimit) exceededLimits.push(`thread limit exceeded (${threadCount}/${threadLimit} calls)`);
|
|
48
|
+
if (runLimit !== void 0 && runCount > runLimit) exceededLimits.push(`run limit exceeded (${runCount}/${runLimit} calls)`);
|
|
49
|
+
const limitsText = exceededLimits.join(" and ");
|
|
50
|
+
return `${toolDesc} call limit reached: ${limitsText}.`;
|
|
60
51
|
}
|
|
61
52
|
/**
|
|
62
53
|
* Exception raised when tool call limits are exceeded.
|
|
@@ -86,7 +77,7 @@ var ToolCallLimitExceededError = class extends Error {
|
|
|
86
77
|
*/
|
|
87
78
|
toolName;
|
|
88
79
|
constructor(threadCount, runCount, threadLimit, runLimit, toolName = void 0) {
|
|
89
|
-
const message =
|
|
80
|
+
const message = buildFinalAIMessageContent(threadCount, runCount, threadLimit, runLimit, toolName);
|
|
90
81
|
super(message);
|
|
91
82
|
this.name = "ToolCallLimitExceededError";
|
|
92
83
|
this.threadCount = threadCount;
|
|
@@ -103,8 +94,20 @@ const ToolCallLimitOptionsSchema = z.object({
|
|
|
103
94
|
toolName: z.string().optional(),
|
|
104
95
|
threadLimit: z.number().optional(),
|
|
105
96
|
runLimit: z.number().optional(),
|
|
106
|
-
exitBehavior: z.enum([
|
|
97
|
+
exitBehavior: z.enum([
|
|
98
|
+
"continue",
|
|
99
|
+
"error",
|
|
100
|
+
"end"
|
|
101
|
+
]).default("continue")
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* Middleware state schema to track the number of model calls made at the thread and run level.
|
|
105
|
+
*/
|
|
106
|
+
const stateSchema = z.object({
|
|
107
|
+
threadToolCallCount: z.record(z.string(), z.number()).default({}),
|
|
108
|
+
runToolCallCount: z.record(z.string(), z.number()).default({})
|
|
107
109
|
});
|
|
110
|
+
const DEFAULT_TOOL_COUNT_KEY = "__all__";
|
|
108
111
|
/**
|
|
109
112
|
* Middleware that tracks tool call counts and enforces limits.
|
|
110
113
|
*
|
|
@@ -123,68 +126,66 @@ const ToolCallLimitOptionsSchema = z.object({
|
|
|
123
126
|
* @param options.threadLimit - Maximum number of tool calls allowed per thread. undefined means no limit.
|
|
124
127
|
* @param options.runLimit - Maximum number of tool calls allowed per run. undefined means no limit.
|
|
125
128
|
* @param options.exitBehavior - What to do when limits are exceeded.
|
|
126
|
-
* - "
|
|
127
|
-
* - "error":
|
|
129
|
+
* - "continue": Block exceeded tools with error messages, let other tools continue. Model decides when to end. (default)
|
|
130
|
+
* - "error": Raise a ToolCallLimitExceededError exception
|
|
131
|
+
* - "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.
|
|
128
132
|
*
|
|
129
|
-
* @throws {Error} If both limits are undefined
|
|
133
|
+
* @throws {Error} If both limits are undefined.
|
|
134
|
+
* @throws {NotImplementedError} If exitBehavior is "end" and there are multiple tool calls.
|
|
130
135
|
*
|
|
131
|
-
* @example
|
|
136
|
+
* @example Continue execution with blocked tools (default)
|
|
132
137
|
* ```ts
|
|
133
138
|
* import { toolCallLimitMiddleware } from "@langchain/langchain/agents/middleware";
|
|
134
139
|
* import { createAgent } from "@langchain/langchain/agents";
|
|
135
140
|
*
|
|
136
|
-
*
|
|
141
|
+
* // Block exceeded tools but let other tools and model continue
|
|
142
|
+
* const limiter = toolCallLimitMiddleware({
|
|
137
143
|
* threadLimit: 20,
|
|
138
144
|
* runLimit: 10,
|
|
139
|
-
* exitBehavior: "
|
|
145
|
+
* exitBehavior: "continue", // default
|
|
140
146
|
* });
|
|
141
147
|
*
|
|
142
148
|
* const agent = createAgent({
|
|
143
149
|
* model: "openai:gpt-4o",
|
|
144
|
-
* middleware: [
|
|
150
|
+
* middleware: [limiter]
|
|
145
151
|
* });
|
|
146
152
|
* ```
|
|
147
153
|
*
|
|
148
|
-
* @example
|
|
154
|
+
* @example Stop immediately when limit exceeded
|
|
149
155
|
* ```ts
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* const searchLimiter = toolCallLimitMiddleware({
|
|
154
|
-
* toolName: "search",
|
|
155
|
-
* threadLimit: 5,
|
|
156
|
-
* runLimit: 3,
|
|
156
|
+
* // End execution immediately with an AI message
|
|
157
|
+
* const limiter = toolCallLimitMiddleware({
|
|
158
|
+
* runLimit: 5,
|
|
157
159
|
* exitBehavior: "end"
|
|
158
160
|
* });
|
|
159
161
|
*
|
|
160
162
|
* const agent = createAgent({
|
|
161
163
|
* model: "openai:gpt-4o",
|
|
162
|
-
* middleware: [
|
|
164
|
+
* middleware: [limiter]
|
|
163
165
|
* });
|
|
164
166
|
* ```
|
|
165
167
|
*
|
|
166
|
-
* @example
|
|
168
|
+
* @example Raise exception on limit
|
|
167
169
|
* ```ts
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* const globalLimiter = toolCallLimitMiddleware({
|
|
172
|
-
* threadLimit: 20,
|
|
173
|
-
* runLimit: 10,
|
|
174
|
-
* exitBehavior: "end"
|
|
175
|
-
* });
|
|
176
|
-
*
|
|
177
|
-
* const searchLimiter = toolCallLimitMiddleware({
|
|
170
|
+
* // Strict limit with exception handling
|
|
171
|
+
* const limiter = toolCallLimitMiddleware({
|
|
178
172
|
* toolName: "search",
|
|
179
173
|
* threadLimit: 5,
|
|
180
|
-
*
|
|
181
|
-
* exitBehavior: "end"
|
|
174
|
+
* exitBehavior: "error"
|
|
182
175
|
* });
|
|
183
176
|
*
|
|
184
177
|
* const agent = createAgent({
|
|
185
178
|
* model: "openai:gpt-4o",
|
|
186
|
-
* middleware: [
|
|
179
|
+
* middleware: [limiter]
|
|
187
180
|
* });
|
|
181
|
+
*
|
|
182
|
+
* try {
|
|
183
|
+
* const result = await agent.invoke({ messages: [new HumanMessage("Task")] });
|
|
184
|
+
* } catch (error) {
|
|
185
|
+
* if (error instanceof ToolCallLimitExceededError) {
|
|
186
|
+
* console.log(`Search limit exceeded: ${error}`);
|
|
187
|
+
* }
|
|
188
|
+
* }
|
|
188
189
|
* ```
|
|
189
190
|
*/
|
|
190
191
|
function toolCallLimitMiddleware(options) {
|
|
@@ -193,47 +194,159 @@ function toolCallLimitMiddleware(options) {
|
|
|
193
194
|
*/
|
|
194
195
|
if (options.threadLimit === void 0 && options.runLimit === void 0) throw new Error("At least one limit must be specified (threadLimit or runLimit)");
|
|
195
196
|
/**
|
|
196
|
-
* Apply default for exitBehavior
|
|
197
|
+
* Apply default for exitBehavior
|
|
197
198
|
*/
|
|
198
|
-
const exitBehavior = options.exitBehavior ?? "
|
|
199
|
-
if (exitBehavior !== "end" && exitBehavior !== "error") throw new Error(`Invalid exit behavior: ${exitBehavior}. Must be 'end' or 'error'`);
|
|
199
|
+
const exitBehavior = options.exitBehavior ?? "continue";
|
|
200
200
|
/**
|
|
201
201
|
* Generate the middleware name based on the tool name
|
|
202
202
|
*/
|
|
203
203
|
const middlewareName = options.toolName ? `ToolCallLimitMiddleware[${options.toolName}]` : "ToolCallLimitMiddleware";
|
|
204
204
|
return createMiddleware({
|
|
205
205
|
name: middlewareName,
|
|
206
|
-
|
|
206
|
+
stateSchema,
|
|
207
|
+
afterModel: {
|
|
207
208
|
canJumpTo: ["end"],
|
|
208
209
|
hook: (state) => {
|
|
209
|
-
const messages = state.messages;
|
|
210
210
|
/**
|
|
211
|
-
*
|
|
211
|
+
* Get the last AI message to check for tool calls
|
|
212
|
+
*/
|
|
213
|
+
const lastAIMessage = [...state.messages].reverse().find(AIMessage.isInstance);
|
|
214
|
+
if (!lastAIMessage || !lastAIMessage.tool_calls) return void 0;
|
|
215
|
+
/**
|
|
216
|
+
* Helper to check if limit would be exceeded by one more call
|
|
217
|
+
*/
|
|
218
|
+
const wouldExceedLimit = (threadCount, runCount) => {
|
|
219
|
+
return options.threadLimit !== void 0 && threadCount + 1 > options.threadLimit || options.runLimit !== void 0 && runCount + 1 > options.runLimit;
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* Helper to check if a tool call matches our filter
|
|
223
|
+
*/
|
|
224
|
+
const matchesToolFilter = (toolCall) => {
|
|
225
|
+
return options.toolName === void 0 || toolCall.name === options.toolName;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* Separate tool calls into allowed and blocked based on limits
|
|
229
|
+
*/
|
|
230
|
+
const separateToolCalls = (toolCalls, threadCount, runCount) => {
|
|
231
|
+
const allowed$1 = [];
|
|
232
|
+
const blocked$1 = [];
|
|
233
|
+
let tempThreadCount = threadCount;
|
|
234
|
+
let tempRunCount = runCount;
|
|
235
|
+
for (const toolCall of toolCalls) {
|
|
236
|
+
if (!matchesToolFilter(toolCall)) continue;
|
|
237
|
+
if (wouldExceedLimit(tempThreadCount, tempRunCount)) blocked$1.push(toolCall);
|
|
238
|
+
else {
|
|
239
|
+
allowed$1.push(toolCall);
|
|
240
|
+
tempThreadCount += 1;
|
|
241
|
+
tempRunCount += 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
allowed: allowed$1,
|
|
246
|
+
blocked: blocked$1,
|
|
247
|
+
finalThreadCount: tempThreadCount + blocked$1.length,
|
|
248
|
+
finalRunCount: tempRunCount + blocked$1.length
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
/**
|
|
252
|
+
* Get the count key for this middleware instance
|
|
253
|
+
*/
|
|
254
|
+
const countKey = options.toolName ?? DEFAULT_TOOL_COUNT_KEY;
|
|
255
|
+
/**
|
|
256
|
+
* Get current counts
|
|
257
|
+
*/
|
|
258
|
+
const threadCounts = { ...state.threadToolCallCount ?? {} };
|
|
259
|
+
const runCounts = { ...state.runToolCallCount ?? {} };
|
|
260
|
+
const currentThreadCount = threadCounts[countKey] ?? 0;
|
|
261
|
+
const currentRunCount = runCounts[countKey] ?? 0;
|
|
262
|
+
/**
|
|
263
|
+
* Separate tool calls into allowed and blocked
|
|
264
|
+
*/
|
|
265
|
+
const { allowed, blocked, finalThreadCount, finalRunCount } = separateToolCalls(lastAIMessage.tool_calls, currentThreadCount, currentRunCount);
|
|
266
|
+
/**
|
|
267
|
+
* Update counts to include ALL tool call attempts (both allowed and blocked)
|
|
268
|
+
*/
|
|
269
|
+
threadCounts[countKey] = finalThreadCount;
|
|
270
|
+
runCounts[countKey] = finalRunCount;
|
|
271
|
+
/**
|
|
272
|
+
* If no tool calls are blocked, just update counts
|
|
273
|
+
*/
|
|
274
|
+
if (blocked.length === 0) {
|
|
275
|
+
if (allowed.length > 0) return {
|
|
276
|
+
threadToolCallCount: threadCounts,
|
|
277
|
+
runToolCallCount: runCounts
|
|
278
|
+
};
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Handle different exit behaviors
|
|
212
283
|
*/
|
|
213
|
-
|
|
284
|
+
if (exitBehavior === "error") throw new ToolCallLimitExceededError(finalThreadCount, finalRunCount, options.threadLimit, options.runLimit, options.toolName);
|
|
214
285
|
/**
|
|
215
|
-
*
|
|
286
|
+
* Build tool message content (sent to model - no thread/run details)
|
|
216
287
|
*/
|
|
217
|
-
const
|
|
218
|
-
const runCount = countToolCallsInMessages(runMessages, options.toolName);
|
|
288
|
+
const toolMsgContent = buildToolMessageContent(options.toolName, finalThreadCount, finalRunCount, options.threadLimit, options.runLimit);
|
|
219
289
|
/**
|
|
220
|
-
*
|
|
290
|
+
* Inject artificial error ToolMessages for blocked tool calls
|
|
221
291
|
*/
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
292
|
+
const artificialMessages = blocked.map((toolCall) => new ToolMessage({
|
|
293
|
+
content: toolMsgContent,
|
|
294
|
+
tool_call_id: toolCall.id,
|
|
295
|
+
name: toolCall.name,
|
|
296
|
+
status: "error"
|
|
297
|
+
}));
|
|
298
|
+
if (exitBehavior === "end") {
|
|
299
|
+
/**
|
|
300
|
+
* Check if there are tool calls to other tools that would continue executing
|
|
301
|
+
* For tool-specific limiters: check for calls to other tools
|
|
302
|
+
* For global limiters: check if there are multiple different tool types
|
|
303
|
+
*/
|
|
304
|
+
let otherTools = [];
|
|
305
|
+
if (options.toolName !== void 0)
|
|
306
|
+
/**
|
|
307
|
+
* Tool-specific limiter: check for calls to other tools
|
|
308
|
+
*/
|
|
309
|
+
otherTools = lastAIMessage.tool_calls.filter((tc) => tc.name !== options.toolName);
|
|
310
|
+
else {
|
|
311
|
+
/**
|
|
312
|
+
* Global limiter: check if there are multiple different tool types
|
|
313
|
+
* If there are allowed calls, those would execute
|
|
314
|
+
* But even if all are blocked, we can't handle multiple tool types with "end"
|
|
315
|
+
*/
|
|
316
|
+
const uniqueToolNames = new Set(lastAIMessage.tool_calls.map((tc) => tc.name).filter(Boolean));
|
|
317
|
+
if (uniqueToolNames.size > 1)
|
|
318
|
+
/**
|
|
319
|
+
* Multiple different tool types - use allowed calls to show which ones
|
|
320
|
+
*/
|
|
321
|
+
otherTools = allowed.length > 0 ? allowed : lastAIMessage.tool_calls;
|
|
322
|
+
}
|
|
323
|
+
if (otherTools.length > 0) {
|
|
324
|
+
const toolNames = Array.from(new Set(otherTools.map((tc) => tc.name).filter(Boolean))).join(", ");
|
|
325
|
+
throw new Error(`Cannot end execution with other tool calls pending. Found calls to: ${toolNames}. Use 'continue' or 'error' behavior instead.`);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Build final AI message content (displayed to user - includes thread/run details)
|
|
329
|
+
*/
|
|
330
|
+
const finalMsgContent = buildFinalAIMessageContent(finalThreadCount, finalRunCount, options.threadLimit, options.runLimit, options.toolName);
|
|
331
|
+
artificialMessages.push(new AIMessage(finalMsgContent));
|
|
332
|
+
return {
|
|
333
|
+
threadToolCallCount: threadCounts,
|
|
334
|
+
runToolCallCount: runCounts,
|
|
335
|
+
jumpTo: "end",
|
|
336
|
+
messages: artificialMessages
|
|
337
|
+
};
|
|
338
|
+
}
|
|
226
339
|
/**
|
|
227
|
-
*
|
|
340
|
+
* For exit_behavior="continue", return error messages to block exceeded tools
|
|
228
341
|
*/
|
|
229
|
-
const limitMessage = buildToolLimitExceededMessage(threadCount, runCount, options.threadLimit, options.runLimit, options.toolName);
|
|
230
|
-
const limitAiMessage = new AIMessage(limitMessage);
|
|
231
342
|
return {
|
|
232
|
-
|
|
233
|
-
|
|
343
|
+
threadToolCallCount: threadCounts,
|
|
344
|
+
runToolCallCount: runCounts,
|
|
345
|
+
messages: artificialMessages
|
|
234
346
|
};
|
|
235
347
|
}
|
|
236
|
-
}
|
|
348
|
+
},
|
|
349
|
+
afterAgent: () => ({ runToolCallCount: {} })
|
|
237
350
|
});
|
|
238
351
|
}
|
|
239
352
|
|