197 lines
5.9 KiB
Python
197 lines
5.9 KiB
Python
"""Custom exceptions and error handlers for the chat agent application."""
|
|
from typing import Any, Dict, Optional
|
|
from fastapi import HTTPException, Request, status
|
|
from fastapi.responses import JSONResponse
|
|
from typing import Union
|
|
from pydantic import BaseModel
|
|
from datetime import datetime
|
|
import json
|
|
from starlette.status import (
|
|
HTTP_400_BAD_REQUEST,
|
|
HTTP_401_UNAUTHORIZED,
|
|
HTTP_403_FORBIDDEN,
|
|
HTTP_404_NOT_FOUND,
|
|
HTTP_429_TOO_MANY_REQUESTS,
|
|
HTTP_500_INTERNAL_SERVER_ERROR,
|
|
)
|
|
|
|
# 创建一个完整的响应模型
|
|
class FullHxfResponseModel(BaseModel):
|
|
"""完整的响应模型,包含状态码、数据、错误信息等"""
|
|
code: int
|
|
status: int
|
|
data: Dict[str, Any]
|
|
error: Optional[Dict[str, Any]]
|
|
message: Optional[str]
|
|
|
|
class HxfResponse(JSONResponse):
|
|
def __init__(self, response: Union[BaseModel, Dict[str, Any]]):
|
|
if isinstance(response, BaseModel):
|
|
data_dict = response.model_dump(mode='json')
|
|
else:
|
|
data_dict = response
|
|
|
|
content = {
|
|
"code": 0,
|
|
"status": status.HTTP_200_OK,
|
|
"data": data_dict,
|
|
"error": None,
|
|
"message": None
|
|
}
|
|
super().__init__(
|
|
content=content,
|
|
status_code=status.HTTP_200_OK,
|
|
media_type="application/json"
|
|
)
|
|
|
|
class HxfErrorResponse(JSONResponse):
|
|
"""Custom JSON response class with standard format."""
|
|
|
|
def __init__(self, message: Union[str, Exception], status_code: int = status.HTTP_401_UNAUTHORIZED):
|
|
"""Return a JSON error response."""
|
|
if isinstance(message, Exception):
|
|
content = {
|
|
"code": -1,
|
|
"status": message.status_code,
|
|
"data": None,
|
|
"error": message.details,
|
|
"message": message.message
|
|
}
|
|
else:
|
|
content = {
|
|
"code": -1,
|
|
"status": status_code,
|
|
"data": None,
|
|
"error": None,
|
|
"message": message
|
|
}
|
|
super().__init__(content=content, status_code=status_code)
|
|
|
|
class ChatAgentException(Exception):
|
|
"""Base exception for chat agent application."""
|
|
|
|
def __init__(
|
|
self,
|
|
message: str,
|
|
status_code: int = HTTP_500_INTERNAL_SERVER_ERROR,
|
|
details: Optional[Dict[str, Any]] = None
|
|
):
|
|
self.message = message
|
|
self.status_code = status_code
|
|
self.details = details or {}
|
|
super().__init__(self.message)
|
|
|
|
|
|
class ValidationError(ChatAgentException):
|
|
"""Validation error exception."""
|
|
|
|
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
super().__init__(message, HTTP_422_UNPROCESSABLE_ENTITY, details)
|
|
|
|
|
|
class AuthenticationError(ChatAgentException):
|
|
"""Authentication error exception."""
|
|
|
|
def __init__(self, message: str = "Authentication failed"):
|
|
super().__init__(message, HTTP_401_UNAUTHORIZED)
|
|
|
|
|
|
class AuthorizationError(ChatAgentException):
|
|
"""Authorization error exception."""
|
|
|
|
def __init__(self, message: str = "Access denied"):
|
|
super().__init__(message, HTTP_403_FORBIDDEN)
|
|
|
|
|
|
class NotFoundError(ChatAgentException):
|
|
"""Resource not found exception."""
|
|
|
|
def __init__(self, message: str = "Resource not found"):
|
|
super().__init__(message, HTTP_404_NOT_FOUND)
|
|
|
|
|
|
class ConversationNotFoundError(NotFoundError):
|
|
"""Conversation not found exception."""
|
|
|
|
def __init__(self, conversation_id: str):
|
|
super().__init__(f"Conversation with ID {conversation_id} not found")
|
|
|
|
|
|
class UserNotFoundError(NotFoundError):
|
|
"""User not found exception."""
|
|
|
|
def __init__(self, user_id: str):
|
|
super().__init__(f"User with ID {user_id} not found")
|
|
|
|
|
|
class ChatServiceError(ChatAgentException):
|
|
"""Chat service error exception."""
|
|
|
|
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
super().__init__(message, HTTP_500_INTERNAL_SERVER_ERROR, details)
|
|
|
|
|
|
class OpenAIError(ChatServiceError):
|
|
"""OpenAI API error exception."""
|
|
|
|
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
super().__init__(f"OpenAI API error: {message}", details)
|
|
|
|
|
|
class RateLimitError(ChatAgentException):
|
|
"""Rate limit exceeded error."""
|
|
pass
|
|
|
|
|
|
class DatabaseError(ChatAgentException):
|
|
"""Database operation error exception."""
|
|
|
|
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
super().__init__(f"Database error: {message}", HTTP_500_INTERNAL_SERVER_ERROR, details)
|
|
|
|
|
|
# Error handlers
|
|
async def chat_agent_exception_handler(request: Request, exc: ChatAgentException) -> JSONResponse:
|
|
"""Handle ChatAgentException and its subclasses."""
|
|
from loguru import logger
|
|
logger.error(
|
|
f"ChatAgentException: {exc.message}",
|
|
extra={
|
|
"status_code": exc.status_code,
|
|
"details": exc.details,
|
|
"path": request.url.path,
|
|
"method": request.method
|
|
}
|
|
)
|
|
|
|
return HxfErrorResponse(exc)
|
|
|
|
|
|
async def http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
|
"""Handle HTTPException."""
|
|
from loguru import logger
|
|
logger.warning(
|
|
f"HTTPException: {exc.detail}",
|
|
extra={
|
|
"status_code": exc.status_code,
|
|
"path": request.url.path,
|
|
"method": request.method
|
|
}
|
|
)
|
|
|
|
return HxfErrorResponse(exc)
|
|
|
|
async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""Handle general exceptions."""
|
|
from loguru import logger
|
|
logger.error(
|
|
f"Unhandled exception: {str(exc)}",
|
|
extra={
|
|
"exception_type": exc.__class__.__name__,
|
|
"path": request.url.path,
|
|
"method": request.method
|
|
},
|
|
exc_info=True
|
|
)
|
|
|
|
return HxfErrorResponse(exc) |