mem0ai-azure-mysql 0.1.115__py3-none-any.whl

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.
Files changed (116) hide show
  1. mem0/__init__.py +6 -0
  2. mem0/client/__init__.py +0 -0
  3. mem0/client/main.py +1535 -0
  4. mem0/client/project.py +860 -0
  5. mem0/client/utils.py +29 -0
  6. mem0/configs/__init__.py +0 -0
  7. mem0/configs/base.py +90 -0
  8. mem0/configs/dbs/__init__.py +4 -0
  9. mem0/configs/dbs/base.py +41 -0
  10. mem0/configs/dbs/mysql.py +25 -0
  11. mem0/configs/embeddings/__init__.py +0 -0
  12. mem0/configs/embeddings/base.py +108 -0
  13. mem0/configs/enums.py +7 -0
  14. mem0/configs/llms/__init__.py +0 -0
  15. mem0/configs/llms/base.py +152 -0
  16. mem0/configs/prompts.py +333 -0
  17. mem0/configs/vector_stores/__init__.py +0 -0
  18. mem0/configs/vector_stores/azure_ai_search.py +59 -0
  19. mem0/configs/vector_stores/baidu.py +29 -0
  20. mem0/configs/vector_stores/chroma.py +40 -0
  21. mem0/configs/vector_stores/elasticsearch.py +47 -0
  22. mem0/configs/vector_stores/faiss.py +39 -0
  23. mem0/configs/vector_stores/langchain.py +32 -0
  24. mem0/configs/vector_stores/milvus.py +43 -0
  25. mem0/configs/vector_stores/mongodb.py +25 -0
  26. mem0/configs/vector_stores/opensearch.py +41 -0
  27. mem0/configs/vector_stores/pgvector.py +37 -0
  28. mem0/configs/vector_stores/pinecone.py +56 -0
  29. mem0/configs/vector_stores/qdrant.py +49 -0
  30. mem0/configs/vector_stores/redis.py +26 -0
  31. mem0/configs/vector_stores/supabase.py +44 -0
  32. mem0/configs/vector_stores/upstash_vector.py +36 -0
  33. mem0/configs/vector_stores/vertex_ai_vector_search.py +27 -0
  34. mem0/configs/vector_stores/weaviate.py +43 -0
  35. mem0/dbs/__init__.py +4 -0
  36. mem0/dbs/base.py +68 -0
  37. mem0/dbs/configs.py +21 -0
  38. mem0/dbs/mysql.py +321 -0
  39. mem0/embeddings/__init__.py +0 -0
  40. mem0/embeddings/aws_bedrock.py +100 -0
  41. mem0/embeddings/azure_openai.py +43 -0
  42. mem0/embeddings/base.py +31 -0
  43. mem0/embeddings/configs.py +30 -0
  44. mem0/embeddings/gemini.py +39 -0
  45. mem0/embeddings/huggingface.py +41 -0
  46. mem0/embeddings/langchain.py +35 -0
  47. mem0/embeddings/lmstudio.py +29 -0
  48. mem0/embeddings/mock.py +11 -0
  49. mem0/embeddings/ollama.py +53 -0
  50. mem0/embeddings/openai.py +49 -0
  51. mem0/embeddings/together.py +31 -0
  52. mem0/embeddings/vertexai.py +54 -0
  53. mem0/graphs/__init__.py +0 -0
  54. mem0/graphs/configs.py +96 -0
  55. mem0/graphs/neptune/__init__.py +0 -0
  56. mem0/graphs/neptune/base.py +410 -0
  57. mem0/graphs/neptune/main.py +372 -0
  58. mem0/graphs/tools.py +371 -0
  59. mem0/graphs/utils.py +97 -0
  60. mem0/llms/__init__.py +0 -0
  61. mem0/llms/anthropic.py +64 -0
  62. mem0/llms/aws_bedrock.py +270 -0
  63. mem0/llms/azure_openai.py +114 -0
  64. mem0/llms/azure_openai_structured.py +76 -0
  65. mem0/llms/base.py +32 -0
  66. mem0/llms/configs.py +34 -0
  67. mem0/llms/deepseek.py +85 -0
  68. mem0/llms/gemini.py +201 -0
  69. mem0/llms/groq.py +88 -0
  70. mem0/llms/langchain.py +65 -0
  71. mem0/llms/litellm.py +87 -0
  72. mem0/llms/lmstudio.py +53 -0
  73. mem0/llms/ollama.py +94 -0
  74. mem0/llms/openai.py +124 -0
  75. mem0/llms/openai_structured.py +52 -0
  76. mem0/llms/sarvam.py +89 -0
  77. mem0/llms/together.py +88 -0
  78. mem0/llms/vllm.py +89 -0
  79. mem0/llms/xai.py +52 -0
  80. mem0/memory/__init__.py +0 -0
  81. mem0/memory/base.py +63 -0
  82. mem0/memory/graph_memory.py +632 -0
  83. mem0/memory/main.py +1843 -0
  84. mem0/memory/memgraph_memory.py +630 -0
  85. mem0/memory/setup.py +56 -0
  86. mem0/memory/storage.py +218 -0
  87. mem0/memory/telemetry.py +90 -0
  88. mem0/memory/utils.py +133 -0
  89. mem0/proxy/__init__.py +0 -0
  90. mem0/proxy/main.py +194 -0
  91. mem0/utils/factory.py +132 -0
  92. mem0/vector_stores/__init__.py +0 -0
  93. mem0/vector_stores/azure_ai_search.py +383 -0
  94. mem0/vector_stores/baidu.py +368 -0
  95. mem0/vector_stores/base.py +58 -0
  96. mem0/vector_stores/chroma.py +229 -0
  97. mem0/vector_stores/configs.py +60 -0
  98. mem0/vector_stores/elasticsearch.py +235 -0
  99. mem0/vector_stores/faiss.py +473 -0
  100. mem0/vector_stores/langchain.py +179 -0
  101. mem0/vector_stores/milvus.py +245 -0
  102. mem0/vector_stores/mongodb.py +293 -0
  103. mem0/vector_stores/opensearch.py +281 -0
  104. mem0/vector_stores/pgvector.py +294 -0
  105. mem0/vector_stores/pinecone.py +373 -0
  106. mem0/vector_stores/qdrant.py +240 -0
  107. mem0/vector_stores/redis.py +295 -0
  108. mem0/vector_stores/supabase.py +237 -0
  109. mem0/vector_stores/upstash_vector.py +293 -0
  110. mem0/vector_stores/vertex_ai_vector_search.py +629 -0
  111. mem0/vector_stores/weaviate.py +316 -0
  112. mem0ai_azure_mysql-0.1.115.data/data/README.md +169 -0
  113. mem0ai_azure_mysql-0.1.115.dist-info/METADATA +224 -0
  114. mem0ai_azure_mysql-0.1.115.dist-info/RECORD +116 -0
  115. mem0ai_azure_mysql-0.1.115.dist-info/WHEEL +4 -0
  116. mem0ai_azure_mysql-0.1.115.dist-info/licenses/LICENSE +201 -0
mem0/client/main.py ADDED
@@ -0,0 +1,1535 @@
1
+ import hashlib
2
+ import logging
3
+ import os
4
+ import warnings
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ import httpx
8
+ import requests
9
+
10
+ from mem0.client.project import AsyncProject, Project
11
+ from mem0.client.utils import api_error_handler
12
+ from mem0.memory.setup import get_user_id, setup_config
13
+ from mem0.memory.telemetry import capture_client_event
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ warnings.filterwarnings("default", category=DeprecationWarning)
18
+
19
+ # Setup user config
20
+ setup_config()
21
+
22
+
23
+ class MemoryClient:
24
+ """Client for interacting with the Mem0 API.
25
+
26
+ This class provides methods to create, retrieve, search, and delete
27
+ memories using the Mem0 API.
28
+
29
+ Attributes:
30
+ api_key (str): The API key for authenticating with the Mem0 API.
31
+ host (str): The base URL for the Mem0 API.
32
+ client (httpx.Client): The HTTP client used for making API requests.
33
+ org_id (str, optional): Organization ID.
34
+ project_id (str, optional): Project ID.
35
+ user_id (str): Unique identifier for the user.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ api_key: Optional[str] = None,
41
+ host: Optional[str] = None,
42
+ org_id: Optional[str] = None,
43
+ project_id: Optional[str] = None,
44
+ client: Optional[httpx.Client] = None,
45
+ ):
46
+ """Initialize the MemoryClient.
47
+
48
+ Args:
49
+ api_key: The API key for authenticating with the Mem0 API. If not
50
+ provided, it will attempt to use the MEM0_API_KEY
51
+ environment variable.
52
+ host: The base URL for the Mem0 API. Defaults to
53
+ "https://api.mem0.ai".
54
+ org_id: The ID of the organization.
55
+ project_id: The ID of the project.
56
+ client: A custom httpx.Client instance. If provided, it will be
57
+ used instead of creating a new one. Note that base_url and
58
+ headers will be set/overridden as needed.
59
+
60
+ Raises:
61
+ ValueError: If no API key is provided or found in the environment.
62
+ """
63
+ self.api_key = api_key or os.getenv("MEM0_API_KEY")
64
+ self.host = host or "https://api.mem0.ai"
65
+ self.org_id = org_id
66
+ self.project_id = project_id
67
+ self.user_id = get_user_id()
68
+
69
+ if not self.api_key:
70
+ raise ValueError("Mem0 API Key not provided. Please provide an API Key.")
71
+
72
+ # Create MD5 hash of API key for user_id
73
+ self.user_id = hashlib.md5(self.api_key.encode()).hexdigest()
74
+
75
+ if client is not None:
76
+ self.client = client
77
+ # Ensure the client has the correct base_url and headers
78
+ self.client.base_url = httpx.URL(self.host)
79
+ self.client.headers.update(
80
+ {
81
+ "Authorization": f"Token {self.api_key}",
82
+ "Mem0-User-ID": self.user_id,
83
+ }
84
+ )
85
+ else:
86
+ self.client = httpx.Client(
87
+ base_url=self.host,
88
+ headers={
89
+ "Authorization": f"Token {self.api_key}",
90
+ "Mem0-User-ID": self.user_id,
91
+ },
92
+ timeout=300,
93
+ )
94
+ self.user_email = self._validate_api_key()
95
+
96
+ # Initialize project manager
97
+ self.project = Project(
98
+ client=self.client,
99
+ org_id=self.org_id,
100
+ project_id=self.project_id,
101
+ user_email=self.user_email,
102
+ )
103
+
104
+ capture_client_event("client.init", self, {"sync_type": "sync"})
105
+
106
+ def _validate_api_key(self):
107
+ """Validate the API key by making a test request."""
108
+ try:
109
+ params = self._prepare_params()
110
+ response = self.client.get("/v1/ping/", params=params)
111
+ data = response.json()
112
+
113
+ response.raise_for_status()
114
+
115
+ if data.get("org_id") and data.get("project_id"):
116
+ self.org_id = data.get("org_id")
117
+ self.project_id = data.get("project_id")
118
+
119
+ return data.get("user_email")
120
+
121
+ except httpx.HTTPStatusError as e:
122
+ try:
123
+ error_data = e.response.json()
124
+ error_message = error_data.get("detail", str(e))
125
+ except Exception:
126
+ error_message = str(e)
127
+ raise ValueError(f"Error: {error_message}")
128
+
129
+ @api_error_handler
130
+ def add(self, messages: List[Dict[str, str]], **kwargs) -> Dict[str, Any]:
131
+ """Add a new memory.
132
+
133
+ Args:
134
+ messages: A list of message dictionaries.
135
+ **kwargs: Additional parameters such as user_id, agent_id, app_id,
136
+ metadata, filters.
137
+
138
+ Returns:
139
+ A dictionary containing the API response.
140
+
141
+ Raises:
142
+ APIError: If the API request fails.
143
+ """
144
+ kwargs = self._prepare_params(kwargs)
145
+ if kwargs.get("output_format") != "v1.1":
146
+ kwargs["output_format"] = "v1.1"
147
+ warnings.warn(
148
+ (
149
+ "output_format='v1.0' is deprecated therefore setting it to "
150
+ "'v1.1' by default. Check out the docs for more information: "
151
+ "https://docs.mem0.ai/platform/quickstart#4-1-create-memories"
152
+ ),
153
+ DeprecationWarning,
154
+ stacklevel=2,
155
+ )
156
+ kwargs["version"] = "v2"
157
+ payload = self._prepare_payload(messages, kwargs)
158
+ response = self.client.post("/v1/memories/", json=payload)
159
+ response.raise_for_status()
160
+ if "metadata" in kwargs:
161
+ del kwargs["metadata"]
162
+ capture_client_event("client.add", self, {"keys": list(kwargs.keys()), "sync_type": "sync"})
163
+ return response.json()
164
+
165
+ @api_error_handler
166
+ def get(self, memory_id: str) -> Dict[str, Any]:
167
+ """Retrieve a specific memory by ID.
168
+
169
+ Args:
170
+ memory_id: The ID of the memory to retrieve.
171
+
172
+ Returns:
173
+ A dictionary containing the memory data.
174
+
175
+ Raises:
176
+ APIError: If the API request fails.
177
+ """
178
+ params = self._prepare_params()
179
+ response = self.client.get(f"/v1/memories/{memory_id}/", params=params)
180
+ response.raise_for_status()
181
+ capture_client_event("client.get", self, {"memory_id": memory_id, "sync_type": "sync"})
182
+ return response.json()
183
+
184
+ @api_error_handler
185
+ def get_all(self, version: str = "v1", **kwargs) -> List[Dict[str, Any]]:
186
+ """Retrieve all memories, with optional filtering.
187
+
188
+ Args:
189
+ version: The API version to use for the search endpoint.
190
+ **kwargs: Optional parameters for filtering (user_id, agent_id,
191
+ app_id, top_k).
192
+
193
+ Returns:
194
+ A list of dictionaries containing memories.
195
+
196
+ Raises:
197
+ APIError: If the API request fails.
198
+ """
199
+ params = self._prepare_params(kwargs)
200
+ if version == "v1":
201
+ response = self.client.get(f"/{version}/memories/", params=params)
202
+ elif version == "v2":
203
+ if "page" in params and "page_size" in params:
204
+ query_params = {
205
+ "page": params.pop("page"),
206
+ "page_size": params.pop("page_size"),
207
+ }
208
+ response = self.client.post(f"/{version}/memories/", json=params, params=query_params)
209
+ else:
210
+ response = self.client.post(f"/{version}/memories/", json=params)
211
+ response.raise_for_status()
212
+ if "metadata" in kwargs:
213
+ del kwargs["metadata"]
214
+ capture_client_event(
215
+ "client.get_all",
216
+ self,
217
+ {
218
+ "api_version": version,
219
+ "keys": list(kwargs.keys()),
220
+ "sync_type": "sync",
221
+ },
222
+ )
223
+ return response.json()
224
+
225
+ @api_error_handler
226
+ def search(self, query: str, version: str = "v1", **kwargs) -> List[Dict[str, Any]]:
227
+ """Search memories based on a query.
228
+
229
+ Args:
230
+ query: The search query string.
231
+ version: The API version to use for the search endpoint.
232
+ **kwargs: Additional parameters such as user_id, agent_id, app_id,
233
+ top_k, filters.
234
+
235
+ Returns:
236
+ A list of dictionaries containing search results.
237
+
238
+ Raises:
239
+ APIError: If the API request fails.
240
+ """
241
+ payload = {"query": query}
242
+ params = self._prepare_params(kwargs)
243
+ payload.update(params)
244
+ response = self.client.post(f"/{version}/memories/search/", json=payload)
245
+ response.raise_for_status()
246
+ if "metadata" in kwargs:
247
+ del kwargs["metadata"]
248
+ capture_client_event(
249
+ "client.search",
250
+ self,
251
+ {
252
+ "api_version": version,
253
+ "keys": list(kwargs.keys()),
254
+ "sync_type": "sync",
255
+ },
256
+ )
257
+ return response.json()
258
+
259
+ @api_error_handler
260
+ def update(
261
+ self,
262
+ memory_id: str,
263
+ text: Optional[str] = None,
264
+ metadata: Optional[Dict[str, Any]] = None,
265
+ ) -> Dict[str, Any]:
266
+ """
267
+ Update a memory by ID.
268
+ Args:
269
+ memory_id (str): Memory ID.
270
+ text (str, optional): Data to update in the memory.
271
+ metadata (dict, optional): Metadata to update in the memory.
272
+ Returns:
273
+ Dict[str, Any]: The response from the server.
274
+ """
275
+ if text is None and metadata is None:
276
+ raise ValueError("Either text or metadata must be provided for update.")
277
+
278
+ payload = {}
279
+ if text is not None:
280
+ payload["text"] = text
281
+ if metadata is not None:
282
+ payload["metadata"] = metadata
283
+
284
+ capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "sync"})
285
+ params = self._prepare_params()
286
+ response = self.client.put(f"/v1/memories/{memory_id}/", json=payload, params=params)
287
+ response.raise_for_status()
288
+ return response.json()
289
+
290
+ @api_error_handler
291
+ def delete(self, memory_id: str) -> Dict[str, Any]:
292
+ """Delete a specific memory by ID.
293
+
294
+ Args:
295
+ memory_id: The ID of the memory to delete.
296
+
297
+ Returns:
298
+ A dictionary containing the API response.
299
+
300
+ Raises:
301
+ APIError: If the API request fails.
302
+ """
303
+ params = self._prepare_params()
304
+ response = self.client.delete(f"/v1/memories/{memory_id}/", params=params)
305
+ response.raise_for_status()
306
+ capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "sync"})
307
+ return response.json()
308
+
309
+ @api_error_handler
310
+ def delete_all(self, **kwargs) -> Dict[str, str]:
311
+ """Delete all memories, with optional filtering.
312
+
313
+ Args:
314
+ **kwargs: Optional parameters for filtering (user_id, agent_id,
315
+ app_id).
316
+
317
+ Returns:
318
+ A dictionary containing the API response.
319
+
320
+ Raises:
321
+ APIError: If the API request fails.
322
+ """
323
+ params = self._prepare_params(kwargs)
324
+ response = self.client.delete("/v1/memories/", params=params)
325
+ response.raise_for_status()
326
+ capture_client_event(
327
+ "client.delete_all",
328
+ self,
329
+ {"keys": list(kwargs.keys()), "sync_type": "sync"},
330
+ )
331
+ return response.json()
332
+
333
+ @api_error_handler
334
+ def history(self, memory_id: str) -> List[Dict[str, Any]]:
335
+ """Retrieve the history of a specific memory.
336
+
337
+ Args:
338
+ memory_id: The ID of the memory to retrieve history for.
339
+
340
+ Returns:
341
+ A list of dictionaries containing the memory history.
342
+
343
+ Raises:
344
+ APIError: If the API request fails.
345
+ """
346
+ params = self._prepare_params()
347
+ response = self.client.get(f"/v1/memories/{memory_id}/history/", params=params)
348
+ response.raise_for_status()
349
+ capture_client_event("client.history", self, {"memory_id": memory_id, "sync_type": "sync"})
350
+ return response.json()
351
+
352
+ @api_error_handler
353
+ def users(self) -> Dict[str, Any]:
354
+ """Get all users, agents, and sessions for which memories exist."""
355
+ params = self._prepare_params()
356
+ response = self.client.get("/v1/entities/", params=params)
357
+ response.raise_for_status()
358
+ capture_client_event("client.users", self, {"sync_type": "sync"})
359
+ return response.json()
360
+
361
+ @api_error_handler
362
+ def delete_users(
363
+ self,
364
+ user_id: Optional[str] = None,
365
+ agent_id: Optional[str] = None,
366
+ app_id: Optional[str] = None,
367
+ run_id: Optional[str] = None,
368
+ ) -> Dict[str, str]:
369
+ """Delete specific entities or all entities if no filters provided.
370
+
371
+ Args:
372
+ user_id: Optional user ID to delete specific user
373
+ agent_id: Optional agent ID to delete specific agent
374
+ app_id: Optional app ID to delete specific app
375
+ run_id: Optional run ID to delete specific run
376
+
377
+ Returns:
378
+ Dict with success message
379
+
380
+ Raises:
381
+ ValueError: If specified entity not found
382
+ APIError: If deletion fails
383
+ """
384
+
385
+ if user_id:
386
+ to_delete = [{"type": "user", "name": user_id}]
387
+ elif agent_id:
388
+ to_delete = [{"type": "agent", "name": agent_id}]
389
+ elif app_id:
390
+ to_delete = [{"type": "app", "name": app_id}]
391
+ elif run_id:
392
+ to_delete = [{"type": "run", "name": run_id}]
393
+ else:
394
+ entities = self.users()
395
+ # Filter entities based on provided IDs using list comprehension
396
+ to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
397
+
398
+ params = self._prepare_params()
399
+
400
+ if not to_delete:
401
+ raise ValueError("No entities to delete")
402
+
403
+ # Delete entities and check response immediately
404
+ for entity in to_delete:
405
+ response = self.client.delete(f"/v2/entities/{entity['type']}/{entity['name']}/", params=params)
406
+ response.raise_for_status()
407
+
408
+ capture_client_event(
409
+ "client.delete_users",
410
+ self,
411
+ {
412
+ "user_id": user_id,
413
+ "agent_id": agent_id,
414
+ "app_id": app_id,
415
+ "run_id": run_id,
416
+ "sync_type": "sync",
417
+ },
418
+ )
419
+ return {
420
+ "message": "Entity deleted successfully."
421
+ if (user_id or agent_id or app_id or run_id)
422
+ else "All users, agents, apps and runs deleted."
423
+ }
424
+
425
+ @api_error_handler
426
+ def reset(self) -> Dict[str, str]:
427
+ """Reset the client by deleting all users and memories.
428
+
429
+ This method deletes all users, agents, sessions, and memories
430
+ associated with the client.
431
+
432
+ Returns:
433
+ Dict[str, str]: Message client reset successful.
434
+
435
+ Raises:
436
+ APIError: If the API request fails.
437
+ """
438
+ # Delete all users, agents, and sessions
439
+ # This will also delete the memories
440
+ self.delete_users()
441
+
442
+ capture_client_event("client.reset", self, {"sync_type": "sync"})
443
+ return {"message": "Client reset successful. All users and memories deleted."}
444
+
445
+ @api_error_handler
446
+ def batch_update(self, memories: List[Dict[str, Any]]) -> Dict[str, Any]:
447
+ """Batch update memories.
448
+
449
+ Args:
450
+ memories: List of memory dictionaries to update. Each dictionary
451
+ must contain:
452
+ - memory_id (str): ID of the memory to update
453
+ - text (str): New text content for the memory
454
+
455
+ Returns:
456
+ str: Message indicating the success of the batch update.
457
+
458
+ Raises:
459
+ APIError: If the API request fails.
460
+ """
461
+ response = self.client.put("/v1/batch/", json={"memories": memories})
462
+ response.raise_for_status()
463
+
464
+ capture_client_event("client.batch_update", self, {"sync_type": "sync"})
465
+ return response.json()
466
+
467
+ @api_error_handler
468
+ def batch_delete(self, memories: List[Dict[str, Any]]) -> Dict[str, Any]:
469
+ """Batch delete memories.
470
+
471
+ Args:
472
+ memories: List of memory dictionaries to delete. Each dictionary
473
+ must contain:
474
+ - memory_id (str): ID of the memory to delete
475
+
476
+ Returns:
477
+ str: Message indicating the success of the batch deletion.
478
+
479
+ Raises:
480
+ APIError: If the API request fails.
481
+ """
482
+ response = self.client.request("DELETE", "/v1/batch/", json={"memories": memories})
483
+ response.raise_for_status()
484
+
485
+ capture_client_event("client.batch_delete", self, {"sync_type": "sync"})
486
+ return response.json()
487
+
488
+ @api_error_handler
489
+ def create_memory_export(self, schema: str, **kwargs) -> Dict[str, Any]:
490
+ """Create a memory export with the provided schema.
491
+
492
+ Args:
493
+ schema: JSON schema defining the export structure
494
+ **kwargs: Optional filters like user_id, run_id, etc.
495
+
496
+ Returns:
497
+ Dict containing export request ID and status message
498
+ """
499
+ response = self.client.post(
500
+ "/v1/exports/",
501
+ json={"schema": schema, **self._prepare_params(kwargs)},
502
+ )
503
+ response.raise_for_status()
504
+ capture_client_event(
505
+ "client.create_memory_export",
506
+ self,
507
+ {
508
+ "schema": schema,
509
+ "keys": list(kwargs.keys()),
510
+ "sync_type": "sync",
511
+ },
512
+ )
513
+ return response.json()
514
+
515
+ @api_error_handler
516
+ def get_memory_export(self, **kwargs) -> Dict[str, Any]:
517
+ """Get a memory export.
518
+
519
+ Args:
520
+ **kwargs: Filters like user_id to get specific export
521
+
522
+ Returns:
523
+ Dict containing the exported data
524
+ """
525
+ response = self.client.post("/v1/exports/get/", json=self._prepare_params(kwargs))
526
+ response.raise_for_status()
527
+ capture_client_event(
528
+ "client.get_memory_export",
529
+ self,
530
+ {"keys": list(kwargs.keys()), "sync_type": "sync"},
531
+ )
532
+ return response.json()
533
+
534
+ @api_error_handler
535
+ def get_summary(self, filters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
536
+ """Get the summary of a memory export.
537
+
538
+ Args:
539
+ filters: Optional filters to apply to the summary request
540
+
541
+ Returns:
542
+ Dict containing the export status and summary data
543
+ """
544
+
545
+ response = self.client.post("/v1/summary/", json=self._prepare_params({"filters": filters}))
546
+ response.raise_for_status()
547
+ capture_client_event("client.get_summary", self, {"sync_type": "sync"})
548
+ return response.json()
549
+
550
+ @api_error_handler
551
+ def get_project(self, fields: Optional[List[str]] = None) -> Dict[str, Any]:
552
+ """Get instructions or categories for the current project.
553
+
554
+ Args:
555
+ fields: List of fields to retrieve
556
+
557
+ Returns:
558
+ Dictionary containing the requested fields.
559
+
560
+ Raises:
561
+ APIError: If the API request fails.
562
+ ValueError: If org_id or project_id are not set.
563
+ """
564
+ logger.warning(
565
+ "get_project() method is going to be deprecated in version v1.0 of the package. Please use the client.project.get() method instead."
566
+ )
567
+ if not (self.org_id and self.project_id):
568
+ raise ValueError("org_id and project_id must be set to access instructions or categories")
569
+
570
+ params = self._prepare_params({"fields": fields})
571
+ response = self.client.get(
572
+ f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
573
+ params=params,
574
+ )
575
+ response.raise_for_status()
576
+ capture_client_event(
577
+ "client.get_project_details",
578
+ self,
579
+ {"fields": fields, "sync_type": "sync"},
580
+ )
581
+ return response.json()
582
+
583
+ @api_error_handler
584
+ def update_project(
585
+ self,
586
+ custom_instructions: Optional[str] = None,
587
+ custom_categories: Optional[List[str]] = None,
588
+ retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
589
+ enable_graph: Optional[bool] = None,
590
+ version: Optional[str] = None,
591
+ ) -> Dict[str, Any]:
592
+ """Update the project settings.
593
+
594
+ Args:
595
+ custom_instructions: New instructions for the project
596
+ custom_categories: New categories for the project
597
+ retrieval_criteria: New retrieval criteria for the project
598
+ enable_graph: Enable or disable the graph for the project
599
+ version: Version of the project
600
+
601
+ Returns:
602
+ Dictionary containing the API response.
603
+
604
+ Raises:
605
+ APIError: If the API request fails.
606
+ ValueError: If org_id or project_id are not set.
607
+ """
608
+ logger.warning(
609
+ "update_project() method is going to be deprecated in version v1.0 of the package. Please use the client.project.update() method instead."
610
+ )
611
+ if not (self.org_id and self.project_id):
612
+ raise ValueError("org_id and project_id must be set to update instructions or categories")
613
+
614
+ if (
615
+ custom_instructions is None
616
+ and custom_categories is None
617
+ and retrieval_criteria is None
618
+ and enable_graph is None
619
+ and version is None
620
+ ):
621
+ raise ValueError(
622
+ "Currently we only support updating custom_instructions or "
623
+ "custom_categories or retrieval_criteria, so you must "
624
+ "provide at least one of them"
625
+ )
626
+
627
+ payload = self._prepare_params(
628
+ {
629
+ "custom_instructions": custom_instructions,
630
+ "custom_categories": custom_categories,
631
+ "retrieval_criteria": retrieval_criteria,
632
+ "enable_graph": enable_graph,
633
+ "version": version,
634
+ }
635
+ )
636
+ response = self.client.patch(
637
+ f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
638
+ json=payload,
639
+ )
640
+ response.raise_for_status()
641
+ capture_client_event(
642
+ "client.update_project",
643
+ self,
644
+ {
645
+ "custom_instructions": custom_instructions,
646
+ "custom_categories": custom_categories,
647
+ "retrieval_criteria": retrieval_criteria,
648
+ "enable_graph": enable_graph,
649
+ "version": version,
650
+ "sync_type": "sync",
651
+ },
652
+ )
653
+ return response.json()
654
+
655
+ def chat(self):
656
+ """Start a chat with the Mem0 AI. (Not implemented)
657
+
658
+ Raises:
659
+ NotImplementedError: This method is not implemented yet.
660
+ """
661
+ raise NotImplementedError("Chat is not implemented yet")
662
+
663
+ @api_error_handler
664
+ def get_webhooks(self, project_id: str) -> Dict[str, Any]:
665
+ """Get webhooks configuration for the project.
666
+
667
+ Args:
668
+ project_id: The ID of the project to get webhooks for.
669
+
670
+ Returns:
671
+ Dictionary containing webhook details.
672
+
673
+ Raises:
674
+ APIError: If the API request fails.
675
+ ValueError: If project_id is not set.
676
+ """
677
+
678
+ response = self.client.get(f"api/v1/webhooks/projects/{project_id}/")
679
+ response.raise_for_status()
680
+ capture_client_event("client.get_webhook", self, {"sync_type": "sync"})
681
+ return response.json()
682
+
683
+ @api_error_handler
684
+ def create_webhook(self, url: str, name: str, project_id: str, event_types: List[str]) -> Dict[str, Any]:
685
+ """Create a webhook for the current project.
686
+
687
+ Args:
688
+ url: The URL to send the webhook to.
689
+ name: The name of the webhook.
690
+ event_types: List of event types to trigger the webhook for.
691
+
692
+ Returns:
693
+ Dictionary containing the created webhook details.
694
+
695
+ Raises:
696
+ APIError: If the API request fails.
697
+ ValueError: If project_id is not set.
698
+ """
699
+
700
+ payload = {"url": url, "name": name, "event_types": event_types}
701
+ response = self.client.post(f"api/v1/webhooks/projects/{project_id}/", json=payload)
702
+ response.raise_for_status()
703
+ capture_client_event("client.create_webhook", self, {"sync_type": "sync"})
704
+ return response.json()
705
+
706
+ @api_error_handler
707
+ def update_webhook(
708
+ self,
709
+ webhook_id: int,
710
+ name: Optional[str] = None,
711
+ url: Optional[str] = None,
712
+ event_types: Optional[List[str]] = None,
713
+ ) -> Dict[str, Any]:
714
+ """Update a webhook configuration.
715
+
716
+ Args:
717
+ webhook_id: ID of the webhook to update
718
+ name: Optional new name for the webhook
719
+ url: Optional new URL for the webhook
720
+ event_types: Optional list of event types to trigger the webhook for.
721
+
722
+ Returns:
723
+ Dictionary containing the updated webhook details.
724
+
725
+ Raises:
726
+ APIError: If the API request fails.
727
+ """
728
+
729
+ payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
730
+ response = self.client.put(f"api/v1/webhooks/{webhook_id}/", json=payload)
731
+ response.raise_for_status()
732
+ capture_client_event("client.update_webhook", self, {"webhook_id": webhook_id, "sync_type": "sync"})
733
+ return response.json()
734
+
735
+ @api_error_handler
736
+ def delete_webhook(self, webhook_id: int) -> Dict[str, str]:
737
+ """Delete a webhook configuration.
738
+
739
+ Args:
740
+ webhook_id: ID of the webhook to delete
741
+
742
+ Returns:
743
+ Dictionary containing success message.
744
+
745
+ Raises:
746
+ APIError: If the API request fails.
747
+ """
748
+
749
+ response = self.client.delete(f"api/v1/webhooks/{webhook_id}/")
750
+ response.raise_for_status()
751
+ capture_client_event(
752
+ "client.delete_webhook",
753
+ self,
754
+ {"webhook_id": webhook_id, "sync_type": "sync"},
755
+ )
756
+ return response.json()
757
+
758
+ @api_error_handler
759
+ def feedback(
760
+ self,
761
+ memory_id: str,
762
+ feedback: Optional[str] = None,
763
+ feedback_reason: Optional[str] = None,
764
+ ) -> Dict[str, str]:
765
+ VALID_FEEDBACK_VALUES = {"POSITIVE", "NEGATIVE", "VERY_NEGATIVE"}
766
+
767
+ feedback = feedback.upper() if feedback else None
768
+ if feedback is not None and feedback not in VALID_FEEDBACK_VALUES:
769
+ raise ValueError(f"feedback must be one of {', '.join(VALID_FEEDBACK_VALUES)} or None")
770
+
771
+ data = {
772
+ "memory_id": memory_id,
773
+ "feedback": feedback,
774
+ "feedback_reason": feedback_reason,
775
+ }
776
+
777
+ response = self.client.post("/v1/feedback/", json=data)
778
+ response.raise_for_status()
779
+ capture_client_event("client.feedback", self, data, {"sync_type": "sync"})
780
+ return response.json()
781
+
782
+ def _prepare_payload(self, messages: List[Dict[str, str]], kwargs: Dict[str, Any]) -> Dict[str, Any]:
783
+ """Prepare the payload for API requests.
784
+
785
+ Args:
786
+ messages: The messages to include in the payload.
787
+ kwargs: Additional keyword arguments to include in the payload.
788
+
789
+ Returns:
790
+ A dictionary containing the prepared payload.
791
+ """
792
+ payload = {}
793
+ payload["messages"] = messages
794
+
795
+ payload.update({k: v for k, v in kwargs.items() if v is not None})
796
+ return payload
797
+
798
+ def _prepare_params(self, kwargs: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
799
+ """Prepare query parameters for API requests.
800
+
801
+ Args:
802
+ kwargs: Keyword arguments to include in the parameters.
803
+
804
+ Returns:
805
+ A dictionary containing the prepared parameters.
806
+
807
+ Raises:
808
+ ValueError: If either org_id or project_id is provided but not both.
809
+ """
810
+
811
+ if kwargs is None:
812
+ kwargs = {}
813
+
814
+ # Add org_id and project_id if both are available
815
+ if self.org_id and self.project_id:
816
+ kwargs["org_id"] = self.org_id
817
+ kwargs["project_id"] = self.project_id
818
+ elif self.org_id or self.project_id:
819
+ raise ValueError("Please provide both org_id and project_id")
820
+
821
+ return {k: v for k, v in kwargs.items() if v is not None}
822
+
823
+
824
+ class AsyncMemoryClient:
825
+ """Asynchronous client for interacting with the Mem0 API.
826
+
827
+ This class provides asynchronous versions of all MemoryClient methods.
828
+ It uses httpx.AsyncClient for making non-blocking API requests.
829
+ """
830
+
831
+ def __init__(
832
+ self,
833
+ api_key: Optional[str] = None,
834
+ host: Optional[str] = None,
835
+ org_id: Optional[str] = None,
836
+ project_id: Optional[str] = None,
837
+ client: Optional[httpx.AsyncClient] = None,
838
+ ):
839
+ """Initialize the AsyncMemoryClient.
840
+
841
+ Args:
842
+ api_key: The API key for authenticating with the Mem0 API. If not
843
+ provided, it will attempt to use the MEM0_API_KEY
844
+ environment variable.
845
+ host: The base URL for the Mem0 API. Defaults to
846
+ "https://api.mem0.ai".
847
+ org_id: The ID of the organization.
848
+ project_id: The ID of the project.
849
+ client: A custom httpx.AsyncClient instance. If provided, it will
850
+ be used instead of creating a new one. Note that base_url
851
+ and headers will be set/overridden as needed.
852
+
853
+ Raises:
854
+ ValueError: If no API key is provided or found in the environment.
855
+ """
856
+ self.api_key = api_key or os.getenv("MEM0_API_KEY")
857
+ self.host = host or "https://api.mem0.ai"
858
+ self.org_id = org_id
859
+ self.project_id = project_id
860
+ self.user_id = get_user_id()
861
+
862
+ if not self.api_key:
863
+ raise ValueError("Mem0 API Key not provided. Please provide an API Key.")
864
+
865
+ # Create MD5 hash of API key for user_id
866
+ self.user_id = hashlib.md5(self.api_key.encode()).hexdigest()
867
+
868
+ if client is not None:
869
+ self.async_client = client
870
+ # Ensure the client has the correct base_url and headers
871
+ self.async_client.base_url = httpx.URL(self.host)
872
+ self.async_client.headers.update(
873
+ {
874
+ "Authorization": f"Token {self.api_key}",
875
+ "Mem0-User-ID": self.user_id,
876
+ }
877
+ )
878
+ else:
879
+ self.async_client = httpx.AsyncClient(
880
+ base_url=self.host,
881
+ headers={
882
+ "Authorization": f"Token {self.api_key}",
883
+ "Mem0-User-ID": self.user_id,
884
+ },
885
+ timeout=300,
886
+ )
887
+
888
+ self.user_email = self._validate_api_key()
889
+
890
+ # Initialize project manager
891
+ self.project = AsyncProject(
892
+ client=self.async_client,
893
+ org_id=self.org_id,
894
+ project_id=self.project_id,
895
+ user_email=self.user_email,
896
+ )
897
+
898
+ capture_client_event("client.init", self, {"sync_type": "async"})
899
+
900
+ def _validate_api_key(self):
901
+ """Validate the API key by making a test request."""
902
+ try:
903
+ params = self._prepare_params()
904
+ response = requests.get(
905
+ f"{self.host}/v1/ping/",
906
+ headers={
907
+ "Authorization": f"Token {self.api_key}",
908
+ "Mem0-User-ID": self.user_id,
909
+ },
910
+ params=params,
911
+ )
912
+ data = response.json()
913
+
914
+ response.raise_for_status()
915
+
916
+ if data.get("org_id") and data.get("project_id"):
917
+ self.org_id = data.get("org_id")
918
+ self.project_id = data.get("project_id")
919
+
920
+ return data.get("user_email")
921
+
922
+ except requests.exceptions.HTTPError as e:
923
+ try:
924
+ error_data = e.response.json()
925
+ error_message = error_data.get("detail", str(e))
926
+ except Exception:
927
+ error_message = str(e)
928
+ raise ValueError(f"Error: {error_message}")
929
+
930
+ def _prepare_payload(self, messages: List[Dict[str, str]], kwargs: Dict[str, Any]) -> Dict[str, Any]:
931
+ """Prepare the payload for API requests.
932
+
933
+ Args:
934
+ messages: The messages to include in the payload.
935
+ kwargs: Additional keyword arguments to include in the payload.
936
+
937
+ Returns:
938
+ A dictionary containing the prepared payload.
939
+ """
940
+ payload = {}
941
+ payload["messages"] = messages
942
+
943
+ payload.update({k: v for k, v in kwargs.items() if v is not None})
944
+ return payload
945
+
946
+ def _prepare_params(self, kwargs: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
947
+ """Prepare query parameters for API requests.
948
+
949
+ Args:
950
+ kwargs: Keyword arguments to include in the parameters.
951
+
952
+ Returns:
953
+ A dictionary containing the prepared parameters.
954
+
955
+ Raises:
956
+ ValueError: If either org_id or project_id is provided but not both.
957
+ """
958
+
959
+ if kwargs is None:
960
+ kwargs = {}
961
+
962
+ # Add org_id and project_id if both are available
963
+ if self.org_id and self.project_id:
964
+ kwargs["org_id"] = self.org_id
965
+ kwargs["project_id"] = self.project_id
966
+ elif self.org_id or self.project_id:
967
+ raise ValueError("Please provide both org_id and project_id")
968
+
969
+ return {k: v for k, v in kwargs.items() if v is not None}
970
+
971
+ async def __aenter__(self):
972
+ return self
973
+
974
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
975
+ await self.async_client.aclose()
976
+
977
+ @api_error_handler
978
+ async def add(self, messages: List[Dict[str, str]], **kwargs) -> Dict[str, Any]:
979
+ kwargs = self._prepare_params(kwargs)
980
+ if kwargs.get("output_format") != "v1.1":
981
+ kwargs["output_format"] = "v1.1"
982
+ warnings.warn(
983
+ (
984
+ "output_format='v1.0' is deprecated therefore setting it to "
985
+ "'v1.1' by default. Check out the docs for more information: "
986
+ "https://docs.mem0.ai/platform/quickstart#4-1-create-memories"
987
+ ),
988
+ DeprecationWarning,
989
+ stacklevel=2,
990
+ )
991
+ kwargs["version"] = "v2"
992
+ payload = self._prepare_payload(messages, kwargs)
993
+ response = await self.async_client.post("/v1/memories/", json=payload)
994
+ response.raise_for_status()
995
+ if "metadata" in kwargs:
996
+ del kwargs["metadata"]
997
+ capture_client_event("client.add", self, {"keys": list(kwargs.keys()), "sync_type": "async"})
998
+ return response.json()
999
+
1000
+ @api_error_handler
1001
+ async def get(self, memory_id: str) -> Dict[str, Any]:
1002
+ params = self._prepare_params()
1003
+ response = await self.async_client.get(f"/v1/memories/{memory_id}/", params=params)
1004
+ response.raise_for_status()
1005
+ capture_client_event("client.get", self, {"memory_id": memory_id, "sync_type": "async"})
1006
+ return response.json()
1007
+
1008
+ @api_error_handler
1009
+ async def get_all(self, version: str = "v1", **kwargs) -> List[Dict[str, Any]]:
1010
+ params = self._prepare_params(kwargs)
1011
+ if version == "v1":
1012
+ response = await self.async_client.get(f"/{version}/memories/", params=params)
1013
+ elif version == "v2":
1014
+ if "page" in params and "page_size" in params:
1015
+ query_params = {
1016
+ "page": params.pop("page"),
1017
+ "page_size": params.pop("page_size"),
1018
+ }
1019
+ response = await self.async_client.post(f"/{version}/memories/", json=params, params=query_params)
1020
+ else:
1021
+ response = await self.async_client.post(f"/{version}/memories/", json=params)
1022
+ response.raise_for_status()
1023
+ if "metadata" in kwargs:
1024
+ del kwargs["metadata"]
1025
+ capture_client_event(
1026
+ "client.get_all",
1027
+ self,
1028
+ {
1029
+ "api_version": version,
1030
+ "keys": list(kwargs.keys()),
1031
+ "sync_type": "async",
1032
+ },
1033
+ )
1034
+ return response.json()
1035
+
1036
+ @api_error_handler
1037
+ async def search(self, query: str, version: str = "v1", **kwargs) -> List[Dict[str, Any]]:
1038
+ payload = {"query": query}
1039
+ payload.update(self._prepare_params(kwargs))
1040
+ response = await self.async_client.post(f"/{version}/memories/search/", json=payload)
1041
+ response.raise_for_status()
1042
+ if "metadata" in kwargs:
1043
+ del kwargs["metadata"]
1044
+ capture_client_event(
1045
+ "client.search",
1046
+ self,
1047
+ {
1048
+ "api_version": version,
1049
+ "keys": list(kwargs.keys()),
1050
+ "sync_type": "async",
1051
+ },
1052
+ )
1053
+ return response.json()
1054
+
1055
+ @api_error_handler
1056
+ async def update(
1057
+ self, memory_id: str, text: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None
1058
+ ) -> Dict[str, Any]:
1059
+ """
1060
+ Update a memory by ID.
1061
+ Args:
1062
+ memory_id (str): Memory ID.
1063
+ text (str, optional): Data to update in the memory.
1064
+ metadata (dict, optional): Metadata to update in the memory.
1065
+ Returns:
1066
+ Dict[str, Any]: The response from the server.
1067
+ """
1068
+ if text is None and metadata is None:
1069
+ raise ValueError("Either text or metadata must be provided for update.")
1070
+
1071
+ payload = {}
1072
+ if text is not None:
1073
+ payload["text"] = text
1074
+ if metadata is not None:
1075
+ payload["metadata"] = metadata
1076
+
1077
+ capture_client_event("client.update", self, {"memory_id": memory_id, "sync_type": "async"})
1078
+ params = self._prepare_params()
1079
+ response = await self.async_client.put(f"/v1/memories/{memory_id}/", json=payload, params=params)
1080
+ response.raise_for_status()
1081
+ return response.json()
1082
+
1083
+ @api_error_handler
1084
+ async def delete(self, memory_id: str) -> Dict[str, Any]:
1085
+ """Delete a specific memory by ID.
1086
+
1087
+ Args:
1088
+ memory_id: The ID of the memory to delete.
1089
+
1090
+ Returns:
1091
+ A dictionary containing the API response.
1092
+
1093
+ Raises:
1094
+ APIError: If the API request fails.
1095
+ """
1096
+ params = self._prepare_params()
1097
+ response = await self.async_client.delete(f"/v1/memories/{memory_id}/", params=params)
1098
+ response.raise_for_status()
1099
+ capture_client_event("client.delete", self, {"memory_id": memory_id, "sync_type": "async"})
1100
+ return response.json()
1101
+
1102
+ @api_error_handler
1103
+ async def delete_all(self, **kwargs) -> Dict[str, str]:
1104
+ """Delete all memories, with optional filtering.
1105
+
1106
+ Args:
1107
+ **kwargs: Optional parameters for filtering (user_id, agent_id, app_id).
1108
+
1109
+ Returns:
1110
+ A dictionary containing the API response.
1111
+
1112
+ Raises:
1113
+ APIError: If the API request fails.
1114
+ """
1115
+ params = self._prepare_params(kwargs)
1116
+ response = await self.async_client.delete("/v1/memories/", params=params)
1117
+ response.raise_for_status()
1118
+ capture_client_event("client.delete_all", self, {"keys": list(kwargs.keys()), "sync_type": "async"})
1119
+ return response.json()
1120
+
1121
+ @api_error_handler
1122
+ async def history(self, memory_id: str) -> List[Dict[str, Any]]:
1123
+ """Retrieve the history of a specific memory.
1124
+
1125
+ Args:
1126
+ memory_id: The ID of the memory to retrieve history for.
1127
+
1128
+ Returns:
1129
+ A list of dictionaries containing the memory history.
1130
+
1131
+ Raises:
1132
+ APIError: If the API request fails.
1133
+ """
1134
+ params = self._prepare_params()
1135
+ response = await self.async_client.get(f"/v1/memories/{memory_id}/history/", params=params)
1136
+ response.raise_for_status()
1137
+ capture_client_event("client.history", self, {"memory_id": memory_id, "sync_type": "async"})
1138
+ return response.json()
1139
+
1140
+ @api_error_handler
1141
+ async def users(self) -> Dict[str, Any]:
1142
+ """Get all users, agents, and sessions for which memories exist."""
1143
+ params = self._prepare_params()
1144
+ response = await self.async_client.get("/v1/entities/", params=params)
1145
+ response.raise_for_status()
1146
+ capture_client_event("client.users", self, {"sync_type": "async"})
1147
+ return response.json()
1148
+
1149
+ @api_error_handler
1150
+ async def delete_users(
1151
+ self,
1152
+ user_id: Optional[str] = None,
1153
+ agent_id: Optional[str] = None,
1154
+ app_id: Optional[str] = None,
1155
+ run_id: Optional[str] = None,
1156
+ ) -> Dict[str, str]:
1157
+ """Delete specific entities or all entities if no filters provided.
1158
+
1159
+ Args:
1160
+ user_id: Optional user ID to delete specific user
1161
+ agent_id: Optional agent ID to delete specific agent
1162
+ app_id: Optional app ID to delete specific app
1163
+ run_id: Optional run ID to delete specific run
1164
+
1165
+ Returns:
1166
+ Dict with success message
1167
+
1168
+ Raises:
1169
+ ValueError: If specified entity not found
1170
+ APIError: If deletion fails
1171
+ """
1172
+
1173
+ if user_id:
1174
+ to_delete = [{"type": "user", "name": user_id}]
1175
+ elif agent_id:
1176
+ to_delete = [{"type": "agent", "name": agent_id}]
1177
+ elif app_id:
1178
+ to_delete = [{"type": "app", "name": app_id}]
1179
+ elif run_id:
1180
+ to_delete = [{"type": "run", "name": run_id}]
1181
+ else:
1182
+ entities = await self.users()
1183
+ # Filter entities based on provided IDs using list comprehension
1184
+ to_delete = [{"type": entity["type"], "name": entity["name"]} for entity in entities["results"]]
1185
+
1186
+ params = self._prepare_params()
1187
+
1188
+ if not to_delete:
1189
+ raise ValueError("No entities to delete")
1190
+
1191
+ # Delete entities and check response immediately
1192
+ for entity in to_delete:
1193
+ response = await self.async_client.delete(f"/v2/entities/{entity['type']}/{entity['name']}/", params=params)
1194
+ response.raise_for_status()
1195
+
1196
+ capture_client_event(
1197
+ "client.delete_users",
1198
+ self,
1199
+ {
1200
+ "user_id": user_id,
1201
+ "agent_id": agent_id,
1202
+ "app_id": app_id,
1203
+ "run_id": run_id,
1204
+ "sync_type": "async",
1205
+ },
1206
+ )
1207
+ return {
1208
+ "message": "Entity deleted successfully."
1209
+ if (user_id or agent_id or app_id or run_id)
1210
+ else "All users, agents, apps and runs deleted."
1211
+ }
1212
+
1213
+ @api_error_handler
1214
+ async def reset(self) -> Dict[str, str]:
1215
+ """Reset the client by deleting all users and memories.
1216
+
1217
+ This method deletes all users, agents, sessions, and memories
1218
+ associated with the client.
1219
+
1220
+ Returns:
1221
+ Dict[str, str]: Message client reset successful.
1222
+
1223
+ Raises:
1224
+ APIError: If the API request fails.
1225
+ """
1226
+ await self.delete_users()
1227
+ capture_client_event("client.reset", self, {"sync_type": "async"})
1228
+ return {"message": "Client reset successful. All users and memories deleted."}
1229
+
1230
+ @api_error_handler
1231
+ async def batch_update(self, memories: List[Dict[str, Any]]) -> Dict[str, Any]:
1232
+ """Batch update memories.
1233
+
1234
+ Args:
1235
+ memories: List of memory dictionaries to update. Each dictionary
1236
+ must contain:
1237
+ - memory_id (str): ID of the memory to update
1238
+ - text (str): New text content for the memory
1239
+
1240
+ Returns:
1241
+ str: Message indicating the success of the batch update.
1242
+
1243
+ Raises:
1244
+ APIError: If the API request fails.
1245
+ """
1246
+ response = await self.async_client.put("/v1/batch/", json={"memories": memories})
1247
+ response.raise_for_status()
1248
+
1249
+ capture_client_event("client.batch_update", self, {"sync_type": "async"})
1250
+ return response.json()
1251
+
1252
+ @api_error_handler
1253
+ async def batch_delete(self, memories: List[Dict[str, Any]]) -> Dict[str, Any]:
1254
+ """Batch delete memories.
1255
+
1256
+ Args:
1257
+ memories: List of memory dictionaries to delete. Each dictionary
1258
+ must contain:
1259
+ - memory_id (str): ID of the memory to delete
1260
+
1261
+ Returns:
1262
+ str: Message indicating the success of the batch deletion.
1263
+
1264
+ Raises:
1265
+ APIError: If the API request fails.
1266
+ """
1267
+ response = await self.async_client.request("DELETE", "/v1/batch/", json={"memories": memories})
1268
+ response.raise_for_status()
1269
+
1270
+ capture_client_event("client.batch_delete", self, {"sync_type": "async"})
1271
+ return response.json()
1272
+
1273
+ @api_error_handler
1274
+ async def create_memory_export(self, schema: str, **kwargs) -> Dict[str, Any]:
1275
+ """Create a memory export with the provided schema.
1276
+
1277
+ Args:
1278
+ schema: JSON schema defining the export structure
1279
+ **kwargs: Optional filters like user_id, run_id, etc.
1280
+
1281
+ Returns:
1282
+ Dict containing export request ID and status message
1283
+ """
1284
+ response = await self.async_client.post("/v1/exports/", json={"schema": schema, **self._prepare_params(kwargs)})
1285
+ response.raise_for_status()
1286
+ capture_client_event(
1287
+ "client.create_memory_export", self, {"schema": schema, "keys": list(kwargs.keys()), "sync_type": "async"}
1288
+ )
1289
+ return response.json()
1290
+
1291
+ @api_error_handler
1292
+ async def get_memory_export(self, **kwargs) -> Dict[str, Any]:
1293
+ """Get a memory export.
1294
+
1295
+ Args:
1296
+ **kwargs: Filters like user_id to get specific export
1297
+
1298
+ Returns:
1299
+ Dict containing the exported data
1300
+ """
1301
+ response = await self.async_client.post("/v1/exports/get/", json=self._prepare_params(kwargs))
1302
+ response.raise_for_status()
1303
+ capture_client_event("client.get_memory_export", self, {"keys": list(kwargs.keys()), "sync_type": "async"})
1304
+ return response.json()
1305
+
1306
+ @api_error_handler
1307
+ async def get_summary(self, filters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
1308
+ """Get the summary of a memory export.
1309
+
1310
+ Args:
1311
+ filters: Optional filters to apply to the summary request
1312
+
1313
+ Returns:
1314
+ Dict containing the export status and summary data
1315
+ """
1316
+
1317
+ response = await self.async_client.post("/v1/summary/", json=self._prepare_params({"filters": filters}))
1318
+ response.raise_for_status()
1319
+ capture_client_event("client.get_summary", self, {"sync_type": "async"})
1320
+ return response.json()
1321
+
1322
+ @api_error_handler
1323
+ async def get_project(self, fields: Optional[List[str]] = None) -> Dict[str, Any]:
1324
+ """Get instructions or categories for the current project.
1325
+
1326
+ Args:
1327
+ fields: List of fields to retrieve
1328
+
1329
+ Returns:
1330
+ Dictionary containing the requested fields.
1331
+
1332
+ Raises:
1333
+ APIError: If the API request fails.
1334
+ ValueError: If org_id or project_id are not set.
1335
+ """
1336
+ logger.warning(
1337
+ "get_project() method is going to be deprecated in version v1.0 of the package. Please use the client.project.get() method instead."
1338
+ )
1339
+ if not (self.org_id and self.project_id):
1340
+ raise ValueError("org_id and project_id must be set to access instructions or categories")
1341
+
1342
+ params = self._prepare_params({"fields": fields})
1343
+ response = await self.async_client.get(
1344
+ f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
1345
+ params=params,
1346
+ )
1347
+ response.raise_for_status()
1348
+ capture_client_event("client.get_project", self, {"fields": fields, "sync_type": "async"})
1349
+ return response.json()
1350
+
1351
+ @api_error_handler
1352
+ async def update_project(
1353
+ self,
1354
+ custom_instructions: Optional[str] = None,
1355
+ custom_categories: Optional[List[str]] = None,
1356
+ retrieval_criteria: Optional[List[Dict[str, Any]]] = None,
1357
+ enable_graph: Optional[bool] = None,
1358
+ version: Optional[str] = None,
1359
+ ) -> Dict[str, Any]:
1360
+ """Update the project settings.
1361
+
1362
+ Args:
1363
+ custom_instructions: New instructions for the project
1364
+ custom_categories: New categories for the project
1365
+ retrieval_criteria: New retrieval criteria for the project
1366
+ enable_graph: Enable or disable the graph for the project
1367
+ version: Version of the project
1368
+
1369
+ Returns:
1370
+ Dictionary containing the API response.
1371
+
1372
+ Raises:
1373
+ APIError: If the API request fails.
1374
+ ValueError: If org_id or project_id are not set.
1375
+ """
1376
+ logger.warning(
1377
+ "update_project() method is going to be deprecated in version v1.0 of the package. Please use the client.project.update() method instead."
1378
+ )
1379
+ if not (self.org_id and self.project_id):
1380
+ raise ValueError("org_id and project_id must be set to update instructions or categories")
1381
+
1382
+ if (
1383
+ custom_instructions is None
1384
+ and custom_categories is None
1385
+ and retrieval_criteria is None
1386
+ and enable_graph is None
1387
+ and version is None
1388
+ ):
1389
+ raise ValueError(
1390
+ "Currently we only support updating custom_instructions or custom_categories or retrieval_criteria, so you must provide at least one of them"
1391
+ )
1392
+
1393
+ payload = self._prepare_params(
1394
+ {
1395
+ "custom_instructions": custom_instructions,
1396
+ "custom_categories": custom_categories,
1397
+ "retrieval_criteria": retrieval_criteria,
1398
+ "enable_graph": enable_graph,
1399
+ "version": version,
1400
+ }
1401
+ )
1402
+ response = await self.async_client.patch(
1403
+ f"/api/v1/orgs/organizations/{self.org_id}/projects/{self.project_id}/",
1404
+ json=payload,
1405
+ )
1406
+ response.raise_for_status()
1407
+ capture_client_event(
1408
+ "client.update_project",
1409
+ self,
1410
+ {
1411
+ "custom_instructions": custom_instructions,
1412
+ "custom_categories": custom_categories,
1413
+ "retrieval_criteria": retrieval_criteria,
1414
+ "enable_graph": enable_graph,
1415
+ "version": version,
1416
+ "sync_type": "async",
1417
+ },
1418
+ )
1419
+ return response.json()
1420
+
1421
+ async def chat(self):
1422
+ """Start a chat with the Mem0 AI. (Not implemented)
1423
+
1424
+ Raises:
1425
+ NotImplementedError: This method is not implemented yet.
1426
+ """
1427
+ raise NotImplementedError("Chat is not implemented yet")
1428
+
1429
+ @api_error_handler
1430
+ async def get_webhooks(self, project_id: str) -> Dict[str, Any]:
1431
+ """Get webhooks configuration for the project.
1432
+
1433
+ Args:
1434
+ project_id: The ID of the project to get webhooks for.
1435
+
1436
+ Returns:
1437
+ Dictionary containing webhook details.
1438
+
1439
+ Raises:
1440
+ APIError: If the API request fails.
1441
+ ValueError: If project_id is not set.
1442
+ """
1443
+
1444
+ response = await self.async_client.get(f"api/v1/webhooks/projects/{project_id}/")
1445
+ response.raise_for_status()
1446
+ capture_client_event("client.get_webhook", self, {"sync_type": "async"})
1447
+ return response.json()
1448
+
1449
+ @api_error_handler
1450
+ async def create_webhook(self, url: str, name: str, project_id: str, event_types: List[str]) -> Dict[str, Any]:
1451
+ """Create a webhook for the current project.
1452
+
1453
+ Args:
1454
+ url: The URL to send the webhook to.
1455
+ name: The name of the webhook.
1456
+ event_types: List of event types to trigger the webhook for.
1457
+
1458
+ Returns:
1459
+ Dictionary containing the created webhook details.
1460
+
1461
+ Raises:
1462
+ APIError: If the API request fails.
1463
+ ValueError: If project_id is not set.
1464
+ """
1465
+
1466
+ payload = {"url": url, "name": name, "event_types": event_types}
1467
+ response = await self.async_client.post(f"api/v1/webhooks/projects/{project_id}/", json=payload)
1468
+ response.raise_for_status()
1469
+ capture_client_event("client.create_webhook", self, {"sync_type": "async"})
1470
+ return response.json()
1471
+
1472
+ @api_error_handler
1473
+ async def update_webhook(
1474
+ self,
1475
+ webhook_id: int,
1476
+ name: Optional[str] = None,
1477
+ url: Optional[str] = None,
1478
+ event_types: Optional[List[str]] = None,
1479
+ ) -> Dict[str, Any]:
1480
+ """Update a webhook configuration.
1481
+
1482
+ Args:
1483
+ webhook_id: ID of the webhook to update
1484
+ name: Optional new name for the webhook
1485
+ url: Optional new URL for the webhook
1486
+ event_types: Optional list of event types to trigger the webhook for.
1487
+
1488
+ Returns:
1489
+ Dictionary containing the updated webhook details.
1490
+
1491
+ Raises:
1492
+ APIError: If the API request fails.
1493
+ """
1494
+
1495
+ payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
1496
+ response = await self.async_client.put(f"api/v1/webhooks/{webhook_id}/", json=payload)
1497
+ response.raise_for_status()
1498
+ capture_client_event("client.update_webhook", self, {"webhook_id": webhook_id, "sync_type": "async"})
1499
+ return response.json()
1500
+
1501
+ @api_error_handler
1502
+ async def delete_webhook(self, webhook_id: int) -> Dict[str, str]:
1503
+ """Delete a webhook configuration.
1504
+
1505
+ Args:
1506
+ webhook_id: ID of the webhook to delete
1507
+
1508
+ Returns:
1509
+ Dictionary containing success message.
1510
+
1511
+ Raises:
1512
+ APIError: If the API request fails.
1513
+ """
1514
+
1515
+ response = await self.async_client.delete(f"api/v1/webhooks/{webhook_id}/")
1516
+ response.raise_for_status()
1517
+ capture_client_event("client.delete_webhook", self, {"webhook_id": webhook_id, "sync_type": "async"})
1518
+ return response.json()
1519
+
1520
+ @api_error_handler
1521
+ async def feedback(
1522
+ self, memory_id: str, feedback: Optional[str] = None, feedback_reason: Optional[str] = None
1523
+ ) -> Dict[str, str]:
1524
+ VALID_FEEDBACK_VALUES = {"POSITIVE", "NEGATIVE", "VERY_NEGATIVE"}
1525
+
1526
+ feedback = feedback.upper() if feedback else None
1527
+ if feedback is not None and feedback not in VALID_FEEDBACK_VALUES:
1528
+ raise ValueError(f"feedback must be one of {', '.join(VALID_FEEDBACK_VALUES)} or None")
1529
+
1530
+ data = {"memory_id": memory_id, "feedback": feedback, "feedback_reason": feedback_reason}
1531
+
1532
+ response = await self.async_client.post("/v1/feedback/", json=data)
1533
+ response.raise_for_status()
1534
+ capture_client_event("client.feedback", self, data, {"sync_type": "async"})
1535
+ return response.json()