Skip to content

Commit fb2f6e1

Browse files
whysosaketchungyau97
authored andcommitted
Adds Mem0 Memory Node (FlowiseAI#4213)
* Adds Mem0 Memory Node * fix: update pnpm-lock.yaml --------- Co-authored-by: Ong Chung Yau <[email protected]> Co-authored-by: chungyau97 <[email protected]>
1 parent 900594b commit fb2f6e1

File tree

5 files changed

+1023
-0
lines changed

5 files changed

+1023
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { INodeParams, INodeCredential } from '../src/Interface'
2+
3+
class Mem0MemoryApi implements INodeCredential {
4+
label: string
5+
name: string
6+
version: number
7+
description: string
8+
inputs: INodeParams[]
9+
10+
constructor() {
11+
this.label = 'Mem0 Memory API'
12+
this.name = 'mem0MemoryApi'
13+
this.version = 1.0
14+
this.description =
15+
'Visit <a target="_blank" href="https://app.mem0.ai/settings/api-keys">Mem0 Platform</a> to get your API credentials'
16+
this.inputs = [
17+
{
18+
label: 'API Key',
19+
name: 'apiKey',
20+
type: 'password',
21+
description: 'API Key from Mem0 dashboard'
22+
}
23+
]
24+
}
25+
}
26+
27+
module.exports = { credClass: Mem0MemoryApi }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
import { Mem0Memory as BaseMem0Memory, Mem0MemoryInput, ClientOptions } from '@mem0/community'
2+
import { MemoryOptions, SearchOptions } from 'mem0ai'
3+
import { BaseMessage } from '@langchain/core/messages'
4+
import { InputValues, MemoryVariables, OutputValues } from '@langchain/core/memory'
5+
import { ICommonObject, IDatabaseEntity } from '../../../src'
6+
import { IMessage, INode, INodeData, INodeParams, MemoryMethods, MessageType } from '../../../src/Interface'
7+
import { getBaseClasses, getCredentialData, getCredentialParam, mapChatMessageToBaseMessage } from '../../../src/utils'
8+
import { DataSource } from 'typeorm'
9+
import { v4 as uuidv4 } from 'uuid'
10+
11+
interface BufferMemoryExtendedInput {
12+
sessionId: string
13+
appDataSource: DataSource
14+
databaseEntities: IDatabaseEntity
15+
chatflowid: string
16+
}
17+
18+
class Mem0_Memory implements INode {
19+
label: string
20+
name: string
21+
version: number
22+
description: string
23+
type: string
24+
icon: string
25+
category: string
26+
baseClasses: string[]
27+
credential: INodeParams
28+
inputs: INodeParams[]
29+
30+
constructor() {
31+
this.label = 'Mem0'
32+
this.name = 'mem0'
33+
this.version = 1.0
34+
this.type = 'Mem0'
35+
this.icon = 'mem0.svg'
36+
this.category = 'Memory'
37+
this.description = 'Stores and manages chat memory using Mem0 service'
38+
this.baseClasses = [this.type, ...getBaseClasses(BaseMem0Memory)]
39+
this.credential = {
40+
label: 'Connect Credential',
41+
name: 'credential',
42+
type: 'credential',
43+
optional: false,
44+
description: 'Configure API Key for Mem0 service',
45+
credentialNames: ['mem0MemoryApi']
46+
}
47+
this.inputs = [
48+
{
49+
label: 'User ID',
50+
name: 'user_id',
51+
type: 'string',
52+
description: 'Unique identifier for the user',
53+
default: 'flowise-default-user',
54+
optional: false
55+
},
56+
{
57+
label: 'Search Only',
58+
name: 'searchOnly',
59+
type: 'boolean',
60+
description: 'Search only mode',
61+
default: false,
62+
optional: true,
63+
additionalParams: true
64+
},
65+
{
66+
label: 'Run ID',
67+
name: 'run_id',
68+
type: 'string',
69+
description: 'Unique identifier for the run session',
70+
default: '',
71+
optional: true,
72+
additionalParams: true
73+
},
74+
{
75+
label: 'Agent ID',
76+
name: 'agent_id',
77+
type: 'string',
78+
description: 'Identifier for the agent',
79+
default: '',
80+
optional: true,
81+
additionalParams: true
82+
},
83+
{
84+
label: 'App ID',
85+
name: 'app_id',
86+
type: 'string',
87+
description: 'Identifier for the application',
88+
default: '',
89+
optional: true,
90+
additionalParams: true
91+
},
92+
{
93+
label: 'Project ID',
94+
name: 'project_id',
95+
type: 'string',
96+
description: 'Identifier for the project',
97+
default: '',
98+
optional: true,
99+
additionalParams: true
100+
},
101+
{
102+
label: 'Organization ID',
103+
name: 'org_id',
104+
type: 'string',
105+
description: 'Identifier for the organization',
106+
default: '',
107+
optional: true,
108+
additionalParams: true
109+
},
110+
{
111+
label: 'Memory Key',
112+
name: 'memoryKey',
113+
type: 'string',
114+
default: 'history',
115+
optional: true,
116+
additionalParams: true
117+
},
118+
{
119+
label: 'Input Key',
120+
name: 'inputKey',
121+
type: 'string',
122+
default: 'input',
123+
optional: true,
124+
additionalParams: true
125+
},
126+
{
127+
label: 'Output Key',
128+
name: 'outputKey',
129+
type: 'string',
130+
default: 'text',
131+
optional: true,
132+
additionalParams: true
133+
}
134+
]
135+
}
136+
137+
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
138+
return await initializeMem0(nodeData, options)
139+
}
140+
}
141+
142+
const initializeMem0 = async (nodeData: INodeData, options: ICommonObject): Promise<BaseMem0Memory> => {
143+
const userId = nodeData.inputs?.user_id as string
144+
if (!userId) {
145+
throw new Error('user_id is required for Mem0Memory')
146+
}
147+
148+
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
149+
const apiKey = getCredentialParam('apiKey', credentialData, nodeData)
150+
151+
const mem0Options: ClientOptions = {
152+
apiKey: apiKey,
153+
host: nodeData.inputs?.host as string,
154+
organizationId: nodeData.inputs?.org_id as string,
155+
projectId: nodeData.inputs?.project_id as string
156+
}
157+
158+
const memoryOptions: MemoryOptions & SearchOptions = {
159+
user_id: userId,
160+
run_id: (nodeData.inputs?.run_id as string) || undefined,
161+
agent_id: (nodeData.inputs?.agent_id as string) || undefined,
162+
app_id: (nodeData.inputs?.app_id as string) || undefined,
163+
project_id: (nodeData.inputs?.project_id as string) || undefined,
164+
org_id: (nodeData.inputs?.org_id as string) || undefined,
165+
api_version: (nodeData.inputs?.api_version as string) || undefined,
166+
enable_graph: (nodeData.inputs?.enable_graph as boolean) || false,
167+
metadata: (nodeData.inputs?.metadata as Record<string, any>) || {},
168+
filters: (nodeData.inputs?.filters as Record<string, any>) || {}
169+
}
170+
171+
const obj: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean } = {
172+
apiKey: apiKey,
173+
humanPrefix: nodeData.inputs?.humanPrefix as string,
174+
aiPrefix: nodeData.inputs?.aiPrefix as string,
175+
inputKey: nodeData.inputs?.inputKey as string,
176+
sessionId: nodeData.inputs?.user_id as string,
177+
mem0Options: mem0Options,
178+
memoryOptions: memoryOptions,
179+
separateMessages: false,
180+
returnMessages: false,
181+
appDataSource: options.appDataSource as DataSource,
182+
databaseEntities: options.databaseEntities as IDatabaseEntity,
183+
chatflowid: options.chatflowid as string,
184+
searchOnly: (nodeData.inputs?.searchOnly as boolean) || false
185+
}
186+
187+
return new Mem0MemoryExtended(obj)
188+
}
189+
190+
interface Mem0MemoryExtendedInput extends Mem0MemoryInput {
191+
memoryOptions?: MemoryOptions | SearchOptions
192+
}
193+
194+
class Mem0MemoryExtended extends BaseMem0Memory implements MemoryMethods {
195+
userId: string
196+
memoryKey: string
197+
inputKey: string
198+
appDataSource: DataSource
199+
databaseEntities: IDatabaseEntity
200+
chatflowid: string
201+
searchOnly: boolean
202+
203+
constructor(fields: Mem0MemoryInput & Mem0MemoryExtendedInput & BufferMemoryExtendedInput & { searchOnly: boolean }) {
204+
super(fields)
205+
this.userId = fields.memoryOptions?.user_id ?? ''
206+
this.memoryKey = 'history'
207+
this.inputKey = fields.inputKey ?? 'input'
208+
this.appDataSource = fields.appDataSource
209+
this.databaseEntities = fields.databaseEntities
210+
this.chatflowid = fields.chatflowid
211+
this.searchOnly = fields.searchOnly
212+
}
213+
214+
async loadMemoryVariables(values: InputValues, overrideUserId = ''): Promise<MemoryVariables> {
215+
if (overrideUserId) {
216+
this.userId = overrideUserId
217+
}
218+
return super.loadMemoryVariables(values)
219+
}
220+
221+
async saveContext(inputValues: InputValues, outputValues: OutputValues, overrideUserId = ''): Promise<void> {
222+
if (overrideUserId) {
223+
this.userId = overrideUserId
224+
}
225+
if (this.searchOnly) {
226+
return
227+
}
228+
return super.saveContext(inputValues, outputValues)
229+
}
230+
231+
async clear(overrideUserId = ''): Promise<void> {
232+
if (overrideUserId) {
233+
this.userId = overrideUserId
234+
}
235+
return super.clear()
236+
}
237+
238+
async getChatMessages(
239+
overrideUserId = '',
240+
returnBaseMessages = false,
241+
prependMessages?: IMessage[]
242+
): Promise<IMessage[] | BaseMessage[]> {
243+
const id = overrideUserId ? overrideUserId : this.userId
244+
if (!id) return []
245+
246+
let chatMessage = await this.appDataSource.getRepository(this.databaseEntities['ChatMessage']).find({
247+
where: {
248+
sessionId: id,
249+
chatflowid: this.chatflowid
250+
},
251+
order: {
252+
createdDate: 'DESC'
253+
},
254+
take: 10
255+
})
256+
257+
chatMessage = chatMessage.reverse()
258+
259+
let returnIMessages: IMessage[] = []
260+
for (const m of chatMessage) {
261+
returnIMessages.push({
262+
message: m.content as string,
263+
type: m.role
264+
})
265+
}
266+
267+
if (prependMessages?.length) {
268+
chatMessage.unshift(...prependMessages)
269+
}
270+
271+
if (returnBaseMessages) {
272+
const memoryVariables = await this.loadMemoryVariables({}, id)
273+
let baseMessages = memoryVariables[this.memoryKey]
274+
275+
const systemMessage = { ...chatMessage[0] }
276+
systemMessage.content = baseMessages
277+
systemMessage.id = uuidv4()
278+
systemMessage.role = 'apiMessage'
279+
280+
chatMessage.unshift(systemMessage)
281+
return await mapChatMessageToBaseMessage(chatMessage)
282+
}
283+
284+
return returnIMessages
285+
}
286+
287+
async addChatMessages(msgArray: { text: string; type: MessageType }[], overrideUserId = ''): Promise<void> {
288+
const id = overrideUserId ? overrideUserId : this.userId
289+
const input = msgArray.find((msg) => msg.type === 'userMessage')
290+
const output = msgArray.find((msg) => msg.type === 'apiMessage')
291+
const inputValues = { [this.inputKey ?? 'input']: input?.text }
292+
const outputValues = { output: output?.text }
293+
294+
await this.saveContext(inputValues, outputValues, id)
295+
}
296+
297+
async clearChatMessages(overrideUserId = ''): Promise<void> {
298+
const id = overrideUserId ? overrideUserId : this.userId
299+
await this.clear(id)
300+
}
301+
}
302+
303+
module.exports = { nodeClass: Mem0_Memory }
Loading

packages/components/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@langchain/qdrant": "^0.0.5",
5757
"@langchain/weaviate": "^0.0.1",
5858
"@langchain/xai": "^0.0.1",
59+
"@mem0/community": "^0.0.1",
5960
"@mendable/firecrawl-js": "^0.0.28",
6061
"@mistralai/mistralai": "0.1.3",
6162
"@modelcontextprotocol/sdk": "^1.6.1",

0 commit comments

Comments
 (0)