diff --git a/backend/app/component/error_format.py b/backend/app/component/error_format.py new file mode 100644 index 000000000..fc26e7050 --- /dev/null +++ b/backend/app/component/error_format.py @@ -0,0 +1,72 @@ +import json +import re + + +def normalize_error_to_openai_format(exception: Exception) -> tuple[str, str | None, dict | None]: + """ + Normalize error to OpenAI-style error structure. + + Args: + exception: The exception to normalize + + Returns: + tuple: (message, error_code, error_object) + """ + raw_msg = str(exception) + error_obj = None + error_code = None + message = raw_msg + + # Match "Error code: - {json}" + m = re.search(r"Error code:\s*(\d+)\s*-\s*(\{.*\})", raw_msg, re.DOTALL) + if m: + error_code = m.group(1) + try: + parsed = json.loads(m.group(2)) + err = parsed.get("error") or parsed + if isinstance(err, dict): + error_obj = { + "message": err.get("message"), + "type": err.get("type"), + "param": err.get("param"), + "code": err.get("code"), + } + if err.get("message"): + message = err.get("message") + if err.get("code"): + error_code = err.get("code") + except Exception: + pass + + # Heuristics if not parsed + if error_obj is None: + lower = raw_msg.lower() + if "invalid_api_key" in lower or "incorrect api key" in lower or "unauthorized" in lower or " 401" in lower: + error_code = "invalid_api_key" + message = "Incorrect API key provided." + error_obj = { + "message": message, + "type": "invalid_request_error", + "param": None, + "code": "invalid_api_key", + } + elif "model_not_found" in lower or "does not exist" in lower or " 404" in lower: + error_code = "model_not_found" + message = "The model does not exist or you do not have access to it." + error_obj = { + "message": message, + "type": "invalid_request_error", + "param": None, + "code": "model_not_found", + } + elif "insufficient_quota" in lower or "quota" in lower or " 429" in lower: + error_code = "insufficient_quota" + message = "You exceeded your current quota, please check your plan and billing details." + error_obj = { + "message": message, + "type": "insufficient_quota", + "param": None, + "code": "insufficient_quota", + } + + return message, error_code, error_obj diff --git a/backend/app/controller/model_controller.py b/backend/app/controller/model_controller.py index 641f73bd5..b36f4f7c2 100644 --- a/backend/app/controller/model_controller.py +++ b/backend/app/controller/model_controller.py @@ -1,6 +1,8 @@ from fastapi import APIRouter from pydantic import BaseModel, Field from app.component.model_validation import create_agent +from camel.types import ModelType +from app.component.error_format import normalize_error_to_openai_format router = APIRouter(tags=["model"]) @@ -18,12 +20,48 @@ class ValidateModelRequest(BaseModel): class ValidateModelResponse(BaseModel): is_valid: bool = Field(..., description="Is valid") is_tool_calls: bool = Field(..., description="Is tool call used") + error_code: str | None = Field(None, description="Error code") + error: dict | None = Field(None, description="OpenAI-style error object") message: str = Field(..., description="Message") @router.post("/model/validate") async def validate_model(request: ValidateModelRequest): try: + # 1) API key validation + if request.api_key is not None and str(request.api_key).strip() == "": + return ValidateModelResponse( + is_valid=False, + is_tool_calls=False, + message="Invalid key. Validation failed.", + error_code="invalid_api_key", + error={ + "message": "Invalid key. Validation failed.", + "type": "invalid_request_error", + "param": None, + "code": "invalid_api_key", + }, + ) + + # 2) Model name validation + if request.model_type: + try: + # Will raise if not a valid enum name + _ = ModelType.from_name(request.model_type) + except Exception as e: + return ValidateModelResponse( + is_valid=False, + is_tool_calls=False, + message="Invalid model name. Validation failed.", + error_code=f"model_not_found : {e}", + error={ + "message": "Invalid model name. Validation failed.", + "type": "invalid_request_error", + "param": None, + "code": "model_not_found", + }, + ) + extra = request.extra_params or {} agent = create_agent( @@ -43,17 +81,33 @@ async def validate_model(request: ValidateModelRequest): """ ) except Exception as e: - return ValidateModelResponse(is_valid=False, is_tool_calls=False, message=str(e)) + # Normalize error to OpenAI-style error structure + message, error_code, error_obj = normalize_error_to_openai_format(e) + + return ValidateModelResponse( + is_valid=False, + is_tool_calls=False, + message=message, + error_code=error_code, + error=error_obj, + ) is_valid = bool(response) is_tool_calls = False - - if response and hasattr(response, 'info') and response.info: + + if response and hasattr(response, "info") and response.info: tool_calls = response.info.get("tool_calls", []) if tool_calls and len(tool_calls) > 0: - is_tool_calls = tool_calls[0].result == "Tool execution completed successfully for https://www.camel-ai.org, Website Content: Welcome to CAMEL AI!" - + is_tool_calls = ( + tool_calls[0].result + == "Tool execution completed successfully for https://www.camel-ai.org, Website Content: Welcome to CAMEL AI!" + ) + return ValidateModelResponse( is_valid=is_valid, is_tool_calls=is_tool_calls, - message="", + message="Validation Success" + if is_tool_calls + else "This model doesn’t support tool calls. please try with another model.", + error_code=None, + error=None, )