From 60670e1e40d304a4c46687a20ecaaf239e729632 Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 21 Aug 2025 10:13:25 +0200 Subject: [PATCH] feat: Provide instance URL to the AI builder (no-changelog) (#18237) Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Eugene --- .../src/ai-workflow-builder-agent.service.ts | 2 + .../src/chains/parameter-updater.ts | 6 +- .../src/chains/prompts/instance-url.ts | 12 ++ .../src/tools/prompts/main-agent.prompt.ts | 10 +- .../src/tools/update-node-parameters.tool.ts | 129 +++++++++++------- .../src/workflow-builder-agent.ts | 11 +- .../services/ai-workflow-builder.service.ts | 9 +- 7 files changed, 124 insertions(+), 55 deletions(-) create mode 100644 packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/instance-url.ts diff --git a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts index 90a64f684d..9bd596f993 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts @@ -30,6 +30,7 @@ export class AiWorkflowBuilderService { private readonly nodeTypes: INodeTypes, private readonly client?: AiAssistantClient, private readonly logger?: Logger, + private readonly instanceUrl?: string, ) { this.parsedNodeTypes = this.getNodeTypes(); } @@ -162,6 +163,7 @@ export class AiWorkflowBuilderService { tracer: this.tracingClient ? new LangChainTracer({ client: this.tracingClient, projectName: 'n8n-workflow-builder' }) : undefined, + instanceUrl: this.instanceUrl, }); return this.agent; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/chains/parameter-updater.ts b/packages/@n8n/ai-workflow-builder.ee/src/chains/parameter-updater.ts index 630d7dc475..82a4668a6b 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/chains/parameter-updater.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/chains/parameter-updater.ts @@ -6,6 +6,7 @@ import { z } from 'zod'; import { LLMServiceError } from '../errors'; import type { ParameterUpdaterOptions } from '../types/config'; +import { instanceUrlPrompt } from './prompts/instance-url'; import { ParameterUpdatePromptBuilder } from './prompts/prompt-builder'; export const parametersSchema = z @@ -40,7 +41,6 @@ const workflowContextPrompt = ` {execution_schema} - Name: {node_name} Type: {node_type} @@ -101,6 +101,10 @@ export const createParameterUpdaterChain = ( text: nodeDefinitionPrompt, cache_control: { type: 'ephemeral' }, }, + { + type: 'text', + text: instanceUrlPrompt, + }, ], ], ]); diff --git a/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/instance-url.ts b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/instance-url.ts new file mode 100644 index 0000000000..0d70bc140f --- /dev/null +++ b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/instance-url.ts @@ -0,0 +1,12 @@ +export const instanceUrlPrompt = ` + +The n8n instance base URL is: {instanceUrl} + +This URL is essential for webhook nodes and chat triggers as it provides the base URL for: +- Webhook URLs that external services need to call +- Chat trigger URLs for conversational interfaces +- Any node that requires the full instance URL to generate proper callback URLs + +When working with webhook or chat trigger nodes, use this URL as the base for constructing proper endpoint URLs. + +`; 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 f6cec88e19..f03f6bdcee 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 @@ -1,5 +1,7 @@ import { ChatPromptTemplate } from '@langchain/core/prompts'; +import { instanceUrlPrompt } from '@/chains/prompts/instance-url'; + const systemPrompt = `You are an AI assistant specialized in creating and editing n8n workflows. Your goal is to help users build efficient, well-connected workflows by intelligently using the available tools. @@ -329,7 +331,9 @@ update_node_parameters({{ }}) Then tell the user: "I've set up the Gmail Tool node with dynamic AI parameters - it will automatically determine recipients and subjects based on context." -`; + + +`; const responsePatterns = ` @@ -392,6 +396,10 @@ export const mainAgentPrompt = ChatPromptTemplate.fromMessages([ text: systemPrompt, cache_control: { type: 'ephemeral' }, }, + { + type: 'text', + text: instanceUrlPrompt, + }, { type: 'text', text: currentWorkflowJson, 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 d7004ebecb..f64b4ad2bf 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 @@ -43,6 +43,74 @@ function buildSuccessMessage(node: INode, changes: string[]): string { return `Successfully updated parameters for node "${node.name}" (${node.type}):\n${changesList}`; } +/** + * Process parameter updates using the LLM chain + */ +async function processParameterUpdates( + node: INode, + nodeType: INodeTypeDescription, + nodeId: string, + changes: string[], + state: ReturnType, + llm: BaseChatModel, + logger?: Logger, + instanceUrl?: string, +): Promise { + const workflow = getCurrentWorkflow(state); + + // Get current parameters + const currentParameters = extractNodeParameters(node); + + // Format inputs for the chain + const formattedChanges = formatChangesForPrompt(changes); + + // Get the node's properties definition as JSON + const nodePropertiesJson = JSON.stringify(nodeType.properties || [], null, 2); + + // Call the parameter updater chain with dynamic prompt building + const parametersChain = createParameterUpdaterChain( + llm, + { + nodeType: node.type, + nodeDefinition: nodeType, + requestedChanges: changes, + }, + logger, + ); + + const newParameters = (await parametersChain.invoke({ + workflow_json: workflow, + execution_schema: state.workflowContext?.executionSchema ?? 'NO SCHEMA', + execution_data: state.workflowContext?.executionData ?? 'NO EXECUTION DATA YET', + node_id: nodeId, + node_name: node.name, + node_type: node.type, + current_parameters: JSON.stringify(currentParameters, null, 2), + node_definition: nodePropertiesJson, + changes: formattedChanges, + instanceUrl: instanceUrl ?? '', + })) as INodeParameters; + + // Ensure newParameters is a valid object + if (!newParameters || typeof newParameters !== 'object') { + throw new ParameterUpdateError('Invalid parameters returned from LLM', { + nodeId, + nodeType: node.type, + }); + } + + // Ensure parameters property exists and is valid + if (!newParameters.parameters || typeof newParameters.parameters !== 'object') { + throw new ParameterUpdateError('Invalid parameters structure returned from LLM', { + nodeId, + nodeType: node.type, + }); + } + + // Fix expression prefixes in the new parameters + return fixExpressionPrefixes(newParameters.parameters) as INodeParameters; +} + /** * Factory function to create the update node parameters tool */ @@ -50,6 +118,7 @@ export function createUpdateNodeParametersTool( nodeTypes: INodeTypeDescription[], llm: BaseChatModel, logger?: Logger, + instanceUrl?: string, ) { return tool( async (input, config) => { @@ -90,61 +159,19 @@ export function createUpdateNodeParametersTool( }); try { - // Get current parameters - const currentParameters = extractNodeParameters(node); - - // Format inputs for the chain - const formattedChanges = formatChangesForPrompt(changes); - - // Get the node's properties definition as JSON - const nodePropertiesJson = JSON.stringify(nodeType.properties || [], null, 2); - - // Call the parameter updater chain with dynamic prompt building - const parametersChain = createParameterUpdaterChain( + const updatedParameters = await processParameterUpdates( + node, + nodeType, + nodeId, + changes, + state, llm, - { - nodeType: node.type, - nodeDefinition: nodeType, - requestedChanges: changes, - }, logger, + instanceUrl, ); - const newParameters = (await parametersChain.invoke({ - workflow_json: workflow, - execution_schema: state.workflowContext?.executionSchema ?? 'NO SCHEMA', - execution_data: state.workflowContext?.executionData ?? 'NO EXECUTION DATA YET', - node_id: nodeId, - node_name: node.name, - node_type: node.type, - current_parameters: JSON.stringify(currentParameters, null, 2), - node_definition: nodePropertiesJson, - changes: formattedChanges, - })) as INodeParameters; - - // Ensure newParameters is a valid object - if (!newParameters || typeof newParameters !== 'object') { - throw new ParameterUpdateError('Invalid parameters returned from LLM', { - nodeId, - nodeType: node.type, - }); - } - - // Ensure parameters property exists and is valid - if (!newParameters.parameters || typeof newParameters.parameters !== 'object') { - throw new ParameterUpdateError('Invalid parameters structure returned from LLM', { - nodeId, - nodeType: node.type, - }); - } - - // Fix expression prefixes in the new parameters - const fixedParameters = fixExpressionPrefixes( - newParameters.parameters, - ) as INodeParameters; - // Create updated node - const updatedNode = updateNodeWithParameters(node, fixedParameters); + const updatedNode = updateNodeWithParameters(node, updatedParameters); // Build success message const message = buildSuccessMessage(node, changes); @@ -154,7 +181,7 @@ export function createUpdateNodeParametersTool( nodeId, nodeName: node.name, nodeType: node.type, - updatedParameters: fixedParameters, + updatedParameters, appliedChanges: changes, message, }; 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 da596021bd..c3511aec88 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 @@ -40,6 +40,7 @@ export interface WorkflowBuilderAgentConfig { checkpointer?: MemorySaver; tracer?: LangChainTracer; autoCompactThresholdTokens?: number; + instanceUrl?: string; } export interface ChatPayload { @@ -59,6 +60,7 @@ export class WorkflowBuilderAgent { private logger?: Logger; private tracer?: LangChainTracer; private autoCompactThresholdTokens: number; + private instanceUrl?: string; constructor(config: WorkflowBuilderAgentConfig) { this.parsedNodeTypes = config.parsedNodeTypes; @@ -69,6 +71,7 @@ export class WorkflowBuilderAgent { this.tracer = config.tracer; this.autoCompactThresholdTokens = config.autoCompactThresholdTokens ?? DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS; + this.instanceUrl = config.instanceUrl; } private createWorkflow() { @@ -78,7 +81,12 @@ export class WorkflowBuilderAgent { createAddNodeTool(this.parsedNodeTypes), createConnectNodesTool(this.parsedNodeTypes, this.logger), createRemoveNodeTool(this.logger), - createUpdateNodeParametersTool(this.parsedNodeTypes, this.llmComplexTask, this.logger), + createUpdateNodeParametersTool( + this.parsedNodeTypes, + this.llmComplexTask, + this.logger, + this.instanceUrl, + ), ]; // Create a map for quick tool lookup @@ -98,6 +106,7 @@ export class WorkflowBuilderAgent { ...state, executionData: state.workflowContext?.executionData ?? {}, executionSchema: state.workflowContext?.executionSchema ?? [], + instanceUrl: this.instanceUrl, }); const response = await this.llmSimpleTask.bindTools(tools).invoke(prompt); diff --git a/packages/cli/src/services/ai-workflow-builder.service.ts b/packages/cli/src/services/ai-workflow-builder.service.ts index 892d1afe9a..ece9d21975 100644 --- a/packages/cli/src/services/ai-workflow-builder.service.ts +++ b/packages/cli/src/services/ai-workflow-builder.service.ts @@ -9,6 +9,7 @@ import type { IUser } from 'n8n-workflow'; import { N8N_VERSION } from '@/constants'; import { License } from '@/license'; import { NodeTypes } from '@/node-types'; +import { UrlService } from '@/services/url.service'; /** * This service wraps the actual AiWorkflowBuilderService to avoid circular dependencies. @@ -23,6 +24,7 @@ export class WorkflowBuilderService { private readonly license: License, private readonly config: GlobalConfig, private readonly logger: Logger, + private readonly urlService: UrlService, ) {} private async getService(): Promise { @@ -43,7 +45,12 @@ export class WorkflowBuilderService { }); } - this.service = new AiWorkflowBuilderService(this.nodeTypes, client, this.logger); + this.service = new AiWorkflowBuilderService( + this.nodeTypes, + client, + this.logger, + this.urlService.getInstanceBaseUrl(), + ); } return this.service; }