Spaces:
Sleeping
Sleeping
| """Application-level error taxonomy for the LLM layer. | |
| This module normalizes provider exceptions into a small stable error contract | |
| that service-layer code can rely on without depending on provider-specific | |
| classes. | |
| """ | |
| from functools import lru_cache | |
| class LLMError(Exception): | |
| """Base exception for all LLM-layer failures.""" | |
| def __init__(self, message: str | None = None): | |
| self.message = message or self.__doc__ | |
| super().__init__(self.message.strip()) | |
| class LLMConfigError(LLMError): | |
| """Model configuration is invalid or missing.""" | |
| class LLMProviderError(LLMError): | |
| """Raised for provider-side failures that are not more specific.""" | |
| class LLMTimeoutError(LLMProviderError): | |
| """Raised when an LLM request times out.""" | |
| class LLMRateLimitError(LLMProviderError): | |
| """Raised when the provider rejects requests due to rate limits.""" | |
| class LLMStructuredOutputError(LLMError): | |
| """The model's response could not be parsed to JSON.""" | |
| class LLMSchemaValidationError(LLMError): | |
| """Parsed JSON schema failed validation.""" | |
| class LLMRetryExhaustedError(LLMError): | |
| """All retries and fallbacks were exhausted.""" | |
| def _litellm_exception_types() -> tuple[type[Exception], ...]: | |
| """Returns LiteLLM exception base types exposed by the SDK.""" | |
| try: | |
| import litellm | |
| except ImportError: | |
| return () | |
| known = getattr(litellm, "LITELLM_EXCEPTION_TYPES", ()) | |
| return tuple(exc for exc in known if isinstance(exc, type) and issubclass(exc, Exception)) | |
| def coerce_provider_exception(error: BaseException) -> BaseException: | |
| """Normalizes provider exceptions into app-level error types. | |
| Args: | |
| error: Exception instance raised by provider/runtime code. | |
| Returns: | |
| BaseException: Mapped application exception when recognized, otherwise | |
| the original exception. | |
| """ | |
| if not isinstance(error, Exception): | |
| return error | |
| try: | |
| import litellm | |
| except ImportError: | |
| return error | |
| timeout_type = getattr(litellm, "Timeout", None) | |
| rate_limit_type = getattr(litellm, "RateLimitError", None) | |
| if isinstance(timeout_type, type) and isinstance(error, timeout_type): | |
| return LLMTimeoutError(str(error)) | |
| if isinstance(rate_limit_type, type) and isinstance(error, rate_limit_type): | |
| return LLMRateLimitError(str(error)) | |
| if _litellm_exception_types() and isinstance(error, _litellm_exception_types()): | |
| return LLMProviderError(str(error)) | |
| return error | |
| def error_type_for_exception(error: BaseException) -> str: | |
| """Maps exceptions to normalized metadata error labels. | |
| Args: | |
| error: Exception to classify. | |
| Returns: | |
| str: Error-category label suitable for telemetry metadata. | |
| """ | |
| normalized = coerce_provider_exception(error) | |
| if isinstance(normalized, LLMTimeoutError): | |
| return "timeout" | |
| if isinstance(normalized, LLMRateLimitError): | |
| return "rate_limit" | |
| if isinstance(normalized, LLMProviderError): | |
| return "provider" | |
| if isinstance(normalized, LLMStructuredOutputError): | |
| return "structured_output" | |
| if isinstance(normalized, LLMSchemaValidationError): | |
| return "schema_validation" | |
| if isinstance(normalized, LLMRetryExhaustedError): | |
| return "retry_exhausted" | |
| return normalized.__class__.__name__.lower() | |