feat: implement structured exception classes with error codes and sug… (#3279)

This commit is contained in:
Brinlee Kidd
2025-09-18 14:01:35 -07:00
committed by GitHub
parent ac72eb5ecc
commit d4e98dba38
5 changed files with 904 additions and 65 deletions
+181 -30
View File
@@ -9,6 +9,7 @@ import requests
from mem0.client.project import AsyncProject, Project
from mem0.client.utils import api_error_handler
# Exception classes are referenced in docstrings only
from mem0.memory.setup import get_user_id, setup_config
from mem0.memory.telemetry import capture_client_event
@@ -139,7 +140,12 @@ class MemoryClient:
A dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
kwargs = self._prepare_params(kwargs)
if kwargs.get("output_format") != "v1.1":
@@ -173,7 +179,12 @@ class MemoryClient:
A dictionary containing the memory data.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params()
response = self.client.get(f"/v1/memories/{memory_id}/", params=params)
@@ -194,7 +205,12 @@ class MemoryClient:
A list of dictionaries containing memories.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params(kwargs)
if version == "v1":
@@ -236,7 +252,12 @@ class MemoryClient:
A list of dictionaries containing search results.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
payload = {"query": query}
params = self._prepare_params(kwargs)
@@ -303,7 +324,12 @@ class MemoryClient:
A dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params()
response = self.client.delete(f"/v1/memories/{memory_id}/", params=params)
@@ -323,7 +349,12 @@ class MemoryClient:
A dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params(kwargs)
response = self.client.delete("/v1/memories/", params=params)
@@ -346,7 +377,12 @@ class MemoryClient:
A list of dictionaries containing the memory history.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params()
response = self.client.get(f"/v1/memories/{memory_id}/history/", params=params)
@@ -384,7 +420,10 @@ class MemoryClient:
Raises:
ValueError: If specified entity not found
APIError: If deletion fails
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
MemoryNotFoundError: If the entity doesn't exist.
NetworkError: If network connectivity issues occur.
"""
if user_id:
@@ -438,10 +477,13 @@ class MemoryClient:
Dict[str, str]: Message client reset successful.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
# Delete all users, agents, and sessions
# This will also delete the memories
self.delete_users()
capture_client_event("client.reset", self, {"sync_type": "sync"})
@@ -459,6 +501,14 @@ class MemoryClient:
Returns:
Dict[str, Any]: The response from the server.
Raises:
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
response = self.client.put("/v1/batch/", json={"memories": memories})
response.raise_for_status()
@@ -479,7 +529,12 @@ class MemoryClient:
str: Message indicating the success of the batch deletion.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
response = self.client.request("DELETE", "/v1/batch/", json={"memories": memories})
response.raise_for_status()
@@ -560,7 +615,12 @@ class MemoryClient:
Dictionary containing the requested fields.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If org_id or project_id are not set.
"""
logger.warning(
@@ -604,7 +664,12 @@ class MemoryClient:
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If org_id or project_id are not set.
"""
logger.warning(
@@ -673,7 +738,12 @@ class MemoryClient:
Dictionary containing webhook details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If project_id is not set.
"""
@@ -695,7 +765,12 @@ class MemoryClient:
Dictionary containing the created webhook details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If project_id is not set.
"""
@@ -725,7 +800,12 @@ class MemoryClient:
Dictionary containing the updated webhook details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
@@ -745,7 +825,12 @@ class MemoryClient:
Dictionary containing success message.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
response = self.client.delete(f"api/v1/webhooks/{webhook_id}/")
@@ -1098,7 +1183,12 @@ class AsyncMemoryClient:
A dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params()
response = await self.async_client.delete(f"/v1/memories/{memory_id}/", params=params)
@@ -1117,7 +1207,12 @@ class AsyncMemoryClient:
A dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params(kwargs)
response = await self.async_client.delete("/v1/memories/", params=params)
@@ -1136,7 +1231,12 @@ class AsyncMemoryClient:
A list of dictionaries containing the memory history.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
params = self._prepare_params()
response = await self.async_client.get(f"/v1/memories/{memory_id}/history/", params=params)
@@ -1174,7 +1274,10 @@ class AsyncMemoryClient:
Raises:
ValueError: If specified entity not found
APIError: If deletion fails
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
MemoryNotFoundError: If the entity doesn't exist.
NetworkError: If network connectivity issues occur.
"""
if user_id:
@@ -1228,7 +1331,12 @@ class AsyncMemoryClient:
Dict[str, str]: Message client reset successful.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
await self.delete_users()
capture_client_event("client.reset", self, {"sync_type": "async"})
@@ -1246,6 +1354,14 @@ class AsyncMemoryClient:
Returns:
Dict[str, Any]: The response from the server.
Raises:
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
response = await self.async_client.put("/v1/batch/", json={"memories": memories})
response.raise_for_status()
@@ -1266,7 +1382,12 @@ class AsyncMemoryClient:
str: Message indicating the success of the batch deletion.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
response = await self.async_client.request("DELETE", "/v1/batch/", json={"memories": memories})
response.raise_for_status()
@@ -1334,7 +1455,12 @@ class AsyncMemoryClient:
Dictionary containing the requested fields.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If org_id or project_id are not set.
"""
logger.warning(
@@ -1374,7 +1500,12 @@ class AsyncMemoryClient:
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If org_id or project_id are not set.
"""
logger.warning(
@@ -1441,7 +1572,12 @@ class AsyncMemoryClient:
Dictionary containing webhook details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If project_id is not set.
"""
@@ -1463,7 +1599,12 @@ class AsyncMemoryClient:
Dictionary containing the created webhook details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
ValueError: If project_id is not set.
"""
@@ -1493,7 +1634,12 @@ class AsyncMemoryClient:
Dictionary containing the updated webhook details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
payload = {k: v for k, v in {"name": name, "url": url, "event_types": event_types}.items() if v is not None}
@@ -1513,7 +1659,12 @@ class AsyncMemoryClient:
Dictionary containing success message.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
MemoryQuotaExceededError: If memory quota is exceeded.
NetworkError: If network connectivity issues occur.
MemoryNotFoundError: If the memory doesn't exist (for updates/deletes).
"""
response = await self.async_client.delete(f"api/v1/webhooks/{webhook_id}/")
+97 -24
View File
@@ -7,6 +7,7 @@ from pydantic import BaseModel, ConfigDict, Field
from mem0.client.utils import api_error_handler
from mem0.memory.telemetry import capture_client_event
# Exception classes are referenced in docstrings only
logger = logging.getLogger(__name__)
@@ -141,7 +142,10 @@ class BaseProject(ABC):
Dictionary containing the requested project fields.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
pass
@@ -159,7 +163,10 @@ class BaseProject(ABC):
Dictionary containing the created project details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id is not set.
"""
pass
@@ -185,7 +192,10 @@ class BaseProject(ABC):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
pass
@@ -199,7 +209,10 @@ class BaseProject(ABC):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
pass
@@ -213,7 +226,10 @@ class BaseProject(ABC):
Dictionary containing the list of project members.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
pass
@@ -231,7 +247,10 @@ class BaseProject(ABC):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
pass
@@ -249,7 +268,10 @@ class BaseProject(ABC):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
pass
@@ -266,7 +288,10 @@ class BaseProject(ABC):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
pass
@@ -310,7 +335,10 @@ class Project(BaseProject):
Dictionary containing the requested project fields.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
params = self._prepare_params({"fields": fields})
@@ -339,7 +367,10 @@ class Project(BaseProject):
Dictionary containing the created project details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id is not set.
"""
if not self.config.org_id:
@@ -382,7 +413,10 @@ class Project(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
if (
@@ -432,7 +466,10 @@ class Project(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
response = self._client.delete(
@@ -455,7 +492,10 @@ class Project(BaseProject):
Dictionary containing the list of project members.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
response = self._client.get(
@@ -482,7 +522,10 @@ class Project(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
if role not in ["READER", "OWNER"]:
@@ -515,7 +558,10 @@ class Project(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
if role not in ["READER", "OWNER"]:
@@ -547,7 +593,10 @@ class Project(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
params = {"email": email}
@@ -603,7 +652,10 @@ class AsyncProject(BaseProject):
Dictionary containing the requested project fields.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
params = self._prepare_params({"fields": fields})
@@ -632,7 +684,10 @@ class AsyncProject(BaseProject):
Dictionary containing the created project details.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id is not set.
"""
if not self.config.org_id:
@@ -675,7 +730,10 @@ class AsyncProject(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
if (
@@ -725,7 +783,10 @@ class AsyncProject(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
response = await self._client.delete(
@@ -748,7 +809,10 @@ class AsyncProject(BaseProject):
Dictionary containing the list of project members.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
response = await self._client.get(
@@ -775,7 +839,10 @@ class AsyncProject(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
if role not in ["READER", "OWNER"]:
@@ -808,7 +875,10 @@ class AsyncProject(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
if role not in ["READER", "OWNER"]:
@@ -840,7 +910,10 @@ class AsyncProject(BaseProject):
Dictionary containing the API response.
Raises:
APIError: If the API request fails.
ValidationError: If the input data is invalid.
AuthenticationError: If authentication fails.
RateLimitError: If rate limits are exceeded.
NetworkError: If network connectivity issues occur.
ValueError: If org_id or project_id are not set.
"""
params = {"email": email}
+91 -5
View File
@@ -1,18 +1,35 @@
import json
import logging
import httpx
from mem0.exceptions import (
NetworkError,
create_exception_from_response,
)
logger = logging.getLogger(__name__)
class APIError(Exception):
"""Exception raised for errors in the API."""
"""Exception raised for errors in the API.
Deprecated: Use specific exception classes from mem0.exceptions instead.
This class is maintained for backward compatibility.
"""
pass
def api_error_handler(func):
"""Decorator to handle API errors consistently."""
"""Decorator to handle API errors consistently.
This decorator catches HTTP and request errors and converts them to
appropriate structured exception classes with detailed error information.
The decorator analyzes HTTP status codes and response content to create
the most specific exception type with helpful error messages, suggestions,
and debug information.
"""
from functools import wraps
@wraps(func)
@@ -21,9 +38,78 @@ def api_error_handler(func):
return func(*args, **kwargs)
except httpx.HTTPStatusError as e:
logger.error(f"HTTP error occurred: {e}")
raise APIError(f"API request failed: {e.response.text}")
# Extract error details from response
response_text = ""
error_details = {}
debug_info = {
"status_code": e.response.status_code,
"url": str(e.request.url),
"method": e.request.method,
}
try:
response_text = e.response.text
# Try to parse JSON response for additional error details
if e.response.headers.get("content-type", "").startswith("application/json"):
error_data = json.loads(response_text)
if isinstance(error_data, dict):
error_details = error_data
response_text = error_data.get("detail", response_text)
except (json.JSONDecodeError, AttributeError):
# Fallback to plain text response
pass
# Add rate limit information if available
if e.response.status_code == 429:
retry_after = e.response.headers.get("Retry-After")
if retry_after:
try:
debug_info["retry_after"] = int(retry_after)
except ValueError:
pass
# Add rate limit headers if available
for header in ["X-RateLimit-Limit", "X-RateLimit-Remaining", "X-RateLimit-Reset"]:
value = e.response.headers.get(header)
if value:
debug_info[header.lower().replace("-", "_")] = value
# Create specific exception based on status code
exception = create_exception_from_response(
status_code=e.response.status_code,
response_text=response_text,
details=error_details,
debug_info=debug_info,
)
raise exception
except httpx.RequestError as e:
logger.error(f"Request error occurred: {e}")
raise APIError(f"Request failed: {str(e)}")
# Determine the appropriate exception type based on error type
if isinstance(e, httpx.TimeoutException):
raise NetworkError(
message=f"Request timed out: {str(e)}",
error_code="NET_TIMEOUT",
suggestion="Please check your internet connection and try again",
debug_info={"error_type": "timeout", "original_error": str(e)},
)
elif isinstance(e, httpx.ConnectError):
raise NetworkError(
message=f"Connection failed: {str(e)}",
error_code="NET_CONNECT",
suggestion="Please check your internet connection and try again",
debug_info={"error_type": "connection", "original_error": str(e)},
)
else:
# Generic network error for other request errors
raise NetworkError(
message=f"Network request failed: {str(e)}",
error_code="NET_GENERIC",
suggestion="Please check your internet connection and try again",
debug_info={"error_type": "request", "original_error": str(e)},
)
return wrapper
+503
View File
@@ -0,0 +1,503 @@
"""Structured exception classes for Mem0 with error codes, suggestions, and debug information.
This module provides a comprehensive set of exception classes that replace the generic
APIError with specific, actionable exceptions. Each exception includes error codes,
user-friendly suggestions, and debug information to enable better error handling
and recovery in applications using Mem0.
Example:
Basic usage:
try:
memory.add(content, user_id=user_id)
except RateLimitError as e:
# Implement exponential backoff
time.sleep(e.debug_info.get('retry_after', 60))
except MemoryQuotaExceededError as e:
# Trigger quota upgrade flow
logger.error(f"Quota exceeded: {e.error_code}")
except ValidationError as e:
# Return user-friendly error
raise HTTPException(400, detail=e.suggestion)
Advanced usage with error context:
try:
memory.update(memory_id, content=new_content)
except MemoryNotFoundError as e:
logger.warning(f"Memory {memory_id} not found: {e.message}")
if e.suggestion:
logger.info(f"Suggestion: {e.suggestion}")
"""
from typing import Any, Dict, Optional
class MemoryError(Exception):
"""Base exception for all memory-related errors.
This is the base class for all Mem0-specific exceptions. It provides a structured
approach to error handling with error codes, contextual details, suggestions for
resolution, and debug information.
Attributes:
message (str): Human-readable error message.
error_code (str): Unique error identifier for programmatic handling.
details (dict): Additional context about the error.
suggestion (str): User-friendly suggestion for resolving the error.
debug_info (dict): Technical debugging information.
Example:
raise MemoryError(
message="Memory operation failed",
error_code="MEM_001",
details={"operation": "add", "user_id": "user123"},
suggestion="Please check your API key and try again",
debug_info={"request_id": "req_456", "timestamp": "2024-01-01T00:00:00Z"}
)
"""
def __init__(
self,
message: str,
error_code: str,
details: Optional[Dict[str, Any]] = None,
suggestion: Optional[str] = None,
debug_info: Optional[Dict[str, Any]] = None,
):
"""Initialize a MemoryError.
Args:
message: Human-readable error message.
error_code: Unique error identifier.
details: Additional context about the error.
suggestion: User-friendly suggestion for resolving the error.
debug_info: Technical debugging information.
"""
self.message = message
self.error_code = error_code
self.details = details or {}
self.suggestion = suggestion
self.debug_info = debug_info or {}
super().__init__(self.message)
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"message={self.message!r}, "
f"error_code={self.error_code!r}, "
f"details={self.details!r}, "
f"suggestion={self.suggestion!r}, "
f"debug_info={self.debug_info!r})"
)
class AuthenticationError(MemoryError):
"""Raised when authentication fails.
This exception is raised when API key validation fails, tokens are invalid,
or authentication credentials are missing or expired.
Common scenarios:
- Invalid API key
- Expired authentication token
- Missing authentication headers
- Insufficient permissions
Example:
raise AuthenticationError(
message="Invalid API key provided",
error_code="AUTH_001",
suggestion="Please check your API key in the Mem0 dashboard"
)
"""
pass
class RateLimitError(MemoryError):
"""Raised when rate limits are exceeded.
This exception is raised when the API rate limit has been exceeded.
It includes information about retry timing and current rate limit status.
The debug_info typically contains:
- retry_after: Seconds to wait before retrying
- limit: Current rate limit
- remaining: Remaining requests in current window
- reset_time: When the rate limit window resets
Example:
raise RateLimitError(
message="Rate limit exceeded",
error_code="RATE_001",
suggestion="Please wait before making more requests",
debug_info={"retry_after": 60, "limit": 100, "remaining": 0}
)
"""
pass
class ValidationError(MemoryError):
"""Raised when input validation fails.
This exception is raised when request parameters, memory content,
or configuration values fail validation checks.
Common scenarios:
- Invalid user_id format
- Missing required fields
- Content too long or too short
- Invalid metadata format
- Malformed filters
Example:
raise ValidationError(
message="Invalid user_id format",
error_code="VAL_001",
details={"field": "user_id", "value": "123", "expected": "string"},
suggestion="User ID must be a non-empty string"
)
"""
pass
class MemoryNotFoundError(MemoryError):
"""Raised when a memory is not found.
This exception is raised when attempting to access, update, or delete
a memory that doesn't exist or is not accessible to the current user.
Example:
raise MemoryNotFoundError(
message="Memory not found",
error_code="MEM_404",
details={"memory_id": "mem_123", "user_id": "user_456"},
suggestion="Please check the memory ID and ensure it exists"
)
"""
pass
class NetworkError(MemoryError):
"""Raised when network connectivity issues occur.
This exception is raised for network-related problems such as
connection timeouts, DNS resolution failures, or service unavailability.
Common scenarios:
- Connection timeout
- DNS resolution failure
- Service temporarily unavailable
- Network connectivity issues
Example:
raise NetworkError(
message="Connection timeout",
error_code="NET_001",
suggestion="Please check your internet connection and try again",
debug_info={"timeout": 30, "endpoint": "api.mem0.ai"}
)
"""
pass
class ConfigurationError(MemoryError):
"""Raised when client configuration is invalid.
This exception is raised when the client is improperly configured,
such as missing required settings or invalid configuration values.
Common scenarios:
- Missing API key
- Invalid host URL
- Incompatible configuration options
- Missing required environment variables
Example:
raise ConfigurationError(
message="API key not configured",
error_code="CFG_001",
suggestion="Set MEM0_API_KEY environment variable or pass api_key parameter"
)
"""
pass
class MemoryQuotaExceededError(MemoryError):
"""Raised when user's memory quota is exceeded.
This exception is raised when the user has reached their memory
storage or usage limits.
The debug_info typically contains:
- current_usage: Current memory usage
- quota_limit: Maximum allowed usage
- usage_type: Type of quota (storage, requests, etc.)
Example:
raise MemoryQuotaExceededError(
message="Memory quota exceeded",
error_code="QUOTA_001",
suggestion="Please upgrade your plan or delete unused memories",
debug_info={"current_usage": 1000, "quota_limit": 1000, "usage_type": "memories"}
)
"""
pass
class MemoryCorruptionError(MemoryError):
"""Raised when memory data is corrupted.
This exception is raised when stored memory data is found to be
corrupted, malformed, or otherwise unreadable.
Example:
raise MemoryCorruptionError(
message="Memory data is corrupted",
error_code="CORRUPT_001",
details={"memory_id": "mem_123"},
suggestion="Please contact support for data recovery assistance"
)
"""
pass
class VectorSearchError(MemoryError):
"""Raised when vector search operations fail.
This exception is raised when vector database operations fail,
such as search queries, embedding generation, or index operations.
Common scenarios:
- Embedding model unavailable
- Vector index corruption
- Search query timeout
- Incompatible vector dimensions
Example:
raise VectorSearchError(
message="Vector search failed",
error_code="VEC_001",
details={"query": "find similar memories", "vector_dim": 1536},
suggestion="Please try a simpler search query"
)
"""
pass
class CacheError(MemoryError):
"""Raised when caching operations fail.
This exception is raised when cache-related operations fail,
such as cache misses, cache invalidation errors, or cache corruption.
Example:
raise CacheError(
message="Cache operation failed",
error_code="CACHE_001",
details={"operation": "get", "key": "user_memories_123"},
suggestion="Cache will be refreshed automatically"
)
"""
pass
# OSS-specific exception classes
class VectorStoreError(MemoryError):
"""Raised when vector store operations fail.
This exception is raised when vector store operations fail,
such as embedding storage, similarity search, or vector operations.
Example:
raise VectorStoreError(
message="Vector store operation failed",
error_code="VECTOR_001",
details={"operation": "search", "collection": "memories"},
suggestion="Please check your vector store configuration and connection"
)
"""
def __init__(self, message: str, error_code: str = "VECTOR_001", details: dict = None,
suggestion: str = "Please check your vector store configuration and connection",
debug_info: dict = None):
super().__init__(message, error_code, details, suggestion, debug_info)
class GraphStoreError(MemoryError):
"""Raised when graph store operations fail.
This exception is raised when graph store operations fail,
such as relationship creation, entity management, or graph queries.
Example:
raise GraphStoreError(
message="Graph store operation failed",
error_code="GRAPH_001",
details={"operation": "create_relationship", "entity": "user_123"},
suggestion="Please check your graph store configuration and connection"
)
"""
def __init__(self, message: str, error_code: str = "GRAPH_001", details: dict = None,
suggestion: str = "Please check your graph store configuration and connection",
debug_info: dict = None):
super().__init__(message, error_code, details, suggestion, debug_info)
class EmbeddingError(MemoryError):
"""Raised when embedding operations fail.
This exception is raised when embedding operations fail,
such as text embedding generation or embedding model errors.
Example:
raise EmbeddingError(
message="Embedding generation failed",
error_code="EMBED_001",
details={"text_length": 1000, "model": "openai"},
suggestion="Please check your embedding model configuration"
)
"""
def __init__(self, message: str, error_code: str = "EMBED_001", details: dict = None,
suggestion: str = "Please check your embedding model configuration",
debug_info: dict = None):
super().__init__(message, error_code, details, suggestion, debug_info)
class LLMError(MemoryError):
"""Raised when LLM operations fail.
This exception is raised when LLM operations fail,
such as text generation, completion, or model inference errors.
Example:
raise LLMError(
message="LLM operation failed",
error_code="LLM_001",
details={"model": "gpt-4", "prompt_length": 500},
suggestion="Please check your LLM configuration and API key"
)
"""
def __init__(self, message: str, error_code: str = "LLM_001", details: dict = None,
suggestion: str = "Please check your LLM configuration and API key",
debug_info: dict = None):
super().__init__(message, error_code, details, suggestion, debug_info)
class DatabaseError(MemoryError):
"""Raised when database operations fail.
This exception is raised when database operations fail,
such as SQLite operations, connection issues, or data corruption.
Example:
raise DatabaseError(
message="Database operation failed",
error_code="DB_001",
details={"operation": "insert", "table": "memories"},
suggestion="Please check your database configuration and connection"
)
"""
def __init__(self, message: str, error_code: str = "DB_001", details: dict = None,
suggestion: str = "Please check your database configuration and connection",
debug_info: dict = None):
super().__init__(message, error_code, details, suggestion, debug_info)
class DependencyError(MemoryError):
"""Raised when required dependencies are missing.
This exception is raised when required dependencies are missing,
such as optional packages for specific providers or features.
Example:
raise DependencyError(
message="Required dependency missing",
error_code="DEPS_001",
details={"package": "kuzu", "feature": "graph_store"},
suggestion="Please install the required dependencies: pip install kuzu"
)
"""
def __init__(self, message: str, error_code: str = "DEPS_001", details: dict = None,
suggestion: str = "Please install the required dependencies",
debug_info: dict = None):
super().__init__(message, error_code, details, suggestion, debug_info)
# Mapping of HTTP status codes to specific exception classes
HTTP_STATUS_TO_EXCEPTION = {
400: ValidationError,
401: AuthenticationError,
403: AuthenticationError,
404: MemoryNotFoundError,
408: NetworkError,
409: ValidationError,
413: MemoryQuotaExceededError,
422: ValidationError,
429: RateLimitError,
500: MemoryError,
502: NetworkError,
503: NetworkError,
504: NetworkError,
}
def create_exception_from_response(
status_code: int,
response_text: str,
error_code: Optional[str] = None,
details: Optional[Dict[str, Any]] = None,
debug_info: Optional[Dict[str, Any]] = None,
) -> MemoryError:
"""Create an appropriate exception based on HTTP response.
This function analyzes the HTTP status code and response to create
the most appropriate exception type with relevant error information.
Args:
status_code: HTTP status code from the response.
response_text: Response body text.
error_code: Optional specific error code.
details: Additional error context.
debug_info: Debug information.
Returns:
An instance of the appropriate MemoryError subclass.
Example:
exception = create_exception_from_response(
status_code=429,
response_text="Rate limit exceeded",
debug_info={"retry_after": 60}
)
# Returns a RateLimitError instance
"""
exception_class = HTTP_STATUS_TO_EXCEPTION.get(status_code, MemoryError)
# Generate error code if not provided
if not error_code:
error_code = f"HTTP_{status_code}"
# Create appropriate suggestion based on status code
suggestions = {
400: "Please check your request parameters and try again",
401: "Please check your API key and authentication credentials",
403: "You don't have permission to perform this operation",
404: "The requested resource was not found",
408: "Request timed out. Please try again",
409: "Resource conflict. Please check your request",
413: "Request too large. Please reduce the size of your request",
422: "Invalid request data. Please check your input",
429: "Rate limit exceeded. Please wait before making more requests",
500: "Internal server error. Please try again later",
502: "Service temporarily unavailable. Please try again later",
503: "Service unavailable. Please try again later",
504: "Gateway timeout. Please try again later",
}
suggestion = suggestions.get(status_code, "Please try again later")
return exception_class(
message=response_text or f"HTTP {status_code} error",
error_code=error_code,
details=details or {},
suggestion=suggestion,
debug_info=debug_info or {},
)
+32 -6
View File
@@ -7,7 +7,6 @@ import logging
import os
import uuid
import warnings
from copy import deepcopy
from datetime import datetime
from typing import Any, Dict, Optional
@@ -21,6 +20,7 @@ from mem0.configs.prompts import (
PROCEDURAL_MEMORY_SYSTEM_PROMPT,
get_update_memory_messages,
)
from mem0.exceptions import ValidationError as Mem0ValidationError
from mem0.memory.base import MemoryBase
from mem0.memory.setup import mem0_dir, setup_config
from mem0.memory.storage import SQLiteManager
@@ -109,7 +109,12 @@ def _build_filters_and_metadata(
session_ids_provided.append("run_id")
if not session_ids_provided:
raise ValueError("At least one of 'user_id', 'agent_id', or 'run_id' must be provided.")
raise Mem0ValidationError(
message="At least one of 'user_id', 'agent_id', or 'run_id' must be provided.",
error_code="VALIDATION_001",
details={"provided_ids": {"user_id": user_id, "agent_id": agent_id, "run_id": run_id}},
suggestion="Please provide at least one identifier to scope the memory operation."
)
# ---------- optional actor filter ----------
resolved_actor_id = actor_id or effective_query_filters.get("actor_id")
@@ -227,6 +232,14 @@ class Memory(MemoryBase):
including a list of memory items affected (added, updated) under a "results" key,
and potentially "relations" if graph store is enabled.
Example for v1.1+: `{"results": [{"id": "...", "memory": "...", "event": "ADD"}]}`
Raises:
Mem0ValidationError: If input validation fails (invalid memory_type, messages format, etc.).
VectorStoreError: If vector store operations fail.
GraphStoreError: If graph store operations fail.
EmbeddingError: If embedding generation fails.
LLMError: If LLM operations fail.
DatabaseError: If database operations fail.
"""
processed_metadata, effective_filters = _build_filters_and_metadata(
@@ -237,8 +250,11 @@ class Memory(MemoryBase):
)
if memory_type is not None and memory_type != MemoryType.PROCEDURAL.value:
raise ValueError(
f"Invalid 'memory_type'. Please pass {MemoryType.PROCEDURAL.value} to create procedural memories."
raise Mem0ValidationError(
message=f"Invalid 'memory_type'. Please pass {MemoryType.PROCEDURAL.value} to create procedural memories.",
error_code="VALIDATION_002",
details={"provided_type": memory_type, "valid_type": MemoryType.PROCEDURAL.value},
suggestion=f"Use '{MemoryType.PROCEDURAL.value}' to create procedural memories."
)
if isinstance(messages, str):
@@ -248,7 +264,12 @@ class Memory(MemoryBase):
messages = [messages]
elif not isinstance(messages, list):
raise ValueError("messages must be str, dict, or list[dict]")
raise Mem0ValidationError(
message="messages must be str, dict, or list[dict]",
error_code="VALIDATION_003",
details={"provided_type": type(messages).__name__, "valid_types": ["str", "dict", "list[dict]"]},
suggestion="Convert your input to a string, dictionary, or list of dictionaries."
)
if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
results = self._create_procedural_memory(messages, metadata=processed_metadata, prompt=prompt)
@@ -1090,7 +1111,12 @@ class AsyncMemory(MemoryBase):
messages = [messages]
elif not isinstance(messages, list):
raise ValueError("messages must be str, dict, or list[dict]")
raise Mem0ValidationError(
message="messages must be str, dict, or list[dict]",
error_code="VALIDATION_003",
details={"provided_type": type(messages).__name__, "valid_types": ["str", "dict", "list[dict]"]},
suggestion="Convert your input to a string, dictionary, or list of dictionaries."
)
if agent_id is not None and memory_type == MemoryType.PROCEDURAL.value:
results = await self._create_procedural_memory(