UNPKG

n8n

Version:

n8n Workflow Automation Tool

290 lines 13.7 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.EvalExecutionService = void 0; const backend_common_1 = require("@n8n/backend-common"); const di_1 = require("@n8n/di"); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const node_crypto_1 = require("node:crypto"); const node_types_1 = require("../../../node-types"); const workflow_execute_additional_data_1 = require("../../../workflow-execute-additional-data"); const workflow_finder_service_1 = require("../../../workflows/workflow-finder.service"); const workflow_sdk_1 = require("@n8n/workflow-sdk"); const pin_data_generator_1 = require("./pin-data-generator"); const workflow_analysis_1 = require("./workflow-analysis"); const mock_handler_1 = require("./mock-handler"); const MAX_OUTPUT_ITEMS_PER_NODE = 10; let EvalExecutionService = class EvalExecutionService { constructor(workflowFinderService, nodeTypes, logger) { this.workflowFinderService = workflowFinderService; this.nodeTypes = nodeTypes; this.logger = logger; } async executeWithLlmMock(workflowId, user, options = {}) { const executionId = (0, node_crypto_1.randomUUID)(); const workflowEntity = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [ 'workflow:execute', ]); if (!workflowEntity) { return this.errorResult(executionId, `Workflow ${workflowId} not found or not accessible`); } const hints = await this.analyzeWorkflow(workflowEntity, options.scenarioHints); return await this.execute(workflowEntity, user, executionId, hints, options.scenarioHints); } async analyzeWorkflow(workflowEntity, scenarioHints) { const hintNodes = (0, workflow_analysis_1.identifyNodesForHints)(workflowEntity); const nodeNames = hintNodes.map((n) => n.name); this.logger.debug(`[EvalMock] Generating hints for ${nodeNames.length} nodes: ${nodeNames.join(', ')}`); const hints = await (0, workflow_analysis_1.generateMockHints)({ workflow: workflowEntity, nodeNames, scenarioHints, }); if (!hints.globalContext && nodeNames.length > 0) { this.logger.warn('[EvalMock] Phase 1 hint generation returned empty — mock responses will lack cross-node consistency'); } this.logger.debug(`[EvalMock] Phase 1 result — globalContext: ${hints.globalContext ? 'present' : 'EMPTY'}, triggerContent keys: ${JSON.stringify(Object.keys(hints.triggerContent))}, nodeHints: ${Object.keys(hints.nodeHints).join(', ')}`); const bypassNodes = (0, workflow_analysis_1.identifyNodesForPinData)(workflowEntity); const bypassNodeNames = bypassNodes.map((n) => n.name); if (bypassNodeNames.length > 0) { this.logger.debug(`[EvalMock] Generating pin data for ${bypassNodeNames.length} bypass nodes: ${bypassNodeNames.join(', ')}`); hints.bypassPinData = await this.generateBypassPinData(workflowEntity, bypassNodeNames, hints.globalContext, scenarioHints); this.logger.debug(`[EvalMock] Phase 1.5 result — pinned nodes: ${Object.keys(hints.bypassPinData).join(', ') || 'none'}`); } return hints; } async generateBypassPinData(workflowEntity, bypassNodeNames, globalContext, scenarioHints) { if (bypassNodeNames.length === 0) return {}; try { const dataDescription = [globalContext, scenarioHints].filter(Boolean).join('\n\n'); const result = await (0, pin_data_generator_1.generatePinData)({ workflow: workflowEntity, nodeNames: bypassNodeNames, instructions: dataDescription ? { dataDescription } : undefined, }); return (0, workflow_sdk_1.normalizePinData)(result); } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); this.logger.error(`[EvalMock] Phase 1.5 pin data generation failed: ${errorMsg}`); return (0, workflow_sdk_1.normalizePinData)(Object.fromEntries(bypassNodeNames.map((nodeName) => [nodeName, [{ json: {} }]]))); } } async execute(workflowEntity, user, executionId, hints, scenarioHints) { const nodeResults = {}; const workflow = this.buildWorkflow(workflowEntity); const startNode = this.findStartNode(workflow); if (!startNode) { return this.errorResult(executionId, 'No trigger or start node found in the workflow'); } const mockHandler = (0, mock_handler_1.createLlmMockHandler)({ scenarioHints, globalContext: hints.globalContext, nodeHints: hints.nodeHints, }); const additionalData = await (0, workflow_execute_additional_data_1.getBase)({ userId: user.id, workflowId: workflowEntity.id, workflowSettings: workflowEntity.settings ?? {}, }); additionalData.evalLlmMockHandler = this.createInterceptingHandler(mockHandler, nodeResults); additionalData.hooks = new n8n_core_1.ExecutionLifecycleHooks('evaluation', executionId, workflowEntity); const triggerPinData = this.buildTriggerPinData(startNode, hints.triggerContent); const pinData = { ...triggerPinData, ...hints.bypassPinData }; const pinDataNodeNames = Object.keys(pinData); this.checkNodeConfig(workflow, nodeResults, pinDataNodeNames); const executionData = this.buildExecutionData(startNode, pinData); if (Object.keys(triggerPinData).length > 0) { const existing = nodeResults[startNode.name]; nodeResults[startNode.name] = { output: null, interceptedRequests: [], executionMode: 'pinned', ...(existing?.configIssues ? { configIssues: existing.configIssues } : {}), }; } for (const nodeName of Object.keys(hints.bypassPinData)) { const existing = nodeResults[nodeName]; nodeResults[nodeName] = { output: null, interceptedRequests: [], executionMode: 'pinned', ...(existing?.configIssues ? { configIssues: existing.configIssues } : {}), }; } try { const result = await this.runWorkflow(workflow, additionalData, executionData); return this.buildResult(executionId, result, nodeResults, hints); } catch (error) { const message = error instanceof Error ? error.message : String(error); this.logger.error(`[EvalMock] Workflow execution failed: ${message}`); return { executionId, success: false, nodeResults, errors: [`Execution failed: ${message}`], hints, }; } } buildWorkflow(workflowEntity) { return new n8n_workflow_1.Workflow({ id: workflowEntity.id, name: workflowEntity.name, nodes: workflowEntity.nodes, connections: workflowEntity.connections, active: false, nodeTypes: this.nodeTypes, staticData: workflowEntity.staticData, settings: workflowEntity.settings ?? {}, }); } findStartNode(workflow) { return workflow.getStartNode() ?? this.findWebhookNode(workflow); } findWebhookNode(workflow) { return Object.values(workflow.nodes).find((node) => { if (node.disabled) return false; const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); return nodeType !== undefined && 'webhook' in nodeType; }); } checkNodeConfig(workflow, nodeResults, pinDataNodeNames) { for (const node of Object.values(workflow.nodes)) { if (node.disabled) continue; const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); if (!nodeType) continue; const issues = n8n_workflow_1.NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node, nodeType.description, pinDataNodeNames); if (issues?.parameters && Object.keys(issues.parameters).length > 0) { const entry = (nodeResults[node.name] ??= { output: null, interceptedRequests: [], executionMode: 'real', }); entry.configIssues = issues.parameters; } } } buildTriggerPinData(startNode, triggerContent) { if (Object.keys(triggerContent).length === 0) return {}; return { [startNode.name]: [{ json: triggerContent }] }; } buildExecutionData(startNode, pinData) { return (0, n8n_workflow_1.createRunExecutionData)({ startData: {}, resultData: { pinData, runData: {} }, executionData: { contextData: {}, metadata: {}, nodeExecutionStack: [ { node: startNode, data: { main: [[{ json: {} }]] }, source: null, }, ], waitingExecution: {}, waitingExecutionSource: {}, }, }); } async runWorkflow(workflow, additionalData, executionData) { const workflowExecute = new n8n_core_1.WorkflowExecute(additionalData, 'evaluation', executionData); return await workflowExecute.processRunExecutionData(workflow); } createInterceptingHandler(mockHandler, nodeResults) { return async (requestOptions, node) => { const entry = (nodeResults[node.name] ??= { output: null, interceptedRequests: [], executionMode: 'mocked', }); entry.executionMode = 'mocked'; const response = await mockHandler(requestOptions, node); entry.interceptedRequests.push({ url: requestOptions.url, method: requestOptions.method ?? 'GET', nodeType: node.type, requestBody: requestOptions.body, mockResponse: response?.body, }); this.logger.debug(`[EvalMock] Intercepted ${requestOptions.method ?? 'GET'} ${requestOptions.url} from "${node.name}" (${node.type})`); return response; }; } buildResult(executionId, result, nodeResults, hints) { const errors = []; const runData = result.data?.resultData?.runData ?? {}; for (const [nodeName, nodeRuns] of Object.entries(runData)) { const entry = (nodeResults[nodeName] ??= { output: null, interceptedRequests: [], executionMode: 'real', }); const lastRun = nodeRuns[nodeRuns.length - 1]; if (lastRun?.startTime) { entry.startTime = lastRun.startTime; } if (lastRun?.data?.main) { const flattened = lastRun.data.main.flat().filter(Boolean); entry.outputCount = flattened.length; const allOutputs = flattened.slice(0, MAX_OUTPUT_ITEMS_PER_NODE); if (allOutputs.length > 0) { entry.output = allOutputs; } } if (lastRun?.error) { errors.push(`Node "${nodeName}": ${lastRun.error.message}`); } } const executionError = result.data?.resultData?.error; if (executionError) { errors.push(`Workflow error: ${executionError.message}`); } return { executionId, success: executionError === undefined && errors.length === 0, nodeResults, errors, hints, }; } errorResult(executionId, message) { return { executionId, success: false, nodeResults: {}, errors: [message], hints: { globalContext: '', triggerContent: {}, nodeHints: {}, warnings: [], bypassPinData: {}, }, }; } }; exports.EvalExecutionService = EvalExecutionService; exports.EvalExecutionService = EvalExecutionService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [workflow_finder_service_1.WorkflowFinderService, node_types_1.NodeTypes, backend_common_1.Logger]) ], EvalExecutionService); //# sourceMappingURL=execution.service.js.map