diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index c6ed7acd5..b21ba0ebc 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -7,16 +7,8 @@ from typing import TYPE_CHECKING, Annotated from uuid import UUID import sqlalchemy as sa -from fastapi import ( - APIRouter, - BackgroundTasks, - Body, - Depends, - HTTPException, - Request, - UploadFile, - status, -) +from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, Request, UploadFile, status +from fastapi.encoders import jsonable_encoder from loguru import logger from sqlmodel import select @@ -35,6 +27,7 @@ from langflow.api.v1.schemas import ( from langflow.custom.custom_component.component import Component from langflow.custom.utils import build_custom_component_template, get_instance_name, update_component_build_config from langflow.exceptions.api import APIException, InvalidChatInputError +from langflow.exceptions.serialization import SerializationError from langflow.graph.graph.base import Graph from langflow.graph.schema import RunOutputs from langflow.helpers.flow import get_flow_by_id_or_endpoint_name @@ -46,9 +39,7 @@ from langflow.services.auth.utils import api_key_security, get_current_active_us from langflow.services.cache.utils import save_uploaded_file from langflow.services.database.models.flow import Flow from langflow.services.database.models.flow.model import FlowRead -from langflow.services.database.models.flow.utils import ( - get_all_webhook_components_in_flow, -) +from langflow.services.database.models.flow.utils import get_all_webhook_components_in_flow from langflow.services.database.models.user.model import User, UserRead from langflow.services.deps import get_session_service, get_settings_service, get_task_service, get_telemetry_service from langflow.services.settings.feature_flags import FEATURE_FLAGS @@ -605,6 +596,9 @@ async def custom_component_update( Returns: dict: The updated custom component node. + Raises: + HTTPException: If there's an error building or updating the component + SerializationError: If there's an error serializing the component to JSON """ try: component = Component(_code=code_request.code) @@ -649,7 +643,11 @@ async def custom_component_update( except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc - return component_node + + try: + return jsonable_encoder(component_node) + except Exception as exc: + raise SerializationError.from_exception(exc, data=component_node) from exc @router.get("/config", response_model=ConfigResponse) diff --git a/src/backend/base/langflow/exceptions/serialization.py b/src/backend/base/langflow/exceptions/serialization.py new file mode 100644 index 000000000..54bc0fd43 --- /dev/null +++ b/src/backend/base/langflow/exceptions/serialization.py @@ -0,0 +1,56 @@ +from typing import Any + +from fastapi import HTTPException, status + + +class SerializationError(HTTPException): + """Exception raised when there are errors serializing data to JSON.""" + + def __init__( + self, + detail: str, + original_error: Exception | None = None, + data: Any = None, + status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR, + ) -> None: + super().__init__(status_code=status_code, detail=detail) + self.original_error = original_error + self.data = data + + @classmethod + def from_exception(cls, exc: Exception, data: Any = None) -> "SerializationError": + """Create a SerializationError from an existing exception.""" + errors = exc.args[0] if exc.args else [] + + if isinstance(errors, list): + for error in errors: + if isinstance(error, TypeError): + if "'coroutine'" in str(error): + return cls( + detail=( + "The component contains async functions that need to be awaited. Please add 'await' " + "before any async function calls in your component code." + ), + original_error=exc, + data=data, + ) + if "vars()" in str(error): + return cls( + detail=( + "The component contains objects that cannot be converted to JSON. Please ensure all " + "properties and return values in your component are basic Python types like strings, " + "numbers, lists, or dictionaries." + ), + original_error=exc, + data=data, + ) + + # Generic error for other cases + return cls( + detail=( + "The component returned invalid data. Please check that all values in your component (properties, " + "return values, etc.) are basic Python types that can be converted to JSON." + ), + original_error=exc, + data=data, + )