mem0ai 2.0.2 → 2.1.1

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
 
@@ -110,25 +121,43 @@ var import_openai2 = __toESM(require("openai"));
110
121
  var OpenAILLM = class {
111
122
  constructor(config) {
112
123
  this.openai = new import_openai2.default({ apiKey: config.apiKey });
113
- this.model = config.model || "gpt-4-turbo-preview";
124
+ this.model = config.model || "gpt-4o-mini";
114
125
  }
115
- async generateResponse(messages, responseFormat) {
126
+ async generateResponse(messages, responseFormat, tools) {
116
127
  const completion = await this.openai.chat.completions.create({
117
- messages: messages.map((msg) => ({
118
- role: msg.role,
119
- content: msg.content
120
- })),
128
+ messages: messages.map((msg) => {
129
+ const role = msg.role;
130
+ return {
131
+ role,
132
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
133
+ };
134
+ }),
121
135
  model: this.model,
122
- response_format: responseFormat
136
+ response_format: responseFormat,
137
+ ...tools && { tools, tool_choice: "auto" }
123
138
  });
124
- return completion.choices[0].message.content || "";
139
+ const response = completion.choices[0].message;
140
+ if (response.tool_calls) {
141
+ return {
142
+ content: response.content || "",
143
+ role: response.role,
144
+ toolCalls: response.tool_calls.map((call) => ({
145
+ name: call.function.name,
146
+ arguments: call.function.arguments
147
+ }))
148
+ };
149
+ }
150
+ return response.content || "";
125
151
  }
126
152
  async generateChat(messages) {
127
153
  const completion = await this.openai.chat.completions.create({
128
- messages: messages.map((msg) => ({
129
- role: msg.role,
130
- content: msg.content
131
- })),
154
+ messages: messages.map((msg) => {
155
+ const role = msg.role;
156
+ return {
157
+ role,
158
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
159
+ };
160
+ }),
132
161
  model: this.model
133
162
  });
134
163
  const response = completion.choices[0].message;
@@ -143,37 +172,57 @@ var OpenAILLM = class {
143
172
  var import_openai3 = __toESM(require("openai"));
144
173
  var OpenAIStructuredLLM = class {
145
174
  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";
175
+ this.openai = new import_openai3.default({ apiKey: config.apiKey });
176
+ this.model = config.model || "gpt-4-turbo-preview";
153
177
  }
154
- async generateResponse(messages, responseFormat) {
155
- const response = await this.client.chat.completions.create({
156
- model: this.model,
178
+ async generateResponse(messages, responseFormat, tools) {
179
+ const completion = await this.openai.chat.completions.create({
157
180
  messages: messages.map((msg) => ({
158
181
  role: msg.role,
159
- content: msg.content
182
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
160
183
  })),
161
- response_format: responseFormat
184
+ model: this.model,
185
+ ...tools ? {
186
+ tools: tools.map((tool) => ({
187
+ type: "function",
188
+ function: {
189
+ name: tool.function.name,
190
+ description: tool.function.description,
191
+ parameters: tool.function.parameters
192
+ }
193
+ })),
194
+ tool_choice: "auto"
195
+ } : responseFormat ? {
196
+ response_format: {
197
+ type: responseFormat.type
198
+ }
199
+ } : {}
162
200
  });
163
- return response.choices[0].message.content || "";
201
+ const response = completion.choices[0].message;
202
+ if (response.tool_calls) {
203
+ return {
204
+ content: response.content || "",
205
+ role: response.role,
206
+ toolCalls: response.tool_calls.map((call) => ({
207
+ name: call.function.name,
208
+ arguments: call.function.arguments
209
+ }))
210
+ };
211
+ }
212
+ return response.content || "";
164
213
  }
165
214
  async generateChat(messages) {
166
- const response = await this.client.chat.completions.create({
167
- model: this.model,
215
+ const completion = await this.openai.chat.completions.create({
168
216
  messages: messages.map((msg) => ({
169
217
  role: msg.role,
170
- content: msg.content
171
- }))
218
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
219
+ })),
220
+ model: this.model
172
221
  });
173
- const message = response.choices[0].message;
222
+ const response = completion.choices[0].message;
174
223
  return {
175
- content: message.content || "",
176
- role: message.role
224
+ content: response.content || "",
225
+ role: response.role
177
226
  };
178
227
  }
179
228
  };
@@ -196,9 +245,9 @@ var AnthropicLLM = class {
196
245
  model: this.model,
197
246
  messages: otherMessages.map((msg) => ({
198
247
  role: msg.role,
199
- content: msg.content
248
+ content: typeof msg.content === "string" ? msg.content : msg.content.image_url.url
200
249
  })),
201
- system: systemMessage == null ? void 0 : systemMessage.content,
250
+ system: typeof (systemMessage == null ? void 0 : systemMessage.content) === "string" ? systemMessage.content : void 0,
202
251
  max_tokens: 4096
203
252
  });
204
253
  return response.content[0].text;
@@ -228,7 +277,7 @@ var GroqLLM = class {
228
277
  model: this.model,
229
278
  messages: messages.map((msg) => ({
230
279
  role: msg.role,
231
- content: msg.content
280
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
232
281
  })),
233
282
  response_format: responseFormat
234
283
  });
@@ -239,7 +288,7 @@ var GroqLLM = class {
239
288
  model: this.model,
240
289
  messages: messages.map((msg) => ({
241
290
  role: msg.role,
242
- content: msg.content
291
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
243
292
  }))
244
293
  });
245
294
  const message = response.choices[0].message;
@@ -1014,7 +1063,7 @@ var EmbedderFactory = class {
1014
1063
  };
1015
1064
  var LLMFactory = class {
1016
1065
  static create(provider, config) {
1017
- switch (provider.toLowerCase()) {
1066
+ switch (provider) {
1018
1067
  case "openai":
1019
1068
  return new OpenAILLM(config);
1020
1069
  case "openai_structured":
@@ -1080,7 +1129,7 @@ function getFactRetrievalMessages(parsedMessages) {
1080
1129
  Input: Me favourite movies are Inception and Interstellar.
1081
1130
  Output: {"facts" : ["Favourite movies are Inception and Interstellar"]}
1082
1131
 
1083
- Return the facts and preferences in a json format as shown above.
1132
+ 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.
1084
1133
 
1085
1134
  Remember the following:
1086
1135
  - Today's date is ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.
@@ -1089,16 +1138,16 @@ function getFactRetrievalMessages(parsedMessages) {
1089
1138
  - If the user asks where you fetched my information, answer that you found from publicly available sources on internet.
1090
1139
  - If you do not find anything relevant in the below conversation, you can return an empty list corresponding to the "facts" key.
1091
1140
  - Create the facts based on the user and assistant messages only. Do not pick anything from the system messages.
1092
- - 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.
1141
+ - 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.
1093
1142
  - DO NOT RETURN ANYTHING ELSE OTHER THAN THE JSON FORMAT.
1094
- - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALUD SUCH AS "\`\`\`json" OR "\`\`\`".
1143
+ - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALID SUCH AS "\`\`\`json" OR "\`\`\`".
1095
1144
  - You should detect the language of the user input and record the facts in the same language.
1096
1145
  - For basic factual statements, break them down into individual facts if they contain multiple pieces of information.
1097
1146
 
1098
- 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.
1147
+ 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.
1099
1148
  You should detect the language of the user input and record the facts in the same language.
1100
1149
  `;
1101
- 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.
1150
+ 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.
1102
1151
 
1103
1152
  Input:
1104
1153
  ${parsedMessages}`;
@@ -1261,14 +1310,14 @@ function getUpdateMemoryMessages(retrievedOldMemory, newRetrievedFacts) {
1261
1310
  ${JSON.stringify(newRetrievedFacts, null, 2)}
1262
1311
 
1263
1312
  Follow the instruction mentioned below:
1264
- - Do not return anything from the custom few shot prompts provided above.
1313
+ - Do not return anything from the custom few shot example prompts provided above.
1265
1314
  - If the current memory is empty, then you have to add the new retrieved facts to the memory.
1266
1315
  - 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.
1267
1316
  - If there is an addition, generate a new key and add the new memory corresponding to it.
1268
1317
  - If there is a deletion, the memory key-value pair should be removed from the memory.
1269
1318
  - If there is an update, the ID key should remain the same and only the value needs to be updated.
1270
1319
  - DO NOT RETURN ANYTHING ELSE OTHER THAN THE JSON FORMAT.
1271
- - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALUD SUCH AS "\`\`\`json" OR "\`\`\`".
1320
+ - DO NOT ADD ANY ADDITIONAL TEXT OR CODEBLOCK IN THE JSON FIELDS WHICH MAKE IT INVALID SUCH AS "\`\`\`json" OR "\`\`\`".
1272
1321
 
1273
1322
  Do not return anything except the JSON format.`;
1274
1323
  }
@@ -1368,6 +1417,21 @@ var DEFAULT_MEMORY_CONFIG = {
1368
1417
  model: "gpt-4-turbo-preview"
1369
1418
  }
1370
1419
  },
1420
+ enableGraph: false,
1421
+ graphStore: {
1422
+ provider: "neo4j",
1423
+ config: {
1424
+ url: process.env.NEO4J_URL || "neo4j://localhost:7687",
1425
+ username: process.env.NEO4J_USERNAME || "neo4j",
1426
+ password: process.env.NEO4J_PASSWORD || "password"
1427
+ },
1428
+ llm: {
1429
+ provider: "openai",
1430
+ config: {
1431
+ model: "gpt-4-turbo-preview"
1432
+ }
1433
+ }
1434
+ },
1371
1435
  historyDbPath: "memory.db"
1372
1436
  };
1373
1437
 
@@ -1401,12 +1465,779 @@ var ConfigManager = class {
1401
1465
  },
1402
1466
  historyDbPath: userConfig.historyDbPath || DEFAULT_MEMORY_CONFIG.historyDbPath,
1403
1467
  customPrompt: userConfig.customPrompt,
1404
- graphStore: userConfig.graphStore
1468
+ graphStore: {
1469
+ ...DEFAULT_MEMORY_CONFIG.graphStore,
1470
+ ...userConfig.graphStore
1471
+ },
1472
+ enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph
1405
1473
  };
1406
1474
  return MemoryConfigSchema.parse(mergedConfig);
1407
1475
  }
1408
1476
  };
1409
1477
 
1478
+ // src/oss/src/memory/graph_memory.ts
1479
+ var import_neo4j_driver = __toESM(require("neo4j-driver"));
1480
+
1481
+ // src/oss/src/utils/bm25.ts
1482
+ var BM25 = class {
1483
+ constructor(documents, k1 = 1.5, b = 0.75) {
1484
+ this.documents = documents;
1485
+ this.k1 = k1;
1486
+ this.b = b;
1487
+ this.docLengths = documents.map((doc) => doc.length);
1488
+ this.avgDocLength = this.docLengths.reduce((a, b2) => a + b2, 0) / documents.length;
1489
+ this.docFreq = /* @__PURE__ */ new Map();
1490
+ this.idf = /* @__PURE__ */ new Map();
1491
+ this.computeIdf();
1492
+ }
1493
+ computeIdf() {
1494
+ const N = this.documents.length;
1495
+ for (const doc of this.documents) {
1496
+ const terms = new Set(doc);
1497
+ for (const term of terms) {
1498
+ this.docFreq.set(term, (this.docFreq.get(term) || 0) + 1);
1499
+ }
1500
+ }
1501
+ for (const [term, freq] of this.docFreq) {
1502
+ this.idf.set(term, Math.log((N - freq + 0.5) / (freq + 0.5) + 1));
1503
+ }
1504
+ }
1505
+ score(query, doc, index) {
1506
+ let score = 0;
1507
+ const docLength = this.docLengths[index];
1508
+ for (const term of query) {
1509
+ const tf = doc.filter((t) => t === term).length;
1510
+ const idf = this.idf.get(term) || 0;
1511
+ score += idf * tf * (this.k1 + 1) / (tf + this.k1 * (1 - this.b + this.b * docLength / this.avgDocLength));
1512
+ }
1513
+ return score;
1514
+ }
1515
+ search(query) {
1516
+ const scores = this.documents.map((doc, idx) => ({
1517
+ doc,
1518
+ score: this.score(query, doc, idx)
1519
+ }));
1520
+ return scores.sort((a, b) => b.score - a.score).map((item) => item.doc);
1521
+ }
1522
+ };
1523
+
1524
+ // src/oss/src/graphs/tools.ts
1525
+ var RELATIONS_TOOL = {
1526
+ type: "function",
1527
+ function: {
1528
+ name: "establish_relationships",
1529
+ description: "Establish relationships among the entities based on the provided text.",
1530
+ parameters: {
1531
+ type: "object",
1532
+ properties: {
1533
+ entities: {
1534
+ type: "array",
1535
+ items: {
1536
+ type: "object",
1537
+ properties: {
1538
+ source: {
1539
+ type: "string",
1540
+ description: "The source entity of the relationship."
1541
+ },
1542
+ relationship: {
1543
+ type: "string",
1544
+ description: "The relationship between the source and destination entities."
1545
+ },
1546
+ destination: {
1547
+ type: "string",
1548
+ description: "The destination entity of the relationship."
1549
+ }
1550
+ },
1551
+ required: ["source", "relationship", "destination"],
1552
+ additionalProperties: false
1553
+ }
1554
+ }
1555
+ },
1556
+ required: ["entities"],
1557
+ additionalProperties: false
1558
+ }
1559
+ }
1560
+ };
1561
+ var EXTRACT_ENTITIES_TOOL = {
1562
+ type: "function",
1563
+ function: {
1564
+ name: "extract_entities",
1565
+ description: "Extract entities and their types from the text.",
1566
+ parameters: {
1567
+ type: "object",
1568
+ properties: {
1569
+ entities: {
1570
+ type: "array",
1571
+ items: {
1572
+ type: "object",
1573
+ properties: {
1574
+ entity: {
1575
+ type: "string",
1576
+ description: "The name or identifier of the entity."
1577
+ },
1578
+ entity_type: {
1579
+ type: "string",
1580
+ description: "The type or category of the entity."
1581
+ }
1582
+ },
1583
+ required: ["entity", "entity_type"],
1584
+ additionalProperties: false
1585
+ },
1586
+ description: "An array of entities with their types."
1587
+ }
1588
+ },
1589
+ required: ["entities"],
1590
+ additionalProperties: false
1591
+ }
1592
+ }
1593
+ };
1594
+ var DELETE_MEMORY_TOOL_GRAPH = {
1595
+ type: "function",
1596
+ function: {
1597
+ name: "delete_graph_memory",
1598
+ description: "Delete the relationship between two nodes.",
1599
+ parameters: {
1600
+ type: "object",
1601
+ properties: {
1602
+ source: {
1603
+ type: "string",
1604
+ description: "The identifier of the source node in the relationship."
1605
+ },
1606
+ relationship: {
1607
+ type: "string",
1608
+ description: "The existing relationship between the source and destination nodes that needs to be deleted."
1609
+ },
1610
+ destination: {
1611
+ type: "string",
1612
+ description: "The identifier of the destination node in the relationship."
1613
+ }
1614
+ },
1615
+ required: ["source", "relationship", "destination"],
1616
+ additionalProperties: false
1617
+ }
1618
+ }
1619
+ };
1620
+
1621
+ // src/oss/src/graphs/utils.ts
1622
+ var EXTRACT_RELATIONS_PROMPT = `
1623
+ 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:
1624
+
1625
+ 1. Extract only explicitly stated information from the text.
1626
+ 2. Establish relationships among the entities provided.
1627
+ 3. Use "USER_ID" as the source entity for any self-references (e.g., "I," "me," "my," etc.) in user messages.
1628
+ CUSTOM_PROMPT
1629
+
1630
+ Relationships:
1631
+ - Use consistent, general, and timeless relationship types.
1632
+ - Example: Prefer "professor" over "became_professor."
1633
+ - Relationships should only be established among the entities explicitly mentioned in the user message.
1634
+
1635
+ Entity Consistency:
1636
+ - Ensure that relationships are coherent and logically align with the context of the message.
1637
+ - Maintain consistent naming for entities across the extracted data.
1638
+
1639
+ 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.
1640
+
1641
+ Adhere strictly to these guidelines to ensure high-quality knowledge graph extraction.
1642
+ `;
1643
+ var DELETE_RELATIONS_SYSTEM_PROMPT = `
1644
+ 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.
1645
+ Input:
1646
+ 1. Existing Graph Memories: A list of current graph memories, each containing source, relationship, and destination information.
1647
+ 2. New Text: The new information to be integrated into the existing graph structure.
1648
+ 3. Use "USER_ID" as node for any self-references (e.g., "I," "me," "my," etc.) in user messages.
1649
+
1650
+ Guidelines:
1651
+ 1. Identification: Use the new information to evaluate existing relationships in the memory graph.
1652
+ 2. Deletion Criteria: Delete a relationship only if it meets at least one of these conditions:
1653
+ - Outdated or Inaccurate: The new information is more recent or accurate.
1654
+ - Contradictory: The new information conflicts with or negates the existing information.
1655
+ 3. DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
1656
+ 4. Comprehensive Analysis:
1657
+ - Thoroughly examine each existing relationship against the new information and delete as necessary.
1658
+ - Multiple deletions may be required based on the new information.
1659
+ 5. Semantic Integrity:
1660
+ - Ensure that deletions maintain or improve the overall semantic structure of the graph.
1661
+ - Avoid deleting relationships that are NOT contradictory/outdated to the new information.
1662
+ 6. Temporal Awareness: Prioritize recency when timestamps are available.
1663
+ 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.
1664
+
1665
+ Note: DO NOT DELETE if their is a possibility of same type of relationship but different destination nodes.
1666
+
1667
+ For example:
1668
+ Existing Memory: alice -- loves_to_eat -- pizza
1669
+ New Information: Alice also loves to eat burger.
1670
+
1671
+ Do not delete in the above example because there is a possibility that Alice loves to eat both pizza and burger.
1672
+
1673
+ Memory Format:
1674
+ source -- relationship -- destination
1675
+
1676
+ Provide a list of deletion instructions, each specifying the relationship to be deleted.
1677
+ `;
1678
+ function getDeleteMessages(existingMemoriesString, data, userId) {
1679
+ return [
1680
+ DELETE_RELATIONS_SYSTEM_PROMPT.replace("USER_ID", userId),
1681
+ `Here are the existing memories: ${existingMemoriesString}
1682
+
1683
+ New Information: ${data}`
1684
+ ];
1685
+ }
1686
+
1687
+ // src/oss/src/utils/logger.ts
1688
+ var logger = {
1689
+ info: (message) => console.log(`[INFO] ${message}`),
1690
+ error: (message) => console.error(`[ERROR] ${message}`),
1691
+ debug: (message) => console.debug(`[DEBUG] ${message}`),
1692
+ warn: (message) => console.warn(`[WARN] ${message}`)
1693
+ };
1694
+
1695
+ // src/oss/src/memory/graph_memory.ts
1696
+ var MemoryGraph = class {
1697
+ constructor(config) {
1698
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1699
+ this.config = config;
1700
+ 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)) {
1701
+ throw new Error("Neo4j configuration is incomplete");
1702
+ }
1703
+ this.graph = import_neo4j_driver.default.driver(
1704
+ config.graphStore.config.url,
1705
+ import_neo4j_driver.default.auth.basic(
1706
+ config.graphStore.config.username,
1707
+ config.graphStore.config.password
1708
+ )
1709
+ );
1710
+ this.embeddingModel = EmbedderFactory.create(
1711
+ this.config.embedder.provider,
1712
+ this.config.embedder.config
1713
+ );
1714
+ this.llmProvider = "openai";
1715
+ if ((_g = this.config.llm) == null ? void 0 : _g.provider) {
1716
+ this.llmProvider = this.config.llm.provider;
1717
+ }
1718
+ if ((_i = (_h = this.config.graphStore) == null ? void 0 : _h.llm) == null ? void 0 : _i.provider) {
1719
+ this.llmProvider = this.config.graphStore.llm.provider;
1720
+ }
1721
+ this.llm = LLMFactory.create(this.llmProvider, this.config.llm.config);
1722
+ this.structuredLlm = LLMFactory.create(
1723
+ "openai_structured",
1724
+ this.config.llm.config
1725
+ );
1726
+ this.threshold = 0.7;
1727
+ }
1728
+ async add(data, filters) {
1729
+ const entityTypeMap = await this._retrieveNodesFromData(data, filters);
1730
+ const toBeAdded = await this._establishNodesRelationsFromData(
1731
+ data,
1732
+ filters,
1733
+ entityTypeMap
1734
+ );
1735
+ const searchOutput = await this._searchGraphDb(
1736
+ Object.keys(entityTypeMap),
1737
+ filters
1738
+ );
1739
+ const toBeDeleted = await this._getDeleteEntitiesFromSearchOutput(
1740
+ searchOutput,
1741
+ data,
1742
+ filters
1743
+ );
1744
+ const deletedEntities = await this._deleteEntities(
1745
+ toBeDeleted,
1746
+ filters["userId"]
1747
+ );
1748
+ const addedEntities = await this._addEntities(
1749
+ toBeAdded,
1750
+ filters["userId"],
1751
+ entityTypeMap
1752
+ );
1753
+ return {
1754
+ deleted_entities: deletedEntities,
1755
+ added_entities: addedEntities,
1756
+ relations: toBeAdded
1757
+ };
1758
+ }
1759
+ async search(query, filters, limit = 100) {
1760
+ const entityTypeMap = await this._retrieveNodesFromData(query, filters);
1761
+ const searchOutput = await this._searchGraphDb(
1762
+ Object.keys(entityTypeMap),
1763
+ filters
1764
+ );
1765
+ if (!searchOutput.length) {
1766
+ return [];
1767
+ }
1768
+ const searchOutputsSequence = searchOutput.map((item) => [
1769
+ item.source,
1770
+ item.relationship,
1771
+ item.destination
1772
+ ]);
1773
+ const bm25 = new BM25(searchOutputsSequence);
1774
+ const tokenizedQuery = query.split(" ");
1775
+ const rerankedResults = bm25.search(tokenizedQuery).slice(0, 5);
1776
+ const searchResults = rerankedResults.map((item) => ({
1777
+ source: item[0],
1778
+ relationship: item[1],
1779
+ destination: item[2]
1780
+ }));
1781
+ logger.info(`Returned ${searchResults.length} search results`);
1782
+ return searchResults;
1783
+ }
1784
+ async deleteAll(filters) {
1785
+ const session = this.graph.session();
1786
+ try {
1787
+ await session.run("MATCH (n {user_id: $user_id}) DETACH DELETE n", {
1788
+ user_id: filters["userId"]
1789
+ });
1790
+ } finally {
1791
+ await session.close();
1792
+ }
1793
+ }
1794
+ async getAll(filters, limit = 100) {
1795
+ const session = this.graph.session();
1796
+ try {
1797
+ const result = await session.run(
1798
+ `
1799
+ MATCH (n {user_id: $user_id})-[r]->(m {user_id: $user_id})
1800
+ RETURN n.name AS source, type(r) AS relationship, m.name AS target
1801
+ LIMIT toInteger($limit)
1802
+ `,
1803
+ { user_id: filters["userId"], limit: Math.floor(Number(limit)) }
1804
+ );
1805
+ const finalResults = result.records.map((record) => ({
1806
+ source: record.get("source"),
1807
+ relationship: record.get("relationship"),
1808
+ target: record.get("target")
1809
+ }));
1810
+ logger.info(`Retrieved ${finalResults.length} relationships`);
1811
+ return finalResults;
1812
+ } finally {
1813
+ await session.close();
1814
+ }
1815
+ }
1816
+ async _retrieveNodesFromData(data, filters) {
1817
+ const tools = [EXTRACT_ENTITIES_TOOL];
1818
+ const searchResults = await this.structuredLlm.generateResponse(
1819
+ [
1820
+ {
1821
+ role: "system",
1822
+ 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.`
1823
+ },
1824
+ { role: "user", content: data }
1825
+ ],
1826
+ { type: "json_object" },
1827
+ tools
1828
+ );
1829
+ let entityTypeMap = {};
1830
+ try {
1831
+ if (typeof searchResults !== "string" && searchResults.toolCalls) {
1832
+ for (const call of searchResults.toolCalls) {
1833
+ if (call.name === "extract_entities") {
1834
+ const args = JSON.parse(call.arguments);
1835
+ for (const item of args.entities) {
1836
+ entityTypeMap[item.entity] = item.entity_type;
1837
+ }
1838
+ }
1839
+ }
1840
+ }
1841
+ } catch (e) {
1842
+ logger.error(`Error in search tool: ${e}`);
1843
+ }
1844
+ entityTypeMap = Object.fromEntries(
1845
+ Object.entries(entityTypeMap).map(([k, v]) => [
1846
+ k.toLowerCase().replace(/ /g, "_"),
1847
+ v.toLowerCase().replace(/ /g, "_")
1848
+ ])
1849
+ );
1850
+ logger.debug(`Entity type map: ${JSON.stringify(entityTypeMap)}`);
1851
+ return entityTypeMap;
1852
+ }
1853
+ async _establishNodesRelationsFromData(data, filters, entityTypeMap) {
1854
+ var _a;
1855
+ let messages;
1856
+ if ((_a = this.config.graphStore) == null ? void 0 : _a.customPrompt) {
1857
+ messages = [
1858
+ {
1859
+ role: "system",
1860
+ content: EXTRACT_RELATIONS_PROMPT.replace(
1861
+ "USER_ID",
1862
+ filters["userId"]
1863
+ ).replace(
1864
+ "CUSTOM_PROMPT",
1865
+ `4. ${this.config.graphStore.customPrompt}`
1866
+ ) + "\nPlease provide your response in JSON format."
1867
+ },
1868
+ { role: "user", content: data }
1869
+ ];
1870
+ } else {
1871
+ messages = [
1872
+ {
1873
+ role: "system",
1874
+ content: EXTRACT_RELATIONS_PROMPT.replace("USER_ID", filters["userId"]) + "\nPlease provide your response in JSON format."
1875
+ },
1876
+ {
1877
+ role: "user",
1878
+ content: `List of entities: ${Object.keys(entityTypeMap)}.
1879
+
1880
+ Text: ${data}`
1881
+ }
1882
+ ];
1883
+ }
1884
+ const tools = [RELATIONS_TOOL];
1885
+ const extractedEntities = await this.structuredLlm.generateResponse(
1886
+ messages,
1887
+ { type: "json_object" },
1888
+ tools
1889
+ );
1890
+ let entities = [];
1891
+ if (typeof extractedEntities !== "string" && extractedEntities.toolCalls) {
1892
+ const toolCall = extractedEntities.toolCalls[0];
1893
+ if (toolCall && toolCall.arguments) {
1894
+ const args = JSON.parse(toolCall.arguments);
1895
+ entities = args.entities || [];
1896
+ }
1897
+ }
1898
+ entities = this._removeSpacesFromEntities(entities);
1899
+ logger.debug(`Extracted entities: ${JSON.stringify(entities)}`);
1900
+ return entities;
1901
+ }
1902
+ async _searchGraphDb(nodeList, filters, limit = 100) {
1903
+ const resultRelations = [];
1904
+ const session = this.graph.session();
1905
+ try {
1906
+ for (const node of nodeList) {
1907
+ const nEmbedding = await this.embeddingModel.embed(node);
1908
+ const cypher = `
1909
+ MATCH (n)
1910
+ WHERE n.embedding IS NOT NULL AND n.user_id = $user_id
1911
+ WITH n,
1912
+ round(reduce(dot = 0.0, i IN range(0, size(n.embedding)-1) | dot + n.embedding[i] * $n_embedding[i]) /
1913
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(n.embedding)-1) | l2 + n.embedding[i] * n.embedding[i])) *
1914
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($n_embedding)-1) | l2 + $n_embedding[i] * $n_embedding[i]))), 4) AS similarity
1915
+ WHERE similarity >= $threshold
1916
+ MATCH (n)-[r]->(m)
1917
+ 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
1918
+ UNION
1919
+ MATCH (n)
1920
+ WHERE n.embedding IS NOT NULL AND n.user_id = $user_id
1921
+ WITH n,
1922
+ round(reduce(dot = 0.0, i IN range(0, size(n.embedding)-1) | dot + n.embedding[i] * $n_embedding[i]) /
1923
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(n.embedding)-1) | l2 + n.embedding[i] * n.embedding[i])) *
1924
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($n_embedding)-1) | l2 + $n_embedding[i] * $n_embedding[i]))), 4) AS similarity
1925
+ WHERE similarity >= $threshold
1926
+ MATCH (m)-[r]->(n)
1927
+ 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
1928
+ ORDER BY similarity DESC
1929
+ LIMIT toInteger($limit)
1930
+ `;
1931
+ const result = await session.run(cypher, {
1932
+ n_embedding: nEmbedding,
1933
+ threshold: this.threshold,
1934
+ user_id: filters["userId"],
1935
+ limit: Math.floor(Number(limit))
1936
+ });
1937
+ resultRelations.push(
1938
+ ...result.records.map((record) => ({
1939
+ source: record.get("source"),
1940
+ source_id: record.get("source_id").toString(),
1941
+ relationship: record.get("relationship"),
1942
+ relation_id: record.get("relation_id").toString(),
1943
+ destination: record.get("destination"),
1944
+ destination_id: record.get("destination_id").toString(),
1945
+ similarity: record.get("similarity")
1946
+ }))
1947
+ );
1948
+ }
1949
+ } finally {
1950
+ await session.close();
1951
+ }
1952
+ return resultRelations;
1953
+ }
1954
+ async _getDeleteEntitiesFromSearchOutput(searchOutput, data, filters) {
1955
+ const searchOutputString = searchOutput.map(
1956
+ (item) => `${item.source} -- ${item.relationship} -- ${item.destination}`
1957
+ ).join("\n");
1958
+ const [systemPrompt, userPrompt] = getDeleteMessages(
1959
+ searchOutputString,
1960
+ data,
1961
+ filters["userId"]
1962
+ );
1963
+ const tools = [DELETE_MEMORY_TOOL_GRAPH];
1964
+ const memoryUpdates = await this.structuredLlm.generateResponse(
1965
+ [
1966
+ { role: "system", content: systemPrompt },
1967
+ { role: "user", content: userPrompt }
1968
+ ],
1969
+ { type: "json_object" },
1970
+ tools
1971
+ );
1972
+ const toBeDeleted = [];
1973
+ if (typeof memoryUpdates !== "string" && memoryUpdates.toolCalls) {
1974
+ for (const item of memoryUpdates.toolCalls) {
1975
+ if (item.name === "delete_graph_memory") {
1976
+ toBeDeleted.push(JSON.parse(item.arguments));
1977
+ }
1978
+ }
1979
+ }
1980
+ const cleanedToBeDeleted = this._removeSpacesFromEntities(toBeDeleted);
1981
+ logger.debug(
1982
+ `Deleted relationships: ${JSON.stringify(cleanedToBeDeleted)}`
1983
+ );
1984
+ return cleanedToBeDeleted;
1985
+ }
1986
+ async _deleteEntities(toBeDeleted, userId) {
1987
+ const results = [];
1988
+ const session = this.graph.session();
1989
+ try {
1990
+ for (const item of toBeDeleted) {
1991
+ const { source, destination, relationship } = item;
1992
+ const cypher = `
1993
+ MATCH (n {name: $source_name, user_id: $user_id})
1994
+ -[r:${relationship}]->
1995
+ (m {name: $dest_name, user_id: $user_id})
1996
+ DELETE r
1997
+ RETURN
1998
+ n.name AS source,
1999
+ m.name AS target,
2000
+ type(r) AS relationship
2001
+ `;
2002
+ const result = await session.run(cypher, {
2003
+ source_name: source,
2004
+ dest_name: destination,
2005
+ user_id: userId
2006
+ });
2007
+ results.push(result.records);
2008
+ }
2009
+ } finally {
2010
+ await session.close();
2011
+ }
2012
+ return results;
2013
+ }
2014
+ async _addEntities(toBeAdded, userId, entityTypeMap) {
2015
+ var _a, _b;
2016
+ const results = [];
2017
+ const session = this.graph.session();
2018
+ try {
2019
+ for (const item of toBeAdded) {
2020
+ const { source, destination, relationship } = item;
2021
+ const sourceType = entityTypeMap[source] || "unknown";
2022
+ const destinationType = entityTypeMap[destination] || "unknown";
2023
+ const sourceEmbedding = await this.embeddingModel.embed(source);
2024
+ const destEmbedding = await this.embeddingModel.embed(destination);
2025
+ const sourceNodeSearchResult = await this._searchSourceNode(
2026
+ sourceEmbedding,
2027
+ userId
2028
+ );
2029
+ const destinationNodeSearchResult = await this._searchDestinationNode(
2030
+ destEmbedding,
2031
+ userId
2032
+ );
2033
+ let cypher;
2034
+ let params;
2035
+ if (destinationNodeSearchResult.length === 0 && sourceNodeSearchResult.length > 0) {
2036
+ cypher = `
2037
+ MATCH (source)
2038
+ WHERE elementId(source) = $source_id
2039
+ MERGE (destination:${destinationType} {name: $destination_name, user_id: $user_id})
2040
+ ON CREATE SET
2041
+ destination.created = timestamp(),
2042
+ destination.embedding = $destination_embedding
2043
+ MERGE (source)-[r:${relationship}]->(destination)
2044
+ ON CREATE SET
2045
+ r.created = timestamp()
2046
+ RETURN source.name AS source, type(r) AS relationship, destination.name AS target
2047
+ `;
2048
+ params = {
2049
+ source_id: sourceNodeSearchResult[0].elementId,
2050
+ destination_name: destination,
2051
+ destination_embedding: destEmbedding,
2052
+ user_id: userId
2053
+ };
2054
+ } else if (destinationNodeSearchResult.length > 0 && sourceNodeSearchResult.length === 0) {
2055
+ cypher = `
2056
+ MATCH (destination)
2057
+ WHERE elementId(destination) = $destination_id
2058
+ MERGE (source:${sourceType} {name: $source_name, user_id: $user_id})
2059
+ ON CREATE SET
2060
+ source.created = timestamp(),
2061
+ source.embedding = $source_embedding
2062
+ MERGE (source)-[r:${relationship}]->(destination)
2063
+ ON CREATE SET
2064
+ r.created = timestamp()
2065
+ RETURN source.name AS source, type(r) AS relationship, destination.name AS target
2066
+ `;
2067
+ params = {
2068
+ destination_id: destinationNodeSearchResult[0].elementId,
2069
+ source_name: source,
2070
+ source_embedding: sourceEmbedding,
2071
+ user_id: userId
2072
+ };
2073
+ } else if (sourceNodeSearchResult.length > 0 && destinationNodeSearchResult.length > 0) {
2074
+ cypher = `
2075
+ MATCH (source)
2076
+ WHERE elementId(source) = $source_id
2077
+ MATCH (destination)
2078
+ WHERE elementId(destination) = $destination_id
2079
+ MERGE (source)-[r:${relationship}]->(destination)
2080
+ ON CREATE SET
2081
+ r.created_at = timestamp(),
2082
+ r.updated_at = timestamp()
2083
+ RETURN source.name AS source, type(r) AS relationship, destination.name AS target
2084
+ `;
2085
+ params = {
2086
+ source_id: (_a = sourceNodeSearchResult[0]) == null ? void 0 : _a.elementId,
2087
+ destination_id: (_b = destinationNodeSearchResult[0]) == null ? void 0 : _b.elementId,
2088
+ user_id: userId
2089
+ };
2090
+ } else {
2091
+ cypher = `
2092
+ MERGE (n:${sourceType} {name: $source_name, user_id: $user_id})
2093
+ ON CREATE SET n.created = timestamp(), n.embedding = $source_embedding
2094
+ ON MATCH SET n.embedding = $source_embedding
2095
+ MERGE (m:${destinationType} {name: $dest_name, user_id: $user_id})
2096
+ ON CREATE SET m.created = timestamp(), m.embedding = $dest_embedding
2097
+ ON MATCH SET m.embedding = $dest_embedding
2098
+ MERGE (n)-[rel:${relationship}]->(m)
2099
+ ON CREATE SET rel.created = timestamp()
2100
+ RETURN n.name AS source, type(rel) AS relationship, m.name AS target
2101
+ `;
2102
+ params = {
2103
+ source_name: source,
2104
+ dest_name: destination,
2105
+ source_embedding: sourceEmbedding,
2106
+ dest_embedding: destEmbedding,
2107
+ user_id: userId
2108
+ };
2109
+ }
2110
+ const result = await session.run(cypher, params);
2111
+ results.push(result.records);
2112
+ }
2113
+ } finally {
2114
+ await session.close();
2115
+ }
2116
+ return results;
2117
+ }
2118
+ _removeSpacesFromEntities(entityList) {
2119
+ return entityList.map((item) => ({
2120
+ ...item,
2121
+ source: item.source.toLowerCase().replace(/ /g, "_"),
2122
+ relationship: item.relationship.toLowerCase().replace(/ /g, "_"),
2123
+ destination: item.destination.toLowerCase().replace(/ /g, "_")
2124
+ }));
2125
+ }
2126
+ async _searchSourceNode(sourceEmbedding, userId, threshold = 0.9) {
2127
+ const session = this.graph.session();
2128
+ try {
2129
+ const cypher = `
2130
+ MATCH (source_candidate)
2131
+ WHERE source_candidate.embedding IS NOT NULL
2132
+ AND source_candidate.user_id = $user_id
2133
+
2134
+ WITH source_candidate,
2135
+ round(
2136
+ reduce(dot = 0.0, i IN range(0, size(source_candidate.embedding)-1) |
2137
+ dot + source_candidate.embedding[i] * $source_embedding[i]) /
2138
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(source_candidate.embedding)-1) |
2139
+ l2 + source_candidate.embedding[i] * source_candidate.embedding[i])) *
2140
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($source_embedding)-1) |
2141
+ l2 + $source_embedding[i] * $source_embedding[i])))
2142
+ , 4) AS source_similarity
2143
+ WHERE source_similarity >= $threshold
2144
+
2145
+ WITH source_candidate, source_similarity
2146
+ ORDER BY source_similarity DESC
2147
+ LIMIT 1
2148
+
2149
+ RETURN elementId(source_candidate) as element_id
2150
+ `;
2151
+ const params = {
2152
+ source_embedding: sourceEmbedding,
2153
+ user_id: userId,
2154
+ threshold
2155
+ };
2156
+ const result = await session.run(cypher, params);
2157
+ return result.records.map((record) => ({
2158
+ elementId: record.get("element_id").toString()
2159
+ }));
2160
+ } finally {
2161
+ await session.close();
2162
+ }
2163
+ }
2164
+ async _searchDestinationNode(destinationEmbedding, userId, threshold = 0.9) {
2165
+ const session = this.graph.session();
2166
+ try {
2167
+ const cypher = `
2168
+ MATCH (destination_candidate)
2169
+ WHERE destination_candidate.embedding IS NOT NULL
2170
+ AND destination_candidate.user_id = $user_id
2171
+
2172
+ WITH destination_candidate,
2173
+ round(
2174
+ reduce(dot = 0.0, i IN range(0, size(destination_candidate.embedding)-1) |
2175
+ dot + destination_candidate.embedding[i] * $destination_embedding[i]) /
2176
+ (sqrt(reduce(l2 = 0.0, i IN range(0, size(destination_candidate.embedding)-1) |
2177
+ l2 + destination_candidate.embedding[i] * destination_candidate.embedding[i])) *
2178
+ sqrt(reduce(l2 = 0.0, i IN range(0, size($destination_embedding)-1) |
2179
+ l2 + $destination_embedding[i] * $destination_embedding[i])))
2180
+ , 4) AS destination_similarity
2181
+ WHERE destination_similarity >= $threshold
2182
+
2183
+ WITH destination_candidate, destination_similarity
2184
+ ORDER BY destination_similarity DESC
2185
+ LIMIT 1
2186
+
2187
+ RETURN elementId(destination_candidate) as element_id
2188
+ `;
2189
+ const params = {
2190
+ destination_embedding: destinationEmbedding,
2191
+ user_id: userId,
2192
+ threshold
2193
+ };
2194
+ const result = await session.run(cypher, params);
2195
+ return result.records.map((record) => ({
2196
+ elementId: record.get("element_id").toString()
2197
+ }));
2198
+ } finally {
2199
+ await session.close();
2200
+ }
2201
+ }
2202
+ };
2203
+
2204
+ // src/oss/src/utils/memory.ts
2205
+ var get_image_description = async (image_url) => {
2206
+ const llm = new OpenAILLM({
2207
+ apiKey: process.env.OPENAI_API_KEY
2208
+ });
2209
+ const response = await llm.generateResponse([
2210
+ {
2211
+ role: "user",
2212
+ content: "Provide a description of the image and do not include any additional text."
2213
+ },
2214
+ {
2215
+ role: "user",
2216
+ content: { type: "image_url", image_url: { url: image_url } }
2217
+ }
2218
+ ]);
2219
+ return response;
2220
+ };
2221
+ var parse_vision_messages = async (messages) => {
2222
+ const parsed_messages = [];
2223
+ for (const message of messages) {
2224
+ let new_message = {
2225
+ role: message.role,
2226
+ content: ""
2227
+ };
2228
+ if (message.role !== "system") {
2229
+ if (typeof message.content === "object" && message.content.type === "image_url") {
2230
+ const description = await get_image_description(
2231
+ message.content.image_url.url
2232
+ );
2233
+ new_message.content = typeof description === "string" ? description : JSON.stringify(description);
2234
+ parsed_messages.push(new_message);
2235
+ } else parsed_messages.push(message);
2236
+ }
2237
+ }
2238
+ return parsed_messages;
2239
+ };
2240
+
1410
2241
  // src/oss/src/memory/index.ts
1411
2242
  var Memory = class _Memory {
1412
2243
  constructor(config = {}) {
@@ -1427,6 +2258,10 @@ var Memory = class _Memory {
1427
2258
  this.db = new SQLiteManager(this.config.historyDbPath || ":memory:");
1428
2259
  this.collectionName = this.config.vectorStore.config.collectionName;
1429
2260
  this.apiVersion = this.config.version || "v1.0";
2261
+ this.enableGraph = this.config.enableGraph || false;
2262
+ if (this.enableGraph && this.config.graphStore) {
2263
+ this.graphMemory = new MemoryGraph(this.config);
2264
+ }
1430
2265
  }
1431
2266
  static fromConfig(configDict) {
1432
2267
  try {
@@ -1455,12 +2290,27 @@ var Memory = class _Memory {
1455
2290
  );
1456
2291
  }
1457
2292
  const parsedMessages = Array.isArray(messages) ? messages : [{ role: "user", content: messages }];
2293
+ const final_parsedMessages = await parse_vision_messages(parsedMessages);
1458
2294
  const vectorStoreResult = await this.addToVectorStore(
1459
- parsedMessages,
2295
+ final_parsedMessages,
1460
2296
  metadata,
1461
2297
  filters
1462
2298
  );
1463
- return { results: vectorStoreResult };
2299
+ let graphResult;
2300
+ if (this.graphMemory) {
2301
+ try {
2302
+ graphResult = await this.graphMemory.add(
2303
+ final_parsedMessages.map((m) => m.content).join("\n"),
2304
+ filters
2305
+ );
2306
+ } catch (error) {
2307
+ console.error("Error adding to graph memory:", error);
2308
+ }
2309
+ }
2310
+ return {
2311
+ results: vectorStoreResult,
2312
+ relations: graphResult == null ? void 0 : graphResult.relations
2313
+ };
1464
2314
  }
1465
2315
  async addToVectorStore(messages, metadata, filters) {
1466
2316
  const parsedMessages = messages.map((m) => m.content).join("\n");
@@ -1604,6 +2454,14 @@ ${parsedMessages}`] : getFactRetrievalMessages(parsedMessages);
1604
2454
  limit,
1605
2455
  filters
1606
2456
  );
2457
+ let graphResults;
2458
+ if (this.graphMemory) {
2459
+ try {
2460
+ graphResults = await this.graphMemory.search(query, filters);
2461
+ } catch (error) {
2462
+ console.error("Error searching graph memory:", error);
2463
+ }
2464
+ }
1607
2465
  const excludedKeys = /* @__PURE__ */ new Set([
1608
2466
  "userId",
1609
2467
  "agentId",
@@ -1625,7 +2483,10 @@ ${parsedMessages}`] : getFactRetrievalMessages(parsedMessages);
1625
2483
  ...mem.payload.agentId && { agentId: mem.payload.agentId },
1626
2484
  ...mem.payload.runId && { runId: mem.payload.runId }
1627
2485
  }));
1628
- return { results };
2486
+ return {
2487
+ results,
2488
+ relations: graphResults
2489
+ };
1629
2490
  }
1630
2491
  async update(memoryId, data) {
1631
2492
  const embedding = await this.embedder.embed(data);
@@ -1659,6 +2520,9 @@ ${parsedMessages}`] : getFactRetrievalMessages(parsedMessages);
1659
2520
  async reset() {
1660
2521
  await this.db.reset();
1661
2522
  await this.vectorStore.deleteCol();
2523
+ if (this.graphMemory) {
2524
+ await this.graphMemory.deleteAll({ userId: "default" });
2525
+ }
1662
2526
  this.vectorStore = VectorStoreFactory.create(
1663
2527
  this.config.vectorStore.provider,
1664
2528
  this.config.vectorStore.config