Spaces:
Running
Running
| """Provider-specific exception mapping.""" | |
| import httpx | |
| import openai | |
| from core.anthropic import get_user_facing_error_message | |
| from providers.exceptions import ( | |
| APIError, | |
| AuthenticationError, | |
| InvalidRequestError, | |
| OverloadedError, | |
| RateLimitError, | |
| ) | |
| from providers.rate_limit import GlobalRateLimiter | |
| def user_visible_message_for_mapped_provider_error( | |
| mapped: Exception, | |
| *, | |
| provider_name: str, | |
| read_timeout_s: float | None, | |
| ) -> str: | |
| """Return the user-visible string after :func:`map_error` (405 + mapped types).""" | |
| if getattr(mapped, "status_code", None) == 405: | |
| return ( | |
| f"Upstream provider {provider_name} rejected the request method " | |
| "or endpoint (HTTP 405)." | |
| ) | |
| return get_user_facing_error_message(mapped, read_timeout_s=read_timeout_s) | |
| def map_error( | |
| e: Exception, *, rate_limiter: GlobalRateLimiter | None = None | |
| ) -> Exception: | |
| """Map OpenAI or HTTPX exception to specific ProviderError. | |
| Streaming transports should pass their scoped limiter (``self._global_rate_limiter``) | |
| so reactive 429 handling applies to the correct provider. Tests may omit | |
| ``rate_limiter`` to use the process-wide singleton. | |
| """ | |
| from loguru import logger | |
| message = get_user_facing_error_message(e) | |
| logger.info( | |
| "map_error: original_exc_type={} message={}", type(e).__name__, message[:100] | |
| ) | |
| limiter = rate_limiter or GlobalRateLimiter.get_instance() | |
| if isinstance(e, openai.AuthenticationError): | |
| logger.info("map_error: mapped to AuthenticationError") | |
| return AuthenticationError(message, raw_error=str(e)) | |
| if isinstance(e, openai.RateLimitError): | |
| limiter.set_blocked(60) | |
| logger.info("map_error: mapped to RateLimitError") | |
| return RateLimitError(message, raw_error=str(e)) | |
| if isinstance(e, openai.BadRequestError): | |
| # Check if it's actually a rate limit in disguise | |
| raw = str(e).lower() | |
| if "too_many_requests" in raw or "rate_limit" in raw or "quota" in raw: | |
| limiter.set_blocked(60) | |
| logger.info("map_error: BadRequestError actually rate limit") | |
| return RateLimitError(message, raw_error=str(e)) | |
| logger.info("map_error: mapped to InvalidRequestError") | |
| return InvalidRequestError(message, raw_error=str(e)) | |
| if isinstance(e, openai.InternalServerError): | |
| raw_message = str(e) | |
| if "overloaded" in raw_message.lower() or "capacity" in raw_message.lower(): | |
| logger.info("map_error: mapped to OverloadedError") | |
| return OverloadedError(message, raw_error=raw_message) | |
| logger.info("map_error: mapped to APIError (InternalServerError)") | |
| return APIError(message, status_code=500, raw_error=str(e)) | |
| if isinstance(e, openai.APIError): | |
| logger.info("map_error: mapped to APIError (openai.APIError)") | |
| return APIError( | |
| message, status_code=getattr(e, "status_code", 500), raw_error=str(e) | |
| ) | |
| if isinstance(e, httpx.HTTPStatusError): | |
| status = e.response.status_code | |
| logger.info("map_error: httpx.HTTPStatusError status={}", status) | |
| if status in (401, 403): | |
| logger.info("map_error: mapped to AuthenticationError (httpx)") | |
| return AuthenticationError(message, raw_error=str(e)) | |
| if status == 429: | |
| limiter.set_blocked(60) | |
| logger.info("map_error: mapped to RateLimitError (httpx)") | |
| return RateLimitError(message, raw_error=str(e)) | |
| if status == 413: | |
| # "Request too large" - often actually a rate limit or quota issue | |
| limiter.set_blocked(60) | |
| logger.info("map_error: mapped to RateLimitError (413)") | |
| return RateLimitError(message, raw_error=str(e)) | |
| if status == 400: | |
| logger.info("map_error: mapped to InvalidRequestError (httpx)") | |
| return InvalidRequestError(message, raw_error=str(e)) | |
| # Check response body for rate limit indicators | |
| try: | |
| body = e.response.json() | |
| if body and isinstance(body, dict): | |
| err_type = body.get("type", "") | |
| if "too_many_requests" in err_type or "rate_limit" in err_type.lower(): | |
| limiter.set_blocked(60) | |
| logger.info("map_error: detected rate limit from response body") | |
| return RateLimitError(message, raw_error=str(e)) | |
| except Exception: | |
| pass | |
| if status >= 500: | |
| if status in (502, 503, 504): | |
| logger.info("map_error: mapped to OverloadedError (httpx)") | |
| return OverloadedError(message, raw_error=str(e)) | |
| logger.info("map_error: mapped to APIError (httpx 5xx)") | |
| return APIError(message, status_code=status, raw_error=str(e)) | |
| logger.info("map_error: mapped to APIError (httpx)") | |
| return APIError(message, status_code=status, raw_error=str(e)) | |
| logger.info("map_error: falling through, returning original exception") | |
| return e | |