diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts index 67a67528db..fb80f166e6 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/Agent.node.ts @@ -263,7 +263,7 @@ export class Agent implements INodeType { icon: 'fa:robot', iconColor: 'black', group: ['transform'], - version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8], + version: [1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], description: 'Generates an action plan and executes it. Can use external tools.', subtitle: "={{ { toolsAgent: 'Tools Agent', conversationalAgent: 'Conversational Agent', openAiFunctionsAgent: 'OpenAI Functions Agent', reActAgent: 'ReAct Agent', sqlAgent: 'SQL Agent', planAndExecuteAgent: 'Plan and Execute Agent' }[$parameter.agent] }}", diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts index db1e0ffbe7..1a99bc1d79 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/agents/ToolsAgent/execute.ts @@ -346,11 +346,20 @@ export async function prepareMessages( outputParser?: N8nOutputParser; }, ): Promise { - const messages: BaseMessagePromptTemplateLike[] = [ - ['system', `{system_message}${options.outputParser ? '\n\n{formatting_instructions}' : ''}`], - ['placeholder', '{chat_history}'], - ['human', '{input}'], - ]; + const useSystemMessage = options.systemMessage ?? ctx.getNode().typeVersion < 1.9; + + const messages: BaseMessagePromptTemplateLike[] = []; + + if (useSystemMessage) { + messages.push([ + 'system', + `{system_message}${options.outputParser ? '\n\n{formatting_instructions}' : ''}`, + ]); + } else if (options.outputParser) { + messages.push(['system', '{formatting_instructions}']); + } + + messages.push(['placeholder', '{chat_history}'], ['human', '{input}']); // If there is binary data and the node option permits it, add a binary message const hasBinaryData = ctx.getInputData()?.[itemIndex]?.binary !== undefined; diff --git a/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent.test.ts b/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent.test.ts index 44ce14d0d0..532a176226 100644 --- a/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/agents/Agent/test/ToolsAgent.test.ts @@ -6,7 +6,7 @@ import { Buffer } from 'buffer'; import { mock } from 'jest-mock-extended'; import type { ToolsAgentAction } from 'langchain/dist/agents/tool_calling/output_parser'; import type { Tool } from 'langchain/tools'; -import type { IExecuteFunctions } from 'n8n-workflow'; +import type { IExecuteFunctions, INode } from 'n8n-workflow'; import { NodeOperationError, BINARY_ENCODING, NodeConnectionTypes } from 'n8n-workflow'; import type { ZodType } from 'zod'; import { z } from 'zod'; @@ -232,6 +232,75 @@ describe('prepareMessages', () => { const hasHumanMessage = messages.some((m) => m instanceof HumanMessage); expect(hasHumanMessage).toBe(false); }); + + it('should not include system_message in prompt templates if not provided after version 1.9', async () => { + const fakeItem = { json: {} }; + const mockNode = mock(); + mockNode.typeVersion = 1.9; + mockContext.getInputData.mockReturnValue([fakeItem]); + mockContext.getNode.mockReturnValue(mockNode); + const messages = await prepareMessages(mockContext, 0, {}); + + expect(messages.length).toBe(3); + expect(messages).not.toContainEqual(['system', '{system_message}']); + }); + + it('should include system_message in prompt templates if provided after version 1.9', async () => { + const fakeItem = { json: {} }; + const mockNode = mock(); + mockNode.typeVersion = 1.9; + mockContext.getInputData.mockReturnValue([fakeItem]); + mockContext.getNode.mockReturnValue(mockNode); + + const messages = await prepareMessages(mockContext, 0, { systemMessage: 'Hello' }); + + expect(messages.length).toBe(4); + expect(messages).toContainEqual(['system', '{system_message}']); + }); + + it('should include system_message in prompt templates if not provided before version 1.9', async () => { + const fakeItem = { json: {} }; + const mockNode = mock(); + mockNode.typeVersion = 1.8; + mockContext.getInputData.mockReturnValue([fakeItem]); + mockContext.getNode.mockReturnValue(mockNode); + + const messages = await prepareMessages(mockContext, 0, {}); + + expect(messages.length).toBe(4); + expect(messages).toContainEqual(['system', '{system_message}']); + }); + + it('should include system_message with formatting_instructions in prompt templates if provided before version 1.9', async () => { + const fakeItem = { json: {} }; + const mockNode = mock(); + mockNode.typeVersion = 1.8; + mockContext.getInputData.mockReturnValue([fakeItem]); + mockContext.getNode.mockReturnValue(mockNode); + + const messages = await prepareMessages(mockContext, 0, { + systemMessage: 'Hello', + outputParser: mock(), + }); + + expect(messages.length).toBe(4); + expect(messages).toContainEqual(['system', '{system_message}\n\n{formatting_instructions}']); + }); + + it('should add formatting instructions when omitting system message after version 1.9', async () => { + const fakeItem = { json: {} }; + const mockNode = mock(); + mockNode.typeVersion = 1.9; + mockContext.getInputData.mockReturnValue([fakeItem]); + mockContext.getNode.mockReturnValue(mockNode); + + const messages = await prepareMessages(mockContext, 0, { + outputParser: mock(), + }); + + expect(messages.length).toBe(4); + expect(messages).toContainEqual(['system', '{formatting_instructions}']); + }); }); describe('preparePrompt', () => {