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