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