mem0 / server /errors.py
keithproject's picture
mem0 self-hosted: full deployment (auth fix, nginx routing, 9router LLM config)
5ec8557
Raw
History Blame Contribute Delete
3.6 kB
import logging
import sys
import uuid
from contextvars import ContextVar
from fastapi import HTTPException, Request
from fastapi.responses import JSONResponse
request_id_var: ContextVar[str] = ContextVar("request_id", default="-")
class UpstreamError(HTTPException):
def __init__(self, code: str, detail: str, request_id: str) -> None:
super().__init__(status_code=502, detail=detail)
self.code = code
self.request_id = request_id
_AUTH_NAMES = {"AuthenticationError", "PermissionDeniedError"}
_RATE_NAMES = {"RateLimitError"}
_TIMEOUT_NAMES = {"APITimeoutError"}
_CONN_NAMES = {"APIConnectionError", "ConnectionError"}
_BAD_REQUEST_NAMES = {"BadRequestError", "UnprocessableEntityError"}
_DB_NAMES = {"OperationalError", "DBAPIError", "DisconnectionError"}
_VECTOR_NAMES = {"UnexpectedResponse", "ResponseHandlingException"}
def _classify_one(exc: BaseException) -> tuple[str, str]:
name = type(exc).__name__
module = getattr(type(exc), "__module__", "") or ""
status = getattr(exc, "status_code", None)
if name in _AUTH_NAMES or status in (401, 403):
return (
"provider_auth_failed",
"Provider rejected the request (authentication). Check your LLM provider API key on the Configuration page.",
)
if name in _RATE_NAMES or status == 429:
return ("provider_rate_limited", "Provider rate limit hit. Retry shortly.")
if name in _TIMEOUT_NAMES or isinstance(exc, TimeoutError):
return ("provider_timeout", "Provider timed out. Retry shortly.")
if name in _CONN_NAMES or (isinstance(status, int) and status >= 500):
return ("provider_unavailable", "Provider is unreachable or returned a server error.")
if name in _BAD_REQUEST_NAMES or status in (400, 422):
return ("provider_bad_request", "Provider rejected the request as malformed.")
if name in _DB_NAMES:
return ("datastore_unavailable", "The memory database is unreachable.")
if name in _VECTOR_NAMES or module.startswith("qdrant_client"):
return ("vector_store_unavailable", "The vector store is unreachable or returned an error.")
return ("unknown", "Upstream provider error.")
def _classify(exc: BaseException | None) -> tuple[str, str]:
# Walk the cause/context chain so wrapped provider errors still classify correctly.
seen: set[int] = set()
current = exc
while current is not None and id(current) not in seen:
seen.add(id(current))
result = _classify_one(current)
if result[0] != "unknown":
return result
current = current.__cause__ or current.__context__
return ("unknown", "Upstream provider error.")
def new_request_id() -> str:
return uuid.uuid4().hex[:8]
def upstream_error() -> UpstreamError:
exc = sys.exc_info()[1]
code, message = _classify(exc)
rid = request_id_var.get()
logging.exception("Upstream provider error (code=%s)", code)
return UpstreamError(code=code, detail=message, request_id=rid)
async def upstream_error_handler(_: Request, exc: UpstreamError) -> JSONResponse:
return JSONResponse(
status_code=exc.status_code,
content={"detail": exc.detail, "code": exc.code, "request_id": exc.request_id},
headers={"X-Request-ID": exc.request_id},
)
def install_request_id_logging() -> None:
old_factory = logging.getLogRecordFactory()
def factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
record.request_id = request_id_var.get()
return record
logging.setLogRecordFactory(factory)