diff --git a/python-api/app/core/exceptions.py b/python-api/app/core/exceptions.py index d9970d5..837f8d3 100644 --- a/python-api/app/core/exceptions.py +++ b/python-api/app/core/exceptions.py @@ -24,9 +24,16 @@ class AppException(HTTPException): status_code: int, message: str = "操作失败", detail: dict | None = None, + *, + error_code: str | None = None, ): - super().__init__(status_code=status_code, detail=detail or {}) + body = detail or {} + body["message"] = message + if error_code: + body["error_code"] = error_code + super().__init__(status_code=status_code, detail=body) self.message = message + self.error_code = error_code class NotFoundException(AppException): @@ -44,7 +51,7 @@ class ValidationException(AppException): def __init__(self, message: str = "参数验证失败"): super().__init__( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, message=message, ) @@ -79,6 +86,17 @@ class BusinessException(AppException): ) +class InsufficientPointsException(AppException): + """积分不足""" + + def __init__(self, message: str = "积分不足"): + super().__init__( + status_code=status.HTTP_402_PAYMENT_REQUIRED, + message=message, + error_code="insufficient_points", + ) + + class ModelUnavailableException(AppException): """AI 模型不可用""" @@ -99,6 +117,50 @@ class TaskFailedException(AppException): ) +class PromptNotFoundException(AppException): + """提示词文件不存在""" + + def __init__(self, message: str = "未找到提示词"): + super().__init__( + status_code=status.HTTP_404_NOT_FOUND, + message=message, + error_code="prompt_not_found", + ) + + +class AIEmptyResponseException(AppException): + """AI 返回内容为空""" + + def __init__(self, message: str = "AI 返回内容为空"): + super().__init__( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message=message, + error_code="empty_result", + ) + + +class AIParseErrorException(AppException): + """AI 返回内容解析失败""" + + def __init__(self, message: str = "AI 返回格式解析失败"): + super().__init__( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + message=message, + error_code="parse_error", + ) + + +class AITimeoutException(AppException): + """AI 调用超时""" + + def __init__(self, message: str = "AI 请求超时,请稍后重试"): + super().__init__( + status_code=status.HTTP_504_GATEWAY_TIMEOUT, + message=message, + error_code="timeout", + ) + + # ═══════════════════════════════════════════════════════════════ # 第三方平台异常(PlatformError 体系) # ═══════════════════════════════════════════════════════════════ @@ -111,14 +173,15 @@ class PlatformErrorType: 确保前端和网关能够统一处理。 """ - RATE_LIMIT = "rate_limit" # 429,可重试 - AUTH_FAILED = "auth_failed" # 401/403,不可重试 - TIMEOUT = "timeout" # 连接/读取超时,可重试 - SERVER_ERROR = "server_error" # 第三方 5xx,可重试 - BAD_REQUEST = "bad_request" # 参数错误,不可重试 + RATE_LIMIT = "rate_limit" # 429,可重试 + AUTH_FAILED = "auth_failed" # 401/403,不可重试 + TIMEOUT = "timeout" # 连接/读取超时,可重试 + SERVER_ERROR = "server_error" # 第三方 5xx,可重试 + BAD_REQUEST = "bad_request" # 参数错误,不可重试 QUOTA_EXHAUSTED = "quota_exhausted" # 额度用完,不可重试(或延迟重试) - NOT_FOUND = "not_found" # 资源不存在,不可重试 - UNKNOWN = "unknown" # 兜底 + NOT_FOUND = "not_found" # 资源不存在,不可重试 + CONTENT_VIOLATION = "content_violation" # 内容安全/审核不通过,不可重试 + UNKNOWN = "unknown" # 兜底 class PlatformError(Exception): @@ -145,12 +208,14 @@ class PlatformError(Exception): retryable: bool = False, error_type: str = PlatformErrorType.UNKNOWN, status_code: int | None = None, + raw_code: str | None = None, ): super().__init__(message) self.platform = platform self.retryable = retryable self.error_type = error_type self.status_code = status_code + self.raw_code = raw_code def to_http_status(self) -> int: """根据 error_type 和 retryable 返回标准 HTTP 状态码""" @@ -161,6 +226,7 @@ class PlatformError(Exception): PlatformErrorType.AUTH_FAILED: 401, PlatformErrorType.BAD_REQUEST: 400, PlatformErrorType.NOT_FOUND: 404, + PlatformErrorType.CONTENT_VIOLATION: 400, } if self.error_type in mapping: return mapping[self.error_type]