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 d924833cb34..8fa6ae44a13 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 @@ -146,14 +146,37 @@ export async function* createStreamProcessor( } } +/** + * Remove context tags from message content that are used for AI context + * but shouldn't be displayed to users + */ +function cleanContextTags(text: string): string { + return text.replace(/\n*[\s\S]*?<\/current_execution_nodes_schemas>/, ''); +} + /** * Format a HumanMessage into the expected output format */ function formatHumanMessage(msg: HumanMessage): Record { + // Handle array content (multi-part messages with text, images, etc.) + if (Array.isArray(msg.content)) { + const textParts = msg.content.filter( + (c): c is { type: string; text: string } => + typeof c === 'object' && c !== null && 'type' in c && c.type === 'text' && 'text' in c, + ); + const text = textParts.map((part) => cleanContextTags(part.text)).join('\n'); + return { + role: 'user', + type: 'message', + text, + }; + } + + // Handle simple string content return { role: 'user', type: 'message', - text: msg.content, + text: cleanContextTags(msg.content), }; } 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 9552c98d3c8..58e5920f9fc 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 @@ -274,6 +274,82 @@ describe('stream-processor', () => { }); }); + it('should format HumanMessage with array content (multi-part messages)', () => { + const messages = [ + new HumanMessage({ + content: [ + { type: 'text', text: 'Part 1' }, + { type: 'text', text: 'Part 2' }, + { type: 'image_url', image_url: 'http://example.com/image.png' }, + ], + }), + ]; + + const result = formatMessages(messages); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + role: 'user', + type: 'message', + text: 'Part 1\nPart 2', + }); + }); + + it('should strip context tags from HumanMessage content', () => { + const messageWithContext = `User question here + +{"nodes": [], "connections": {}} + + +{"runData": {}} + + +[{"nodeName": "test"}] +`; + + const messages = [new HumanMessage(messageWithContext)]; + + const result = formatMessages(messages); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + role: 'user', + type: 'message', + text: 'User question here', + }); + }); + + it('should strip context tags from HumanMessage array content', () => { + const messages = [ + new HumanMessage({ + content: [ + { + type: 'text', + text: `Workflow executed successfully. + +{"nodes": []} + + +{"runData": {}} + + +[{"nodeName": "Manual Trigger"}] +`, + }, + ], + }), + ]; + + const result = formatMessages(messages); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + role: 'user', + type: 'message', + text: 'Workflow executed successfully.', + }); + }); + it('should format AIMessage with text content', () => { const messages = [new AIMessage('Response from AI')];