diff --git a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts index 49d15cb61..2ea7ba048 100644 --- a/packages/components/nodes/memory/DynamoDb/DynamoDb.ts +++ b/packages/components/nodes/memory/DynamoDb/DynamoDb.ts @@ -65,37 +65,47 @@ class DynamoDb_Memory implements INode { } ] } + async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const tableName = nodeData.inputs?.tableName as string - const partitionKey = nodeData.inputs?.partitionKey as string - const sessionId = nodeData.inputs?.sessionId as string - const region = nodeData.inputs?.region as string - const accessKey = nodeData.inputs?.accessKey as string - const secretAccessKey = nodeData.inputs?.secretAccessKey as string - const memoryKey = nodeData.inputs?.memoryKey as string + return initalizeDynamoDB(nodeData, options) + } - const chatId = options.chatId - - const dynamoDb = new DynamoDBChatMessageHistory({ - tableName, - partitionKey, - sessionId: sessionId ? sessionId : chatId, - config: { - region, - credentials: { - accessKeyId: accessKey, - secretAccessKey - } - } - }) - - const memory = new BufferMemory({ - memoryKey, - chatHistory: dynamoDb, - returnMessages: true - }) - return memory + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const dynamodbMemory = initalizeDynamoDB(nodeData, options) + dynamodbMemory.clear() } } +const initalizeDynamoDB = (nodeData: INodeData, options: ICommonObject): BufferMemory => { + const tableName = nodeData.inputs?.tableName as string + const partitionKey = nodeData.inputs?.partitionKey as string + const sessionId = nodeData.inputs?.sessionId as string + const region = nodeData.inputs?.region as string + const accessKey = nodeData.inputs?.accessKey as string + const secretAccessKey = nodeData.inputs?.secretAccessKey as string + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options.chatId + + const dynamoDb = new DynamoDBChatMessageHistory({ + tableName, + partitionKey, + sessionId: sessionId ? sessionId : chatId, + config: { + region, + credentials: { + accessKeyId: accessKey, + secretAccessKey + } + } + }) + + const memory = new BufferMemory({ + memoryKey, + chatHistory: dynamoDb, + returnMessages: true + }) + return memory +} + module.exports = { nodeClass: DynamoDb_Memory } diff --git a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts index 01d57614b..b710b30ab 100644 --- a/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts +++ b/packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts @@ -64,35 +64,44 @@ class MotorMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, 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 apiKey = nodeData.inputs?.apiKey as string - const clientId = nodeData.inputs?.clientId as string + return initalizeMotorhead(nodeData, options) + } - const chatId = options?.chatId as string - - let obj: MotorheadMemoryInput = { - returnMessages: true, - sessionId: sessionId ? sessionId : chatId, - memoryKey - } - - if (baseURL) { - obj = { - ...obj, - url: baseURL - } - } else { - obj = { - ...obj, - apiKey, - clientId - } - } - - return new MotorheadMemory(obj) + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const motorhead = initalizeMotorhead(nodeData, options) + motorhead.clear() } } +const initalizeMotorhead = (nodeData: INodeData, options: ICommonObject): MotorheadMemory => { + const memoryKey = nodeData.inputs?.memoryKey as string + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + const clientId = nodeData.inputs?.clientId as string + + const chatId = options?.chatId as string + + let obj: MotorheadMemoryInput = { + returnMessages: true, + sessionId: sessionId ? sessionId : chatId, + memoryKey + } + + if (baseURL) { + obj = { + ...obj, + url: baseURL + } + } else { + obj = { + ...obj, + apiKey, + clientId + } + } + + return new MotorheadMemory(obj) +} + module.exports = { nodeClass: MotorMemory_Memory } diff --git a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts index 2b4e51c26..cd406f59e 100644 --- a/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts +++ b/packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts @@ -56,31 +56,40 @@ class RedisBackedChatMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const baseURL = nodeData.inputs?.baseURL as string - const sessionId = nodeData.inputs?.sessionId as string - const sessionTTL = nodeData.inputs?.sessionTTL as number - const memoryKey = nodeData.inputs?.memoryKey as string + return initalizeRedis(nodeData, options) + } - const chatId = options?.chatId as string - - const redisClient = createClient({ url: baseURL }) - let obj: RedisChatMessageHistoryInput = { - sessionId: sessionId ? sessionId : chatId, - client: redisClient - } - - if (sessionTTL) { - obj = { - ...obj, - sessionTTL - } - } - - let redisChatMessageHistory = new RedisChatMessageHistory(obj) - let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true }) - - return redis + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const redis = initalizeRedis(nodeData, options) + redis.clear() } } +const initalizeRedis = (nodeData: INodeData, options: ICommonObject): BufferMemory => { + const baseURL = nodeData.inputs?.baseURL as string + const sessionId = nodeData.inputs?.sessionId as string + const sessionTTL = nodeData.inputs?.sessionTTL as number + const memoryKey = nodeData.inputs?.memoryKey as string + + const chatId = options?.chatId as string + + const redisClient = createClient({ url: baseURL }) + let obj: RedisChatMessageHistoryInput = { + sessionId: sessionId ? sessionId : chatId, + client: redisClient + } + + if (sessionTTL) { + obj = { + ...obj, + sessionTTL + } + } + + let redisChatMessageHistory = new RedisChatMessageHistory(obj) + let redis = new BufferMemory({ memoryKey, chatHistory: redisChatMessageHistory, returnMessages: true }) + + return redis +} + module.exports = { nodeClass: RedisBackedChatMemory_Memory } diff --git a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts index 6e1a14bd7..d9bac948a 100644 --- a/packages/components/nodes/memory/ZepMemory/ZepMemory.ts +++ b/packages/components/nodes/memory/ZepMemory/ZepMemory.ts @@ -104,31 +104,11 @@ class ZepMemory_Memory implements INode { } async init(nodeData: INodeData, _: string, options: ICommonObject): Promise { - const baseURL = nodeData.inputs?.baseURL as string - const aiPrefix = nodeData.inputs?.aiPrefix as string - const humanPrefix = nodeData.inputs?.humanPrefix as string - const memoryKey = nodeData.inputs?.memoryKey as string - const inputKey = nodeData.inputs?.inputKey as string const autoSummaryTemplate = nodeData.inputs?.autoSummaryTemplate as string const autoSummary = nodeData.inputs?.autoSummary as boolean - const sessionId = nodeData.inputs?.sessionId as string - const apiKey = nodeData.inputs?.apiKey as string const k = nodeData.inputs?.k as string - const chatId = options?.chatId as string - - const obj: ZepMemoryInput = { - baseURL, - sessionId: sessionId ? sessionId : chatId, - aiPrefix, - humanPrefix, - returnMessages: true, - memoryKey, - inputKey - } - if (apiKey) obj.apiKey = apiKey - - let zep = new ZepMemory(obj) + let zep = initalizeZep(nodeData, options) // hack to support summary let tmpFunc = zep.loadMemoryVariables @@ -153,6 +133,37 @@ class ZepMemory_Memory implements INode { } return zep } + + async clearSessionMemory(nodeData: INodeData, options: ICommonObject): Promise { + const zep = initalizeZep(nodeData, options) + zep.clear() + } +} + +const initalizeZep = (nodeData: INodeData, options: ICommonObject) => { + const baseURL = nodeData.inputs?.baseURL as string + const aiPrefix = nodeData.inputs?.aiPrefix as string + const humanPrefix = nodeData.inputs?.humanPrefix as string + const memoryKey = nodeData.inputs?.memoryKey as string + const inputKey = nodeData.inputs?.inputKey as string + + const sessionId = nodeData.inputs?.sessionId as string + const apiKey = nodeData.inputs?.apiKey as string + + const chatId = options?.chatId as string + + const obj: ZepMemoryInput = { + baseURL, + sessionId: sessionId ? sessionId : chatId, + aiPrefix, + humanPrefix, + returnMessages: true, + memoryKey, + inputKey + } + if (apiKey) obj.apiKey = apiKey + + return new ZepMemory(obj) } module.exports = { nodeClass: ZepMemory_Memory } diff --git a/packages/components/src/Interface.ts b/packages/components/src/Interface.ts index d9233e490..862b81ac8 100644 --- a/packages/components/src/Interface.ts +++ b/packages/components/src/Interface.ts @@ -96,6 +96,7 @@ export interface INode extends INodeProperties { } init?(nodeData: INodeData, input: string, options?: ICommonObject): Promise run?(nodeData: INodeData, input: string, options?: ICommonObject): Promise + clearSessionMemory?(nodeData: INodeData, options?: ICommonObject): Promise } export interface INodeData extends INodeProperties { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 0f87aebad..e11fa283b 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -39,7 +39,8 @@ import { isFlowValidForStream, isVectorStoreFaiss, databaseEntities, - getApiKey + getApiKey, + clearSessionMemory } from './utils' import { cloneDeep } from 'lodash' import { getDataSource } from './DataSource' @@ -320,6 +321,19 @@ export class App { // Delete all chatmessages from chatflowid this.app.delete('/api/v1/chatmessage/:id', async (req: Request, res: Response) => { + const chatflow = await this.AppDataSource.getRepository(ChatFlow).findOneBy({ + id: req.params.id + }) + if (!chatflow) { + res.status(404).send(`Chatflow ${req.params.id} not found`) + return + } + const flowData = chatflow.flowData + const parsedFlowData: IReactFlowObject = JSON.parse(flowData) + const nodes = parsedFlowData.nodes + let chatId = await getChatId(chatflow.id) + if (!chatId) chatId = chatflow.id + clearSessionMemory(nodes, this.nodesPool.componentNodes, chatId, req.query.sessionId as string) const results = await this.AppDataSource.getRepository(ChatMessage).delete({ chatflowid: req.params.id }) return res.json(results) }) @@ -662,7 +676,7 @@ export class App { if (!chatflow) return res.status(404).send(`Chatflow ${chatflowid} not found`) let chatId = await getChatId(chatflow.id) - if (!chatId) chatId = Date.now().toString() + if (!chatId) chatId = chatflowid if (!isInternal) { await this.validateKey(req, res, chatflow) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 3ee7a25b2..e13bca979 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -267,6 +267,29 @@ export const buildLangchain = async ( return flowNodes } +/** + * Clear memory + * @param {IReactFlowNode[]} reactFlowNodes + * @param {IComponentNodes} componentNodes + * @param {string} chatId + * @param {string} sessionId + */ +export const clearSessionMemory = async ( + reactFlowNodes: IReactFlowNode[], + componentNodes: IComponentNodes, + chatId: string, + sessionId?: string +) => { + for (const node of reactFlowNodes) { + if (node.data.category !== 'Memory') continue + const nodeInstanceFilePath = componentNodes[node.data.name].filePath as string + const nodeModule = await import(nodeInstanceFilePath) + const newNodeInstance = new nodeModule.nodeClass() + if (sessionId && node.data.inputs) node.data.inputs.sessionId = sessionId + if (newNodeInstance.clearSessionMemory) await newNodeInstance?.clearSessionMemory(node.data, { chatId }) + } +} + /** * Get variable value from outputResponses.output * @param {string} paramValue