Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion backend/apps/config_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from apps.user_app import router as user_router
from apps.invitation_app import router as invitation_router
from consts.const import IS_SPEED_MODE
from consts.exceptions import AppException

# Import monitoring utilities
from utils.monitoring import monitoring_manager
Expand Down Expand Up @@ -84,9 +85,27 @@ async def http_exception_handler(request, exc):
)


# Global exception handler for all uncaught exceptions
# Global exception handler for AppException
@app.exception_handler(AppException)
async def app_exception_handler(request, exc):
logger.error(f"AppException: {exc.error_code.value} - {exc.message}")
return JSONResponse(
status_code=exc.http_status,
content={
"code": exc.error_code.value,
"message": exc.message,
"details": exc.details if exc.details else None
},
)


# Global exception handler for all uncaught exceptions (excluding AppException)
@app.exception_handler(Exception)
async def generic_exception_handler(request, exc):
# Don't catch AppException - it has its own handler
if isinstance(exc, AppException):
return await app_exception_handler(request, exc)

logger.error(f"Generic Exception: {exc}")
return JSONResponse(
status_code=500,
Expand Down
31 changes: 10 additions & 21 deletions backend/apps/dify_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from fastapi import APIRouter, Header, HTTPException, Query
from fastapi.responses import JSONResponse

from consts.error_code import ErrorCode
from consts.exceptions import AppException
from services.dify_service import fetch_dify_datasets_impl
from utils.auth_utils import get_current_user_id

Expand All @@ -35,18 +37,10 @@ async def fetch_dify_datasets_api(
# Normalize URL by removing trailing slash
dify_api_base = dify_api_base.rstrip('/')
except Exception as e:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to fetch Dify datasets: {str(e)}"
)
logger.error(f"Invalid Dify configuration: {e}")
raise AppException(ErrorCode.DIFY_CONFIG_INVALID,
f"Invalid URL format: {str(e)}")

try:
_, tenant_id = get_current_user_id(authorization)
except Exception as e:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to fetch Dify datasets: {str(e)}"
)

try:
result = fetch_dify_datasets_impl(
Expand All @@ -57,15 +51,10 @@ async def fetch_dify_datasets_api(
status_code=HTTPStatus.OK,
content=result
)
except ValueError as e:
logger.warning(f"Invalid Dify configuration: {e}")
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(e)
)
except AppException:
# Re-raise AppException to be handled by global middleware
raise
except Exception as e:
logger.error(f"Failed to fetch Dify datasets: {e}")
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Failed to fetch Dify datasets: {str(e)}"
)
raise AppException(ErrorCode.DIFY_SERVICE_ERROR,
f"Failed to fetch Dify datasets: {str(e)}")
19 changes: 18 additions & 1 deletion backend/apps/northbound_base_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi.middleware.cors import CORSMiddleware

from .northbound_app import router as northbound_router
from consts.exceptions import LimitExceededError, UnauthorizedError, SignatureValidationError
from consts.exceptions import AppException

logger = logging.getLogger("northbound_base_app")

Expand Down Expand Up @@ -38,8 +38,25 @@ async def northbound_http_exception_handler(request, exc):
)


@northbound_app.exception_handler(AppException)
async def northbound_app_exception_handler(request, exc):
logger.error(f"Northbound AppException: {exc.error_code.value} - {exc.message}")
return JSONResponse(
status_code=exc.http_status,
content={
"code": exc.error_code.value,
"message": exc.message,
"details": exc.details if exc.details else None
},
)


@northbound_app.exception_handler(Exception)
async def northbound_generic_exception_handler(request, exc):
# Don't catch AppException - it has its own handler
if isinstance(exc, AppException):
return await northbound_app_exception_handler(request, exc)

logger.error(f"Northbound Generic Exception: {exc}")
return JSONResponse(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
Expand Down
28 changes: 25 additions & 3 deletions backend/apps/runtime_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

# Import monitoring utilities
from utils.monitoring import monitoring_manager
# Import exception handling
from consts.exceptions import AppException
from middleware.exception_handler import ExceptionHandlerMiddleware

# Create logger instance
logger = logging.getLogger("runtime_app")
Expand All @@ -26,6 +29,9 @@
allow_headers=["*"],
)

# Add global exception handler middleware
app.add_middleware(ExceptionHandlerMiddleware)

app.include_router(agent_router)
app.include_router(conversation_management_router)
app.include_router(memory_config_router)
Expand All @@ -46,13 +52,29 @@ async def http_exception_handler(request, exc):
)


# Global exception handler for all uncaught exceptions
# Global exception handler for AppException
@app.exception_handler(AppException)
async def app_exception_handler(request, exc):
logger.error(f"AppException: {exc.error_code.value} - {exc.message}")
return JSONResponse(
status_code=exc.http_status,
content={
"code": exc.error_code.value,
"message": exc.message,
"details": exc.details if exc.details else None
},
)


# Global exception handler for all uncaught exceptions (excluding AppException)
@app.exception_handler(Exception)
async def generic_exception_handler(request, exc):
# Don't catch AppException - it has its own handler
if isinstance(exc, AppException):
return await app_exception_handler(request, exc)

logger.error(f"Generic Exception: {exc}")
return JSONResponse(
status_code=500,
content={"message": "Internal server error, please try again later."},
)


174 changes: 174 additions & 0 deletions backend/consts/error_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
"""
Error code definitions for the application.

Format: XYYZZZ
- X: Error level (1=System, 2=Auth, 3=Business, 4=External)
- YY: Module number (01-99)
- ZZZ: Error sequence (001-999)

Module Numbers:
- 01: System
- 02: Auth
- 03: User
- 04: Tenant
- 05: Agent
- 06: Tool/MCP
- 07: Conversation
- 08: Memory
- 09: Knowledge
- 10: Model
- 11: Voice
- 12: File
- 13: Invitation
- 14: Group
- 15: Data
- 16: External
- 20: Validation
- 21: Resource
- 22: RateLimit
"""

from enum import Enum


class ErrorCode(int, Enum):
"""Business error codes."""

# ==================== System Level Errors (10xxxx) ====================
UNKNOWN_ERROR = 101001
SERVICE_UNAVAILABLE = 101002
DATABASE_ERROR = 101003
TIMEOUT = 101004
INTERNAL_ERROR = 101005

# ==================== Auth Level Errors (102xxx) ====================
UNAUTHORIZED = 102001
TOKEN_EXPIRED = 102002
TOKEN_INVALID = 102003
SIGNATURE_INVALID = 102004
FORBIDDEN = 102005

# ==================== User Module Errors (103xxx) ====================
USER_NOT_FOUND = 103001
USER_REGISTRATION_FAILED = 103002
USER_ALREADY_EXISTS = 103003
INVALID_CREDENTIALS = 103004

# ==================== Tenant Module Errors (104xxx) ====================
TENANT_NOT_FOUND = 104001
TENANT_DISABLED = 104002
TENANT_CONFIG_ERROR = 104003

# ==================== Agent Module Errors (105xxx) ====================
AGENT_NOT_FOUND = 105001
AGENT_RUN_FAILED = 105002
AGENT_NAME_DUPLICATE = 105003
AGENT_DISABLED = 105004
AGENT_VERSION_NOT_FOUND = 105005

# ==================== Tool/MCP Module Errors (106xxx) ====================
TOOL_NOT_FOUND = 106001
TOOL_EXECUTION_FAILED = 106002
TOOL_CONFIG_INVALID = 106003

# MCP specific errors (1061xx)
MCP_CONNECTION_FAILED = 106101
MCP_NAME_ILLEGAL = 106102
MCP_CONTAINER_ERROR = 106103

# ==================== Conversation Module Errors (107xxx) ====================
CONVERSATION_NOT_FOUND = 107001
CONVERSATION_SAVE_FAILED = 107002
MESSAGE_NOT_FOUND = 107003
CONVERSATION_TITLE_GENERATION_FAILED = 107004

# ==================== Memory Module Errors (108xxx) ====================
MEMORY_NOT_FOUND = 108001
MEMORY_PREPARATION_FAILED = 108002
MEMORY_CONFIG_INVALID = 108003

# ==================== Knowledge Module Errors (109xxx) ====================
KNOWLEDGE_NOT_FOUND = 109001
KNOWLEDGE_SYNC_FAILED = 109002
INDEX_NOT_FOUND = 109003
KNOWLEDGE_SEARCH_FAILED = 109004
KNOWLEDGE_UPLOAD_FAILED = 109005

# ==================== Model Module Errors (110xxx) ====================
MODEL_NOT_FOUND = 110001
MODEL_CONFIG_INVALID = 110002
MODEL_HEALTH_CHECK_FAILED = 110003
MODEL_PROVIDER_ERROR = 110004

# ==================== Voice Module Errors (111xxx) ====================
VOICE_SERVICE_ERROR = 111001
STT_CONNECTION_FAILED = 111002
TTS_CONNECTION_FAILED = 111003
VOICE_CONFIG_INVALID = 111004

# ==================== File Module Errors (112xxx) ====================
FILE_NOT_FOUND = 112001
FILE_UPLOAD_FAILED = 112002
FILE_TOO_LARGE = 112003
FILE_TYPE_NOT_ALLOWED = 112004
FILE_PREPROCESS_FAILED = 112005

# ==================== Invitation Module Errors (113xxx) ====================
INVITE_CODE_NOT_FOUND = 113001
INVITE_CODE_INVALID = 113002
INVITE_CODE_EXPIRED = 113003

# ==================== Group Module Errors (114xxx) ====================
GROUP_NOT_FOUND = 114001
GROUP_ALREADY_EXISTS = 114002
MEMBER_NOT_IN_GROUP = 114003

# ==================== Data Process Module Errors (115xxx) ====================
DATA_PROCESS_FAILED = 115001
DATA_PARSE_FAILED = 115002

# ==================== External Service Errors (116xxx) ====================
ME_CONNECTION_FAILED = 116001
DATAMATE_CONNECTION_FAILED = 116002
DIFY_SERVICE_ERROR = 116003
EXTERNAL_API_ERROR = 116004

# Dify specific errors (1161xx) - simplified
DIFY_CONFIG_INVALID = 116101
DIFY_CONNECTION_ERROR = 116102
DIFY_AUTH_ERROR = 116103
DIFY_RATE_LIMIT = 116104
DIFY_RESPONSE_ERROR = 116105

# ==================== Validation Errors (120xxx) ====================
VALIDATION_ERROR = 120001
PARAMETER_INVALID = 120002
MISSING_REQUIRED_FIELD = 120003

# ==================== Resource Errors (121xxx) ====================
RESOURCE_NOT_FOUND = 121001
RESOURCE_ALREADY_EXISTS = 121002
RESOURCE_DISABLED = 121003

# ==================== Rate Limit Errors (122xxx) ====================
RATE_LIMIT_EXCEEDED = 122001


# HTTP status code mapping
ERROR_CODE_HTTP_STATUS = {
ErrorCode.UNAUTHORIZED: 401,
ErrorCode.TOKEN_EXPIRED: 401,
ErrorCode.TOKEN_INVALID: 401,
ErrorCode.SIGNATURE_INVALID: 401,
ErrorCode.FORBIDDEN: 403,
ErrorCode.RATE_LIMIT_EXCEEDED: 429,
ErrorCode.DIFY_RATE_LIMIT: 429,
ErrorCode.VALIDATION_ERROR: 400,
ErrorCode.PARAMETER_INVALID: 400,
ErrorCode.MISSING_REQUIRED_FIELD: 400,
ErrorCode.FILE_TOO_LARGE: 413,
ErrorCode.DIFY_CONFIG_INVALID: 401,
ErrorCode.DIFY_AUTH_ERROR: 401,
ErrorCode.DIFY_CONNECTION_ERROR: 502,
ErrorCode.DIFY_RESPONSE_ERROR: 502,
}
Loading
Loading