Spaces:
Sleeping
Sleeping
| """Custom error handlers for user-friendly error messages. | |
| Provides consistent, helpful error messages for common failure scenarios. | |
| """ | |
| from typing import Dict, Any, Optional | |
| from fastapi import Request, status | |
| from fastapi.responses import JSONResponse | |
| from openai import APIStatusError, APITimeoutError, APIConnectionError | |
| from qdrant_client.http.exceptions import UnexpectedResponse | |
| from src.utils.logger import get_logger | |
| logger = get_logger(__name__) | |
| class ErrorMessages: | |
| """Centralized error message templates for user-friendly responses""" | |
| # Authentication errors | |
| SESSION_EXPIRED = "Your session has expired. Please log in again." | |
| INVALID_CREDENTIALS = "Invalid email or password. Please try again." | |
| UNAUTHORIZED = "You must be logged in to access this resource." | |
| ACCOUNT_EXISTS = "An account with this email already exists." | |
| # API errors | |
| API_TIMEOUT = "The service is taking longer than expected. Please try again." | |
| API_UNAVAILABLE = "The AI service is temporarily unavailable. Please try again in a few moments." | |
| API_RATE_LIMIT = "Too many requests. Please wait a moment before trying again." | |
| # Vector database errors | |
| VECTOR_DB_ERROR = "Unable to search book content. Please try again." | |
| NO_RESULTS = "No relevant content found for your question. Try rephrasing or asking something else." | |
| # Validation errors | |
| INVALID_INPUT = "Invalid input provided. Please check your data and try again." | |
| MESSAGE_TOO_LONG = "Your message is too long. Please keep it under 10,000 characters." | |
| SELECTED_TEXT_REQUIRED = "Please select some text first before asking a question about it." | |
| # Database errors | |
| DATABASE_ERROR = "A database error occurred. Please try again." | |
| CONNECTION_ERROR = "Unable to connect to the database. Please try again later." | |
| # Generic errors | |
| INTERNAL_ERROR = "An unexpected error occurred. Our team has been notified." | |
| NOT_FOUND = "The requested resource was not found." | |
| def create_error_response( | |
| message: str, | |
| status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| details: Optional[Dict[str, Any]] = None | |
| ) -> JSONResponse: | |
| """Create a standardized error response. | |
| Args: | |
| message: User-friendly error message | |
| status_code: HTTP status code | |
| details: Optional additional error details (for debugging) | |
| Returns: | |
| JSONResponse with error information | |
| """ | |
| content = { | |
| "error": True, | |
| "message": message, | |
| "status_code": status_code | |
| } | |
| if details: | |
| content["details"] = details | |
| return JSONResponse( | |
| status_code=status_code, | |
| content=content | |
| ) | |
| async def openai_error_handler(request: Request, exc: Exception) -> JSONResponse: | |
| """Handle OpenAI API errors with user-friendly messages. | |
| Args: | |
| request: The request that caused the error | |
| exc: The exception that was raised | |
| Returns: | |
| JSONResponse with user-friendly error | |
| """ | |
| if isinstance(exc, APIStatusError): | |
| if exc.status_code == 429: | |
| logger.warning(f"OpenAI rate limit hit for {request.url.path}") | |
| return create_error_response( | |
| ErrorMessages.API_RATE_LIMIT, | |
| status.HTTP_429_TOO_MANY_REQUESTS, | |
| {"retry_after": exc.response.headers.get("Retry-After", "60")} | |
| ) | |
| elif exc.status_code >= 500: | |
| logger.error(f"OpenAI server error: {exc.message}") | |
| return create_error_response( | |
| ErrorMessages.API_UNAVAILABLE, | |
| status.HTTP_503_SERVICE_UNAVAILABLE | |
| ) | |
| else: | |
| logger.error(f"OpenAI API error ({exc.status_code}): {exc.message}") | |
| return create_error_response( | |
| ErrorMessages.API_UNAVAILABLE, | |
| status.HTTP_502_BAD_GATEWAY | |
| ) | |
| elif isinstance(exc, APITimeoutError): | |
| logger.warning(f"OpenAI API timeout for {request.url.path}") | |
| return create_error_response( | |
| ErrorMessages.API_TIMEOUT, | |
| status.HTTP_504_GATEWAY_TIMEOUT | |
| ) | |
| elif isinstance(exc, APIConnectionError): | |
| logger.error(f"OpenAI connection error: {exc}") | |
| return create_error_response( | |
| ErrorMessages.API_UNAVAILABLE, | |
| status.HTTP_503_SERVICE_UNAVAILABLE | |
| ) | |
| # Default OpenAI error | |
| logger.error(f"Unexpected OpenAI error: {exc}") | |
| return create_error_response( | |
| ErrorMessages.INTERNAL_ERROR, | |
| status.HTTP_500_INTERNAL_SERVER_ERROR | |
| ) | |
| async def qdrant_error_handler(request: Request, exc: Exception) -> JSONResponse: | |
| """Handle Qdrant errors with user-friendly messages. | |
| Args: | |
| request: The request that caused the error | |
| exc: The exception that was raised | |
| Returns: | |
| JSONResponse with user-friendly error | |
| """ | |
| if isinstance(exc, UnexpectedResponse): | |
| logger.error(f"Qdrant error for {request.url.path}: {exc}") | |
| return create_error_response( | |
| ErrorMessages.VECTOR_DB_ERROR, | |
| status.HTTP_503_SERVICE_UNAVAILABLE | |
| ) | |
| logger.error(f"Unexpected Qdrant error: {exc}") | |
| return create_error_response( | |
| ErrorMessages.VECTOR_DB_ERROR, | |
| status.HTTP_500_INTERNAL_SERVER_ERROR | |
| ) | |
| async def validation_error_handler(request: Request, exc: Exception) -> JSONResponse: | |
| """Handle validation errors with user-friendly messages. | |
| Args: | |
| request: The request that caused the error | |
| exc: The exception that was raised | |
| Returns: | |
| JSONResponse with user-friendly error | |
| """ | |
| from pydantic import ValidationError | |
| if isinstance(exc, ValidationError): | |
| # Extract field-specific errors | |
| errors = [] | |
| for error in exc.errors(): | |
| field = " -> ".join(str(loc) for loc in error["loc"]) | |
| message = error["msg"] | |
| errors.append(f"{field}: {message}") | |
| logger.warning(f"Validation error for {request.url.path}: {errors}") | |
| return create_error_response( | |
| ErrorMessages.INVALID_INPUT, | |
| status.HTTP_422_UNPROCESSABLE_ENTITY, | |
| {"validation_errors": errors} | |
| ) | |
| return create_error_response( | |
| ErrorMessages.INVALID_INPUT, | |
| status.HTTP_400_BAD_REQUEST | |
| ) | |