From a9d59ed84aad55169d37fb5d9ca563b451da5b97 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Mon, 1 Sep 2025 11:36:12 +0200 Subject: [PATCH] feat: Decrease AI Builder wordiness (no-changelog) (#18920) --- .../evaluations/README.md | 1 + .../src/tools/add-node.tool.ts | 5 +- .../src/tools/prompts/main-agent.prompt.ts | 62 ++--- .../src/utils/stream-processor.ts | 17 +- .../src/utils/test/stream-processor.test.ts | 12 + .../AskAssistantChat/AskAssistantChat.test.ts | 247 +++++++++++++++++- .../AskAssistantChat/AskAssistantChat.vue | 31 ++- .../messages/MessageWrapper.vue | 27 +- .../AskAssistantChat/messages/helpers.ts | 30 +++ 9 files changed, 350 insertions(+), 82 deletions(-) create mode 100644 packages/frontend/@n8n/design-system/src/components/AskAssistantChat/messages/helpers.ts diff --git a/packages/@n8n/ai-workflow-builder.ee/evaluations/README.md b/packages/@n8n/ai-workflow-builder.ee/evaluations/README.md index f1c72922dd..2109f8b698 100644 --- a/packages/@n8n/ai-workflow-builder.ee/evaluations/README.md +++ b/packages/@n8n/ai-workflow-builder.ee/evaluations/README.md @@ -161,6 +161,7 @@ The AI Workflow Builder agent needs access to node definitions to generate workf 1. Run your n8n instance 2. Download the node definitions from locally running n8n instance(http://localhost:5678/types/nodes.json) 3. Save the node definitions to `evaluations/nodes.json` +` curl -o evaluations/nodes.json http://localhost:5678/types/nodes.json` The evaluation will fail with a clear error message if `nodes.json` is missing. diff --git a/packages/@n8n/ai-workflow-builder.ee/src/tools/add-node.tool.ts b/packages/@n8n/ai-workflow-builder.ee/src/tools/add-node.tool.ts index 999f9a6e8d..015a55dc98 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/tools/add-node.tool.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/tools/add-node.tool.ts @@ -13,7 +13,7 @@ import { findNodeType } from './helpers/validation'; import type { AddedNode } from '../types/nodes'; import type { AddNodeOutput, ToolError } from '../types/tools'; -const DISPLAY_TITLE = 'Adding node'; +const DISPLAY_TITLE = 'Adding nodes'; /** * Schema for node creation input @@ -77,7 +77,8 @@ function getCustomNodeTitle( } } - return DISPLAY_TITLE; + // single "node" not plural "nodes" because this pertains to this specific tool call + return 'Adding node'; } /** 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 68ed382b76..fb01294166 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 @@ -3,26 +3,21 @@ 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. - - -ALWAYS end your workflow mutation responses with a brief note that the workflow can be adjusted if needed. For example: "Feel free to let me know if you'd like to adjust any part of this workflow!" This is mandatory for all workflow mutation responses. - - After receiving tool results, reflect on their quality and determine optimal next steps. Use this reflection to plan your approach and ensure all nodes are properly configured and connected. -Be warm, helpful, and most importantlyconcise. Focus on actionable information. -- Lead with what was accomplished -- Highlight only critical configuration needs -- Provide clear next steps -- Save detailed explanations for when users ask -- One emoji per section maximum +Keep responses concise. + +CRITICAL: Do NOT provide commentary between tool calls. Execute tools silently. +- NO progress messages like "Perfect!", "Now let me...", "Excellent!" +- NO descriptions of what was built or how it works +- NO workflow features or capabilities explanations +- Only respond AFTER all tools are complete +- Response should only contain setup/usage information - -For maximum efficiency, invoke all relevant tools simultaneously when performing independent operations. This significantly reduces wait time and improves user experience. Parallel execution guidelines: - ALL tools support parallel execution, including add_nodes @@ -316,7 +311,7 @@ When unsure about specific values: - Add nodes and connections confidently - For uncertain parameters, use update_node_parameters with clear placeholders - For tool nodes with dynamic values, use $fromAI expressions instead of placeholders -- Always mention what needs user input in your response +- Always mention what needs user to configure in the setup response Example for regular nodes: update_node_parameters({{ @@ -329,41 +324,30 @@ update_node_parameters({{ nodeId: "gmailTool1", instructions: ["Set sendTo to {{ $fromAI('to') }}", "Set subject to {{ $fromAI('subject') }}"] }}) - -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 = ` -After completing workflow tasks, follow this structure: +IMPORTANT: Only provide ONE response AFTER all tool execution is complete. -1. **Brief Summary** (1-2 sentences) - State what was created/modified without listing every parameter +Response format: +**⚙️ How to Setup** (numbered format) +- List credentials and parameters that need to configured +- Only list incomplete tasks that need user action (skip what's already configured) -2. **Key Requirements** (if any) - - Credentials needed - - Parameters the user should verify - - Any manual configuration required +**ℹ️ How to Use** +- Only essential user actions (what to click, where to go) -3. **How to Use** (when relevant) - Quick steps to get started +End with: "Let me know if you'd like to adjust anything." -4. **Next Steps** (if applicable) - What the user might want to do next - - -Be warm, helpful, and most importantly concise. Focus on actionable information. -- Lead with what was accomplished -- Provide clear next steps -- Highlight only critical configuration needs -- Be warm and encouraging without excessive enthusiasm -- Use emojis sparingly (1-2 max per response) -- Focus on what the user needs to know -- Expand details only when asked -- End with a brief note that the workflow can be adjusted if needed - +ABSOLUTELY FORBIDDEN IN BUILDING MODE: +- Any text between tool calls +- Progress updates during execution +- "Perfect!", "Now let me...", "Excellent!" +- Describing what was built +- Explaining workflow functionality `; diff --git a/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts b/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts index c7e9b694ff..1483762617 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/utils/stream-processor.ts @@ -59,6 +59,7 @@ export function processStreamChunk(streamMode: string, chunk: unknown): StreamOu if ((agentChunk?.compact_messages?.messages ?? []).length > 0) { const lastMessage = agentChunk.compact_messages!.messages![agentChunk.compact_messages!.messages!.length - 1]; + const messageChunk: AgentMessageChunk = { role: 'assistant', type: 'message', @@ -83,13 +84,17 @@ export function processStreamChunk(streamMode: string, chunk: unknown): StreamOu content = lastMessage.content; } - const messageChunk: AgentMessageChunk = { - role: 'assistant', - type: 'message', - text: content, - }; + if (content) { + const messageChunk: AgentMessageChunk = { + role: 'assistant', + type: 'message', + text: content, + }; - return { messages: [messageChunk] }; + return { messages: [messageChunk] }; + } + + return null; } } diff --git a/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts b/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts index ceedc7b896..98b434e7ef 100644 --- a/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts +++ b/packages/@n8n/ai-workflow-builder.ee/src/utils/test/stream-processor.test.ts @@ -86,6 +86,18 @@ describe('stream-processor', () => { expect(message.text).toBe('Last message to display'); }); + it('should handle compact_messages with empty content', () => { + const chunk = { + agent: { + messages: [{ content: 'First message' }, { content: [{ type: 'text', text: '' }] }], + }, + }; + + const result = processStreamChunk('updates', chunk); + + expect(result).toEqual(null); + }); + it('should handle process_operations with workflow update', () => { const workflowData = { nodes: [{ id: 'node1', name: 'Test Node' }], diff --git a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts index 05b52fcf72..5eacff3d36 100644 --- a/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts +++ b/packages/frontend/@n8n/design-system/src/components/AskAssistantChat/AskAssistantChat.test.ts @@ -1,9 +1,8 @@ import { render } from '@testing-library/vue'; import { vi } from 'vitest'; -import { n8nHtml } from '@n8n/design-system/directives'; - import AskAssistantChat from './AskAssistantChat.vue'; +import { n8nHtml } from '../../directives'; import type { Props as MessageWrapperProps } from './messages/MessageWrapper.vue'; import type { ChatUI } from '../../types/assistant'; @@ -388,6 +387,70 @@ describe('AskAssistantChat', () => { }); }); + it('should collapse tool messages with same toolName with hidden messages in between', () => { + const messages: Array = [ + createToolMessage({ + id: '1', + status: 'running', + displayTitle: 'Searching...', + updates: [{ type: 'progress', data: { status: 'Initializing search' } }], + }), + { + id: '2', + role: 'assistant', + type: 'agent-suggestion', + title: 'Agent Suggestion', + content: 'This is a suggestion from the agent', + suggestionId: 'test', + quickReplies: [ + { type: 'accept', text: 'Accept suggestion' }, + { type: 'reject', text: 'Reject suggestion' }, + ], + read: true, + }, + createToolMessage({ + id: '2', + status: 'running', + displayTitle: 'Still searching...', + customDisplayTitle: 'Custom Search Title', + updates: [{ type: 'progress', data: { status: 'Processing results' } }], + }), + { + id: 'test', + role: 'assistant', + type: 'workflow-updated', + codeSnippet: '', + }, + createToolMessage({ + id: '3', + status: 'completed', + displayTitle: 'Search Complete', + updates: [{ type: 'output', data: { result: 'Found 10 items' } }], + }), + ]; + + renderWithMessages(messages); + + expectMessageWrapperCalledTimes(1); + const props = getMessageWrapperProps(); + + expectToolMessage(props, { + id: '3', + role: 'assistant', + type: 'tool', + toolName: 'search', + status: 'running', + displayTitle: 'Still searching...', + customDisplayTitle: 'Custom Search Title', + updates: [ + { type: 'progress', data: { status: 'Initializing search' } }, + { type: 'progress', data: { status: 'Processing results' } }, + { type: 'output', data: { result: 'Found 10 items' } }, + ], + read: true, + }); + }); + it('should not collapse tool messages with different toolNames', () => { const messages = [ createToolMessage({ @@ -738,4 +801,184 @@ describe('AskAssistantChat', () => { ); }); }); + + describe('Quick Replies', () => { + const renderWithQuickReplies = ( + messages: ChatUI.AssistantMessage[], + streaming = false, + loadingMessage?: string, + ) => { + return render(AskAssistantChat, { + global: { + directives: { n8nHtml }, + stubs: { + ...Object.fromEntries(stubs.map((stub) => [stub, true])), + 'n8n-button': { template: '