Skip to content

Adds Mem0 Memory Node #4213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/components/credentials/Mem0MemoryApi.credential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { INodeParams, INodeCredential } from '../src/Interface'

class Mem0MemoryApi implements INodeCredential {
label: string
name: string
version: number
description: string
inputs: INodeParams[]

constructor() {
this.label = 'Mem0 Memory API'
this.name = 'mem0MemoryApi'
this.version = 1.0
this.description =
'Visit <a target="_blank" href="https://app.mem0.ai/settings/api-keys">Mem0 Platform</a> to get your API credentials'
this.inputs = [
{
label: 'API Key',
name: 'apiKey',
type: 'password',
description: 'API Key from Mem0 dashboard'
}
]
}
}

module.exports = { credClass: Mem0MemoryApi }
303 changes: 303 additions & 0 deletions packages/components/nodes/memory/Mem0/Mem0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import { Mem0Memory as BaseMem0Memory, Mem0MemoryInput, ClientOptions } from '@mem0/community'
import { MemoryOptions, SearchOptions } from 'mem0ai'
import { BaseMessage } from '@langchain/core/messages'
import { InputValues, MemoryVariables, OutputValues } from '@langchain/core/memory'
import { ICommonObject, IDatabaseEntity } from '../../../src'
import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam, mapChatMessageToBaseMessage } from '../../../src/utils'
import { DataSource } from 'typeorm'
import { v4 as uuidv4 } from 'uuid'

interface BufferMemoryExtendedInput {
sessionId: string
appDataSource: DataSource
databaseEntities: IDatabaseEntity
chatflowid: string
}

class Mem0_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 = 'Mem0'
this.name = 'mem0'
this.version = 1.0
this.type = 'Mem0'
this.icon = 'mem0.svg'
this.category = 'Memory'
this.description = 'Stores and manages chat memory using Mem0 service'
this.baseClasses = [this.type, ...getBaseClasses(BaseMem0Memory)]
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
optional: false,
description: 'Configure API Key for Mem0 service',
credentialNames: ['mem0MemoryApi']
}
this.inputs = [
{
label: 'User ID',
name: 'user_id',
type: 'string',
description: 'Unique identifier for the user',
default: 'flowise-default-user',
optional: false
},
{
label: 'Search Only',
name: 'searchOnly',
type: 'boolean',
description: 'Search only mode',
default: false,
optional: true,
additionalParams: true
},
{
label: 'Run ID',
name: 'run_id',
type: 'string',
description: 'Unique identifier for the run session',
default: '',
optional: true,
additionalParams: true
},
{
label: 'Agent ID',
name: 'agent_id',
type: 'string',
description: 'Identifier for the agent',
default: '',
optional: true,
additionalParams: true
},
{
label: 'App ID',
name: 'app_id',
type: 'string',
description: 'Identifier for the application',
default: '',
optional: true,
additionalParams: true
},
{
label: 'Project ID',
name: 'project_id',
type: 'string',
description: 'Identifier for the project',
default: '',
optional: true,
additionalParams: true
},
{
label: 'Organization ID',
name: 'org_id',
type: 'string',
description: 'Identifier for the organization',
default: '',
optional: true,
additionalParams: true
},
{
label: 'Memory Key',
name: 'memoryKey',
type: 'string',
default: 'history',
optional: true,
additionalParams: true
},
{
label: 'Input Key',
name: 'inputKey',
type: 'string',
default: 'input',
optional: true,
additionalParams: true
},
{
label: 'Output Key',
name: 'outputKey',
type: 'string',
default: 'text',
optional: true,
additionalParams: true
}
]
}

async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
return await initializeMem0(nodeData, options)
}
}

const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Promise<BaseMem0Memory> => {
const userId = nodeData.inputs?.user_id as string
if (!userId) {
throw new Error('user_id is required for Mem0Memory')
}

const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)

const mem0Options: ClientOptions = {
apiKey: apiKey,
host: nodeData.inputs?.host as string,
organizationId: nodeData.inputs?.org_id as string,
projectId: nodeData.inputs?.project_id as string
}

const memoryOptions: MemoryOptions & SearchOptions = {
user_id: userId,
run_id: (nodeData.inputs?.run_id as string) || undefined,
agent_id: (nodeData.inputs?.agent_id as string) || undefined,
app_id: (nodeData.inputs?.app_id as string) || undefined,
project_id: (nodeData.inputs?.project_id as string) || undefined,
org_id: (nodeData.inputs?.org_id as string) || undefined,
api_version: (nodeData.inputs?.api_version as string) || undefined,
enable_graph: (nodeData.inputs?.enable_graph as boolean) || false,
metadata: (nodeData.inputs?.metadata as Record<string, any>) || {},
filters: (nodeData.inputs?.filters as Record<string, any>) || {}
}

const obj: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean } = {
apiKey: apiKey,
humanPrefix: nodeData.inputs?.humanPrefix as string,
aiPrefix: nodeData.inputs?.aiPrefix as string,
inputKey: nodeData.inputs?.inputKey as string,
sessionId: nodeData.inputs?.user_id as string,
mem0Options: mem0Options,
memoryOptions: memoryOptions,
separateMessages: false,
returnMessages: false,
appDataSource: options.appDataSource as DataSource,
databaseEntities: options.databaseEntities as IDatabaseEntity,
chatflowid: options.chatflowid as string,
searchOnly: (nodeData.inputs?.searchOnly as boolean) || false
}

return new Mem0MemoryExtended(obj)
}

interface Mem0MemoryExtendedInput extends Mem0MemoryInput {
memoryOptions?: MemoryOptions | SearchOptions
}

class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods {
userId: string
memoryKey: string
inputKey: string
appDataSource: DataSource
databaseEntities: IDatabaseEntity
chatflowid: string
searchOnly: boolean

constructor(fields: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean }) {
super(fields)
this.userId = fields.memoryOptions?.user_id ?? ''
this.memoryKey = 'history'
this.inputKey = fields.inputKey ?? 'input'
this.appDataSource = fields.appDataSource
this.databaseEntities = fields.databaseEntities
this.chatflowid = fields.chatflowid
this.searchOnly = fields.searchOnly
}

async loadMemoryVariables(values: InputValues, overrideUserId = ''): Promise<MemoryVariables> {
if (overrideUserId) {
this.userId = overrideUserId
}
return super.loadMemoryVariables(values)
}

async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideUserId = ''): Promise<void> {
if (overrideUserId) {
this.userId = overrideUserId
}
if (this.searchOnly) {
return
}
return super.saveContext(inputValues, outputValues)
}

async clear(overrideUserId = ''): Promise<void> {
if (overrideUserId) {
this.userId = overrideUserId
}
return super.clear()
}

async getChatMessages(
overrideUserId = '',
returnBaseMessages = false,
prependMessages?: IMessage[]
): Promise<IMessage[] | BaseMessage[]> {
const id = overrideUserId ? overrideUserId : this.userId
if (!id) return []

let chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({
where: {
sessionId: id,
chatflowid: this.chatflowid
},
order: {
createdDate: 'DESC'
},
take: 10
})

chatMessage = chatMessage.reverse()

let returnIMessages: IMessage[] = []
for (const m of chatMessage) {
returnIMessages.push({
message: m.content as string,
type: m.role
})
}

if (prependMessages?.length) {
chatMessage.unshift(...prependMessages)
}

if (returnBaseMessages) {
const memoryVariables = await this.loadMemoryVariables({}, id)
let baseMessages = memoryVariables[this.memoryKey]

const systemMessage = { ...chatMessage[0] }
systemMessage.content = baseMessages
systemMessage.id = uuidv4()
systemMessage.role = 'apiMessage'

chatMessage.unshift(systemMessage)
return await mapChatMessageToBaseMessage(chatMessage)
}

return returnIMessages
}

async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideUserId = ''): Promise<void> {
const id = overrideUserId ? overrideUserId : this.userId
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(overrideUserId = ''): Promise<void> {
const id = overrideUserId ? overrideUserId : this.userId
await this.clear(id)
}
}

module.exports = { nodeClass: Mem0_Memory }
3 changes: 3 additions & 0 deletions packages/components/nodes/memory/Mem0/mem0.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@langchain/qdrant": "^0.0.5",
"@langchain/weaviate": "^0.0.1",
"@langchain/xai": "^0.0.1",
"@mem0/community": "^0.0.1",
"@mendable/firecrawl-js": "^0.0.28",
"@mistralai/mistralai": "0.1.3",
"@modelcontextprotocol/sdk": "^1.6.1",
Expand Down
Loading