feat(Mem0): Add option to use Flowise Chat ID (#4257)

* feat(Mem0): Add option to use Flowise Chat ID

* refactor(Mem0): Simplify prependMessages handling in getChatMessages
This commit is contained in:
toi500 2025-04-07 05:46:32 +02:00 committed by GitHub
parent b988cae58c
commit ca69a39b82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 128 additions and 56 deletions

View File

@ -30,7 +30,7 @@ class Mem0_Memory implements INode {
constructor() { constructor() {
this.label = 'Mem0' this.label = 'Mem0'
this.name = 'mem0' this.name = 'mem0'
this.version = 1.0 this.version = 1.1
this.type = 'Mem0' this.type = 'Mem0'
this.icon = 'mem0.svg' this.icon = 'mem0.svg'
this.category = 'Memory' this.category = 'Memory'
@ -49,9 +49,18 @@ class Mem0_Memory implements INode {
label: 'User ID', label: 'User ID',
name: 'user_id', name: 'user_id',
type: 'string', type: 'string',
description: 'Unique identifier for the user', description: 'Unique identifier for the user. Required only if "Use Flowise Chat ID" is OFF.',
default: 'flowise-default-user', default: 'flowise-default-user',
optional: false optional: true
},
// Added toggle to use Flowise chat ID
{
label: 'Use Flowise Chat ID',
name: 'useFlowiseChatId',
type: 'boolean',
description: 'Use the Flowise internal Chat ID as the Mem0 User ID, overriding the "User ID" field above.',
default: false,
optional: true
}, },
{ {
label: 'Search Only', label: 'Search Only',
@ -140,9 +149,11 @@ class Mem0_Memory implements INode {
} }
const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Promise<BaseMem0Memory> => { const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Promise<BaseMem0Memory> => {
const userId = nodeData.inputs?.user_id as string const initialUserId = nodeData.inputs?.user_id as string
if (!userId) { const useFlowiseChatId = nodeData.inputs?.useFlowiseChatId as boolean
throw new Error('user_id is required for Mem0Memory')
if (!useFlowiseChatId && !initialUserId) {
throw new Error('User ID field cannot be empty when "Use Flowise Chat ID" is OFF.')
} }
const credentialData = await getCredentialData(nodeData.credential ?? '', options) const credentialData = await getCredentialData(nodeData.credential ?? '', options)
@ -155,8 +166,12 @@ const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Prom
projectId: nodeData.inputs?.project_id as string projectId: nodeData.inputs?.project_id as string
} }
const memOptionsUserId = initialUserId
const constructorSessionId = initialUserId || (useFlowiseChatId ? 'flowise-chat-id-placeholder' : '')
const memoryOptions: MemoryOptions & SearchOptions = { const memoryOptions: MemoryOptions & SearchOptions = {
user_id: userId, user_id: memOptionsUserId,
run_id: (nodeData.inputs?.run_id as string) || undefined, run_id: (nodeData.inputs?.run_id as string) || undefined,
agent_id: (nodeData.inputs?.agent_id as string) || undefined, agent_id: (nodeData.inputs?.agent_id as string) || undefined,
app_id: (nodeData.inputs?.app_id as string) || undefined, app_id: (nodeData.inputs?.app_id as string) || undefined,
@ -168,12 +183,13 @@ const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Prom
filters: (nodeData.inputs?.filters as Record<string, any>) || {} filters: (nodeData.inputs?.filters as Record<string, any>) || {}
} }
const obj: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean } = { const obj: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean; useFlowiseChatId: boolean } =
{
apiKey: apiKey, apiKey: apiKey,
humanPrefix: nodeData.inputs?.humanPrefix as string, humanPrefix: nodeData.inputs?.humanPrefix as string,
aiPrefix: nodeData.inputs?.aiPrefix as string, aiPrefix: nodeData.inputs?.aiPrefix as string,
inputKey: nodeData.inputs?.inputKey as string, inputKey: nodeData.inputs?.inputKey as string,
sessionId: nodeData.inputs?.user_id as string, sessionId: constructorSessionId,
mem0Options: mem0Options, mem0Options: mem0Options,
memoryOptions: memoryOptions, memoryOptions: memoryOptions,
separateMessages: false, separateMessages: false,
@ -181,7 +197,8 @@ const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Prom
appDataSource: options.appDataSource as DataSource, appDataSource: options.appDataSource as DataSource,
databaseEntities: options.databaseEntities as IDatabaseEntity, databaseEntities: options.databaseEntities as IDatabaseEntity,
chatflowid: options.chatflowid as string, chatflowid: options.chatflowid as string,
searchOnly: (nodeData.inputs?.searchOnly as boolean) || false searchOnly: (nodeData.inputs?.searchOnly as boolean) || false,
useFlowiseChatId: useFlowiseChatId
} }
return new Mem0MemoryExtended(obj) return new Mem0MemoryExtended(obj)
@ -189,9 +206,11 @@ const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Prom
interface Mem0MemoryExtendedInput extends Mem0MemoryInput { interface Mem0MemoryExtendedInput extends Mem0MemoryInput {
memoryOptions?: MemoryOptions | SearchOptions memoryOptions?: MemoryOptions | SearchOptions
useFlowiseChatId: boolean
} }
class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods { class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods {
initialUserId: string
userId: string userId: string
memoryKey: string memoryKey: string
inputKey: string inputKey: string
@ -199,38 +218,71 @@ class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods {
databaseEntities: IDatabaseEntity databaseEntities: IDatabaseEntity
chatflowid: string chatflowid: string
searchOnly: boolean searchOnly: boolean
useFlowiseChatId: boolean
constructor(fields: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean }) { constructor(
fields: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean; useFlowiseChatId: boolean }
) {
super(fields) super(fields)
this.userId = fields.memoryOptions?.user_id ?? '' this.initialUserId = fields.memoryOptions?.user_id ?? ''
this.userId = this.initialUserId
this.memoryKey = 'history' this.memoryKey = 'history'
this.inputKey = fields.inputKey ?? 'input' this.inputKey = fields.inputKey ?? 'input'
this.appDataSource = fields.appDataSource this.appDataSource = fields.appDataSource
this.databaseEntities = fields.databaseEntities this.databaseEntities = fields.databaseEntities
this.chatflowid = fields.chatflowid this.chatflowid = fields.chatflowid
this.searchOnly = fields.searchOnly this.searchOnly = fields.searchOnly
this.useFlowiseChatId = fields.useFlowiseChatId
}
// Selects Mem0 user_id based on toggle state (Flowise chat ID or input field)
private getEffectiveUserId(overrideUserId?: string): string {
let effectiveUserId: string | undefined
if (this.useFlowiseChatId) {
if (overrideUserId) {
effectiveUserId = overrideUserId
} else {
throw new Error('Mem0: "Use Flowise Chat ID" is ON, but no runtime chat ID (overrideUserId) was provided.')
}
} else {
// If toggle is OFF, ALWAYS use the ID from the input field.
effectiveUserId = this.initialUserId
}
// This check is now primarily for the case where the toggle is OFF and the initialUserId was somehow empty (should be caught by init validation).
if (!effectiveUserId) {
throw new Error('Mem0: Could not determine a valid User ID for the operation. Check User ID input field.')
}
return effectiveUserId
} }
async loadMemoryVariables(values: InputValues, overrideUserId = ''): Promise<MemoryVariables> { async loadMemoryVariables(values: InputValues, overrideUserId = ''): Promise<MemoryVariables> {
if (overrideUserId) { const effectiveUserId = this.getEffectiveUserId(overrideUserId)
this.userId = overrideUserId this.userId = effectiveUserId
if (this.memoryOptions) {
this.memoryOptions.user_id = effectiveUserId
} }
return super.loadMemoryVariables(values) return super.loadMemoryVariables(values)
} }
async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideUserId = ''): Promise<void> { async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideUserId = ''): Promise<void> {
if (overrideUserId) {
this.userId = overrideUserId
}
if (this.searchOnly) { if (this.searchOnly) {
return return
} }
const effectiveUserId = this.getEffectiveUserId(overrideUserId)
this.userId = effectiveUserId
if (this.memoryOptions) {
this.memoryOptions.user_id = effectiveUserId
}
return super.saveContext(inputValues, outputValues) return super.saveContext(inputValues, outputValues)
} }
async clear(overrideUserId = ''): Promise<void> { async clear(overrideUserId = ''): Promise<void> {
if (overrideUserId) { const effectiveUserId = this.getEffectiveUserId(overrideUserId)
this.userId = overrideUserId this.userId = effectiveUserId
if (this.memoryOptions) {
this.memoryOptions.user_id = effectiveUserId
} }
return super.clear() return super.clear()
} }
@ -240,12 +292,15 @@ class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods {
returnBaseMessages = false, returnBaseMessages = false,
prependMessages?: IMessage[] prependMessages?: IMessage[]
): Promise<IMessage[] | BaseMessage[]> { ): Promise<IMessage[] | BaseMessage[]> {
const id = overrideUserId ? overrideUserId : this.userId const flowiseSessionId = overrideUserId
if (!id) return [] if (!flowiseSessionId) {
console.warn('Mem0: getChatMessages called without overrideUserId (Flowise Session ID). Cannot fetch DB messages.')
return []
}
let chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({ let chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({
where: { where: {
sessionId: id, sessionId: flowiseSessionId,
chatflowid: this.chatflowid chatflowid: this.chatflowid
}, },
order: { order: {
@ -253,31 +308,35 @@ class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods {
}, },
take: 10 take: 10
}) })
chatMessage = chatMessage.reverse() chatMessage = chatMessage.reverse()
let returnIMessages: IMessage[] = [] let returnIMessages: IMessage[] = chatMessage.map((m) => ({
for (const m of chatMessage) {
returnIMessages.push({
message: m.content as string, message: m.content as string,
type: m.role type: m.role as MessageType
}) }))
}
if (prependMessages?.length) { if (prependMessages?.length) {
chatMessage.unshift(...prependMessages) returnIMessages.unshift(...prependMessages)
// Reverted to original simpler unshift
chatMessage.unshift(...(prependMessages as any)) // Cast as any
} }
if (returnBaseMessages) { if (returnBaseMessages) {
const memoryVariables = await this.loadMemoryVariables({}, id) const memoryVariables = await this.loadMemoryVariables({}, overrideUserId)
let baseMessages = memoryVariables[this.memoryKey] const mem0History = memoryVariables[this.memoryKey]
const systemMessage = { ...chatMessage[0] } if (mem0History && typeof mem0History === 'string') {
systemMessage.content = baseMessages const systemMessage = {
systemMessage.id = uuidv4() role: 'apiMessage' as MessageType,
systemMessage.role = 'apiMessage' content: mem0History,
id: uuidv4()
}
// Ensure Mem0 history message also conforms structurally if mapChatMessageToBaseMessage is strict
chatMessage.unshift(systemMessage as any) // Cast needed if mixing structures
} else if (mem0History) {
console.warn('Mem0 history is not a string, cannot prepend directly.')
}
chatMessage.unshift(systemMessage)
return await mapChatMessageToBaseMessage(chatMessage) return await mapChatMessageToBaseMessage(chatMessage)
} }
@ -285,18 +344,31 @@ class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods {
} }
async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideUserId = ''): Promise<void> { async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideUserId = ''): Promise<void> {
const id = overrideUserId ? overrideUserId : this.userId const effectiveUserId = this.getEffectiveUserId(overrideUserId)
const input = msgArray.find((msg) => msg.type === 'userMessage') const input = msgArray.find((msg) => msg.type === 'userMessage')
const output = msgArray.find((msg) => msg.type === 'apiMessage') 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) if (input && output) {
const inputValues = { [this.inputKey ?? 'input']: input.text }
const outputValues = { output: output.text }
await this.saveContext(inputValues, outputValues, effectiveUserId)
} else {
console.warn('Mem0: Could not find both input and output messages to save context.')
}
} }
async clearChatMessages(overrideUserId = ''): Promise<void> { async clearChatMessages(overrideUserId = ''): Promise<void> {
const id = overrideUserId ? overrideUserId : this.userId const effectiveUserId = this.getEffectiveUserId(overrideUserId)
await this.clear(id) await this.clear(effectiveUserId)
const flowiseSessionId = overrideUserId
if (flowiseSessionId) {
await this.appDataSource
.getRepository(this.databaseEntities['ChatMessage'])
.delete({ sessionId: flowiseSessionId, chatflowid: this.chatflowid })
} else {
console.warn('Mem0: clearChatMessages called without overrideUserId (Flowise Session ID). Cannot clear DB messages.')
}
} }
} }