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:
parent
b988cae58c
commit
ca69a39b82
|
|
@ -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.')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue