mem0ai 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/oss/index.js CHANGED
@@ -77,8 +77,19 @@ var MemoryConfigSchema = import_zod.z.object({
77
77
  }),
78
78
  historyDbPath: import_zod.z.string().optional(),
79
79
  customPrompt: import_zod.z.string().optional(),
80
+ enableGraph: import_zod.z.boolean().optional(),
80
81
  graphStore: import_zod.z.object({
81
- config: import_zod.z.any().optional()
82
+ provider: import_zod.z.string(),
83
+ config: import_zod.z.object({
84
+ url: import_zod.z.string(),
85
+ username: import_zod.z.string(),
86
+ password: import_zod.z.string()
87
+ }),
88
+ llm: import_zod.z.object({
89
+ provider: import_zod.z.string(),
90
+ config: import_zod.z.record(import_zod.z.string(), import_zod.z.any())
91
+ }).optional(),
92
+ customPrompt: import_zod.z.string().optional()
82
93
  }).optional()
83
94
  });
84
95
 
@@ -112,16 +123,28 @@ var OpenAILLM = class {
112
123
  this.openai = new import_openai2.default({ apiKey: config.apiKey });
113
124
  this.model = config.model || "gpt-4-turbo-preview";
114
125
  }
115
- async generateResponse(messages, responseFormat) {
126
+ async generateResponse(messages, responseFormat, tools) {
116
127
  const completion = await this.openai.chat.completions.create({
117
128
  messages: messages.map((msg) => ({
118
129
  role: msg.role,
119
130
  content: msg.content
120
131
  })),
121
132
  model: this.model,
122
- response_format: responseFormat
133
+ response_format: responseFormat,
134
+ ...tools && { tools, tool_choice: "auto" }
123
135
  });
124
- return completion.choices[0].message.content || "";
136
+ const response = completion.choices[0].message;
137
+ if (response.tool_calls) {
138
+ return {
139
+ content: response.content || "",
140
+ role: response.role,
141
+ toolCalls: response.tool_calls.map((call) => ({
142
+ name: call.function.name,
143
+ arguments: call.function.arguments
144
+ }))
145
+ };
146
+ }
147
+ return response.content || "";
125
148
  }
126
149
  async generateChat(messages) {
127
150
  const completion = await this.openai.chat.completions.create({
@@ -143,37 +166,57 @@ var OpenAILLM = class {
143
166
  var import_openai3 = __toESM(require("openai"));
144
167
  var OpenAIStructuredLLM = class {
145
168
  constructor(config) {
146
- const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
147
- if (!apiKey) {
148
- throw new Error("OpenAI API key is required");
149
- }
150
- const baseUrl = process.env.OPENAI_API_BASE || "https://api.openai.com/v1";
151
- this.client = new import_openai3.default({ apiKey, baseURL: baseUrl });
152
- this.model = config.model || "gpt-4-0125-preview";
169
+ this.openai = new import_openai3.default({ apiKey: config.apiKey });
170
+ this.model = config.model || "gpt-4-turbo-preview";
153
171
  }
154
- async generateResponse(messages, responseFormat) {
155
- const response = await this.client.chat.completions.create({
156
- model: this.model,
172
+ async generateResponse(messages, responseFormat, tools) {
173
+ const completion = await this.openai.chat.completions.create({
157
174
  messages: messages.map((msg) => ({
158
175
  role: msg.role,
159
176
  content: msg.content
160
177
  })),
161
- response_format: responseFormat
178
+ model: this.model,
179
+ ...tools ? {
180
+ tools: tools.map((tool) => ({
181
+ type: "function",
182
+ function: {
183
+ name: tool.function.name,
184
+ description: tool.function.description,
185
+ parameters: tool.function.parameters
186
+ }
187
+ })),
188
+ tool_choice: "auto"
189
+ } : responseFormat ? {
190
+ response_format: {
191
+ type: responseFormat.type
192
+ }
193
+ } : {}
162
194
  });
163
- return response.choices[0].message.content || "";
195
+ const response = completion.choices[0].message;
196
+ if (response.tool_calls) {
197
+ return {
198
+ content: response.content || "",
199
+ role: response.role,
200
+ toolCalls: response.tool_calls.map((call) => ({
201
+ name: call.function.name,
202
+ arguments: call.function.arguments
203
+ }))
204
+ };
205
+ }
206
+ return response.content || "";
164
207
  }
165
208
  async generateChat(messages) {
166
- const response = await this.client.chat.completions.create({
167
- model: this.model,
209
+ const completion = await this.openai.chat.completions.create({
168
210
  messages: messages.map((msg) => ({
169
211
  role: msg.role,
170
212
  content: msg.content
171
- }))
213
+ })),
214
+ model: this.model
172
215
  });
173
- const message = response.choices[0].message;
216
+ const response = completion.choices[0].message;
174
217
  return {
175
- content: message.content || "",
176
- role: message.role
218
+ content: response.content || "",
219
+ role: response.role
177
220
  };
178
221
  }
179
222
  };
@@ -251,10 +294,50 @@ var GroqLLM = class {
251
294
  };
252
295
 
253
296
  // src/oss/src/vector_stores/memory.ts
297
+ var import_sqlite3 = __toESM(require("sqlite3"));
298
+ var import_path = __toESM(require("path"));
254
299
  var MemoryVectorStore = class {
255
300
  constructor(config) {
256
- this.vectors = /* @__PURE__ */ new Map();
257
301
  this.dimension = config.dimension || 1536;
302
+ this.dbPath = import_path.default.join(process.cwd(), "vector_store.db");
303
+ if (config.dbPath) {
304
+ this.dbPath = config.dbPath;
305
+ }
306
+ this.db = new import_sqlite3.default.Database(this.dbPath);
307
+ this.init().catch(console.error);
308
+ }
309
+ async init() {
310
+ await this.run(`
311
+ CREATE TABLE IF NOT EXISTS vectors (
312
+ id TEXT PRIMARY KEY,
313
+ vector BLOB NOT NULL,
314
+ payload TEXT NOT NULL
315
+ )
316
+ `);
317
+ }
318
+ async run(sql, params = []) {
319
+ return new Promise((resolve, reject) => {
320
+ this.db.run(sql, params, (err) => {
321
+ if (err) reject(err);
322
+ else resolve();
323
+ });
324
+ });
325
+ }
326
+ async all(sql, params = []) {
327
+ return new Promise((resolve, reject) => {
328
+ this.db.all(sql, params, (err, rows) => {
329
+ if (err) reject(err);
330
+ else resolve(rows);
331
+ });
332
+ });
333
+ }
334
+ async getOne(sql, params = []) {
335
+ return new Promise((resolve, reject) => {
336
+ this.db.get(sql, params, (err, row) => {
337
+ if (err) reject(err);
338
+ else resolve(row);
339
+ });
340
+ });
258
341
  }
259
342
  cosineSimilarity(a, b) {
260
343
  let dotProduct = 0;
@@ -280,11 +363,11 @@ var MemoryVectorStore = class {
280
363
  `Vector dimension mismatch. Expected ${this.dimension}, got ${vectors[i].length}`
281
364
  );
282
365
  }
283
- this.vectors.set(ids[i], {
284
- id: ids[i],
285
- vector: vectors[i],
286
- payload: payloads[i]
287
- });
366
+ const vectorBuffer = Buffer.from(new Float32Array(vectors[i]).buffer);
367
+ await this.run(
368
+ `INSERT OR REPLACE INTO vectors (id, vector, payload) VALUES (?, ?, ?)`,
369
+ [ids[i], vectorBuffer, JSON.stringify(payloads[i])]
370
+ );
288
371
  }
289
372
  }
290
373
  async search(query, limit = 10, filters) {
@@ -293,13 +376,21 @@ var MemoryVectorStore = class {
293
376
  `Query dimension mismatch. Expected ${this.dimension}, got ${query.length}`
294
377
  );
295
378
  }
379
+ const rows = await this.all(`SELECT * FROM vectors`);
296
380
  const results = [];
297
- for (const vector of this.vectors.values()) {
298
- if (this.filterVector(vector, filters)) {
299
- const score = this.cosineSimilarity(query, vector.vector);
381
+ for (const row of rows) {
382
+ const vector = new Float32Array(row.vector.buffer);
383
+ const payload = JSON.parse(row.payload);
384
+ const memoryVector = {
385
+ id: row.id,
386
+ vector: Array.from(vector),
387
+ payload
388
+ };
389
+ if (this.filterVector(memoryVector, filters)) {
390
+ const score = this.cosineSimilarity(query, Array.from(vector));
300
391
  results.push({
301
- id: vector.id,
302
- payload: vector.payload,
392
+ id: memoryVector.id,
393
+ payload: memoryVector.payload,
303
394
  score
304
395
  });
305
396
  }
@@ -308,11 +399,14 @@ var MemoryVectorStore = class {
308
399
  return results.slice(0, limit);
309
400
  }
310
401
  async get(vectorId) {
311
- const vector = this.vectors.get(vectorId);
312
- if (!vector) return null;
402
+ const row = await this.getOne(`SELECT * FROM vectors WHERE id = ?`, [
403
+ vectorId
404
+ ]);
405
+ if (!row) return null;
406
+ const payload = JSON.parse(row.payload);
313
407
  return {
314
- id: vector.id,
315
- payload: vector.payload
408
+ id: row.id,
409
+ payload
316
410
  };
317
411
  }
318
412
  async update(vectorId, vector, payload) {
@@ -321,27 +415,34 @@ var MemoryVectorStore = class {
321
415
  `Vector dimension mismatch. Expected ${this.dimension}, got ${vector.length}`
322
416
  );
323
417
  }
324
- const existing = this.vectors.get(vectorId);
325
- if (!existing) throw new Error(`Vector with ID ${vectorId} not found`);
326
- this.vectors.set(vectorId, {
327
- id: vectorId,
328
- vector,
329
- payload
330
- });
418
+ const vectorBuffer = Buffer.from(new Float32Array(vector).buffer);
419
+ await this.run(`UPDATE vectors SET vector = ?, payload = ? WHERE id = ?`, [
420
+ vectorBuffer,
421
+ JSON.stringify(payload),
422
+ vectorId
423
+ ]);
331
424
  }
332
425
  async delete(vectorId) {
333
- this.vectors.delete(vectorId);
426
+ await this.run(`DELETE FROM vectors WHERE id = ?`, [vectorId]);
334
427
  }
335
428
  async deleteCol() {
336
- this.vectors.clear();
429
+ await this.run(`DROP TABLE IF EXISTS vectors`);
430
+ await this.init();
337
431
  }
338
432
  async list(filters, limit = 100) {
433
+ const rows = await this.all(`SELECT * FROM vectors`);
339
434
  const results = [];
340
- for (const vector of this.vectors.values()) {
341
- if (this.filterVector(vector, filters)) {
435
+ for (const row of rows) {
436
+ const payload = JSON.parse(row.payload);
437
+ const memoryVector = {
438
+ id: row.id,
439
+ vector: Array.from(new Float32Array(row.vector.buffer)),
440
+ payload
441
+ };
442
+ if (this.filterVector(memoryVector, filters)) {
342
443
  results.push({
343
- id: vector.id,
344
- payload: vector.payload
444
+ id: memoryVector.id,
445
+ payload: memoryVector.payload
345
446
  });
346
447
  }
347
448
  }
@@ -956,7 +1057,7 @@ var EmbedderFactory = class {
956
1057
  };
957
1058
  var LLMFactory = class {
958
1059
  static create(provider, config) {
959
- switch (provider.toLowerCase()) {
1060
+ switch (provider) {
960
1061
  case "openai":
961
1062
  return new OpenAILLM(config);
962
1063
  case "openai_structured":
@@ -1022,7 +1123,7 @@ function getFactRetrievalMessages(parsedMessages) {
1022
1123
  Input: Me favourite movies are Inception and Interstellar.
1023
1124
  Output: {"facts" : ["Favourite movies are Inception and Interstellar"]}
1024
1125
 
1025
- Return the facts and preferences in a json format as shown above.
1126
+ Return the facts and preferences in a JSON format as shown above. You MUST return a valid JSON object with a 'facts' key containing an array of strings.
1026
1127
 
1027
1128
  Remember the following:
1028
1129
  - Today's date is ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.
@@ -1031,16 +1132,16 @@ function getFactRetrievalMessages(parsedMessages) {
1031
1132
  - If the user asks where you fetched my information, answer that you found from publicly available sources on internet.
1032
1133
  - If you do not find anything relevant in the below conversation, you can return an empty list corresponding to the "facts" key.
1033
1134
  - Create the facts based on the user and assistant messages only. Do not pick anything from the system messages.
1034
- - Make sure to return the response in the format mentioned in the examples. The response should be in json with a key as "facts" and corresponding value will be a list of strings.
1135
+ - Make sure to return the response in the JSON format mentioned in the examples. The response should be in JSON with a key as "facts" and corresponding value will be a list of strings.
1035
1136
  - DO NOT RETURN ANYTHING ELSE OTHER THAN THE JSON FORMAT.
1036
- - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALUD SUCH AS "\`\`\`json" OR "\`\`\`".
1137
+ - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALID SUCH AS "\`\`\`json" OR "\`\`\`".
1037
1138
  - You should detect the language of the user input and record the facts in the same language.
1038
1139
  - For basic factual statements, break them down into individual facts if they contain multiple pieces of information.
1039
1140
 
1040
- Following is a conversation between the user and the assistant. You have to extract the relevant facts and preferences about the user, if any, from the conversation and return them in the json format as shown above.
1141
+ Following is a conversation between the user and the assistant. You have to extract the relevant facts and preferences about the user, if any, from the conversation and return them in the JSON format as shown above.
1041
1142
  You should detect the language of the user input and record the facts in the same language.
1042
1143
  `;
1043
- const userPrompt = `Following is a conversation between the user and the assistant. You have to extract the relevant facts and preferences about the user, if any, from the conversation and return them in the json format as shown above.
1144
+ const userPrompt = `Following is a conversation between the user and the assistant. You have to extract the relevant facts and preferences about the user, if any, from the conversation and return them in the JSON format as shown above.
1044
1145
 
1045
1146
  Input:
1046
1147
  ${parsedMessages}`;
@@ -1203,14 +1304,14 @@ function getUpdateMemoryMessages(retrievedOldMemory, newRetrievedFacts) {
1203
1304
  ${JSON.stringify(newRetrievedFacts, null, 2)}
1204
1305
 
1205
1306
  Follow the instruction mentioned below:
1206
- - Do not return anything from the custom few shot prompts provided above.
1307
+ - Do not return anything from the custom few shot example prompts provided above.
1207
1308
  - If the current memory is empty, then you have to add the new retrieved facts to the memory.
1208
1309
  - You should return the updated memory in only JSON format as shown below. The memory key should be the same if no changes are made.
1209
1310
  - If there is an addition, generate a new key and add the new memory corresponding to it.
1210
1311
  - If there is a deletion, the memory key-value pair should be removed from the memory.
1211
1312
  - If there is an update, the ID key should remain the same and only the value needs to be updated.
1212
1313
  - DO NOT RETURN ANYTHING ELSE OTHER THAN THE JSON FORMAT.
1213
- - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALUD SUCH AS "\`\`\`json" OR "\`\`\`".
1314
+ - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALID SUCH AS "\`\`\`json" OR "\`\`\`".
1214
1315
 
1215
1316
  Do not return anything except the JSON format.`;
1216
1317
  }
@@ -1219,10 +1320,10 @@ function removeCodeBlocks(text) {
1219
1320
  }
1220
1321
 
1221
1322
  // src/oss/src/storage/SQLiteManager.ts
1222
- var import_sqlite3 = __toESM(require("sqlite3"));
1323
+ var import_sqlite32 = __toESM(require("sqlite3"));
1223
1324
  var SQLiteManager = class {
1224
1325
  constructor(dbPath) {
1225
- this.db = new import_sqlite3.default.Database(dbPath);
1326
+ this.db = new import_sqlite32.default.Database(dbPath);
1226
1327
  this.init().catch(console.error);
1227
1328
  }
1228
1329
  async init() {
@@ -1310,6 +1411,21 @@ var DEFAULT_MEMORY_CONFIG = {
1310
1411
  model: "gpt-4-turbo-preview"
1311
1412
  }
1312
1413
  },
1414
+ enableGraph: false,
1415
+ graphStore: {
1416
+ provider: "neo4j",
1417
+ config: {
1418
+ url: process.env.NEO4J_URL || "neo4j://localhost:7687",
1419
+ username: process.env.NEO4J_USERNAME || "neo4j",
1420
+ password: process.env.NEO4J_PASSWORD || "password"
1421
+ },
1422
+ llm: {
1423
+ provider: "openai",
1424
+ config: {
1425
+ model: "gpt-4-turbo-preview"
1426
+ }
1427
+ }
1428
+ },
1313
1429
  historyDbPath: "memory.db"
1314
1430
  };
1315
1431
 
@@ -1343,12 +1459,742 @@ var ConfigManager = class {
1343
1459
  },
1344
1460
  historyDbPath: userConfig.historyDbPath || DEFAULT_MEMORY_CONFIG.historyDbPath,
1345
1461
  customPrompt: userConfig.customPrompt,
1346
- graphStore: userConfig.graphStore
1462
+ graphStore: {
1463
+ ...DEFAULT_MEMORY_CONFIG.graphStore,
1464
+ ...userConfig.graphStore
1465
+ },
1466
+ enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph
1347
1467
  };
1348
1468
  return MemoryConfigSchema.parse(mergedConfig);
1349
1469
  }
1350
1470
  };
1351
1471
 
1472
+ // src/oss/src/memory/graph_memory.ts
1473
+ var import_neo4j_driver = __toESM(require("neo4j-driver"));
1474
+
1475
+ // src/oss/src/utils/bm25.ts
1476
+ var BM25 = class {
1477
+ constructor(documents, k1 = 1.5, b = 0.75) {
1478
+ this.documents = documents;
1479
+ this.k1 = k1;
1480
+ this.b = b;
1481
+ this.docLengths = documents.map((doc) => doc.length);
1482
+ this.avgDocLength = this.docLengths.reduce((a, b2) => a + b2, 0) / documents.length;
1483
+ this.docFreq = /* @__PURE__ */ new Map();
1484
+ this.idf = /* @__PURE__ */ new Map();
1485
+ this.computeIdf();
1486
+ }
1487
+ computeIdf() {
1488
+ const N = this.documents.length;
1489
+ for (const doc of this.documents) {
1490
+ const terms = new Set(doc);
1491
+ for (const term of terms) {
1492
+ this.docFreq.set(term, (this.docFreq.get(term) || 0) + 1);
1493
+ }
1494
+ }
1495
+ for (const [term, freq] of this.docFreq) {
1496
+ this.idf.set(term, Math.log((N - freq + 0.5) / (freq + 0.5) + 1));
1497
+ }
1498
+ }
1499
+ score(query, doc, index) {
1500
+ let score = 0;
1501
+ const docLength = this.docLengths[index];
1502
+ for (const term of query) {
1503
+ const tf = doc.filter((t) => t === term).length;
1504
+ const idf = this.idf.get(term) || 0;
1505
+ score += idf * tf * (this.k1 + 1) / (tf + this.k1 * (1 - this.b + this.b * docLength / this.avgDocLength));
1506
+ }
1507
+ return score;
1508
+ }
1509
+ search(query) {
1510
+ const scores = this.documents.map((doc, idx) => ({
1511
+ doc,
1512
+ score: this.score(query, doc, idx)
1513
+ }));
1514
+ return scores.sort((a, b) => b.score - a.score).map((item) => item.doc);
1515
+ }
1516
+ };
1517
+
1518
+ // src/oss/src/graphs/tools.ts
1519
+ var RELATIONS_TOOL = {
1520
+ type: "function",
1521
+ function: {
1522
+ name: "establish_relationships",
1523
+ description: "Establish relationships among the entities based on the provided text.",
1524
+ parameters: {
1525
+ type: "object",
1526
+ properties: {
1527
+ entities: {
1528
+ type: "array",
1529
+ items: {
1530
+ type: "object",
1531
+ properties: {
1532
+ source: {
1533
+ type: "string",
1534
+ description: "The source entity of the relationship."
1535
+ },
1536
+ relationship: {
1537
+ type: "string",
1538
+ description: "The relationship between the source and destination entities."
1539
+ },
1540
+ destination: {
1541
+ type: "string",
1542
+ description: "The destination entity of the relationship."
1543
+ }
1544
+ },
1545
+ required: ["source", "relationship", "destination"],
1546
+ additionalProperties: false
1547
+ }
1548
+ }
1549
+ },
1550
+ required: ["entities"],
1551
+ additionalProperties: false
1552
+ }
1553
+ }
1554
+ };
1555
+ var EXTRACT_ENTITIES_TOOL = {
1556
+ type: "function",
1557
+ function: {
1558
+ name: "extract_entities",
1559
+ description: "Extract entities and their types from the text.",
1560
+ parameters: {
1561
+ type: "object",
1562
+ properties: {
1563
+ entities: {
1564
+ type: "array",
1565
+ items: {
1566
+ type: "object",
1567
+ properties: {
1568
+ entity: {
1569
+ type: "string",
1570
+ description: "The name or identifier of the entity."
1571
+ },
1572
+ entity_type: {
1573
+ type: "string",
1574
+ description: "The type or category of the entity."
1575
+ }
1576
+ },
1577
+ required: ["entity", "entity_type"],
1578
+ additionalProperties: false
1579
+ },
1580
+ description: "An array of entities with their types."
1581
+ }
1582
+ },
1583
+ required: ["entities"],
1584
+ additionalProperties: false
1585
+ }
1586
+ }
1587
+ };
1588
+ var DELETE_MEMORY_TOOL_GRAPH = {
1589
+ type: "function",
1590
+ function: {
1591
+ name: "delete_graph_memory",
1592
+ description: "Delete the relationship between two nodes.",
1593
+ parameters: {
1594
+ type: "object",
1595
+ properties: {
1596
+ source: {
1597
+ type: "string",
1598
+ description: "The identifier of the source node in the relationship."
1599
+ },
1600
+ relationship: {
1601
+ type: "string",
1602
+ description: "The existing relationship between the source and destination nodes that needs to be deleted."
1603
+ },
1604
+ destination: {
1605
+ type: "string",
1606
+ description: "The identifier of the destination node in the relationship."
1607
+ }
1608
+ },
1609
+ required: ["source", "relationship", "destination"],
1610
+ additionalProperties: false
1611
+ }
1612
+ }
1613
+ };
1614
+
1615
+ // src/oss/src/graphs/utils.ts
1616
+ var EXTRACT_RELATIONS_PROMPT = `
1617
+ You are an advanced algorithm designed to extract structured information from text to construct knowledge graphs. Your goal is to capture comprehensive and accurate information. Follow these key principles:
1618
+
1619
+ 1. Extract only explicitly stated information from the text.
1620
+ 2. Establish relationships among the entities provided.
1621
+ 3. Use "USER_ID" as the source entity for any self-references (e.g., "I," "me," "my," etc.) in user messages.
1622
+ CUSTOM_PROMPT
1623
+
1624
+ Relationships:
1625
+ - Use consistent, general, and timeless relationship types.
1626
+ - Example: Prefer "professor" over "became_professor."
1627
+ - Relationships should only be established among the entities explicitly mentioned in the user message.
1628
+
1629
+ Entity Consistency:
1630
+ - Ensure that relationships are coherent and logically align with the context of the message.
1631
+ - Maintain consistent naming for entities across the extracted data.
1632
+
1633
+ Strive to construct a coherent and easily understandable knowledge graph by eshtablishing all the relationships among the entities and adherence to the user's context.
1634
+
1635
+ Adhere strictly to these guidelines to ensure high-quality knowledge graph extraction.
1636
+ `;
1637
+ var DELETE_RELATIONS_SYSTEM_PROMPT = `
1638
+ You are a graph memory manager specializing in identifying, managing, and optimizing relationships within graph-based memories. Your primary task is to analyze a list of existing relationships and determine which ones should be deleted based on the new information provided.
1639
+ Input:
1640
+ 1. Existing Graph Memories: A list of current graph memories, each containing source, relationship, and destination information.
1641
+ 2. New Text: The new information to be integrated into the existing graph structure.
1642
+ 3. Use "USER_ID" as node for any self-references (e.g., "I," "me," "my," etc.) in user messages.
1643
+
1644
+ Guidelines:
1645
+ 1. Identification: Use the new information to evaluate existing relationships in the memory graph.
1646
+ 2. Deletion Criteria: Delete a relationship only if it meets at least one of these conditions:
1647
+ - Outdated or Inaccurate: The new information is more recent or accurate.
1648
+ - Contradictory: The new information conflicts with or negates the existing information.
1649
+ 3. DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
1650
+ 4. Comprehensive Analysis:
1651
+ - Thoroughly examine each existing relationship against the new information and delete as necessary.
1652
+ - Multiple deletions may be required based on the new information.
1653
+ 5. Semantic Integrity:
1654
+ - Ensure that deletions maintain or improve the overall semantic structure of the graph.
1655
+ - Avoid deleting relationships that are NOT contradictory/outdated to the new information.
1656
+ 6. Temporal Awareness: Prioritize recency when timestamps are available.
1657
+ 7. Necessity Principle: Only DELETE relationships that must be deleted and are contradictory/outdated to the new information to maintain an accurate and coherent memory graph.
1658
+
1659
+ Note: DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
1660
+
1661
+ For example:
1662
+ Existing Memory: alice -- loves_to_eat -- pizza
1663
+ New Information: Alice also loves to eat burger.
1664
+
1665
+ Do not delete in the above example because there is a possibility that Alice loves to eat both pizza and burger.
1666
+
1667
+ Memory Format:
1668
+ source -- relationship -- destination
1669
+
1670
+ Provide a list of deletion instructions, each specifying the relationship to be deleted.
1671
+ `;
1672
+ function getDeleteMessages(existingMemoriesString, data, userId) {
1673
+ return [
1674
+ DELETE_RELATIONS_SYSTEM_PROMPT.replace("USER_ID", userId),
1675
+ `Here are the existing memories: ${existingMemoriesString}
1676
+
1677
+ New Information: ${data}`
1678
+ ];
1679
+ }
1680
+
1681
+ // src/oss/src/utils/logger.ts
1682
+ var logger = {
1683
+ info: (message) => console.log(`[INFO] ${message}`),
1684
+ error: (message) => console.error(`[ERROR] ${message}`),
1685
+ debug: (message) => console.debug(`[DEBUG] ${message}`),
1686
+ warn: (message) => console.warn(`[WARN] ${message}`)
1687
+ };
1688
+
1689
+ // src/oss/src/memory/graph_memory.ts
1690
+ var MemoryGraph = class {
1691
+ constructor(config) {
1692
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1693
+ this.config = config;
1694
+ if (!((_b = (_a = config.graphStore) == null ? void 0 : _a.config) == null ? void 0 : _b.url) || !((_d = (_c = config.graphStore) == null ? void 0 : _c.config) == null ? void 0 : _d.username) || !((_f = (_e = config.graphStore) == null ? void 0 : _e.config) == null ? void 0 : _f.password)) {
1695
+ throw new Error("Neo4j configuration is incomplete");
1696
+ }
1697
+ this.graph = import_neo4j_driver.default.driver(
1698
+ config.graphStore.config.url,
1699
+ import_neo4j_driver.default.auth.basic(
1700
+ config.graphStore.config.username,
1701
+ config.graphStore.config.password
1702
+ )
1703
+ );
1704
+ this.embeddingModel = EmbedderFactory.create(
1705
+ this.config.embedder.provider,
1706
+ this.config.embedder.config
1707
+ );
1708
+ this.llmProvider = "openai";
1709
+ if ((_g = this.config.llm) == null ? void 0 : _g.provider) {
1710
+ this.llmProvider = this.config.llm.provider;
1711
+ }
1712
+ if ((_i = (_h = this.config.graphStore) == null ? void 0 : _h.llm) == null ? void 0 : _i.provider) {
1713
+ this.llmProvider = this.config.graphStore.llm.provider;
1714
+ }
1715
+ this.llm = LLMFactory.create(this.llmProvider, this.config.llm.config);
1716
+ this.structuredLlm = LLMFactory.create(
1717
+ "openai_structured",
1718
+ this.config.llm.config
1719
+ );
1720
+ this.threshold = 0.7;
1721
+ }
1722
+ async add(data, filters) {
1723
+ const entityTypeMap = await this._retrieveNodesFromData(data, filters);
1724
+ const toBeAdded = await this._establishNodesRelationsFromData(
1725
+ data,
1726
+ filters,
1727
+ entityTypeMap
1728
+ );
1729
+ const searchOutput = await this._searchGraphDb(
1730
+ Object.keys(entityTypeMap),
1731
+ filters
1732
+ );
1733
+ const toBeDeleted = await this._getDeleteEntitiesFromSearchOutput(
1734
+ searchOutput,
1735
+ data,
1736
+ filters
1737
+ );
1738
+ const deletedEntities = await this._deleteEntities(
1739
+ toBeDeleted,
1740
+ filters["userId"]
1741
+ );
1742
+ const addedEntities = await this._addEntities(
1743
+ toBeAdded,
1744
+ filters["userId"],
1745
+ entityTypeMap
1746
+ );
1747
+ return {
1748
+ deleted_entities: deletedEntities,
1749
+ added_entities: addedEntities,
1750
+ relations: toBeAdded
1751
+ };
1752
+ }
1753
+ async search(query, filters, limit = 100) {
1754
+ const entityTypeMap = await this._retrieveNodesFromData(query, filters);
1755
+ const searchOutput = await this._searchGraphDb(
1756
+ Object.keys(entityTypeMap),
1757
+ filters
1758
+ );
1759
+ if (!searchOutput.length) {
1760
+ return [];
1761
+ }
1762
+ const searchOutputsSequence = searchOutput.map((item) => [
1763
+ item.source,
1764
+ item.relationship,
1765
+ item.destination
1766
+ ]);
1767
+ const bm25 = new BM25(searchOutputsSequence);
1768
+ const tokenizedQuery = query.split(" ");
1769
+ const rerankedResults = bm25.search(tokenizedQuery).slice(0, 5);
1770
+ const searchResults = rerankedResults.map((item) => ({
1771
+ source: item[0],
1772
+ relationship: item[1],
1773
+ destination: item[2]
1774
+ }));
1775
+ logger.info(`Returned ${searchResults.length} search results`);
1776
+ return searchResults;
1777
+ }
1778
+ async deleteAll(filters) {
1779
+ const session = this.graph.session();
1780
+ try {
1781
+ await session.run("MATCH (n {user_id: $user_id}) DETACH DELETE n", {
1782
+ user_id: filters["userId"]
1783
+ });
1784
+ } finally {
1785
+ await session.close();
1786
+ }
1787
+ }
1788
+ async getAll(filters, limit = 100) {
1789
+ const session = this.graph.session();
1790
+ try {
1791
+ const result = await session.run(
1792
+ `
1793
+ MATCH (n {user_id: $user_id})-[r]->(m {user_id: $user_id})
1794
+ RETURN n.name AS source, type(r) AS relationship, m.name AS target
1795
+ LIMIT toInteger($limit)
1796
+ `,
1797
+ { user_id: filters["userId"], limit: Math.floor(Number(limit)) }
1798
+ );
1799
+ const finalResults = result.records.map((record) => ({
1800
+ source: record.get("source"),
1801
+ relationship: record.get("relationship"),
1802
+ target: record.get("target")
1803
+ }));
1804
+ logger.info(`Retrieved ${finalResults.length} relationships`);
1805
+ return finalResults;
1806
+ } finally {
1807
+ await session.close();
1808
+ }
1809
+ }
1810
+ async _retrieveNodesFromData(data, filters) {
1811
+ const tools = [EXTRACT_ENTITIES_TOOL];
1812
+ const searchResults = await this.structuredLlm.generateResponse(
1813
+ [
1814
+ {
1815
+ role: "system",
1816
+ content: `You are a smart assistant who understands entities and their types in a given text. If user message contains self reference such as 'I', 'me', 'my' etc. then use ${filters["userId"]} as the source entity. Extract all the entities from the text. ***DO NOT*** answer the question itself if the given text is a question.`
1817
+ },
1818
+ { role: "user", content: data }
1819
+ ],
1820
+ { type: "json_object" },
1821
+ tools
1822
+ );
1823
+ let entityTypeMap = {};
1824
+ try {
1825
+ if (typeof searchResults !== "string" && searchResults.toolCalls) {
1826
+ for (const call of searchResults.toolCalls) {
1827
+ if (call.name === "extract_entities") {
1828
+ const args = JSON.parse(call.arguments);
1829
+ for (const item of args.entities) {
1830
+ entityTypeMap[item.entity] = item.entity_type;
1831
+ }
1832
+ }
1833
+ }
1834
+ }
1835
+ } catch (e) {
1836
+ logger.error(`Error in search tool: ${e}`);
1837
+ }
1838
+ entityTypeMap = Object.fromEntries(
1839
+ Object.entries(entityTypeMap).map(([k, v]) => [
1840
+ k.toLowerCase().replace(/ /g, "_"),
1841
+ v.toLowerCase().replace(/ /g, "_")
1842
+ ])
1843
+ );
1844
+ logger.debug(`Entity type map: ${JSON.stringify(entityTypeMap)}`);
1845
+ return entityTypeMap;
1846
+ }
1847
+ async _establishNodesRelationsFromData(data, filters, entityTypeMap) {
1848
+ var _a;
1849
+ let messages;
1850
+ if ((_a = this.config.graphStore) == null ? void 0 : _a.customPrompt) {
1851
+ messages = [
1852
+ {
1853
+ role: "system",
1854
+ content: EXTRACT_RELATIONS_PROMPT.replace(
1855
+ "USER_ID",
1856
+ filters["userId"]
1857
+ ).replace(
1858
+ "CUSTOM_PROMPT",
1859
+ `4. ${this.config.graphStore.customPrompt}`
1860
+ ) + "\nPlease provide your response in JSON format."
1861
+ },
1862
+ { role: "user", content: data }
1863
+ ];
1864
+ } else {
1865
+ messages = [
1866
+ {
1867
+ role: "system",
1868
+ content: EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["userId"]) + "\nPlease provide your response in JSON format."
1869
+ },
1870
+ {
1871
+ role: "user",
1872
+ content: `List of entities: ${Object.keys(entityTypeMap)}.
1873
+
1874
+ Text: ${data}`
1875
+ }
1876
+ ];
1877
+ }
1878
+ const tools = [RELATIONS_TOOL];
1879
+ const extractedEntities = await this.structuredLlm.generateResponse(
1880
+ messages,
1881
+ { type: "json_object" },
1882
+ tools
1883
+ );
1884
+ let entities = [];
1885
+ if (typeof extractedEntities !== "string" && extractedEntities.toolCalls) {
1886
+ const toolCall = extractedEntities.toolCalls[0];
1887
+ if (toolCall && toolCall.arguments) {
1888
+ const args = JSON.parse(toolCall.arguments);
1889
+ entities = args.entities || [];
1890
+ }
1891
+ }
1892
+ entities = this._removeSpacesFromEntities(entities);
1893
+ logger.debug(`Extracted entities: ${JSON.stringify(entities)}`);
1894
+ return entities;
1895
+ }
1896
+ async _searchGraphDb(nodeList, filters, limit = 100) {
1897
+ const resultRelations = [];
1898
+ const session = this.graph.session();
1899
+ try {
1900
+ for (const node of nodeList) {
1901
+ const nEmbedding = await this.embeddingModel.embed(node);
1902
+ const cypher = `
1903
+ MATCH (n)
1904
+ WHERE n.embedding IS NOT NULL AND n.user_id = $user_id
1905
+ WITH n,
1906
+ round(reduce(dot = 0.0, i IN range(0, size(n.embedding)-1) | dot + n.embedding[i] * $n_embedding[i]) /
1907
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(n.embedding)-1) | l2 + n.embedding[i] * n.embedding[i])) *
1908
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($n_embedding)-1) | l2 + $n_embedding[i] * $n_embedding[i]))), 4) AS similarity
1909
+ WHERE similarity >= $threshold
1910
+ MATCH (n)-[r]->(m)
1911
+ RETURN n.name AS source, elementId(n) AS source_id, type(r) AS relationship, elementId(r) AS relation_id, m.name AS destination, elementId(m) AS destination_id, similarity
1912
+ UNION
1913
+ MATCH (n)
1914
+ WHERE n.embedding IS NOT NULL AND n.user_id = $user_id
1915
+ WITH n,
1916
+ round(reduce(dot = 0.0, i IN range(0, size(n.embedding)-1) | dot + n.embedding[i] * $n_embedding[i]) /
1917
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(n.embedding)-1) | l2 + n.embedding[i] * n.embedding[i])) *
1918
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($n_embedding)-1) | l2 + $n_embedding[i] * $n_embedding[i]))), 4) AS similarity
1919
+ WHERE similarity >= $threshold
1920
+ MATCH (m)-[r]->(n)
1921
+ RETURN m.name AS source, elementId(m) AS source_id, type(r) AS relationship, elementId(r) AS relation_id, n.name AS destination, elementId(n) AS destination_id, similarity
1922
+ ORDER BY similarity DESC
1923
+ LIMIT toInteger($limit)
1924
+ `;
1925
+ const result = await session.run(cypher, {
1926
+ n_embedding: nEmbedding,
1927
+ threshold: this.threshold,
1928
+ user_id: filters["userId"],
1929
+ limit: Math.floor(Number(limit))
1930
+ });
1931
+ resultRelations.push(
1932
+ ...result.records.map((record) => ({
1933
+ source: record.get("source"),
1934
+ source_id: record.get("source_id").toString(),
1935
+ relationship: record.get("relationship"),
1936
+ relation_id: record.get("relation_id").toString(),
1937
+ destination: record.get("destination"),
1938
+ destination_id: record.get("destination_id").toString(),
1939
+ similarity: record.get("similarity")
1940
+ }))
1941
+ );
1942
+ }
1943
+ } finally {
1944
+ await session.close();
1945
+ }
1946
+ return resultRelations;
1947
+ }
1948
+ async _getDeleteEntitiesFromSearchOutput(searchOutput, data, filters) {
1949
+ const searchOutputString = searchOutput.map(
1950
+ (item) => `${item.source} -- ${item.relationship} -- ${item.destination}`
1951
+ ).join("\n");
1952
+ const [systemPrompt, userPrompt] = getDeleteMessages(
1953
+ searchOutputString,
1954
+ data,
1955
+ filters["userId"]
1956
+ );
1957
+ const tools = [DELETE_MEMORY_TOOL_GRAPH];
1958
+ const memoryUpdates = await this.structuredLlm.generateResponse(
1959
+ [
1960
+ { role: "system", content: systemPrompt },
1961
+ { role: "user", content: userPrompt }
1962
+ ],
1963
+ { type: "json_object" },
1964
+ tools
1965
+ );
1966
+ const toBeDeleted = [];
1967
+ if (typeof memoryUpdates !== "string" && memoryUpdates.toolCalls) {
1968
+ for (const item of memoryUpdates.toolCalls) {
1969
+ if (item.name === "delete_graph_memory") {
1970
+ toBeDeleted.push(JSON.parse(item.arguments));
1971
+ }
1972
+ }
1973
+ }
1974
+ const cleanedToBeDeleted = this._removeSpacesFromEntities(toBeDeleted);
1975
+ logger.debug(
1976
+ `Deleted relationships: ${JSON.stringify(cleanedToBeDeleted)}`
1977
+ );
1978
+ return cleanedToBeDeleted;
1979
+ }
1980
+ async _deleteEntities(toBeDeleted, userId) {
1981
+ const results = [];
1982
+ const session = this.graph.session();
1983
+ try {
1984
+ for (const item of toBeDeleted) {
1985
+ const { source, destination, relationship } = item;
1986
+ const cypher = `
1987
+ MATCH (n {name: $source_name, user_id: $user_id})
1988
+ -[r:${relationship}]->
1989
+ (m {name: $dest_name, user_id: $user_id})
1990
+ DELETE r
1991
+ RETURN
1992
+ n.name AS source,
1993
+ m.name AS target,
1994
+ type(r) AS relationship
1995
+ `;
1996
+ const result = await session.run(cypher, {
1997
+ source_name: source,
1998
+ dest_name: destination,
1999
+ user_id: userId
2000
+ });
2001
+ results.push(result.records);
2002
+ }
2003
+ } finally {
2004
+ await session.close();
2005
+ }
2006
+ return results;
2007
+ }
2008
+ async _addEntities(toBeAdded, userId, entityTypeMap) {
2009
+ var _a, _b;
2010
+ const results = [];
2011
+ const session = this.graph.session();
2012
+ try {
2013
+ for (const item of toBeAdded) {
2014
+ const { source, destination, relationship } = item;
2015
+ const sourceType = entityTypeMap[source] || "unknown";
2016
+ const destinationType = entityTypeMap[destination] || "unknown";
2017
+ const sourceEmbedding = await this.embeddingModel.embed(source);
2018
+ const destEmbedding = await this.embeddingModel.embed(destination);
2019
+ const sourceNodeSearchResult = await this._searchSourceNode(
2020
+ sourceEmbedding,
2021
+ userId
2022
+ );
2023
+ const destinationNodeSearchResult = await this._searchDestinationNode(
2024
+ destEmbedding,
2025
+ userId
2026
+ );
2027
+ let cypher;
2028
+ let params;
2029
+ if (destinationNodeSearchResult.length === 0 && sourceNodeSearchResult.length > 0) {
2030
+ cypher = `
2031
+ MATCH (source)
2032
+ WHERE elementId(source) = $source_id
2033
+ MERGE (destination:${destinationType} {name: $destination_name, user_id: $user_id})
2034
+ ON CREATE SET
2035
+ destination.created = timestamp(),
2036
+ destination.embedding = $destination_embedding
2037
+ MERGE (source)-[r:${relationship}]->(destination)
2038
+ ON CREATE SET
2039
+ r.created = timestamp()
2040
+ RETURN source.name AS source, type(r) AS relationship, destination.name AS target
2041
+ `;
2042
+ params = {
2043
+ source_id: sourceNodeSearchResult[0].elementId,
2044
+ destination_name: destination,
2045
+ destination_embedding: destEmbedding,
2046
+ user_id: userId
2047
+ };
2048
+ } else if (destinationNodeSearchResult.length > 0 && sourceNodeSearchResult.length === 0) {
2049
+ cypher = `
2050
+ MATCH (destination)
2051
+ WHERE elementId(destination) = $destination_id
2052
+ MERGE (source:${sourceType} {name: $source_name, user_id: $user_id})
2053
+ ON CREATE SET
2054
+ source.created = timestamp(),
2055
+ source.embedding = $source_embedding
2056
+ MERGE (source)-[r:${relationship}]->(destination)
2057
+ ON CREATE SET
2058
+ r.created = timestamp()
2059
+ RETURN source.name AS source, type(r) AS relationship, destination.name AS target
2060
+ `;
2061
+ params = {
2062
+ destination_id: destinationNodeSearchResult[0].elementId,
2063
+ source_name: source,
2064
+ source_embedding: sourceEmbedding,
2065
+ user_id: userId
2066
+ };
2067
+ } else if (sourceNodeSearchResult.length > 0 && destinationNodeSearchResult.length > 0) {
2068
+ cypher = `
2069
+ MATCH (source)
2070
+ WHERE elementId(source) = $source_id
2071
+ MATCH (destination)
2072
+ WHERE elementId(destination) = $destination_id
2073
+ MERGE (source)-[r:${relationship}]->(destination)
2074
+ ON CREATE SET
2075
+ r.created_at = timestamp(),
2076
+ r.updated_at = timestamp()
2077
+ RETURN source.name AS source, type(r) AS relationship, destination.name AS target
2078
+ `;
2079
+ params = {
2080
+ source_id: (_a = sourceNodeSearchResult[0]) == null ? void 0 : _a.elementId,
2081
+ destination_id: (_b = destinationNodeSearchResult[0]) == null ? void 0 : _b.elementId,
2082
+ user_id: userId
2083
+ };
2084
+ } else {
2085
+ cypher = `
2086
+ MERGE (n:${sourceType} {name: $source_name, user_id: $user_id})
2087
+ ON CREATE SET n.created = timestamp(), n.embedding = $source_embedding
2088
+ ON MATCH SET n.embedding = $source_embedding
2089
+ MERGE (m:${destinationType} {name: $dest_name, user_id: $user_id})
2090
+ ON CREATE SET m.created = timestamp(), m.embedding = $dest_embedding
2091
+ ON MATCH SET m.embedding = $dest_embedding
2092
+ MERGE (n)-[rel:${relationship}]->(m)
2093
+ ON CREATE SET rel.created = timestamp()
2094
+ RETURN n.name AS source, type(rel) AS relationship, m.name AS target
2095
+ `;
2096
+ params = {
2097
+ source_name: source,
2098
+ dest_name: destination,
2099
+ source_embedding: sourceEmbedding,
2100
+ dest_embedding: destEmbedding,
2101
+ user_id: userId
2102
+ };
2103
+ }
2104
+ const result = await session.run(cypher, params);
2105
+ results.push(result.records);
2106
+ }
2107
+ } finally {
2108
+ await session.close();
2109
+ }
2110
+ return results;
2111
+ }
2112
+ _removeSpacesFromEntities(entityList) {
2113
+ return entityList.map((item) => ({
2114
+ ...item,
2115
+ source: item.source.toLowerCase().replace(/ /g, "_"),
2116
+ relationship: item.relationship.toLowerCase().replace(/ /g, "_"),
2117
+ destination: item.destination.toLowerCase().replace(/ /g, "_")
2118
+ }));
2119
+ }
2120
+ async _searchSourceNode(sourceEmbedding, userId, threshold = 0.9) {
2121
+ const session = this.graph.session();
2122
+ try {
2123
+ const cypher = `
2124
+ MATCH (source_candidate)
2125
+ WHERE source_candidate.embedding IS NOT NULL
2126
+ AND source_candidate.user_id = $user_id
2127
+
2128
+ WITH source_candidate,
2129
+ round(
2130
+ reduce(dot = 0.0, i IN range(0, size(source_candidate.embedding)-1) |
2131
+ dot + source_candidate.embedding[i] * $source_embedding[i]) /
2132
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(source_candidate.embedding)-1) |
2133
+ l2 + source_candidate.embedding[i] * source_candidate.embedding[i])) *
2134
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($source_embedding)-1) |
2135
+ l2 + $source_embedding[i] * $source_embedding[i])))
2136
+ , 4) AS source_similarity
2137
+ WHERE source_similarity >= $threshold
2138
+
2139
+ WITH source_candidate, source_similarity
2140
+ ORDER BY source_similarity DESC
2141
+ LIMIT 1
2142
+
2143
+ RETURN elementId(source_candidate) as element_id
2144
+ `;
2145
+ const params = {
2146
+ source_embedding: sourceEmbedding,
2147
+ user_id: userId,
2148
+ threshold
2149
+ };
2150
+ const result = await session.run(cypher, params);
2151
+ return result.records.map((record) => ({
2152
+ elementId: record.get("element_id").toString()
2153
+ }));
2154
+ } finally {
2155
+ await session.close();
2156
+ }
2157
+ }
2158
+ async _searchDestinationNode(destinationEmbedding, userId, threshold = 0.9) {
2159
+ const session = this.graph.session();
2160
+ try {
2161
+ const cypher = `
2162
+ MATCH (destination_candidate)
2163
+ WHERE destination_candidate.embedding IS NOT NULL
2164
+ AND destination_candidate.user_id = $user_id
2165
+
2166
+ WITH destination_candidate,
2167
+ round(
2168
+ reduce(dot = 0.0, i IN range(0, size(destination_candidate.embedding)-1) |
2169
+ dot + destination_candidate.embedding[i] * $destination_embedding[i]) /
2170
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(destination_candidate.embedding)-1) |
2171
+ l2 + destination_candidate.embedding[i] * destination_candidate.embedding[i])) *
2172
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($destination_embedding)-1) |
2173
+ l2 + $destination_embedding[i] * $destination_embedding[i])))
2174
+ , 4) AS destination_similarity
2175
+ WHERE destination_similarity >= $threshold
2176
+
2177
+ WITH destination_candidate, destination_similarity
2178
+ ORDER BY destination_similarity DESC
2179
+ LIMIT 1
2180
+
2181
+ RETURN elementId(destination_candidate) as element_id
2182
+ `;
2183
+ const params = {
2184
+ destination_embedding: destinationEmbedding,
2185
+ user_id: userId,
2186
+ threshold
2187
+ };
2188
+ const result = await session.run(cypher, params);
2189
+ return result.records.map((record) => ({
2190
+ elementId: record.get("element_id").toString()
2191
+ }));
2192
+ } finally {
2193
+ await session.close();
2194
+ }
2195
+ }
2196
+ };
2197
+
1352
2198
  // src/oss/src/memory/index.ts
1353
2199
  var Memory = class _Memory {
1354
2200
  constructor(config = {}) {
@@ -1369,6 +2215,10 @@ var Memory = class _Memory {
1369
2215
  this.db = new SQLiteManager(this.config.historyDbPath || ":memory:");
1370
2216
  this.collectionName = this.config.vectorStore.config.collectionName;
1371
2217
  this.apiVersion = this.config.version || "v1.0";
2218
+ this.enableGraph = this.config.enableGraph || false;
2219
+ if (this.enableGraph && this.config.graphStore) {
2220
+ this.graphMemory = new MemoryGraph(this.config);
2221
+ }
1372
2222
  }
1373
2223
  static fromConfig(configDict) {
1374
2224
  try {
@@ -1402,7 +2252,21 @@ var Memory = class _Memory {
1402
2252
  metadata,
1403
2253
  filters
1404
2254
  );
1405
- return { results: vectorStoreResult };
2255
+ let graphResult;
2256
+ if (this.graphMemory) {
2257
+ try {
2258
+ graphResult = await this.graphMemory.add(
2259
+ parsedMessages.map((m) => m.content).join("\n"),
2260
+ filters
2261
+ );
2262
+ } catch (error) {
2263
+ console.error("Error adding to graph memory:", error);
2264
+ }
2265
+ }
2266
+ return {
2267
+ results: vectorStoreResult,
2268
+ relations: graphResult == null ? void 0 : graphResult.relations
2269
+ };
1406
2270
  }
1407
2271
  async addToVectorStore(messages, metadata, filters) {
1408
2272
  const parsedMessages = messages.map((m) => m.content).join("\n");
@@ -1546,6 +2410,14 @@ ${parsedMessages}`] : getFactRetrievalMessages(parsedMessages);
1546
2410
  limit,
1547
2411
  filters
1548
2412
  );
2413
+ let graphResults;
2414
+ if (this.graphMemory) {
2415
+ try {
2416
+ graphResults = await this.graphMemory.search(query, filters);
2417
+ } catch (error) {
2418
+ console.error("Error searching graph memory:", error);
2419
+ }
2420
+ }
1549
2421
  const excludedKeys = /* @__PURE__ */ new Set([
1550
2422
  "userId",
1551
2423
  "agentId",
@@ -1567,7 +2439,10 @@ ${parsedMessages}`] : getFactRetrievalMessages(parsedMessages);
1567
2439
  ...mem.payload.agentId && { agentId: mem.payload.agentId },
1568
2440
  ...mem.payload.runId && { runId: mem.payload.runId }
1569
2441
  }));
1570
- return { results };
2442
+ return {
2443
+ results,
2444
+ relations: graphResults
2445
+ };
1571
2446
  }
1572
2447
  async update(memoryId, data) {
1573
2448
  const embedding = await this.embedder.embed(data);
@@ -1601,6 +2476,9 @@ ${parsedMessages}`] : getFactRetrievalMessages(parsedMessages);
1601
2476
  async reset() {
1602
2477
  await this.db.reset();
1603
2478
  await this.vectorStore.deleteCol();
2479
+ if (this.graphMemory) {
2480
+ await this.graphMemory.deleteAll({ userId: "default" });
2481
+ }
1604
2482
  this.vectorStore = VectorStoreFactory.create(
1605
2483
  this.config.vectorStore.provider,
1606
2484
  this.config.vectorStore.config