Spaces:
Paused
Paused
| 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) |