diff --git a/packages/@n8n/ai-workflow-builder.ee/package.json b/packages/@n8n/ai-workflow-builder.ee/package.json index 32532f4de2..90610f5b3e 100644 --- a/packages/@n8n/ai-workflow-builder.ee/package.json +++ b/packages/@n8n/ai-workflow-builder.ee/package.json @@ -47,6 +47,7 @@ "@n8n/di": "workspace:*", "@n8n_io/ai-assistant-sdk": "catalog:", "langsmith": "^0.3.45", + "lodash": "catalog:", "n8n-workflow": "workspace:*", "picocolors": "catalog:", "zod": "catalog:" @@ -54,10 +55,10 @@ "devDependencies": { "@n8n/typescript-config": "workspace:*", "@types/cli-progress": "^3.11.5", - "p-limit": "^3.1.0", "cli-progress": "^3.12.0", "cli-table3": "^0.6.3", "jest-mock-extended": "^3.0.4", - "madge": "^8.0.0" + "madge": "^8.0.0", + "p-limit": "^3.1.0" } } diff --git a/packages/@n8n/ai-workflow-builder.ee/src/constants.ts b/packages/@n8n/ai-workflow-builder.ee/src/constants.ts index 4b62aa7573..66b7f7c258 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/constants.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/constants.ts @@ -1,3 +1,36 @@ +/** + * Maximum length of user prompt message in characters. + * Prevents excessively long messages that could consume too many tokens. + */ export const MAX_AI_BUILDER_PROMPT_LENGTH = 1000; // characters +/** + * Token limits for the LLM context window. + */ +export const MAX_TOTAL_TOKENS = 200_000; // Total context window size (input + output) +export const MAX_OUTPUT_TOKENS = 16_000; // Reserved tokens for model response +export const MAX_INPUT_TOKENS = MAX_TOTAL_TOKENS - MAX_OUTPUT_TOKENS - 10_000; // Available tokens for input (with some buffer to account for estimation errors) + +/** + * Maximum length of individual parameter value that can be retrieved via tool call. + * Prevents tool responses from becoming too large and filling up the context. + */ +export const MAX_PARAMETER_VALUE_LENGTH = 30_000; // Maximum length of individual parameter value that can be retrieved via tool call + +/** + * Token threshold for automatically compacting conversation history. + * When conversation exceeds this limit, older messages are summarized to free up space. + */ export const DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS = 20_000; // Tokens threshold for auto-compacting the conversation + +/** + * Maximum token count for workflow JSON after trimming. + * Used to determine when a workflow is small enough to include in context. + */ +export const MAX_WORKFLOW_LENGTH_TOKENS = 30_000; // Tokens + +/** + * Average character-to-token ratio for Anthropic models. + * Used for rough token count estimation from character counts. + */ +export const AVG_CHARS_PER_TOKEN_ANTHROPIC = 2.5; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/errors/index.ts b/packages/@n8n/ai-workflow-builder.ee/src/errors/index.ts index 704b8ca4d7..5b3eeb7b6c 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/errors/index.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/errors/index.ts @@ -133,6 +133,27 @@ export class ParameterUpdateError extends OperationalError { } } +/** + * Error thrown when parameter value is too large to retrieve + */ +export class ParameterTooLargeError extends OperationalError { + constructor( + message: string, + options?: OperationalErrorOptions & { nodeId?: string; parameter?: string; maxSize?: number }, + ) { + super(message, { + ...options, + tags: { + ...options?.tags, + nodeId: options?.nodeId, + parameter: options?.parameter, + maxSize: options?.maxSize, + }, + shouldReport: false, + }); + } +} + /** * Error thrown when workflow state is invalid */ diff --git a/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts b/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts index 88262cea0b..883d7c36e5 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/llm-config.ts @@ -1,4 +1,6 @@ // Different LLMConfig type for this file - specific to LLM providers +import { MAX_OUTPUT_TOKENS } from '@/constants'; + interface LLMProviderConfig { apiKey: string; baseUrl?: string; @@ -51,7 +53,7 @@ export const anthropicClaudeSonnet4 = async (config: LLMProviderConfig) => { model: 'claude-sonnet-4-20250514', apiKey: config.apiKey, temperature: 0, - maxTokens: 16000, + maxTokens: MAX_OUTPUT_TOKENS, anthropicApiUrl: config.baseUrl, clientOptions: { defaultHeaders: config.headers, diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/get-node-parameter.tool.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/get-node-parameter.tool.ts new file mode 100644 index 0000000000..191cb343af --- /dev/null +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/get-node-parameter.tool.ts @@ -0,0 +1,157 @@ +import { tool } from '@langchain/core/tools'; +import type { Logger } from '@n8n/backend-common'; +import get from 'lodash/get'; +import type { INode, NodeParameterValueType } from 'n8n-workflow'; +import { z } from 'zod'; + +import { MAX_PARAMETER_VALUE_LENGTH } from '@/constants'; +import { + createNodeParameterTooLargeError, + getCurrentWorkflow, + getWorkflowState, +} from '@/tools/helpers'; + +import { ValidationError, ToolExecutionError } from '../errors'; +import { createProgressReporter, reportProgress } from './helpers/progress'; +import { createSuccessResponse, createErrorResponse } from './helpers/response'; +import { validateNodeExists, createNodeNotFoundError } from './helpers/validation'; +import type { GetNodeParameterOutput } from '../types/tools'; + +/** + * Schema for getting specific node parameter + */ +const getNodeParameterSchema = z.object({ + nodeId: z.string().describe('The ID of the node to extract parameter value'), + path: z + .string() + .describe('Path to the specific parameter to extract, e.g., "url" or "options.baseUrl"'), +}); + +function extractParameterValue(node: INode, path: string): NodeParameterValueType | undefined { + const nodeParameters = node.parameters; + + return get(nodeParameters, path); +} + +function formatNodeParameter(path: string, value: NodeParameterValueType): string { + const parts = []; + + parts.push(''); + parts.push(''); + parts.push(path); + parts.push(''); + parts.push(''); + + if (typeof value === 'string') { + parts.push(value); + } else { + parts.push(JSON.stringify(value, null, 2)); + } + + parts.push(''); + parts.push(''); + + return parts.join('\n'); +} + +/** + * Factory function to create the get node parameter tool + */ +export function createGetNodeParameterTool(logger?: Logger) { + const DISPLAY_TITLE = 'Getting node parameter'; + + const dynamicTool = tool( + (input: unknown, config) => { + const reporter = createProgressReporter(config, 'get_node_parameter', DISPLAY_TITLE); + + try { + // Validate input using Zod schema + const validatedInput = getNodeParameterSchema.parse(input); + const { nodeId, path } = validatedInput; + + // Report tool start + reporter.start(validatedInput); + + // Report progress + logger?.debug(`Looking up parameter ${path} for ${nodeId}...`); + reportProgress(reporter, `Looking up parameter ${path} for ${nodeId}...`); + + // Get current state + const state = getWorkflowState(); + const workflow = getCurrentWorkflow(state); + + // Find the node + const node = validateNodeExists(nodeId, workflow.nodes); + if (!node) { + logger?.debug(`Node with ID ${nodeId} not found`); + const error = createNodeNotFoundError(nodeId); + reporter.error(error); + return createErrorResponse(config, error); + } + + // Extract + const parameterValue = extractParameterValue(node, path); + if (parameterValue === undefined) { + logger?.debug(`Parameter path ${path} not found in node ${node.name}`); + const error = new ValidationError( + `Parameter path "${path}" not found in node "${node.name}"`, + { + extra: { nodeId, path }, + }, + ); + reporter.error(error); + return createErrorResponse(config, error); + } + + // Report completion + logger?.debug(`Parameter value for path ${path} in node ${node.name} retrieved`); + + const formattedParameterValue = formatNodeParameter(path, parameterValue); + + if (formattedParameterValue.length > MAX_PARAMETER_VALUE_LENGTH) { + const error = createNodeParameterTooLargeError(nodeId, path, MAX_PARAMETER_VALUE_LENGTH); + reporter.error(error); + return createErrorResponse(config, error); + } + + const output: GetNodeParameterOutput = { + message: 'Parameter value retrieved successfully', + }; + reporter.complete(output); + + // Return success response + return createSuccessResponse(config, formattedParameterValue); + } catch (error) { + // Handle validation or unexpected errors + if (error instanceof z.ZodError) { + const validationError = new ValidationError('Invalid input parameters', { + extra: { errors: error.errors }, + }); + reporter.error(validationError); + return createErrorResponse(config, validationError); + } + + const toolError = new ToolExecutionError( + error instanceof Error ? error.message : 'Unknown error occurred', + { + toolName: 'get_node_parameter', + cause: error instanceof Error ? error : undefined, + }, + ); + reporter.error(toolError); + return createErrorResponse(config, toolError); + } + }, + { + name: 'get_node_parameter', + description: + 'Get the value of a specific parameter of a specific node. Use this ONLY to retrieve parameters omitted in the workflow JSON context because of the size.', + schema: getNodeParameterSchema, + }, + ); + + return { + tool: dynamicTool, + displayTitle: DISPLAY_TITLE, + }; +} diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/helpers/validation.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/helpers/validation.ts index 57aab8b6a2..9074f5a9ab 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/tools/helpers/validation.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/helpers/validation.ts @@ -4,6 +4,7 @@ import { ConnectionError, NodeNotFoundError, NodeTypeNotFoundError, + ParameterTooLargeError, ValidationError, } from '../../errors'; import type { ToolError } from '../../types/tools'; @@ -106,6 +107,27 @@ export function createNodeTypeNotFoundError(nodeTypeName: string): ToolError { }; } +/** + * Create a node parameter is too large error + */ +export function createNodeParameterTooLargeError( + nodeId: string, + parameter: string, + maxSize: number, +): ToolError { + const error = new ParameterTooLargeError('Parameter value is too large to retrieve', { + parameter, + nodeId, + maxSize, + }); + + return { + message: error.message, + code: 'NODE_PARAMETER_TOO_LARGE', + details: { nodeId, parameter, maxSize: maxSize.toString() }, + }; +} + /** * Check if a workflow has nodes */ diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/prompts/main-agent.prompt.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/prompts/main-agent.prompt.ts index f03f6bdcee..68ed382b76 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/tools/prompts/main-agent.prompt.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/prompts/main-agent.prompt.ts @@ -370,7 +370,11 @@ Be warm, helpful, and most importantly concise. Focus on actionable information. const currentWorkflowJson = ` {workflowJSON} -`; + + +Note: Large property values of the nodes in the workflow JSON above may be trimmed to fit within token limits. +Use get_node_parameter tool to get full details when needed. +`; const currentExecutionData = ` diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/update-node-parameters.tool.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/update-node-parameters.tool.ts index a82f2e4735..eb9e86f7b5 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/tools/update-node-parameters.tool.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/update-node-parameters.tool.ts @@ -3,8 +3,11 @@ import { tool } from '@langchain/core/tools'; import type { INode, INodeTypeDescription, INodeParameters, Logger } from 'n8n-workflow'; import { z } from 'zod'; +import { trimWorkflowJSON } from '@/utils/trim-workflow-context'; + import { createParameterUpdaterChain } from '../chains/parameter-updater'; import { ValidationError, ParameterUpdateError, ToolExecutionError } from '../errors'; +import type { UpdateNodeParametersOutput } from '../types/tools'; import { createProgressReporter, reportProgress } from './helpers/progress'; import { createSuccessResponse, createErrorResponse } from './helpers/response'; import { getCurrentWorkflow, getWorkflowState, updateNodeInWorkflow } from './helpers/state'; @@ -20,7 +23,6 @@ import { updateNodeWithParameters, fixExpressionPrefixes, } from './utils/parameter-update.utils'; -import type { UpdateNodeParametersOutput } from '../types/tools'; const DISPLAY_TITLE = 'Updating node parameters'; @@ -81,7 +83,7 @@ async function processParameterUpdates( ); const newParameters = (await parametersChain.invoke({ - workflow_json: workflow, + workflow_json: trimWorkflowJSON(workflow), execution_schema: state.workflowContext?.executionSchema ?? 'NO SCHEMA', execution_data: state.workflowContext?.executionData ?? 'NO EXECUTION DATA YET', node_id: nodeId, diff --git a/packages/@n8n/ai-workflow-builder.ee/src/types/tools.ts b/packages/@n8n/ai-workflow-builder.ee/src/types/tools.ts index 9675ba140b..13c8d93d2b 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/types/tools.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/types/tools.ts @@ -125,3 +125,10 @@ export interface NodeSearchOutput { totalResults: number; message: string; } + +/** + * Output type for get node parameter tool + */ +export interface GetNodeParameterOutput { + message: string; // This is only to report success or error, without actual value (we don't need to send it to the frontend) +} diff --git a/packages/@n8n/ai-workflow-builder.ee/src/utils/test/trim-workflow-context.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/utils/test/trim-workflow-context.test.ts new file mode 100644 index 0000000000..9f4f0fb2ab --- /dev/null +++ b/packages/@n8n/ai-workflow-builder.ee/src/utils/test/trim-workflow-context.test.ts @@ -0,0 +1,287 @@ +import type { INode } from 'n8n-workflow'; + +import { createNode, createWorkflow } from '../../../test/test-utils'; +import type { SimpleWorkflow } from '../../types/workflow'; +import { trimWorkflowJSON } from '../trim-workflow-context'; + +describe('trimWorkflowJSON', () => { + describe('Small workflows', () => { + it('should not modify small workflows that fit within token limits', () => { + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'HTTP Request', + type: 'n8n-nodes-base.httpRequest', + parameters: { + url: 'https://api.example.com/endpoint', + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + // Workflow should be unchanged + expect(result).toEqual(workflow); + expect(result.nodes[0].parameters).toEqual(workflow.nodes[0].parameters); + }); + + it('should handle empty workflows', () => { + const workflow: SimpleWorkflow = createWorkflow([]); + + const result = trimWorkflowJSON(workflow); + + expect(result.nodes).toEqual([]); + expect(result.connections).toEqual({}); + }); + + it('should handle workflows with nodes but no parameters', () => { + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'Start', + type: 'n8n-nodes-base.start', + parameters: {}, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + expect(result).toEqual(workflow); + }); + }); + + describe('Large parameter trimming', () => { + it('should trim large string parameters', () => { + const largeString = 'x'.repeat(15000); + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'Code Node', + type: 'n8n-nodes-base.code', + parameters: { + jsCode: largeString, + smallParam: 'keep this', + }, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + expect(result.nodes[0].parameters.jsCode).toBe('[Large value omitted]'); + expect(result.nodes[0].parameters.smallParam).toBe('keep this'); + }); + + it('should trim large arrays', () => { + const largeArray = new Array(5000).fill({ item: 'data' }); + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'Item Lists', + type: 'n8n-nodes-base.itemLists', + parameters: { + items: largeArray, + operation: 'aggregateItems', + }, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + expect(result.nodes[0].parameters.items).toBe('[Large array omitted]'); + expect(result.nodes[0].parameters.operation).toBe('aggregateItems'); + }); + + it('should trim large objects', () => { + const largeObject: Record = {}; + for (let i = 0; i < 1000; i++) { + largeObject[`key_${i}`] = `value_${i}`.repeat(20); + } + + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'Function', + type: 'n8n-nodes-base.function', + parameters: { + functionCode: 'return items;', + data: largeObject, + }, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + expect(result.nodes[0].parameters.data).toBe('[Large object omitted]'); + expect(result.nodes[0].parameters.functionCode).toBe('return items;'); + }); + + it('should handle null and undefined parameters correctly', () => { + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'Test Node', + type: 'n8n-nodes-base.test', + parameters: { + nullValue: null, + // eslint-disable-next-line no-undefined + undefinedValue: undefined, + validValue: 'test', + }, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + expect(result.nodes[0].parameters.nullValue).toBeNull(); + expect(result.nodes[0].parameters.undefinedValue).toBeUndefined(); + expect(result.nodes[0].parameters.validValue).toBe('test'); + }); + }); + + describe('Progressive trimming', () => { + it('should apply progressively more aggressive trimming for very large workflows', () => { + // Create a workflow with parameters at different size levels + const medium = 'x'.repeat(3000); // Will be kept at 10000 and 5000 threshold, trimmed at 2000 + const large = 'y'.repeat(7000); // Will be kept at 10000 threshold, trimmed at 5000 + const veryLarge = 'z'.repeat(12000); // Will be trimmed at 10000 threshold + + const nodes: INode[] = []; + // Add many nodes to create a large workflow + for (let i = 0; i < 20; i++) { + nodes.push( + createNode({ + id: `node${i}`, + name: `Node ${i}`, + type: 'n8n-nodes-base.code', + parameters: { + medium, + large, + veryLarge, + }, + }), + ); + } + + const workflow: SimpleWorkflow = createWorkflow(nodes); + const result = trimWorkflowJSON(workflow); + + // All veryLarge and large parameters should be trimmed + result.nodes.forEach((node) => { + expect(node.parameters?.veryLarge).toBe('[Large value omitted]'); + expect(node.parameters?.large).toBe('[Large value omitted]'); + expect(node.parameters?.medium).toBe(medium); + }); + }); + }); + + describe('Edge cases', () => { + it('should preserve connections', () => { + const workflow: SimpleWorkflow = { + name: 'Connected Workflow', + nodes: [ + createNode({ id: 'node1', name: 'Start' }), + createNode({ id: 'node2', name: 'End' }), + ], + connections: { + node1: { + main: [[{ node: 'node2', type: 'main', index: 0 }]], + }, + }, + }; + + const result = trimWorkflowJSON(workflow); + + // Connections should be preserved + expect(result.connections).toEqual(workflow.connections); + }); + + it('should handle workflows with mixed parameter types', () => { + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'Mixed Node', + type: 'n8n-nodes-base.mixed', + parameters: { + stringValue: 'normal string', + numberValue: 42, + booleanValue: true, + largeString: 'x'.repeat(15000), + array: ['item1', 'item2'], + objectValue: { key: 'value' }, + largeArray: new Array(5000).fill('item'), + }, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + // Normal parameters should be preserved + expect(result.nodes[0].parameters.stringValue).toBe('normal string'); + expect(result.nodes[0].parameters.numberValue).toBe(42); + expect(result.nodes[0].parameters.booleanValue).toBe(true); + expect(result.nodes[0].parameters.array).toEqual(['item1', 'item2']); + expect(result.nodes[0].parameters.objectValue).toEqual({ key: 'value' }); + // Large parameters should be trimmed + expect(result.nodes[0].parameters.largeString).toBe('[Large value omitted]'); + expect(result.nodes[0].parameters.largeArray).toBe('[Large array omitted]'); + }); + + it('should handle deeply nested parameters', () => { + const workflow: SimpleWorkflow = createWorkflow([ + createNode({ + id: 'node1', + name: 'Nested Node', + type: 'n8n-nodes-base.nested', + parameters: { + level1: { + level2: { + level3: { + largeData: 'x'.repeat(15000), + }, + }, + }, + }, + }), + ]); + + const result = trimWorkflowJSON(workflow); + + // The entire nested object gets evaluated as a whole + // If the stringified nested object is too large, it gets replaced + expect(result.nodes[0].parameters.level1).toBe('[Large object omitted]'); + }); + + it('should return most aggressively trimmed version if nothing fits', () => { + // Create an enormous workflow that won't fit even with aggressive trimming + const nodes: INode[] = []; + for (let i = 0; i < 1000; i++) { + nodes.push( + createNode({ + id: `node${i}`, + name: `Node ${i}`, + type: 'n8n-nodes-base.code', + parameters: { + data1: 'x'.repeat(2000), + data2: 'y'.repeat(2000), + data3: 'z'.repeat(2000), + }, + }), + ); + } + + const workflow: SimpleWorkflow = createWorkflow(nodes); + const result = trimWorkflowJSON(workflow); + + // All large parameters should be trimmed with the most aggressive threshold (1000) + result.nodes.forEach((node) => { + expect(node.parameters?.data1).toBe('[Large value omitted]'); + expect(node.parameters?.data2).toBe('[Large value omitted]'); + expect(node.parameters?.data3).toBe('[Large value omitted]'); + }); + }); + }); +}); diff --git a/packages/@n8n/ai-workflow-builder.ee/src/utils/token-usage.ts b/packages/@n8n/ai-workflow-builder.ee/src/utils/token-usage.ts index 81681113ab..e18c75e3c9 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/utils/token-usage.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/utils/token-usage.ts @@ -1,5 +1,8 @@ +import type { BaseMessage } from '@langchain/core/messages'; import { AIMessage } from '@langchain/core/messages'; +import { AVG_CHARS_PER_TOKEN_ANTHROPIC } from '@/constants'; + export type AIMessageWithUsageMetadata = AIMessage & { response_metadata: { usage: { @@ -34,3 +37,32 @@ export function extractLastTokenUsage(messages: unknown[]): TokenUsage | undefin return lastAiAssistantMessage.response_metadata.usage; } + +function concatenateMessageContent(messages: BaseMessage[]): string { + return messages.reduce((acc: string, message) => { + if (typeof message.content === 'string') { + return acc + message.content; + } else if (Array.isArray(message.content)) { + return ( + acc + + message.content.reduce((innerAcc: string, item) => { + if (typeof item === 'object' && item !== null && 'text' in item) { + return innerAcc + item.text; + } + return innerAcc; + }, '') + ); + } + return acc; + }, ''); +} + +export function estimateTokenCountFromString(text: string): number { + return Math.ceil(text.length / AVG_CHARS_PER_TOKEN_ANTHROPIC); // Rough estimate +} + +export function estimateTokenCountFromMessages(messages: BaseMessage[]): number { + const entireInput = concatenateMessageContent(messages); + + return estimateTokenCountFromString(entireInput); +} diff --git a/packages/@n8n/ai-workflow-builder.ee/src/utils/trim-workflow-context.ts b/packages/@n8n/ai-workflow-builder.ee/src/utils/trim-workflow-context.ts new file mode 100644 index 0000000000..6c83676566 --- /dev/null +++ b/packages/@n8n/ai-workflow-builder.ee/src/utils/trim-workflow-context.ts @@ -0,0 +1,104 @@ +import type { INodeParameters, NodeParameterValueType } from 'n8n-workflow'; + +import { MAX_WORKFLOW_LENGTH_TOKENS } from '@/constants'; +import type { SimpleWorkflow } from '@/types'; +import { estimateTokenCountFromString } from '@/utils/token-usage'; + +/** + * Thresholds for progressively trimming large parameter values. + * Each iteration uses a more aggressive threshold if the workflow is still too large. + */ +const MAX_PARAMETER_VALUE_LENGTH_THRESHOLDS = [10000, 5000, 2000, 1000]; + +/** + * Trims a parameter value if it exceeds the specified threshold. + * Replaces large values with placeholders to reduce token usage. + * + * @param value - The parameter value to potentially trim + * @param threshold - The maximum allowed length in characters + * @returns The original value if under threshold, or a placeholder string if too large + */ +function trimParameterValue( + value: NodeParameterValueType, + threshold: number, +): NodeParameterValueType { + // Handle undefined and null values directly without stringification + if (value === undefined || value === null) { + return value; + } + + const valueStr = JSON.stringify(value); + if (valueStr.length > threshold) { + // Return type-specific placeholder messages + if (typeof value === 'string') { + return '[Large value omitted]'; + } else if (Array.isArray(value)) { + return '[Large array omitted]'; + } else if (typeof value === 'object' && value !== null) { + return '[Large object omitted]'; + } + } + + return value; +} + +/** + * Simplifies a workflow by trimming large parameter values of its nodes based on the given threshold. + * Creates a copy of the workflow to avoid mutations. + * + * @param workflow - The workflow to simplify + * @param threshold - The maximum allowed length for parameter values + * @returns A new workflow object with trimmed parameters + */ +function trimWorkflowJsonWithThreshold( + workflow: SimpleWorkflow, + threshold: number, +): SimpleWorkflow { + const simplifiedWorkflow = { ...workflow }; + if (simplifiedWorkflow.nodes) { + simplifiedWorkflow.nodes = simplifiedWorkflow.nodes.map((node) => { + const simplifiedNode = { ...node }; + + // Process each parameter and replace large values with placeholders + if (simplifiedNode.parameters) { + const simplifiedParameters: INodeParameters = {}; + for (const [key, value] of Object.entries(simplifiedNode.parameters)) { + simplifiedParameters[key] = trimParameterValue(value, threshold); + } + simplifiedNode.parameters = simplifiedParameters; + } + + return simplifiedNode; + }); + } + + return simplifiedWorkflow; +} + +/** + * Trims workflow JSON to fit within token limits by progressively applying more aggressive trimming. + * Iterates through different thresholds until the workflow fits within MAX_WORKFLOW_LENGTH_TOKENS. + * + * @param workflow - The workflow to trim + * @returns A simplified workflow that fits within token limits, or the most aggressively trimmed version + */ +export function trimWorkflowJSON(workflow: SimpleWorkflow): SimpleWorkflow { + // Try progressively more aggressive trimming thresholds + for (const threshold of MAX_PARAMETER_VALUE_LENGTH_THRESHOLDS) { + const simplified = trimWorkflowJsonWithThreshold(workflow, threshold); + const workflowStr = JSON.stringify(simplified); + const estimatedTokens = estimateTokenCountFromString(workflowStr); + + // If the workflow fits within the token limit, return it + if (estimatedTokens <= MAX_WORKFLOW_LENGTH_TOKENS) { + return simplified; + } + } + + // If even the most aggressive trimming doesn't fit, return the most trimmed version + // This ensures we always return something, even if it still exceeds the limit + return trimWorkflowJsonWithThreshold( + workflow, + MAX_PARAMETER_VALUE_LENGTH_THRESHOLDS[MAX_PARAMETER_VALUE_LENGTH_THRESHOLDS.length - 1], + ); +} diff --git a/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts b/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts index 6c8a93cab4..f55dbde796 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/workflow-builder-agent.ts @@ -13,10 +13,17 @@ import { type NodeExecutionSchema, } from 'n8n-workflow'; +import { + DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS, + MAX_AI_BUILDER_PROMPT_LENGTH, + MAX_INPUT_TOKENS, +} from '@/constants'; +import { createGetNodeParameterTool } from '@/tools/get-node-parameter.tool'; +import { trimWorkflowJSON } from '@/utils/trim-workflow-context'; + import { conversationCompactChain } from './chains/conversation-compact'; import { workflowNameChain } from './chains/workflow-name'; -import { DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS, MAX_AI_BUILDER_PROMPT_LENGTH } from './constants'; -import { LLMServiceError, ValidationError } from './errors'; +import { LLMServiceError, ValidationError, WorkflowStateError } from './errors'; import { createAddNodeTool } from './tools/add-node.tool'; import { createConnectNodesTool } from './tools/connect-nodes.tool'; import { createNodeDetailsTool } from './tools/node-details.tool'; @@ -27,7 +34,7 @@ import { createUpdateNodeParametersTool } from './tools/update-node-parameters.t import type { SimpleWorkflow } from './types/workflow'; import { processOperations } from './utils/operations-processor'; import { createStreamProcessor, formatMessages, type BuilderTool } from './utils/stream-processor'; -import { extractLastTokenUsage } from './utils/token-usage'; +import { estimateTokenCountFromMessages, extractLastTokenUsage } from './utils/token-usage'; import { executeToolsInParallel } from './utils/tool-executor'; import { WorkflowState } from './workflow-state'; @@ -86,6 +93,7 @@ export class WorkflowBuilderAgent { this.logger, this.instanceUrl, ), + createGetNodeParameterTool(), ]; } @@ -110,10 +118,20 @@ export class WorkflowBuilderAgent { const prompt = await mainAgentPrompt.invoke({ ...state, + workflowJSON: trimWorkflowJSON(state.workflowJSON), executionData: state.workflowContext?.executionData ?? {}, executionSchema: state.workflowContext?.executionSchema ?? [], instanceUrl: this.instanceUrl, }); + + const estimatedTokens = estimateTokenCountFromMessages(prompt.messages); + + if (estimatedTokens > MAX_INPUT_TOKENS) { + throw new WorkflowStateError( + 'The current conversation and workflow state is too large to process. Try to simplify your workflow by breaking it into smaller parts.', + ); + } + const response = await this.llmSimpleTask.bindTools(tools).invoke(prompt); return { messages: [response] }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index daef0a2cbb..54f2a2cfe9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -417,6 +417,9 @@ importers: langsmith: specifier: ^0.3.45 version: 0.3.45(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) + lodash: + specifier: 'catalog:' + version: 4.17.21 n8n-workflow: specifier: workspace:* version: link:../../workflow @@ -999,7 +1002,7 @@ importers: version: 4.3.0 '@getzep/zep-cloud': specifier: 1.0.12 - version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(316b19288832115574731e049dc7676a)) + version: 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a)) '@getzep/zep-js': specifier: 0.9.0 version: 0.9.0 @@ -1026,7 +1029,7 @@ importers: version: 0.3.4(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13) '@langchain/community': specifier: 'catalog:' - version: 0.3.50(ccee17333f80550b1303d83de2b6f79a) + version: 0.3.50(7d9026709e640c92cdf2ea22646a0399) '@langchain/core': specifier: 'catalog:' version: 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) @@ -1143,7 +1146,7 @@ importers: version: 23.0.1 langchain: specifier: 0.3.30 - version: 0.3.30(316b19288832115574731e049dc7676a) + version: 0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a) lodash: specifier: 'catalog:' version: 4.17.21 @@ -3813,12 +3816,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.18.6': - resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.25.9': resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} engines: {node: '>=6.9.0'} @@ -5027,10 +5024,6 @@ packages: resolution: {integrity: sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jest/schemas@29.6.0': - resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5058,18 +5051,10 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.11': resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} @@ -5079,9 +5064,6 @@ packages: '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} @@ -17865,8 +17847,8 @@ snapshots: dependencies: '@babel/parser': 7.27.5 '@babel/types': 7.27.6 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': @@ -18049,17 +18031,17 @@ snapshots: '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.10)': dependencies: @@ -18074,62 +18056,57 @@ snapshots: '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 - - '@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.26.10)': - dependencies: - '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': dependencies: @@ -19132,7 +19109,7 @@ snapshots: '@gar/promisify@1.1.3': optional: true - '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(316b19288832115574731e049dc7676a))': + '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a))': dependencies: form-data: 4.0.4 node-fetch: 2.7.0(encoding@0.1.13) @@ -19141,7 +19118,7 @@ snapshots: zod: 3.25.67 optionalDependencies: '@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) - langchain: 0.3.30(316b19288832115574731e049dc7676a) + langchain: 0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a) transitivePeerDependencies: - encoding @@ -19493,7 +19470,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.6.2 '@jest/types': 29.6.1 - '@types/node': 20.19.1 + '@types/node': 20.19.10 jest-mock: 29.6.2 '@jest/expect-utils@29.6.2': @@ -19511,7 +19488,7 @@ snapshots: dependencies: '@jest/types': 29.6.1 '@sinonjs/fake-timers': 10.0.2 - '@types/node': 20.19.1 + '@types/node': 20.19.10 jest-message-util: 29.6.2 jest-mock: 29.6.2 jest-util: 29.6.2 @@ -19532,7 +19509,7 @@ snapshots: '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 '@jest/types': 29.6.1 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.30 '@types/node': 20.19.10 chalk: 4.1.2 collect-v8-coverage: 1.0.1 @@ -19558,10 +19535,6 @@ snapshots: dependencies: '@sinclair/typebox': 0.25.21 - '@jest/schemas@29.6.0': - dependencies: - '@sinclair/typebox': 0.27.8 - '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -19590,7 +19563,7 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@jest/types': 29.6.1 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.30 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -19608,10 +19581,10 @@ snapshots: '@jest/types@29.6.1': dependencies: - '@jest/schemas': 29.6.0 + '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/yargs': 17.0.19 chalk: 4.1.2 @@ -19620,16 +19593,8 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.30 - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} - '@jridgewell/source-map@0.3.11': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -19639,11 +19604,6 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping@0.3.30': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -19712,7 +19672,7 @@ snapshots: - aws-crt - encoding - '@langchain/community@0.3.50(ccee17333f80550b1303d83de2b6f79a)': + '@langchain/community@0.3.50(7d9026709e640c92cdf2ea22646a0399)': dependencies: '@browserbasehq/stagehand': 1.9.0(@playwright/test@1.54.2)(deepmerge@4.3.1)(dotenv@16.6.1)(encoding@0.1.13)(openai@5.12.2(ws@8.18.3)(zod@3.25.67))(zod@3.25.67) '@ibm-cloud/watsonx-ai': 1.1.2 @@ -19724,7 +19684,7 @@ snapshots: flat: 5.0.2 ibm-cloud-sdk-core: 5.3.2 js-yaml: 4.1.0 - langchain: 0.3.30(316b19288832115574731e049dc7676a) + langchain: 0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a) langsmith: 0.3.55(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) openai: 5.12.2(ws@8.18.3)(zod@3.25.67) uuid: 10.0.0 @@ -19738,7 +19698,7 @@ snapshots: '@aws-sdk/credential-provider-node': 3.808.0 '@azure/storage-blob': 12.26.0 '@browserbasehq/sdk': 2.6.0(encoding@0.1.13) - '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(316b19288832115574731e049dc7676a)) + '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(encoding@0.1.13)(langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a)) '@getzep/zep-js': 0.9.0 '@google-ai/generativelanguage': 2.6.0(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13) @@ -20846,7 +20806,7 @@ snapshots: dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 - picomatch: 4.0.2 + picomatch: 4.0.3 optionalDependencies: rollup: 4.46.2 @@ -21985,7 +21945,7 @@ snapshots: '@types/asn1@0.2.0': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/aws4@1.11.2': dependencies: @@ -22108,12 +22068,12 @@ snapshots: '@types/ftp@0.3.33': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/glob@8.0.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/gm@1.25.0': dependencies: @@ -22127,7 +22087,7 @@ snapshots: '@types/http-proxy@1.17.16': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/humanize-duration@3.27.1': {} @@ -22177,7 +22137,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 optional: true '@types/jsonpath@0.2.0': {} @@ -22394,7 +22354,7 @@ snapshots: '@types/ssh2@1.11.6': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/sshpk@1.17.4': dependencies: @@ -22432,7 +22392,7 @@ snapshots: '@types/tedious@4.0.9': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/temp@0.9.4': dependencies: @@ -22440,7 +22400,7 @@ snapshots: '@types/through@0.0.30': dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/tough-cookie@4.0.2': {} @@ -23105,7 +23065,7 @@ snapshots: acorn-walk@8.3.4: dependencies: - acorn: 8.14.0 + acorn: 8.15.0 acorn@7.4.1: {} @@ -23505,7 +23465,7 @@ snapshots: axios-retry@4.5.0(axios@1.11.0(debug@4.4.1)): dependencies: - axios: 1.11.0(debug@4.4.1) + axios: 1.11.0(debug@4.3.6) is-retry-allowed: 2.2.0 axios-retry@4.5.0(axios@1.11.0): @@ -23567,7 +23527,7 @@ snapshots: babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-plugin-utils': 7.27.1 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -25572,7 +25532,7 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) is-core-module: 2.16.1 resolve: 1.22.10 transitivePeerDependencies: @@ -25596,7 +25556,7 @@ snapshots: eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.29.0(jiti@1.21.7))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.3)(eslint@9.29.0(jiti@1.21.7)): dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) optionalDependencies: '@typescript-eslint/parser': 8.35.0(eslint@9.29.0(jiti@1.21.7))(typescript@5.9.2) eslint: 9.29.0(jiti@1.21.7) @@ -25635,7 +25595,7 @@ snapshots: array.prototype.findlastindex: 1.2.6 array.prototype.flat: 1.3.3 array.prototype.flatmap: 1.3.3 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 eslint: 9.29.0(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 @@ -26074,10 +26034,6 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.4.6(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - fdir@6.4.6(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -26594,7 +26550,7 @@ snapshots: array-parallel: 0.1.3 array-series: 0.1.5 cross-spawn: 7.0.6 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -26664,7 +26620,7 @@ snapshots: groq-sdk@0.19.0(encoding@0.1.13): dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 '@types/node-fetch': 2.6.12 abort-controller: 3.0.0 agentkeepalive: 4.6.0 @@ -26945,7 +26901,7 @@ snapshots: '@types/debug': 4.1.12 '@types/node': 20.19.10 '@types/tough-cookie': 4.0.5 - axios: 1.11.0(debug@4.4.1) + axios: 1.11.0(debug@4.3.6) camelcase: 6.3.0 debug: 4.4.1(supports-color@8.1.1) dotenv: 16.6.1 @@ -26955,7 +26911,7 @@ snapshots: isstream: 0.1.2 jsonwebtoken: 9.0.2 mime-types: 2.1.35 - retry-axios: 2.6.0(axios@1.11.0) + retry-axios: 2.6.0(axios@1.11.0(debug@4.4.1)) tough-cookie: 4.1.4 transitivePeerDependencies: - supports-color @@ -27765,7 +27721,7 @@ snapshots: jest-mock@29.6.2: dependencies: '@jest/types': 29.6.1 - '@types/node': 20.17.57 + '@types/node': 20.19.10 jest-util: 29.6.2 jest-pnp-resolver@1.2.2(jest-resolve@29.6.2): @@ -27850,7 +27806,7 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/generator': 7.26.10 - '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.26.10) + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.26.10) '@babel/types': 7.27.6 '@jest/expect-utils': 29.6.2 @@ -27883,7 +27839,7 @@ snapshots: jest-util@29.6.2: dependencies: '@jest/types': 29.6.1 - '@types/node': 20.19.1 + '@types/node': 20.19.10 chalk: 4.1.2 ci-info: 3.8.0 graceful-fs: 4.2.11 @@ -28208,7 +28164,7 @@ snapshots: kuler@2.0.0: {} - langchain@0.3.30(316b19288832115574731e049dc7676a): + langchain@0.3.30(e7c2f10ddf33088da1e6affdf0fc6c0a): dependencies: '@langchain/core': 0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)) '@langchain/openai': 0.6.7(@langchain/core@0.3.68(@opentelemetry/api@1.9.0)(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(openai@5.12.2(ws@8.18.3)(zod@3.25.67)))(ws@8.18.3) @@ -29999,7 +29955,7 @@ snapshots: pdf-parse@1.1.1: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) node-ensure: 0.0.0 transitivePeerDependencies: - supports-color @@ -30441,7 +30397,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.19.1 + '@types/node': 20.19.10 long: 5.3.2 proxy-addr@2.0.7: @@ -30962,7 +30918,7 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - retry-axios@2.6.0(axios@1.11.0): + retry-axios@2.6.0(axios@1.11.0(debug@4.4.1)): dependencies: axios: 1.11.0(debug@4.3.6) @@ -30989,7 +30945,7 @@ snapshots: rhea@1.0.24: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -32193,8 +32149,8 @@ snapshots: tinyglobby@0.2.14: dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 tinypool@1.1.1: {} @@ -32411,7 +32367,7 @@ snapshots: ts-type@3.0.1(ts-toolbelt@9.6.0): dependencies: - '@types/node': 20.19.1 + '@types/node': 20.19.10 ts-toolbelt: 9.6.0 tslib: 2.8.1 typedarray-dts: 1.0.0