diff --git a/packages/components/credentials/MotorheadMemoryApi.credential.ts b/packages/components/credentials/MotorheadMemoryApi.credential.ts deleted file mode 100644 index 68a18ec1c..000000000 --- a/packages/components/credentials/MotorheadMemoryApi.credential.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { INodeParams, INodeCredential } from '../src/Interface' - -class MotorheadMemoryApi implements INodeCredential { - label: string - name: string - version: number - description: string - inputs: INodeParams[] - - constructor() { - this.label = 'Motorhead Memory API' - this.name = 'motorheadMemoryApi' - this.version = 1.0 - this.description = - 'Refer to official guide on how to create API key and Client ID on Motorhead Memory' - this.inputs = [ - { - label: 'Client ID', - name: 'clientId', - type: 'string' - }, - { - label: 'API Key', - name: 'apiKey', - type: 'password' - } - ] - } -} - -module.exports = { credClass: MotorheadMemoryApi } diff --git a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts index 354063804..b5acd994d 100644 --- a/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts +++ b/packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts @@ -9,7 +9,7 @@ import { RunnableSequence } from '@langchain/core/runnables' import { ChatConversationalAgent } from 'langchain/agents' import { getBaseClasses } from '../../../src/utils' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' -import { IVisionChatModal, FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' +import { IVisionChatModal, FlowiseMemory, ICommonObject, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' import { AgentExecutor } from '../../../src/agents' import { addImagesToMessages, llmSupportsVision } from '../../../src/multiModalUtils' import { checkInputs, Moderation } from '../../moderation/Moderation' @@ -92,7 +92,7 @@ class ConversationalAgent_Agents implements INode { } async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { - return prepareAgent(nodeData, options, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + return prepareAgent(nodeData, options, { sessionId: this.sessionId, chatId: options.chatId, input }) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -109,12 +109,7 @@ class ConversationalAgent_Agents implements INode { return formatResponse(e.message) } } - const executor = await prepareAgent( - nodeData, - options, - { sessionId: this.sessionId, chatId: options.chatId, input }, - options.chatHistory - ) + const executor = await prepareAgent(nodeData, options, { sessionId: this.sessionId, chatId: options.chatId, input }) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -178,8 +173,7 @@ class ConversationalAgent_Agents implements INode { const prepareAgent = async ( nodeData: INodeData, options: ICommonObject, - flowObj: { sessionId?: string; chatId?: string; input?: string }, - chatHistory: IMessage[] = [] + flowObj: { sessionId?: string; chatId?: string; input?: string } ) => { const model = nodeData.inputs?.model as BaseChatModel let tools = nodeData.inputs?.tools as Tool[] @@ -238,7 +232,7 @@ const prepareAgent = async ( [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, agent_scratchpad: async (i: { input: string; steps: AgentStep[] }) => await constructScratchPad(i.steps), [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { - const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + const messages = (await memory.getChatMessages(flowObj?.sessionId, true)) as BaseMessage[] return messages ?? [] } }, diff --git a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts index 31ba9724e..a32af6311 100644 --- a/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts +++ b/packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts @@ -6,7 +6,7 @@ import { RunnableSequence } from '@langchain/core/runnables' import { ChatOpenAI, formatToOpenAIFunction } from '@langchain/openai' import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts' import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' -import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams } from '../../../src/Interface' +import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { AgentExecutor, formatAgentSteps } from '../../../src/agents' @@ -77,7 +77,7 @@ class ConversationalRetrievalAgent_Agents implements INode { } async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { - return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -95,7 +95,7 @@ class ConversationalRetrievalAgent_Agents implements INode { } } - const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -127,11 +127,7 @@ class ConversationalRetrievalAgent_Agents implements INode { } } -const prepareAgent = ( - nodeData: INodeData, - flowObj: { sessionId?: string; chatId?: string; input?: string }, - chatHistory: IMessage[] = [] -) => { +const prepareAgent = (nodeData: INodeData, flowObj: { sessionId?: string; chatId?: string; input?: string }) => { const model = nodeData.inputs?.model as ChatOpenAI const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string @@ -156,7 +152,7 @@ const prepareAgent = ( [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { - const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + const messages = (await memory.getChatMessages(flowObj?.sessionId, true)) as BaseMessage[] return messages ?? [] } }, diff --git a/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts b/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts index 81efe43b0..862433147 100644 --- a/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts +++ b/packages/components/nodes/agents/MRKLAgentChat/MRKLAgentChat.ts @@ -125,8 +125,7 @@ class MRKLAgentChat_Agents implements INode { const callbacks = await additionalCallbacks(nodeData, options) - const prevChatHistory = options.chatHistory - const chatHistory = ((await memory.getChatMessages(this.sessionId, false, prevChatHistory)) as IMessage[]) ?? [] + const chatHistory = ((await memory.getChatMessages(this.sessionId, false)) as IMessage[]) ?? [] const chatHistoryString = chatHistory.map((hist) => hist.message).join('\\n') const result = await executor.invoke({ input, chat_history: chatHistoryString }, { callbacks }) diff --git a/packages/components/nodes/agents/MistralAIToolAgent/MistralAIToolAgent.ts b/packages/components/nodes/agents/MistralAIToolAgent/MistralAIToolAgent.ts index f4a33a388..3e0f515fd 100644 --- a/packages/components/nodes/agents/MistralAIToolAgent/MistralAIToolAgent.ts +++ b/packages/components/nodes/agents/MistralAIToolAgent/MistralAIToolAgent.ts @@ -8,7 +8,7 @@ import { convertToOpenAITool } from '@langchain/core/utils/function_calling' import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts' import { OpenAIToolsAgentOutputParser } from 'langchain/agents/openai/output_parser' import { getBaseClasses } from '../../../src/utils' -import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' +import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { AgentExecutor, formatAgentSteps } from '../../../src/agents' import { Moderation, checkInputs, streamResponse } from '../../moderation/Moderation' @@ -75,7 +75,7 @@ class MistralAIToolAgent_Agents implements INode { } async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { - return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -93,7 +93,7 @@ class MistralAIToolAgent_Agents implements INode { } } - const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -154,11 +154,7 @@ class MistralAIToolAgent_Agents implements INode { } } -const prepareAgent = ( - nodeData: INodeData, - flowObj: { sessionId?: string; chatId?: string; input?: string }, - chatHistory: IMessage[] = [] -) => { +const prepareAgent = (nodeData: INodeData, flowObj: { sessionId?: string; chatId?: string; input?: string }) => { const model = nodeData.inputs?.model as ChatOpenAI const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string @@ -183,7 +179,7 @@ const prepareAgent = ( [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { - const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + const messages = (await memory.getChatMessages(flowObj?.sessionId, true)) as BaseMessage[] return messages ?? [] } }, diff --git a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts index 7d7cb1300..374deaf3b 100644 --- a/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts +++ b/packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts @@ -7,7 +7,7 @@ import { ChatOpenAI, formatToOpenAIFunction } from '@langchain/openai' import { ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts' import { OpenAIFunctionsAgentOutputParser } from 'langchain/agents/openai/output_parser' import { getBaseClasses } from '../../../src/utils' -import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' +import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { AgentExecutor, formatAgentSteps } from '../../../src/agents' import { Moderation, checkInputs } from '../../moderation/Moderation' @@ -74,7 +74,7 @@ class OpenAIFunctionAgent_Agents implements INode { } async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { - return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -92,7 +92,7 @@ class OpenAIFunctionAgent_Agents implements INode { } } - const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -153,11 +153,7 @@ class OpenAIFunctionAgent_Agents implements INode { } } -const prepareAgent = ( - nodeData: INodeData, - flowObj: { sessionId?: string; chatId?: string; input?: string }, - chatHistory: IMessage[] = [] -) => { +const prepareAgent = (nodeData: INodeData, flowObj: { sessionId?: string; chatId?: string; input?: string }) => { const model = nodeData.inputs?.model as ChatOpenAI const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string @@ -182,7 +178,7 @@ const prepareAgent = ( [inputKey]: (i: { input: string; steps: AgentStep[] }) => i.input, agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => formatAgentSteps(i.steps), [memoryKey]: async (_: { input: string; steps: AgentStep[] }) => { - const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + const messages = (await memory.getChatMessages(flowObj?.sessionId, true)) as BaseMessage[] return messages ?? [] } }, diff --git a/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent.ts b/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent.ts index acafb0c58..713a40af3 100644 --- a/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent.ts +++ b/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent.ts @@ -8,7 +8,7 @@ import { convertToOpenAITool } from '@langchain/core/utils/function_calling' import { formatToOpenAIToolMessages } from 'langchain/agents/format_scratchpad/openai_tools' import { OpenAIToolsAgentOutputParser, type ToolsAgentStep } from 'langchain/agents/openai/output_parser' import { getBaseClasses } from '../../../src/utils' -import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' +import { FlowiseMemory, ICommonObject, INode, INodeData, INodeParams, IUsedTool } from '../../../src/Interface' import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler' import { AgentExecutor } from '../../../src/agents' import { Moderation, checkInputs } from '../../moderation/Moderation' @@ -75,7 +75,7 @@ class OpenAIToolAgent_Agents implements INode { } async init(nodeData: INodeData, input: string, options: ICommonObject): Promise { - return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + return prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) } async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { @@ -93,7 +93,7 @@ class OpenAIToolAgent_Agents implements INode { } } - const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + const executor = prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -154,11 +154,7 @@ class OpenAIToolAgent_Agents implements INode { } } -const prepareAgent = ( - nodeData: INodeData, - flowObj: { sessionId?: string; chatId?: string; input?: string }, - chatHistory: IMessage[] = [] -) => { +const prepareAgent = (nodeData: INodeData, flowObj: { sessionId?: string; chatId?: string; input?: string }) => { const model = nodeData.inputs?.model as ChatOpenAI const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string @@ -181,7 +177,7 @@ const prepareAgent = ( [inputKey]: (i: { input: string; steps: ToolsAgentStep[] }) => i.input, agent_scratchpad: (i: { input: string; steps: ToolsAgentStep[] }) => formatToOpenAIToolMessages(i.steps), [memoryKey]: async (_: { input: string; steps: ToolsAgentStep[] }) => { - const messages = (await memory.getChatMessages(flowObj?.sessionId, true, chatHistory)) as BaseMessage[] + const messages = (await memory.getChatMessages(flowObj?.sessionId, true)) as BaseMessage[] return messages ?? [] } }, diff --git a/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent_LlamaIndex.ts b/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent_LlamaIndex.ts index f9747e859..7a33c6d82 100644 --- a/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent_LlamaIndex.ts +++ b/packages/components/nodes/agents/OpenAIToolAgent/OpenAIToolAgent_LlamaIndex.ts @@ -61,7 +61,7 @@ class OpenAIFunctionAgent_LlamaIndex_Agents implements INode { return null } - async run(nodeData: INodeData, input: string, options: ICommonObject): Promise { + async run(nodeData: INodeData, input: string): Promise { const memory = nodeData.inputs?.memory as FlowiseMemory const model = nodeData.inputs?.model as OpenAI const systemMessage = nodeData.inputs?.systemMessage as string @@ -77,7 +77,7 @@ class OpenAIFunctionAgent_LlamaIndex_Agents implements INode { }) } - const msgs = (await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[] + const msgs = (await memory.getChatMessages(this.sessionId, false)) as IMessage[] for (const message of msgs) { if (message.type === 'apiMessage') { chatHistory.push({ diff --git a/packages/components/nodes/agents/XMLAgent/XMLAgent.ts b/packages/components/nodes/agents/XMLAgent/XMLAgent.ts index b51d67027..1a68616a4 100644 --- a/packages/components/nodes/agents/XMLAgent/XMLAgent.ts +++ b/packages/components/nodes/agents/XMLAgent/XMLAgent.ts @@ -116,7 +116,7 @@ class XMLAgent_Agents implements INode { return formatResponse(e.message) } } - const executor = await prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }, options.chatHistory) + const executor = await prepareAgent(nodeData, { sessionId: this.sessionId, chatId: options.chatId, input }) const loggerHandler = new ConsoleCallbackHandler(options.logger) const callbacks = await additionalCallbacks(nodeData, options) @@ -177,11 +177,7 @@ class XMLAgent_Agents implements INode { } } -const prepareAgent = async ( - nodeData: INodeData, - flowObj: { sessionId?: string; chatId?: string; input?: string }, - chatHistory: IMessage[] = [] -) => { +const prepareAgent = async (nodeData: INodeData, flowObj: { sessionId?: string; chatId?: string; input?: string }) => { const model = nodeData.inputs?.model as BaseChatModel const memory = nodeData.inputs?.memory as FlowiseMemory const systemMessage = nodeData.inputs?.systemMessage as string @@ -207,7 +203,7 @@ const prepareAgent = async ( const llmWithStop = model.bind({ stop: ['', ''] }) - const messages = (await memory.getChatMessages(flowObj.sessionId, false, chatHistory)) as IMessage[] + const messages = (await memory.getChatMessages(flowObj.sessionId, false)) as IMessage[] let chatHistoryMsgTxt = '' for (const message of messages) { if (message.type === 'apiMessage') { diff --git a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts index 94c1e14f5..99aebdf6c 100644 --- a/packages/components/nodes/chains/ConversationChain/ConversationChain.ts +++ b/packages/components/nodes/chains/ConversationChain/ConversationChain.ts @@ -217,7 +217,6 @@ const prepareChatPrompt = (nodeData: INodeData, humanImageMessages: MessageConte } const prepareChain = (nodeData: INodeData, options: ICommonObject, sessionId?: string) => { - const chatHistory = options.chatHistory let model = nodeData.inputs?.model as BaseChatModel const memory = nodeData.inputs?.memory as FlowiseMemory const memoryKey = memory.memoryKey ?? 'chat_history' @@ -253,7 +252,7 @@ const prepareChain = (nodeData: INodeData, options: ICommonObject, sessionId?: s { [inputKey]: (input: { input: string }) => input.input, [memoryKey]: async () => { - const history = await memory.getChatMessages(sessionId, true, chatHistory) + const history = await memory.getChatMessages(sessionId, true) return history }, ...promptVariables diff --git a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts index ec45b684f..bb68bc476 100644 --- a/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts +++ b/packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts @@ -1,4 +1,5 @@ import { applyPatch } from 'fast-json-patch' +import { DataSource } from 'typeorm' import { BaseLanguageModel } from '@langchain/core/language_models/base' import { BaseRetriever } from '@langchain/core/retrievers' import { PromptTemplate, ChatPromptTemplate, MessagesPlaceholder } from '@langchain/core/prompts' @@ -11,9 +12,18 @@ import { StringOutputParser } from '@langchain/core/output_parsers' import type { Document } from '@langchain/core/documents' import { BufferMemoryInput } from 'langchain/memory' import { ConversationalRetrievalQAChain } from 'langchain/chains' -import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' +import { getBaseClasses, mapChatMessageToBaseMessage } from '../../../src/utils' import { ConsoleCallbackHandler, additionalCallbacks } from '../../../src/handler' -import { FlowiseMemory, ICommonObject, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' +import { + FlowiseMemory, + ICommonObject, + IMessage, + INode, + INodeData, + INodeParams, + IDatabaseEntity, + MemoryMethods +} from '../../../src/Interface' import { QA_TEMPLATE, REPHRASE_TEMPLATE, RESPONSE_TEMPLATE } from './prompts' type RetrievalChainInput = { @@ -166,6 +176,10 @@ class ConversationalRetrievalQAChain_Chains implements INode { const responsePrompt = nodeData.inputs?.responsePrompt as string const returnSourceDocuments = nodeData.inputs?.returnSourceDocuments as boolean + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const chatflowid = options.chatflowid as string + let customResponsePrompt = responsePrompt // If the deprecated systemMessagePrompt is still exists if (systemMessagePrompt) { @@ -178,7 +192,9 @@ class ConversationalRetrievalQAChain_Chains implements INode { memory = new BufferMemory({ returnMessages: true, memoryKey: 'chat_history', - inputKey: 'input' + appDataSource, + databaseEntities, + chatflowid }) } @@ -194,7 +210,7 @@ class ConversationalRetrievalQAChain_Chains implements INode { } const answerChain = createChain(model, vectorStoreRetriever, rephrasePrompt, customResponsePrompt) - const history = ((await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[]) ?? [] + const history = ((await memory.getChatMessages(this.sessionId, false)) as IMessage[]) ?? [] const loggerHandler = new ConsoleCallbackHandler(options.logger) const additionalCallback = await additionalCallbacks(nodeData, options) @@ -367,31 +383,59 @@ const createChain = ( return conversationalQAChain } +interface BufferMemoryExtendedInput { + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string +} + class BufferMemory extends FlowiseMemory implements MemoryMethods { - constructor(fields: BufferMemoryInput) { + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string + + constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) + this.appDataSource = fields.appDataSource + this.databaseEntities = fields.databaseEntities + this.chatflowid = fields.chatflowid } - async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { - await this.chatHistory.clear() + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + if (!overrideSessionId) return [] - for (const msg of prevHistory) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + const chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({ + where: { + sessionId: overrideSessionId, + chatflowid: this.chatflowid + }, + order: { + createdDate: 'ASC' + } + }) + + if (returnBaseMessages) { + return mapChatMessageToBaseMessage(chatMessage) } - const memoryResult = await this.loadMemoryVariables({}) - const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] - return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + let returnIMessages: IMessage[] = [] + for (const m of chatMessage) { + returnIMessages.push({ + message: m.content as string, + type: m.role + }) + } + return returnIMessages } async addChatMessages(): Promise { - // adding chat messages will be done on the fly in getChatMessages() + // adding chat messages is done on server level return } async clearChatMessages(): Promise { - await this.clear() + // clearing chat messages is done on server level + return } } diff --git a/packages/components/nodes/engine/ChatEngine/ContextChatEngine.ts b/packages/components/nodes/engine/ChatEngine/ContextChatEngine.ts index 262ceb7c2..f7e3ff4fc 100644 --- a/packages/components/nodes/engine/ChatEngine/ContextChatEngine.ts +++ b/packages/components/nodes/engine/ChatEngine/ContextChatEngine.ts @@ -83,7 +83,7 @@ class ContextChatEngine_LlamaIndex implements INode { const chatEngine = new ContextChatEngine({ chatModel: model, retriever: vectorStoreRetriever }) - const msgs = (await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[] + const msgs = (await memory.getChatMessages(this.sessionId, false)) as IMessage[] for (const message of msgs) { if (message.type === 'apiMessage') { chatHistory.push({ diff --git a/packages/components/nodes/engine/ChatEngine/SimpleChatEngine.ts b/packages/components/nodes/engine/ChatEngine/SimpleChatEngine.ts index 9bc9f3c05..221c60ada 100644 --- a/packages/components/nodes/engine/ChatEngine/SimpleChatEngine.ts +++ b/packages/components/nodes/engine/ChatEngine/SimpleChatEngine.ts @@ -68,7 +68,7 @@ class SimpleChatEngine_LlamaIndex implements INode { const chatEngine = new SimpleChatEngine({ llm: model }) - const msgs = (await memory.getChatMessages(this.sessionId, false, options.chatHistory)) as IMessage[] + const msgs = (await memory.getChatMessages(this.sessionId, false)) as IMessage[] for (const message of msgs) { if (message.type === 'apiMessage') { chatHistory.push({ diff --git a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts index 620183fa0..80bf7f963 100644 --- a/packages/components/nodes/memory/BufferMemory/BufferMemory.ts +++ b/packages/components/nodes/memory/BufferMemory/BufferMemory.ts @@ -1,7 +1,17 @@ -import { FlowiseMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' -import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' +import { + FlowiseMemory, + IDatabaseEntity, + ICommonObject, + IMessage, + INode, + INodeData, + INodeParams, + MemoryMethods +} from '../../../src/Interface' +import { getBaseClasses, mapChatMessageToBaseMessage } from '../../../src/utils' import { BufferMemory, BufferMemoryInput } from 'langchain/memory' import { BaseMessage } from '@langchain/core/messages' +import { DataSource } from 'typeorm' class BufferMemory_Memory implements INode { label: string @@ -17,64 +27,109 @@ class BufferMemory_Memory implements INode { constructor() { this.label = 'Buffer Memory' this.name = 'bufferMemory' - this.version = 1.0 + this.version = 2.0 this.type = 'BufferMemory' this.icon = 'memory.svg' this.category = 'Memory' - this.description = 'Remembers previous conversational back and forths directly' + this.description = 'Retrieve chat messages stored in database' this.baseClasses = [this.type, ...getBaseClasses(BufferMemory)] this.inputs = [ + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: + 'If not specified, a random id will be used. Learn more', + default: '', + additionalParams: true, + optional: true + }, { label: 'Memory Key', name: 'memoryKey', type: 'string', - default: 'chat_history' - }, - { - label: 'Input Key', - name: 'inputKey', - type: 'string', - default: 'input' + default: 'chat_history', + additionalParams: true } ] } - async init(nodeData: INodeData): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const inputKey = nodeData.inputs?.inputKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const sessionId = nodeData.inputs?.sessionId as string + const memoryKey = (nodeData.inputs?.memoryKey as string) ?? 'chat_history' + + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const chatflowid = options.chatflowid as string + return new BufferMemoryExtended({ returnMessages: true, memoryKey, - inputKey + sessionId, + appDataSource, + databaseEntities, + chatflowid }) } } +interface BufferMemoryExtendedInput { + sessionId: string + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string +} + class BufferMemoryExtended extends FlowiseMemory implements MemoryMethods { - constructor(fields: BufferMemoryInput) { + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string + sessionId = '' + + constructor(fields: BufferMemoryInput & BufferMemoryExtendedInput) { super(fields) + this.sessionId = fields.sessionId + this.appDataSource = fields.appDataSource + this.databaseEntities = fields.databaseEntities + this.chatflowid = fields.chatflowid } - async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { - await this.chatHistory.clear() + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + const id = overrideSessionId ? overrideSessionId : this.sessionId + if (!id) return [] - for (const msg of prevHistory) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + const chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({ + where: { + sessionId: id, + chatflowid: this.chatflowid + }, + order: { + createdDate: 'ASC' + } + }) + + if (returnBaseMessages) { + return mapChatMessageToBaseMessage(chatMessage) } - const memoryResult = await this.loadMemoryVariables({}) - const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] - return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + let returnIMessages: IMessage[] = [] + for (const m of chatMessage) { + returnIMessages.push({ + message: m.content as string, + type: m.role + }) + } + return returnIMessages } async addChatMessages(): Promise { - // adding chat messages will be done on the fly in getChatMessages() + // adding chat messages is done on server level return } async clearChatMessages(): Promise { - await this.clear() + // clearing chat messages is done on server level + return } } diff --git a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts index 754c2e8a0..66d5549c8 100644 --- a/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts +++ b/packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts @@ -1,7 +1,17 @@ -import { FlowiseWindowMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' -import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' +import { + FlowiseWindowMemory, + ICommonObject, + IDatabaseEntity, + IMessage, + INode, + INodeData, + INodeParams, + MemoryMethods +} from '../../../src/Interface' +import { getBaseClasses, mapChatMessageToBaseMessage } from '../../../src/utils' import { BufferWindowMemory, BufferWindowMemoryInput } from 'langchain/memory' import { BaseMessage } from '@langchain/core/messages' +import { DataSource } from 'typeorm' class BufferWindowMemory_Memory implements INode { label: string @@ -17,77 +27,124 @@ class BufferWindowMemory_Memory implements INode { constructor() { this.label = 'Buffer Window Memory' this.name = 'bufferWindowMemory' - this.version = 1.0 + this.version = 2.0 this.type = 'BufferWindowMemory' this.icon = 'memory.svg' this.category = 'Memory' this.description = 'Uses a window of size k to surface the last k back-and-forth to use as memory' this.baseClasses = [this.type, ...getBaseClasses(BufferWindowMemory)] this.inputs = [ - { - label: 'Memory Key', - name: 'memoryKey', - type: 'string', - default: 'chat_history' - }, - { - label: 'Input Key', - name: 'inputKey', - type: 'string', - default: 'input' - }, { label: 'Size', name: 'k', type: 'number', default: '4', description: 'Window of size k to surface the last k back-and-forth to use as memory.' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: + 'If not specified, a random id will be used. Learn more', + default: '', + optional: true, + additionalParams: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true } ] } - async init(nodeData: INodeData): Promise { - const memoryKey = nodeData.inputs?.memoryKey as string - const inputKey = nodeData.inputs?.inputKey as string + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const k = nodeData.inputs?.k as string + const sessionId = nodeData.inputs?.sessionId as string + const memoryKey = (nodeData.inputs?.memoryKey as string) ?? 'chat_history' - const obj: Partial = { + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const chatflowid = options.chatflowid as string + + const obj: Partial & BufferMemoryExtendedInput = { returnMessages: true, - memoryKey: memoryKey, - inputKey: inputKey, - k: parseInt(k, 10) + sessionId, + memoryKey, + k: parseInt(k, 10), + appDataSource, + databaseEntities, + chatflowid } return new BufferWindowMemoryExtended(obj) } } +interface BufferMemoryExtendedInput { + sessionId: string + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string +} + class BufferWindowMemoryExtended extends FlowiseWindowMemory implements MemoryMethods { - constructor(fields: BufferWindowMemoryInput) { + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string + sessionId = '' + + constructor(fields: BufferWindowMemoryInput & BufferMemoryExtendedInput) { super(fields) + this.sessionId = fields.sessionId + this.appDataSource = fields.appDataSource + this.databaseEntities = fields.databaseEntities + this.chatflowid = fields.chatflowid } - async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { - await this.chatHistory.clear() + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + const id = overrideSessionId ? overrideSessionId : this.sessionId + if (!id) return [] - // Insert into chatHistory - for (const msg of prevHistory) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) + let chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({ + where: { + sessionId: id, + chatflowid: this.chatflowid + }, + take: this.k + 1, + order: { + createdDate: 'DESC' // we get the latest top K + } + }) + + // reverse the order of human and ai messages + if (chatMessage.length) chatMessage.reverse() + + if (returnBaseMessages) { + return mapChatMessageToBaseMessage(chatMessage) } - const memoryResult = await this.loadMemoryVariables({}) - const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] - return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + let returnIMessages: IMessage[] = [] + for (const m of chatMessage) { + returnIMessages.push({ + message: m.content as string, + type: m.role + }) + } + return returnIMessages } async addChatMessages(): Promise { - // adding chat messages will be done on the fly in getChatMessages() + // adding chat messages is done on server level return } async clearChatMessages(): Promise { - await this.clear() + // clearing chat messages is done on server level + return } } diff --git a/packages/components/nodes/memory/ConversationSummaryBufferMemory/ConversationSummaryBufferMemory.ts b/packages/components/nodes/memory/ConversationSummaryBufferMemory/ConversationSummaryBufferMemory.ts new file mode 100644 index 000000000..c57560b73 --- /dev/null +++ b/packages/components/nodes/memory/ConversationSummaryBufferMemory/ConversationSummaryBufferMemory.ts @@ -0,0 +1,187 @@ +import { + IMessage, + IDatabaseEntity, + INode, + INodeData, + INodeParams, + MemoryMethods, + ICommonObject, + FlowiseSummaryBufferMemory +} from '../../../src/Interface' +import { getBaseClasses, mapChatMessageToBaseMessage } from '../../../src/utils' +import { BaseLanguageModel } from '@langchain/core/language_models/base' +import { BaseMessage, getBufferString } from '@langchain/core/messages' +import { ConversationSummaryBufferMemory, ConversationSummaryBufferMemoryInput } from 'langchain/memory' +import { DataSource } from 'typeorm' + +class ConversationSummaryBufferMemory_Memory implements INode { + label: string + name: string + version: number + description: string + type: string + icon: string + category: string + baseClasses: string[] + inputs: INodeParams[] + + constructor() { + this.label = 'Conversation Summary Buffer Memory' + this.name = 'conversationSummaryBufferMemory' + this.version = 1.0 + this.type = 'ConversationSummaryBufferMemory' + this.icon = 'memory.svg' + this.category = 'Memory' + this.description = 'Uses token length to decide when to summarize conversations' + this.baseClasses = [this.type, ...getBaseClasses(ConversationSummaryBufferMemory)] + this.inputs = [ + { + label: 'Chat Model', + name: 'model', + type: 'BaseChatModel' + }, + { + label: 'Max Token Limit', + name: 'maxTokenLimit', + type: 'number', + default: 2000, + description: 'Summarize conversations once token limit is reached. Default to 2000' + }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: + 'If not specified, a random id will be used. Learn more', + default: '', + optional: true, + additionalParams: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true + } + ] + } + + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { + const model = nodeData.inputs?.model as BaseLanguageModel + const _maxTokenLimit = nodeData.inputs?.maxTokenLimit as string + const maxTokenLimit = _maxTokenLimit ? parseInt(_maxTokenLimit, 10) : 2000 + const sessionId = nodeData.inputs?.sessionId as string + const memoryKey = (nodeData.inputs?.memoryKey as string) ?? 'chat_history' + + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const chatflowid = options.chatflowid as string + + const obj: ConversationSummaryBufferMemoryInput & BufferMemoryExtendedInput = { + llm: model, + sessionId, + memoryKey, + maxTokenLimit, + returnMessages: true, + appDataSource, + databaseEntities, + chatflowid + } + + return new ConversationSummaryBufferMemoryExtended(obj) + } +} + +interface BufferMemoryExtendedInput { + sessionId: string + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string +} + +class ConversationSummaryBufferMemoryExtended extends FlowiseSummaryBufferMemory implements MemoryMethods { + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string + sessionId = '' + + constructor(fields: ConversationSummaryBufferMemoryInput & BufferMemoryExtendedInput) { + super(fields) + this.sessionId = fields.sessionId + this.appDataSource = fields.appDataSource + this.databaseEntities = fields.databaseEntities + this.chatflowid = fields.chatflowid + } + + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + const id = overrideSessionId ? overrideSessionId : this.sessionId + if (!id) return [] + + let chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({ + where: { + sessionId: id, + chatflowid: this.chatflowid + }, + order: { + createdDate: 'ASC' + } + }) + + let baseMessages = mapChatMessageToBaseMessage(chatMessage) + + // Prune baseMessages if it exceeds max token limit + if (this.movingSummaryBuffer) { + baseMessages = [new this.summaryChatMessageClass(this.movingSummaryBuffer), ...baseMessages] + } + + let currBufferLength = 0 + + if (this.llm && typeof this.llm !== 'string') { + currBufferLength = await this.llm.getNumTokens(getBufferString(baseMessages, this.humanPrefix, this.aiPrefix)) + if (currBufferLength > this.maxTokenLimit) { + const prunedMemory = [] + while (currBufferLength > this.maxTokenLimit) { + const poppedMessage = baseMessages.shift() + if (poppedMessage) { + prunedMemory.push(poppedMessage) + currBufferLength = await this.llm.getNumTokens(getBufferString(baseMessages, this.humanPrefix, this.aiPrefix)) + } + } + this.movingSummaryBuffer = await this.predictNewSummary(prunedMemory, this.movingSummaryBuffer) + } + } + + // ----------- Finished Pruning --------------- + + if (this.movingSummaryBuffer) { + baseMessages = [new this.summaryChatMessageClass(this.movingSummaryBuffer), ...baseMessages] + } + + if (returnBaseMessages) { + return baseMessages + } + + let returnIMessages: IMessage[] = [] + for (const m of baseMessages) { + returnIMessages.push({ + message: m.content as string, + type: m._getType() === 'human' ? 'userMessage' : 'apiMessage' + }) + } + + return returnIMessages + } + + async addChatMessages(): Promise { + // adding chat messages is done on server level + return + } + + async clearChatMessages(): Promise { + // clearing chat messages is done on server level + return + } +} + +module.exports = { nodeClass: ConversationSummaryBufferMemory_Memory } diff --git a/packages/components/nodes/memory/ConversationSummaryBufferMemory/memory.svg b/packages/components/nodes/memory/ConversationSummaryBufferMemory/memory.svg new file mode 100644 index 000000000..c7aadc968 --- /dev/null +++ b/packages/components/nodes/memory/ConversationSummaryBufferMemory/memory.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts index 88c8e34c8..29614da5c 100644 --- a/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts +++ b/packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts @@ -1,8 +1,18 @@ -import { FlowiseSummaryMemory, IMessage, INode, INodeData, INodeParams, MemoryMethods } from '../../../src/Interface' -import { convertBaseMessagetoIMessage, getBaseClasses } from '../../../src/utils' +import { + FlowiseSummaryMemory, + IMessage, + IDatabaseEntity, + INode, + INodeData, + INodeParams, + MemoryMethods, + ICommonObject +} from '../../../src/Interface' +import { getBaseClasses, mapChatMessageToBaseMessage } from '../../../src/utils' import { BaseLanguageModel } from '@langchain/core/language_models/base' -import { BaseMessage } from '@langchain/core/messages' +import { BaseMessage, SystemMessage } from '@langchain/core/messages' import { ConversationSummaryMemory, ConversationSummaryMemoryInput } from 'langchain/memory' +import { DataSource } from 'typeorm' class ConversationSummaryMemory_Memory implements INode { label: string @@ -18,7 +28,7 @@ class ConversationSummaryMemory_Memory implements INode { constructor() { this.label = 'Conversation Summary Memory' this.name = 'conversationSummaryMemory' - this.version = 1.0 + this.version = 2.0 this.type = 'ConversationSummaryMemory' this.icon = 'memory.svg' this.category = 'Memory' @@ -30,67 +40,123 @@ class ConversationSummaryMemory_Memory implements INode { name: 'model', type: 'BaseChatModel' }, + { + label: 'Session Id', + name: 'sessionId', + type: 'string', + description: + 'If not specified, a random id will be used. Learn more', + default: '', + optional: true, + additionalParams: true + }, { label: 'Memory Key', name: 'memoryKey', type: 'string', - default: 'chat_history' - }, - { - label: 'Input Key', - name: 'inputKey', - type: 'string', - default: 'input' + default: 'chat_history', + additionalParams: true } ] } - async init(nodeData: INodeData): Promise { + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { const model = nodeData.inputs?.model as BaseLanguageModel - const memoryKey = nodeData.inputs?.memoryKey as string - const inputKey = nodeData.inputs?.inputKey as string + const sessionId = nodeData.inputs?.sessionId as string + const memoryKey = (nodeData.inputs?.memoryKey as string) ?? 'chat_history' - const obj: ConversationSummaryMemoryInput = { + const appDataSource = options.appDataSource as DataSource + const databaseEntities = options.databaseEntities as IDatabaseEntity + const chatflowid = options.chatflowid as string + + const obj: ConversationSummaryMemoryInput & BufferMemoryExtendedInput = { llm: model, - returnMessages: true, memoryKey, - inputKey + returnMessages: true, + sessionId, + appDataSource, + databaseEntities, + chatflowid } return new ConversationSummaryMemoryExtended(obj) } } +interface BufferMemoryExtendedInput { + sessionId: string + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string +} + class ConversationSummaryMemoryExtended extends FlowiseSummaryMemory implements MemoryMethods { - constructor(fields: ConversationSummaryMemoryInput) { + appDataSource: DataSource + databaseEntities: IDatabaseEntity + chatflowid: string + sessionId = '' + + constructor(fields: ConversationSummaryMemoryInput & BufferMemoryExtendedInput) { super(fields) + this.sessionId = fields.sessionId + this.appDataSource = fields.appDataSource + this.databaseEntities = fields.databaseEntities + this.chatflowid = fields.chatflowid } - async getChatMessages(_?: string, returnBaseMessages = false, prevHistory: IMessage[] = []): Promise { - await this.chatHistory.clear() - this.buffer = '' + async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { + const id = overrideSessionId ? overrideSessionId : this.sessionId + if (!id) return [] - for (const msg of prevHistory) { - if (msg.type === 'userMessage') await this.chatHistory.addUserMessage(msg.message) - else if (msg.type === 'apiMessage') await this.chatHistory.addAIChatMessage(msg.message) - } + this.buffer = '' + let chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({ + where: { + sessionId: id, + chatflowid: this.chatflowid + }, + order: { + createdDate: 'ASC' + } + }) + + const baseMessages = mapChatMessageToBaseMessage(chatMessage) // Get summary - const chatMessages = await this.chatHistory.getMessages() - this.buffer = chatMessages.length ? await this.predictNewSummary(chatMessages.slice(-2), this.buffer) : '' + if (this.llm && typeof this.llm !== 'string') { + this.buffer = baseMessages.length ? await this.predictNewSummary(baseMessages.slice(-2), this.buffer) : '' + } - const memoryResult = await this.loadMemoryVariables({}) - const baseMessages = memoryResult[this.memoryKey ?? 'chat_history'] - return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) + if (returnBaseMessages) { + return [new SystemMessage(this.buffer)] + } + + if (this.buffer) { + return [ + { + message: this.buffer, + type: 'apiMessage' + } + ] + } + + let returnIMessages: IMessage[] = [] + for (const m of chatMessage) { + returnIMessages.push({ + message: m.content as string, + type: m.role + }) + } + return returnIMessages } async addChatMessages(): Promise { - // adding chat messages will be done on the fly in getChatMessages() + // adding chat messages is done on server level return } async clearChatMessages(): Promise { - await this.clear() + // clearing chat messages is done on server level + return } } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts deleted file mode 100644 index c53376c05..000000000 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface' -import { convertBaseMessagetoIMessage, getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils' -import { ICommonObject } from '../../../src' -import { MotorheadMemory, MotorheadMemoryInput, InputValues, OutputValues } from 'langchain/memory' -import fetch from 'node-fetch' -import { AIMessage, BaseMessage, ChatMessage, HumanMessage } from '@langchain/core/messages' - -type MotorheadMessage = { - content: string - role: 'Human' | 'AI' -} - -class MotorMemory_Memory implements INode { - label: string - name: string - version: number - description: string - type: string - icon: string - category: string - baseClasses: string[] - credential: INodeParams - inputs: INodeParams[] - - constructor() { - this.label = 'Motorhead Memory' - this.name = 'motorheadMemory' - this.version = 1.0 - this.type = 'MotorheadMemory' - this.icon = 'motorhead.svg' - this.category = 'Memory' - this.description = 'Use Motorhead Memory to store chat conversations' - this.baseClasses = [this.type, ...getBaseClasses(MotorheadMemory)] - this.credential = { - label: 'Connect Credential', - name: 'credential', - type: 'credential', - optional: true, - description: 'Only needed when using hosted solution - https://getmetal.io', - credentialNames: ['motorheadMemoryApi'] - } - this.inputs = [ - { - label: 'Base URL', - name: 'baseURL', - type: 'string', - optional: true, - description: 'To use the online version, leave the URL blank. More details at https://getmetal.io.' - }, - { - label: 'Session Id', - name: 'sessionId', - type: 'string', - description: - 'If not specified, a random id will be used. Learn more', - default: '', - additionalParams: true, - optional: true - }, - { - label: 'Memory Key', - name: 'memoryKey', - type: 'string', - default: 'chat_history', - additionalParams: true - } - ] - } - - async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - return initalizeMotorhead(nodeData, options) - } -} - -const initalizeMotorhead = async (nodeData: INodeData, options: ICommonObject): Promise => { - const memoryKey = nodeData.inputs?.memoryKey as string - const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string - - const credentialData = await getCredentialData(nodeData.credential ?? '', options) - const apiKey = getCredentialParam('apiKey', credentialData, nodeData) - const clientId = getCredentialParam('clientId', credentialData, nodeData) - - let obj: MotorheadMemoryInput = { - returnMessages: true, - sessionId, - memoryKey - } - - if (baseURL) { - obj = { - ...obj, - url: baseURL - } - } else { - obj = { - ...obj, - apiKey, - clientId - } - } - - const motorheadMemory = new MotorheadMemoryExtended(obj) - - // Get messages from sessionId - await motorheadMemory.init() - - return motorheadMemory -} - -class MotorheadMemoryExtended extends MotorheadMemory implements MemoryMethods { - constructor(fields: MotorheadMemoryInput) { - super(fields) - } - - async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideSessionId = ''): Promise { - if (overrideSessionId) { - this.sessionId = overrideSessionId - } - return super.saveContext(inputValues, outputValues) - } - - async clear(overrideSessionId = ''): Promise { - if (overrideSessionId) { - this.sessionId = overrideSessionId - } - try { - await this.caller.call(fetch, `${this.url}/sessions/${this.sessionId}/memory`, { - //@ts-ignore - signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined, - headers: this._getHeaders() as ICommonObject, - method: 'DELETE' - }) - } catch (error) { - console.error('Error deleting session: ', error) - } - - // Clear the superclass's chat history - await this.chatHistory.clear() - await super.clear() - } - - async getChatMessages(overrideSessionId = '', returnBaseMessages = false): Promise { - const id = overrideSessionId ? overrideSessionId : this.sessionId - try { - const resp = await this.caller.call(fetch, `${this.url}/sessions/${id}/memory`, { - //@ts-ignore - signal: this.timeout ? AbortSignal.timeout(this.timeout) : undefined, - headers: this._getHeaders() as ICommonObject, - method: 'GET' - }) - const data = await resp.json() - const rawStoredMessages: MotorheadMessage[] = data?.data?.messages ?? [] - - const baseMessages = rawStoredMessages.reverse().map((message) => { - const { content, role } = message - if (role === 'Human') { - return new HumanMessage(content) - } else if (role === 'AI') { - return new AIMessage(content) - } else { - // default to generic ChatMessage - return new ChatMessage(content, role) - } - }) - - return returnBaseMessages ? baseMessages : convertBaseMessagetoIMessage(baseMessages) - } catch (error) { - console.error('Error getting session: ', error) - return [] - } - } - - async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId = ''): Promise { - const id = overrideSessionId ? overrideSessionId : this.sessionId - const input = msgArray.find((msg) => msg.type === 'userMessage') - const output = msgArray.find((msg) => msg.type === 'apiMessage') - const inputValues = { [this.inputKey ?? 'input']: input?.text } - const outputValues = { output: output?.text } - - await this.saveContext(inputValues, outputValues, id) - } - - async clearChatMessages(overrideSessionId = ''): Promise { - const id = overrideSessionId ? overrideSessionId : this.sessionId - await this.clear(id) - } -} - -module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/motorhead.svg b/packages/components/nodes/memory/MotorheadMemory/motorhead.svg deleted file mode 100644 index 55ca8c7d4..000000000 --- a/packages/components/nodes/memory/MotorheadMemory/motorhead.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts index 617aa71d1..a2201790c 100644 --- a/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts @@ -40,7 +40,7 @@ class UpstashRedisBackedChatMemory_Memory implements INode { constructor() { this.label = 'Upstash Redis-Backed Chat Memory' this.name = 'upstashRedisBackedChatMemory' - this.version = 1.0 + this.version = 2.0 this.type = 'UpstashRedisBackedChatMemory' this.icon = 'upstash.svg' this.category = 'Memory' @@ -77,6 +77,13 @@ class UpstashRedisBackedChatMemory_Memory implements INode { description: 'Omit this parameter to make sessions never expire', additionalParams: true, optional: true + }, + { + label: 'Memory Key', + name: 'memoryKey', + type: 'string', + default: 'chat_history', + additionalParams: true } ] } @@ -89,8 +96,8 @@ class UpstashRedisBackedChatMemory_Memory implements INode { const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject): Promise => { const baseURL = nodeData.inputs?.baseURL as string const sessionId = nodeData.inputs?.sessionId as string + const memoryKey = nodeData.inputs?.memoryKey as string const _sessionTTL = nodeData.inputs?.sessionTTL as string - const sessionTTL = _sessionTTL ? parseInt(_sessionTTL, 10) : undefined const credentialData = await getCredentialData(nodeData.credential ?? '', options) @@ -108,7 +115,7 @@ const initalizeUpstashRedis = async (nodeData: INodeData, options: ICommonObject }) const memory = new BufferMemoryExtended({ - memoryKey: 'chat_history', + memoryKey: memoryKey ?? 'chat_history', chatHistory: redisChatMessageHistory, sessionId, sessionTTL, diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index 33f50d4fa..ce7b752de 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -234,40 +234,34 @@ export class VectorStoreRetriever { * Implement abstract classes and interface for memory */ import { BaseMessage } from '@langchain/core/messages' -import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory } from 'langchain/memory' +import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory, ConversationSummaryBufferMemory } from 'langchain/memory' export interface MemoryMethods { - getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean, prevHistory?: IMessage[]): Promise + getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise clearChatMessages(overrideSessionId?: string): Promise } export abstract class FlowiseMemory extends BufferMemory implements MemoryMethods { - abstract getChatMessages( - overrideSessionId?: string, - returnBaseMessages?: boolean, - prevHistory?: IMessage[] - ): Promise + abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise } export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods { - abstract getChatMessages( - overrideSessionId?: string, - returnBaseMessages?: boolean, - prevHistory?: IMessage[] - ): Promise + abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise } export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods { - abstract getChatMessages( - overrideSessionId?: string, - returnBaseMessages?: boolean, - prevHistory?: IMessage[] - ): Promise + abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise + abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise + abstract clearChatMessages(overrideSessionId?: string): Promise +} + +export abstract class FlowiseSummaryBufferMemory extends ConversationSummaryBufferMemory implements MemoryMethods { + abstract getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean): Promise abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise abstract clearChatMessages(overrideSessionId?: string): Promise } diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index cad672680..d9e170389 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -7,7 +7,6 @@ import { z } from 'zod' import { DataSource } from 'typeorm' import { ICommonObject, IDatabaseEntity, IMessage, INodeData, IVariable } from './Interface' import { AES, enc } from 'crypto-js' -import { ChatMessageHistory } from 'langchain/memory' import { AIMessage, HumanMessage, BaseMessage } from '@langchain/core/messages' export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}} @@ -576,22 +575,21 @@ export const getUserHome = (): string => { } /** - * Map incoming chat history to ChatMessageHistory - * @param {ICommonObject} options - * @returns {ChatMessageHistory} + * Map ChatMessage to BaseMessage + * @param {IChatMessage[]} chatmessages + * @returns {BaseMessage[]} */ -export const mapChatHistory = (options: ICommonObject): ChatMessageHistory => { +export const mapChatMessageToBaseMessage = (chatmessages: any[] = []): BaseMessage[] => { const chatHistory = [] - const histories: IMessage[] = options.chatHistory ?? [] - for (const message of histories) { - if (message.type === 'apiMessage') { - chatHistory.push(new AIMessage(message.message)) - } else if (message.type === 'userMessage') { - chatHistory.push(new HumanMessage(message.message)) + for (const message of chatmessages) { + if (message.role === 'apiMessage') { + chatHistory.push(new AIMessage(message.content)) + } else if (message.role === 'userMessage') { + chatHistory.push(new HumanMessage(message.content)) } } - return new ChatMessageHistory(chatHistory) + return chatHistory } /** @@ -615,7 +613,7 @@ export const convertChatHistoryToText = (chatHistory: IMessage[] = []): string = /** * Serialize array chat history to string - * @param {IMessage[]} chatHistory + * @param {string | Array} chatHistory * @returns {string} */ export const serializeChatHistory = (chatHistory: string | Array) => { diff --git a/packages/server/marketplaces/chatflows/API Agent OpenAI.json b/packages/server/marketplaces/chatflows/API Agent OpenAI.json index 33d66bb2e..694ba2e80 100644 --- a/packages/server/marketplaces/chatflows/API Agent OpenAI.json +++ b/packages/server/marketplaces/chatflows/API Agent OpenAI.json @@ -642,32 +642,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/API Agent.json b/packages/server/marketplaces/chatflows/API Agent.json index 7f3f90b46..5c361f645 100644 --- a/packages/server/marketplaces/chatflows/API Agent.json +++ b/packages/server/marketplaces/chatflows/API Agent.json @@ -178,32 +178,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Claude LLM.json b/packages/server/marketplaces/chatflows/Claude LLM.json index 3bda2a2ca..80b52d04f 100644 --- a/packages/server/marketplaces/chatflows/Claude LLM.json +++ b/packages/server/marketplaces/chatflows/Claude LLM.json @@ -15,32 +15,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Conversational Agent.json b/packages/server/marketplaces/chatflows/Conversational Agent.json index 3d9340de8..bb7dac035 100644 --- a/packages/server/marketplaces/chatflows/Conversational Agent.json +++ b/packages/server/marketplaces/chatflows/Conversational Agent.json @@ -54,32 +54,36 @@ "data": { "id": "bufferMemory_1", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_1-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_1-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_1-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Multiple VectorDB.json b/packages/server/marketplaces/chatflows/Multiple VectorDB.json index 3de2a08ce..6d8cfa804 100644 --- a/packages/server/marketplaces/chatflows/Multiple VectorDB.json +++ b/packages/server/marketplaces/chatflows/Multiple VectorDB.json @@ -1550,32 +1550,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/OpenAI Agent.json b/packages/server/marketplaces/chatflows/OpenAI Agent.json index 1d6498f47..79096b0ef 100644 --- a/packages/server/marketplaces/chatflows/OpenAI Agent.json +++ b/packages/server/marketplaces/chatflows/OpenAI Agent.json @@ -54,32 +54,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Simple Chat Engine.json b/packages/server/marketplaces/chatflows/Simple Chat Engine.json index fd17ded16..81749697d 100644 --- a/packages/server/marketplaces/chatflows/Simple Chat Engine.json +++ b/packages/server/marketplaces/chatflows/Simple Chat Engine.json @@ -83,32 +83,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json index d3688e0ef..1a2cb25f1 100644 --- a/packages/server/marketplaces/chatflows/Simple Conversation Chain.json +++ b/packages/server/marketplaces/chatflows/Simple Conversation Chain.json @@ -257,32 +257,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/marketplaces/chatflows/WebBrowser.json b/packages/server/marketplaces/chatflows/WebBrowser.json index d27298d2f..c3e2de670 100644 --- a/packages/server/marketplaces/chatflows/WebBrowser.json +++ b/packages/server/marketplaces/chatflows/WebBrowser.json @@ -15,32 +15,36 @@ "data": { "id": "bufferMemory_0", "label": "Buffer Memory", - "version": 1, + "version": 2, "name": "bufferMemory", "type": "BufferMemory", "baseClasses": ["BufferMemory", "BaseChatMemory", "BaseMemory"], "category": "Memory", - "description": "Remembers previous conversational back and forths directly", + "description": "Retrieve chat messages stored in database", "inputParams": [ + { + "label": "Session Id", + "name": "sessionId", + "type": "string", + "description": "If not specified, a random id will be used. Learn more", + "default": "", + "additionalParams": true, + "optional": true, + "id": "bufferMemory_0-input-sessionId-string" + }, { "label": "Memory Key", "name": "memoryKey", "type": "string", "default": "chat_history", + "additionalParams": true, "id": "bufferMemory_0-input-memoryKey-string" - }, - { - "label": "Input Key", - "name": "inputKey", - "type": "string", - "default": "input", - "id": "bufferMemory_0-input-inputKey-string" } ], "inputAnchors": [], "inputs": { - "memoryKey": "chat_history", - "inputKey": "input" + "sessionId": "", + "memoryKey": "chat_history" }, "outputAnchors": [ { diff --git a/packages/server/src/Interface.ts b/packages/server/src/Interface.ts index 7b405a90b..106cf27a5 100644 --- a/packages/server/src/Interface.ts +++ b/packages/server/src/Interface.ts @@ -195,7 +195,6 @@ export interface IMessage { export interface IncomingInput { question: string - history: IMessage[] overrideConfig?: ICommonObject socketIOClientId?: string chatId?: string diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts index 16cdd88cb..0dbc71976 100644 --- a/packages/server/src/utils/buildChatflow.ts +++ b/packages/server/src/utils/buildChatflow.ts @@ -132,7 +132,6 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter incomingInput = { question: req.body.question ?? 'hello', overrideConfig, - history: [], socketIOClientId: req.body.socketIOClientId } } @@ -146,8 +145,7 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter // Get session ID const memoryNode = findMemoryNode(nodes, edges) const memoryType = memoryNode?.data.label - let sessionId = undefined - if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) + let sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) /* Reuse the flow without having to rebuild (to avoid duplicated upsert, recomputation, reinitialization of memory) when all these conditions met: * - Node Data already exists in pool @@ -225,9 +223,9 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter // Once custom function ending node exists, flow is always unavailable to stream isStreamValid = isEndingNodeExists ? false : isStreamValid - let chatHistory: IMessage[] = incomingInput.history ?? [] + let chatHistory: IMessage[] = [] - // When {{chat_history}} is used in Prompt Template, fetch the chat conversations from memory node + // When {{chat_history}} is used in Format Prompt Value, fetch the chat conversations from memory node for (const endingNode of endingNodes) { const endingNodeData = endingNode.data @@ -238,16 +236,15 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter if (!memoryNode) continue - if (!chatHistory.length && (incomingInput.chatId || incomingInput.overrideConfig?.sessionId)) { - chatHistory = await getSessionChatHistory( - memoryNode, - appServer.nodesPool.componentNodes, - incomingInput, - appServer.AppDataSource, - databaseEntities, - logger - ) - } + chatHistory = await getSessionChatHistory( + chatflowid, + getMemorySessionId(memoryNode, incomingInput, chatId, isInternal), + memoryNode, + appServer.nodesPool.componentNodes, + appServer.AppDataSource, + databaseEntities, + logger + ) } /*** Get Starting Nodes with Reversed Graph ***/ @@ -314,7 +311,6 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter ? await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatId, chatflowid, - chatHistory: incomingInput.history, logger, appDataSource: appServer.AppDataSource, databaseEntities, @@ -326,7 +322,6 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter : await nodeInstance.run(nodeToExecuteData, incomingInput.question, { chatId, chatflowid, - chatHistory: incomingInput.history, logger, appDataSource: appServer.AppDataSource, databaseEntities, diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 15aa626a5..94551c133 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1144,16 +1144,18 @@ export const redactCredentialWithPasswordType = ( * API/Embed + UI: * (3) Hard-coded sessionId in UI * (4) Not specified on UI nor API, default to chatId - * @param {any} instance + * @param {IReactFlowNode | undefined} memoryNode * @param {IncomingInput} incomingInput * @param {string} chatId + * @param {boolean} isInternal + * @returns {string} */ export const getMemorySessionId = ( - memoryNode: IReactFlowNode, + memoryNode: IReactFlowNode | undefined, incomingInput: IncomingInput, chatId: string, isInternal: boolean -): string | undefined => { +): string => { if (!isInternal) { // Provided in API body - incomingInput.overrideConfig: { sessionId: 'abc' } if (incomingInput.overrideConfig?.sessionId) { @@ -1166,7 +1168,7 @@ export const getMemorySessionId = ( } // Hard-coded sessionId in UI - if (memoryNode.data.inputs?.sessionId) { + if (memoryNode && memoryNode.data.inputs?.sessionId) { return memoryNode.data.inputs.sessionId } @@ -1175,18 +1177,21 @@ export const getMemorySessionId = ( } /** - * Replace chatHistory if incomingInput.history is empty and sessionId/chatId is provided + * Get chat messages from sessionId * @param {IReactFlowNode} memoryNode - * @param {IncomingInput} incomingInput + * @param {string} sessionId + * @param {IReactFlowNode} memoryNode + * @param {IComponentNodes} componentNodes * @param {DataSource} appDataSource * @param {IDatabaseEntity} databaseEntities * @param {any} logger - * @returns {string} + * @returns {IMessage[]} */ export const getSessionChatHistory = async ( + chatflowid: string, + sessionId: string, memoryNode: IReactFlowNode, componentNodes: IComponentNodes, - incomingInput: IncomingInput, appDataSource: DataSource, databaseEntities: IDatabaseEntity, logger: any @@ -1196,19 +1201,18 @@ export const getSessionChatHistory = async ( const newNodeInstance = new nodeModule.nodeClass() // Replace memory's sessionId/chatId - if (incomingInput.overrideConfig?.sessionId && memoryNode.data.inputs) { - memoryNode.data.inputs.sessionId = incomingInput.overrideConfig.sessionId - } else if (incomingInput.chatId && memoryNode.data.inputs) { - memoryNode.data.inputs.sessionId = incomingInput.chatId + if (memoryNode.data.inputs) { + memoryNode.data.inputs.sessionId = sessionId } const initializedInstance: FlowiseMemory = await newNodeInstance.init(memoryNode.data, '', { + chatflowid, appDataSource, databaseEntities, logger }) - return (await initializedInstance.getChatMessages()) as IMessage[] + return (await initializedInstance.getChatMessages(sessionId)) as IMessage[] } /** @@ -1216,7 +1220,7 @@ export const getSessionChatHistory = async ( * In a chatflow, there should only be 1 memory node * @param {IReactFlowNode[]} nodes * @param {IReactFlowEdge[]} edges - * @returns {string | undefined} + * @returns {IReactFlowNode | undefined} */ export const findMemoryNode = (nodes: IReactFlowNode[], edges: IReactFlowEdge[]): IReactFlowNode | undefined => { const memoryNodes = nodes.filter((node) => node.data.category === 'Memory') @@ -1228,6 +1232,7 @@ export const findMemoryNode = (nodes: IReactFlowNode[], edges: IReactFlowEdge[]) return memoryNode } } + return undefined } diff --git a/packages/server/src/utils/upsertVector.ts b/packages/server/src/utils/upsertVector.ts index 0913786c0..73c6e3fb5 100644 --- a/packages/server/src/utils/upsertVector.ts +++ b/packages/server/src/utils/upsertVector.ts @@ -1,7 +1,7 @@ import { Request } from 'express' import * as fs from 'fs' import { cloneDeep, omit } from 'lodash' -import { ICommonObject } from 'flowise-components' +import { ICommonObject, IMessage } from 'flowise-components' import telemetryService from '../services/telemetry' import logger from '../utils/logger' import { @@ -66,7 +66,6 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) => incomingInput = { question: req.body.question ?? 'hello', overrideConfig, - history: [], stopNodeId: req.body.stopNodeId } } @@ -78,14 +77,13 @@ export const upsertVector = async (req: Request, isInternal: boolean = false) => const edges = parsedFlowData.edges let stopNodeId = incomingInput?.stopNodeId ?? '' - let chatHistory = incomingInput?.history + let chatHistory: IMessage[] = [] let chatId = incomingInput.chatId ?? '' let isUpsert = true // Get session ID const memoryNode = findMemoryNode(nodes, edges) - let sessionId = undefined - if (memoryNode) sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) + let sessionId = getMemorySessionId(memoryNode, incomingInput, chatId, isInternal) const vsNodes = nodes.filter( (node) => diff --git a/packages/ui/src/utils/genericHelper.js b/packages/ui/src/utils/genericHelper.js index bc6ebeb09..b71779ddc 100644 --- a/packages/ui/src/utils/genericHelper.js +++ b/packages/ui/src/utils/genericHelper.js @@ -519,11 +519,10 @@ export const formatDataGridRows = (rows) => { } } -export const setLocalStorageChatflow = (chatflowid, chatId, chatHistory) => { +export const setLocalStorageChatflow = (chatflowid, chatId) => { const chatDetails = localStorage.getItem(`${chatflowid}_INTERNAL`) const obj = {} if (chatId) obj.chatId = chatId - if (chatHistory) obj.chatHistory = chatHistory if (!chatDetails) { localStorage.setItem(`${chatflowid}_INTERNAL`, JSON.stringify(obj)) diff --git a/packages/ui/src/views/chatmessage/ChatMessage.jsx b/packages/ui/src/views/chatmessage/ChatMessage.jsx index 216e88691..d7e6de72c 100644 --- a/packages/ui/src/views/chatmessage/ChatMessage.jsx +++ b/packages/ui/src/views/chatmessage/ChatMessage.jsx @@ -392,11 +392,10 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews clearPreviews() setMessages((prevMessages) => [...prevMessages, { message: input, type: 'userMessage', fileUploads: urls }]) - // Send user question and history to API + // Send user question to Prediction Internal API try { const params = { question: input, - history: messages.filter((msg) => msg.message !== 'Hi there! How can I help?'), chatId } if (urls && urls.length > 0) params.uploads = urls @@ -447,7 +446,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews } ]) } - setLocalStorageChatflow(chatflowid, data.chatId, messages) + setLocalStorageChatflow(chatflowid, data.chatId) setLoading(false) setUserInput('') setTimeout(() => { @@ -520,7 +519,7 @@ export const ChatMessage = ({ open, chatflowid, isDialog, previews, setPreviews return obj }) setMessages((prevMessages) => [...prevMessages, ...loadedMessages]) - setLocalStorageChatflow(chatflowid, chatId, messages) + setLocalStorageChatflow(chatflowid, chatId) } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx b/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx index dc237b1a4..60c697e05 100644 --- a/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx +++ b/packages/ui/src/views/marketplaces/MarketplaceCanvasNode.jsx @@ -129,7 +129,16 @@ const MarketplaceCanvasNode = ({ data }) => { ))} {data.inputParams.find((param) => param.additionalParams) && ( -
+
param.additionalParams).length === + data.inputParams.length + data.inputAnchors.length + ? 20 + : 0 + }} + >