From 5015290dbeb29aac2a5f1cc3b25937661d9b458d Mon Sep 17 00:00:00 2001 From: Yiorgis Gozadinos Date: Wed, 11 Jun 2025 09:54:49 +0200 Subject: [PATCH] fix(Chat Memory Manager Node): Fix simplifyMessages to not overwrite consecutive messages of same type (#16168) --- .../MemoryManager/MemoryManager.node.ts | 42 +++++--- .../test/MemoryManager.node.test.ts | 102 ++++++++++++++++++ 2 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/test/MemoryManager.node.test.ts diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts index 6bfbe19779..efa5c76e5f 100644 --- a/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/MemoryManager.node.ts @@ -1,6 +1,7 @@ /* eslint-disable n8n-nodes-base/node-dirname-against-convention */ import type { BaseChatMemory } from '@langchain/community/memory/chat_memory'; -import { AIMessage, SystemMessage, HumanMessage, type BaseMessage } from '@langchain/core/messages'; +import type { MessageContent, BaseMessage } from '@langchain/core/messages'; +import { AIMessage, SystemMessage, HumanMessage } from '@langchain/core/messages'; import { NodeConnectionTypes } from 'n8n-workflow'; import type { IDataObject, @@ -17,24 +18,31 @@ interface MessageRecord { hideFromUI: boolean; } -function simplifyMessages(messages: BaseMessage[]) { - const chunkedMessages = []; - for (let i = 0; i < messages.length; i += 2) { - chunkedMessages.push([messages[i], messages[i + 1]]); +export function simplifyMessages(messages: BaseMessage[]): Array> { + if (messages.length === 0) return []; + + const result: Array> = []; + let index = 0; + + while (index < messages.length) { + const currentGroup: Record = {}; + + do { + const message = messages[index]; + const messageType = message.getType(); + + if (messageType in currentGroup) { + break; + } + + currentGroup[messageType] = message.content; + index++; + } while (index < messages.length); + + result.push(currentGroup); } - const transformedMessages = chunkedMessages.map((exchange) => { - const simplified = { - [exchange[0]._getType()]: exchange[0].content, - }; - - if (exchange[1]) { - simplified[exchange[1]._getType()] = exchange[1].content; - } - - return simplified; - }); - return transformedMessages; + return result; } const prepareOutputSetup = (ctx: IExecuteFunctions, version: number, memory: BaseChatMemory) => { diff --git a/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/test/MemoryManager.node.test.ts b/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/test/MemoryManager.node.test.ts new file mode 100644 index 0000000000..96d5e0c073 --- /dev/null +++ b/packages/@n8n/nodes-langchain/nodes/memory/MemoryManager/test/MemoryManager.node.test.ts @@ -0,0 +1,102 @@ +import { AIMessage, HumanMessage, SystemMessage } from '@langchain/core/messages'; + +import { simplifyMessages } from '../MemoryManager.node'; + +describe('simplifyMessages', () => { + it('should handle single message', () => { + const messages = [new HumanMessage('Hello')]; + const result = simplifyMessages(messages); + expect(result).toEqual([{ human: 'Hello' }]); + }); + + it('should group different message types together', () => { + const messages = [ + new HumanMessage('Hello, how are you?'), + new AIMessage("I'm doing well, thank you for asking! How about you?"), + ]; + const result = simplifyMessages(messages); + expect(result).toEqual([ + { + human: 'Hello, how are you?', + ai: "I'm doing well, thank you for asking! How about you?", + }, + ]); + }); + + it('should separate consecutive messages of same type into different groups', () => { + const messages = [ + new HumanMessage('First human message'), + new HumanMessage('Second human message'), + new AIMessage('AI response'), + ]; + const result = simplifyMessages(messages); + expect(result).toEqual([ + { human: 'First human message' }, + { human: 'Second human message', ai: 'AI response' }, + ]); + }); + + it('should handle three consecutive messages of same type', () => { + const messages = [new HumanMessage('1'), new HumanMessage('2'), new HumanMessage('3')]; + const result = simplifyMessages(messages); + expect(result).toEqual([{ human: '1' }, { human: '2' }, { human: '3' }]); + }); + + it('should handle mixed message types with grouping', () => { + const messages = [ + new SystemMessage('System message'), + new HumanMessage('Hello'), + new AIMessage('Hi there'), + new HumanMessage('Another human message'), + new AIMessage('Another AI message'), + ]; + const result = simplifyMessages(messages); + expect(result).toEqual([ + { + system: 'System message', + human: 'Hello', + ai: 'Hi there', + }, + { + human: 'Another human message', + ai: 'Another AI message', + }, + ]); + }); + + it('should handle system messages correctly', () => { + const messages = [ + new SystemMessage('System instruction'), + new HumanMessage('User question'), + new AIMessage('AI response'), + ]; + const result = simplifyMessages(messages); + expect(result).toEqual([ + { + system: 'System instruction', + human: 'User question', + ai: 'AI response', + }, + ]); + }); + + it('should handle alternating same types correctly', () => { + const messages = [ + new HumanMessage('Human 1'), + new AIMessage('AI 1'), + new HumanMessage('Human 2'), + new AIMessage('AI 2'), + ]; + const result = simplifyMessages(messages); + expect(result).toEqual([ + { + human: 'Human 1', + ai: 'AI 1', + }, + { + human: 'Human 2', + ai: 'AI 2', + }, + ]); + }); +});