From 9802322ae93c0088dac7cd4c68f6774c4f36707f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 11 Mar 2024 14:27:15 -0300 Subject: [PATCH 1/6] Make update_build_config use always the latest version --- src/backend/langflow/api/v1/endpoints.py | 78 +++++++++--- src/backend/langflow/api/v1/schemas.py | 21 +++- .../langflow/interface/custom/utils.py | 116 +++++++++--------- src/frontend/src/controllers/API/index.ts | 3 + src/frontend/src/utils/parameterUtils.ts | 6 + 5 files changed, 141 insertions(+), 83 deletions(-) diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 1d9950585..ce20860c3 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -8,7 +8,7 @@ from sqlmodel import Session, select from langflow.api.utils import update_frontend_node_with_template_values from langflow.api.v1.schemas import ( - CustomComponentCode, + CustomComponentRequest, InputValueRequest, ProcessResponse, RunResponse, @@ -52,7 +52,9 @@ def get_all( raise HTTPException(status_code=500, detail=str(exc)) from exc -@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True) +@router.post( + "/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True +) async def run_flow_with_caching( session: Annotated[Session, Depends(get_session)], flow_id: str, @@ -103,7 +105,9 @@ async def run_flow_with_caching( """ try: if inputs is not None: - input_values: list[dict[str, Union[str, list[str]]]] = [_input.model_dump() for _input in inputs] + input_values: list[dict[str, Union[str, list[str]]]] = [ + _input.model_dump() for _input in inputs + ] else: input_values = [{}] @@ -111,7 +115,9 @@ async def run_flow_with_caching( outputs = [] if session_id: - session_data = await session_service.load_session(session_id, flow_id=flow_id) + session_data = await session_service.load_session( + session_id, flow_id=flow_id + ) graph, artifacts = session_data if session_data else (None, None) task_result: Any = None if not graph: @@ -130,7 +136,11 @@ async def run_flow_with_caching( else: # Get the flow that matches the flow_id and belongs to the user # flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first() - flow = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first() + flow = session.exec( + select(Flow) + .where(Flow.id == flow_id) + .where(Flow.user_id == api_key_user.id) + ).first() if flow is None: raise ValueError(f"Flow {flow_id} not found") @@ -154,12 +164,18 @@ async def run_flow_with_caching( # StatementError('(builtins.ValueError) badly formed hexadecimal UUID string') if "badly formed hexadecimal UUID string" in str(exc): # This means the Flow ID is not a valid UUID which means it can't find the flow - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail=str(exc) + ) from exc except ValueError as exc: if f"Flow {flow_id} not found" in str(exc): - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail=str(exc) + ) from exc else: - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc) + ) from exc @router.post( @@ -188,7 +204,8 @@ async def process( """ # Raise a depreciation warning logger.warning( - "The /process endpoint is deprecated and will be removed in a future version. " "Please use /run instead." + "The /process endpoint is deprecated and will be removed in a future version. " + "Please use /run instead." ) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -253,19 +270,23 @@ def get_version(): @router.post("/custom_component", status_code=HTTPStatus.OK) async def custom_component( - raw_code: CustomComponentCode, + raw_code: CustomComponentRequest, user: User = Depends(get_current_active_user), ): component = CustomComponent(code=raw_code.code) - built_frontend_node = build_custom_component_template(component, user_id=user.id) + built_frontend_node, _ = build_custom_component_template(component, user_id=user.id) - built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code.frontend_node) + built_frontend_node = update_frontend_node_with_template_values( + built_frontend_node, raw_code.frontend_node + ) return built_frontend_node @router.post("/custom_component/reload", status_code=HTTPStatus.OK) -async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)): +async def reload_custom_component( + path: str, user: User = Depends(get_current_active_user) +): from langflow.interface.custom.utils import build_custom_component_template try: @@ -275,23 +296,40 @@ async def reload_custom_component(path: str, user: User = Depends(get_current_ac raise ValueError(content) extractor = CustomComponent(code=content) - return build_custom_component_template(extractor, user_id=user.id) + frontend_node, _ = build_custom_component_template(extractor, user_id=user.id) + return frontend_node except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) @router.post("/custom_component/update", status_code=HTTPStatus.OK) async def custom_component_update( - raw_code: CustomComponentCode, + code_request: CustomComponentRequest, user: User = Depends(get_current_active_user), ): - component = CustomComponent(code=raw_code.code) + """ + Update a custom component with the provided code request. - component_node = build_custom_component_template( + This endpoint generates the CustomComponentFrontendNode normally but then runs the `update_build_config` method + on the latest version of the template. This ensures that every time it runs, it has the latest version of the template. + + Args: + code_request (CustomComponentRequest): The code request containing the updated code for the custom component. + user (User, optional): The user making the request. Defaults to the current active user. + + Returns: + dict: The updated custom component node. + + """ + component = CustomComponent(code=code_request.code) + + component_node, cc_instance = build_custom_component_template( component, user_id=user.id, - update_field=raw_code.field, - update_field_value=raw_code.field_value, ) - # Update the field + updated_build_config = cc_instance.update_build_config( + code_request.template, code_request.field_value, code_request.field_name + ) + component_node["template"] = updated_build_config + return component_node diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 70a60de5b..334db7b91 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -4,8 +4,16 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Union from uuid import UUID -from pydantic import BaseModel, Field, RootModel, field_validator, model_serializer +from pydantic import ( + BaseModel, + Field, + RootModel, + field_serializer, + field_validator, + model_serializer, +) +from langflow.schema import dotdict from langflow.services.database.models.api_key.model import ApiKeyRead from langflow.services.database.models.base import orjson_dumps from langflow.services.database.models.flow import FlowCreate, FlowRead @@ -158,15 +166,22 @@ class StreamData(BaseModel): data: dict def __str__(self) -> str: - return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n" + return ( + f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n" + ) -class CustomComponentCode(BaseModel): +class CustomComponentRequest(BaseModel): code: str field: Optional[str] = None field_value: Optional[Any] = None + template: Optional[Union[dict, dotdict]] = None frontend_node: Optional[dict] = None + @field_serializer("template") + def template_into_dotdict(v): + return dotdict(v) + class CustomComponentResponseError(BaseModel): detail: str diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index 05f8de842..43ee7db7c 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -3,7 +3,7 @@ import contextlib import re import traceback import warnings -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Tuple, Union from uuid import UUID from fastapi import HTTPException @@ -34,14 +34,18 @@ class UpdateBuildConfigError(Exception): pass -def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]): +def add_output_types( + frontend_node: CustomComponentFrontendNode, return_types: List[str] +): """Add output types to the frontend node""" for return_type in return_types: if return_type is None: raise HTTPException( status_code=400, detail={ - "error": ("Invalid return type. Please check your code and try again."), + "error": ( + "Invalid return type. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) @@ -73,14 +77,18 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List frontend_node.field_order = field_order -def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]): +def add_base_classes( + frontend_node: CustomComponentFrontendNode, return_types: List[str] +): """Add base classes to the frontend node""" for return_type_instance in return_types: if return_type_instance is None: raise HTTPException( status_code=400, detail={ - "error": ("Invalid return type. Please check your code and try again."), + "error": ( + "Invalid return type. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) @@ -115,7 +123,9 @@ def get_field_properties(extra_field): # a required field is a field that does not contain # optional in field_type # and a field that does not have a default value - field_required = "optional" not in field_type.lower() and isinstance(field_value, MissingDefault) + field_required = "optional" not in field_type.lower() and isinstance( + field_value, MissingDefault + ) field_value = field_value if not isinstance(field_value, MissingDefault) else None if not field_required: @@ -164,10 +174,14 @@ def add_new_custom_field( # If options is a list, then it's a dropdown # If options is None, then it's a list of strings is_list = isinstance(field_config.get("options"), list) - field_config["is_list"] = is_list or field_config.get("list", False) or field_contains_list + field_config["is_list"] = ( + is_list or field_config.get("list", False) or field_contains_list + ) if "name" in field_config: - warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.") + warnings.warn( + "The 'name' key in field_config is used to build the object and can't be changed." + ) required = field_config.pop("required", field_required) placeholder = field_config.pop("placeholder", "") @@ -206,7 +220,9 @@ def add_extra_fields(frontend_node, field_config, function_args): ]: continue - field_name, field_type, field_value, field_required = get_field_properties(extra_field) + field_name, field_type, field_value, field_required = get_field_properties( + extra_field + ) config = _field_config.pop(field_name, {}) frontend_node = add_new_custom_field( frontend_node, @@ -216,13 +232,17 @@ def add_extra_fields(frontend_node, field_config, function_args): field_required, config, ) - if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config.keys()): + if "kwargs" in function_args_names and not all( + key in function_args_names for key in field_config.keys() + ): for field_name, field_config in _field_config.copy().items(): if "name" not in field_config or field_name == "code": continue config = _field_config.get(field_name, {}) config = config.model_dump() if isinstance(config, BaseModel) else config - field_name, field_type, field_value, field_required = get_field_properties(extra_field=config) + field_name, field_type, field_value, field_required = get_field_properties( + extra_field=config + ) frontend_node = add_new_custom_field( frontend_node, field_name, @@ -243,9 +263,7 @@ def get_field_dict(field: Union[TemplateField, dict]): def run_build_config( custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None, - update_field=None, - update_field_value=None, -): +) -> Tuple[dict, CustomComponent]: """Build the field configuration for a custom component""" try: @@ -260,7 +278,9 @@ def run_build_config( raise HTTPException( status_code=400, detail={ - "error": ("Invalid type convertion. Please check your code and try again."), + "error": ( + "Invalid type convertion. Please check your code and try again." + ), "traceback": traceback.format_exc(), }, ) from exc @@ -274,38 +294,6 @@ def run_build_config( # as a dict with the same keys as TemplateField field_dict = get_field_dict(field) build_config[field_name] = field_dict - # This has to be done to set refresh if options or value are callable - if update_field is not None and field_name != update_field: - build_config = update_field_dict( - custom_component_instance=custom_instance, - field_dict=field_dict, - build_config=build_config, - call=False, - ) - continue - try: - build_config = update_field_dict( - custom_component_instance=custom_instance, - field_dict=field_dict, - build_config=build_config, - update_field=update_field, - update_field_value=update_field_value, - call=True, - ) - build_config[field_name] = field_dict - except Exception as exc: - logger.error(f"Error while getting build_config: {str(exc)}") - if isinstance(exc, UpdateBuildConfigError): - message = str(exc) - else: - message = f"Error while getting build_config: {str(exc)}" - raise HTTPException( - status_code=400, - detail={ - "error": message, - "traceback": traceback.format_exc(), - }, - ) from exc return build_config, custom_instance @@ -358,9 +346,7 @@ def add_code_field(frontend_node: CustomComponentFrontendNode, raw_code, field_c def build_custom_component_template( custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None, - update_field: Optional[str] = None, - update_field_value: Optional[str] = None, -) -> Optional[Dict[str, Any]]: +) -> Tuple[Dict[str, Any], CustomComponent]: """Build a custom component template for the langchain""" try: frontend_node = build_frontend_node(custom_component.template_config) @@ -368,29 +354,35 @@ def build_custom_component_template( field_config, custom_instance = run_build_config( custom_component, user_id=user_id, - update_field=update_field, - update_field_value=update_field_value, ) entrypoint_args = custom_component.get_function_entrypoint_args add_extra_fields(frontend_node, field_config, entrypoint_args) - frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {})) + frontend_node = add_code_field( + frontend_node, custom_component.code, field_config.get("code", {}) + ) - add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type) - add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type) + add_base_classes( + frontend_node, custom_component.get_function_entrypoint_return_type + ) + add_output_types( + frontend_node, custom_component.get_function_entrypoint_return_type + ) reorder_fields(frontend_node, custom_instance._get_field_order()) - return frontend_node.to_dict(add_name=False) + return frontend_node.to_dict(add_name=False), custom_instance except Exception as exc: if isinstance(exc, HTTPException): raise exc raise HTTPException( status_code=400, detail={ - "error": (f"Something went wrong while building the custom component. Hints: {str(exc)}"), + "error": ( + f"Something went wrong while building the custom component. Hints: {str(exc)}" + ), "traceback": traceback.format_exc(), }, ) from exc @@ -403,7 +395,7 @@ def create_component_template(component): component_extractor = CustomComponent(code=component_code) - component_template = build_custom_component_template(component_extractor) + component_template, _ = build_custom_component_template(component_extractor) if not component_template["output_types"] and component_output_types: component_template["output_types"] = component_output_types @@ -426,7 +418,9 @@ def build_custom_components(components_paths: List[str]): custom_component_dict = build_custom_component_list_from_path(path_str) if custom_component_dict: category = next(iter(custom_component_dict)) - logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}") + logger.info( + f"Loading {len(custom_component_dict[category])} component(s) from category {category}" + ) custom_components_from_file = merge_nested_dicts_with_renaming( custom_components_from_file, custom_component_dict ) @@ -461,7 +455,9 @@ def update_field_dict( build_config = dd_build_config except Exception as exc: logger.error(f"Error while running update_build_config: {str(exc)}") - raise UpdateBuildConfigError(f"Error while running update_build_config: {str(exc)}") from exc + raise UpdateBuildConfigError( + f"Error while running update_build_config: {str(exc)}" + ) from exc # Let's check if "range_spec" is a RangeSpec object if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec): diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index a8ee9c6e7..694428a22 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -4,6 +4,7 @@ import { BASE_URL_API } from "../../constants/constants"; import { api } from "../../controllers/API/api"; import { APIObjectType, + APITemplateType, Component, LoginType, Users, @@ -369,11 +370,13 @@ export async function postCustomComponent( export async function postCustomComponentUpdate( code: string, + template: APITemplateType, field: string, field_value: any ): Promise> { return await api.post(`${BASE_URL_API}custom_component/update`, { code, + template, field, field_value, }); diff --git a/src/frontend/src/utils/parameterUtils.ts b/src/frontend/src/utils/parameterUtils.ts index 9635ce96d..d950ac0c1 100644 --- a/src/frontend/src/utils/parameterUtils.ts +++ b/src/frontend/src/utils/parameterUtils.ts @@ -9,9 +9,15 @@ export const handleUpdateValues = async (name: string, data: NodeDataType) => { console.error("Code not found in the template"); return; } + const template = data.node?.template; + if (!template) { + console.error("No template found in the node."); + return; + } try { let newTemplate = await postCustomComponentUpdate( code, + template, name, data.node?.template[name]?.value ) From ef4b9f96e55f8063e81d923dd394b8ec5cb802ec Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 11 Mar 2024 14:27:23 -0300 Subject: [PATCH 2/6] Refactor code for improved readability and maintainability --- tests/test_helper_components.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_helper_components.py b/tests/test_helper_components.py index 28b84a42e..82515b699 100644 --- a/tests/test_helper_components.py +++ b/tests/test_helper_components.py @@ -24,7 +24,9 @@ def test_document_to_record_component(): # Act # Replace with your actual test data - document = Document(page_content="key: value", metadata={"url": "https://example.com"}) + document = Document( + page_content="key: value", metadata={"url": "https://example.com"} + ) result = document_to_record_component.build(document) # Assert @@ -37,12 +39,14 @@ def test_uuid_generator_component(): uuid_generator_component = helpers.UUIDGeneratorComponent() uuid_generator_component.code = open(helpers.IDGenerator.__file__, "r").read() - frontend_node = build_custom_component_template(uuid_generator_component) + frontend_node, _ = build_custom_component_template(uuid_generator_component) # Act build_config = frontend_node.get("template") field_name = "unique_id" - build_config = uuid_generator_component.update_build_config(build_config, None, field_name) + build_config = uuid_generator_component.update_build_config( + build_config, None, field_name + ) unique_id = build_config["unique_id"]["value"] result = uuid_generator_component.build(unique_id) From fde57a3463090a0204463581437d47ae82b0626f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 11 Mar 2024 15:17:00 -0300 Subject: [PATCH 3/6] Add model_config to CustomComponentRequest schema --- src/backend/langflow/api/v1/schemas.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 334db7b91..7da46aaf0 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -6,6 +6,7 @@ from uuid import UUID from pydantic import ( BaseModel, + ConfigDict, Field, RootModel, field_serializer, @@ -172,10 +173,11 @@ class StreamData(BaseModel): class CustomComponentRequest(BaseModel): + model_config = ConfigDict(arbitrary_types_allowed=True) code: str field: Optional[str] = None field_value: Optional[Any] = None - template: Optional[Union[dict, dotdict]] = None + template: Optional[dict] = None frontend_node: Optional[dict] = None @field_serializer("template") From 603dba8a4ac5f2c0bf62e1bc4b5dba8f643d8d31 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 11 Mar 2024 15:59:01 -0300 Subject: [PATCH 4/6] Refactor code to handle optional properties in GenericNode --- src/frontend/src/CustomNodes/GenericNode/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 7775e7f94..122ea7d3f 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -85,8 +85,8 @@ export default function GenericNode({ // return if (!thisNodeTemplate.code) return; - const currentCode = thisNodeTemplate.code.value; - const thisNodesCode = data.node!.template.code.value; + const currentCode = thisNodeTemplate.code?.value; + const thisNodesCode = data.node!.template?.code?.value; if (currentCode !== thisNodesCode) { addToOutdatedNodes(data.id); setIsOutdated(true); @@ -96,7 +96,7 @@ export default function GenericNode({ setIsOutdated(false); } // template.code can be undefined - }, [data.node?.template.code.value]); + }, [data.node?.template?.code?.value]); const updateNodeCode = useCallback( (newNodeClass: APIClassType, code: string, name: string) => { From 230982161ed20673108d117fab72517d627dde4c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 11 Mar 2024 16:41:25 -0300 Subject: [PATCH 5/6] Makes all vertices be considered for output retrieval --- src/backend/langflow/graph/graph/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/graph/graph/base.py b/src/backend/langflow/graph/graph/base.py index fa4a0e5eb..96da64eda 100644 --- a/src/backend/langflow/graph/graph/base.py +++ b/src/backend/langflow/graph/graph/base.py @@ -162,14 +162,16 @@ class Graph: if vertex is None: raise ValueError(f"Vertex {vertex_id} not found") vertex.update_raw_params({"session_id": session_id}) + # Process the graph try: await self.process() self.increment_run_count() except Exception as exc: logger.exception(exc) raise ValueError(f"Error running graph: {exc}") from exc + # Get the outputs vertex_outputs = [] - for vertex_id in self._is_output_vertices: + for vertex_id in self.vertices: vertex = self.get_vertex(vertex_id) if vertex is None: raise ValueError(f"Vertex {vertex_id} not found") @@ -178,6 +180,7 @@ class Graph: await vertex.consume_async_generator() if not outputs or (vertex.display_name in outputs or vertex.id in outputs): vertex_outputs.append(vertex.result) + return vertex_outputs async def run( From bcab53818eebff3da7eed9021ee3ded968b4ea03 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Mon, 11 Mar 2024 16:51:08 -0300 Subject: [PATCH 6/6] Refactor API endpoints and schemas --- src/backend/langflow/api/v1/endpoints.py | 41 +++-------- src/backend/langflow/api/v1/schemas.py | 4 +- .../langflow/interface/custom/utils.py | 68 +++++-------------- 3 files changed, 28 insertions(+), 85 deletions(-) diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index ce20860c3..44a12a0d5 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -52,9 +52,7 @@ def get_all( raise HTTPException(status_code=500, detail=str(exc)) from exc -@router.post( - "/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True -) +@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True) async def run_flow_with_caching( session: Annotated[Session, Depends(get_session)], flow_id: str, @@ -105,9 +103,7 @@ async def run_flow_with_caching( """ try: if inputs is not None: - input_values: list[dict[str, Union[str, list[str]]]] = [ - _input.model_dump() for _input in inputs - ] + input_values: list[dict[str, Union[str, list[str]]]] = [_input.model_dump() for _input in inputs] else: input_values = [{}] @@ -115,9 +111,7 @@ async def run_flow_with_caching( outputs = [] if session_id: - session_data = await session_service.load_session( - session_id, flow_id=flow_id - ) + session_data = await session_service.load_session(session_id, flow_id=flow_id) graph, artifacts = session_data if session_data else (None, None) task_result: Any = None if not graph: @@ -136,11 +130,7 @@ async def run_flow_with_caching( else: # Get the flow that matches the flow_id and belongs to the user # flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first() - flow = session.exec( - select(Flow) - .where(Flow.id == flow_id) - .where(Flow.user_id == api_key_user.id) - ).first() + flow = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first() if flow is None: raise ValueError(f"Flow {flow_id} not found") @@ -164,18 +154,12 @@ async def run_flow_with_caching( # StatementError('(builtins.ValueError) badly formed hexadecimal UUID string') if "badly formed hexadecimal UUID string" in str(exc): # This means the Flow ID is not a valid UUID which means it can't find the flow - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail=str(exc) - ) from exc + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc except ValueError as exc: if f"Flow {flow_id} not found" in str(exc): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail=str(exc) - ) from exc + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc else: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc) - ) from exc + raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc @router.post( @@ -204,8 +188,7 @@ async def process( """ # Raise a depreciation warning logger.warning( - "The /process endpoint is deprecated and will be removed in a future version. " - "Please use /run instead." + "The /process endpoint is deprecated and will be removed in a future version. " "Please use /run instead." ) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, @@ -277,16 +260,12 @@ async def custom_component( built_frontend_node, _ = build_custom_component_template(component, user_id=user.id) - built_frontend_node = update_frontend_node_with_template_values( - built_frontend_node, raw_code.frontend_node - ) + built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code.frontend_node) return built_frontend_node @router.post("/custom_component/reload", status_code=HTTPStatus.OK) -async def reload_custom_component( - path: str, user: User = Depends(get_current_active_user) -): +async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)): from langflow.interface.custom.utils import build_custom_component_template try: diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 7da46aaf0..9d8314d48 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -167,9 +167,7 @@ class StreamData(BaseModel): data: dict def __str__(self) -> str: - return ( - f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n" - ) + return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n" class CustomComponentRequest(BaseModel): diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index 43ee7db7c..c9d1cfedd 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -34,18 +34,14 @@ class UpdateBuildConfigError(Exception): pass -def add_output_types( - frontend_node: CustomComponentFrontendNode, return_types: List[str] -): +def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]): """Add output types to the frontend node""" for return_type in return_types: if return_type is None: raise HTTPException( status_code=400, detail={ - "error": ( - "Invalid return type. Please check your code and try again." - ), + "error": ("Invalid return type. Please check your code and try again."), "traceback": traceback.format_exc(), }, ) @@ -77,18 +73,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List frontend_node.field_order = field_order -def add_base_classes( - frontend_node: CustomComponentFrontendNode, return_types: List[str] -): +def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]): """Add base classes to the frontend node""" for return_type_instance in return_types: if return_type_instance is None: raise HTTPException( status_code=400, detail={ - "error": ( - "Invalid return type. Please check your code and try again." - ), + "error": ("Invalid return type. Please check your code and try again."), "traceback": traceback.format_exc(), }, ) @@ -123,9 +115,7 @@ def get_field_properties(extra_field): # a required field is a field that does not contain # optional in field_type # and a field that does not have a default value - field_required = "optional" not in field_type.lower() and isinstance( - field_value, MissingDefault - ) + field_required = "optional" not in field_type.lower() and isinstance(field_value, MissingDefault) field_value = field_value if not isinstance(field_value, MissingDefault) else None if not field_required: @@ -174,14 +164,10 @@ def add_new_custom_field( # If options is a list, then it's a dropdown # If options is None, then it's a list of strings is_list = isinstance(field_config.get("options"), list) - field_config["is_list"] = ( - is_list or field_config.get("list", False) or field_contains_list - ) + field_config["is_list"] = is_list or field_config.get("list", False) or field_contains_list if "name" in field_config: - warnings.warn( - "The 'name' key in field_config is used to build the object and can't be changed." - ) + warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.") required = field_config.pop("required", field_required) placeholder = field_config.pop("placeholder", "") @@ -220,9 +206,7 @@ def add_extra_fields(frontend_node, field_config, function_args): ]: continue - field_name, field_type, field_value, field_required = get_field_properties( - extra_field - ) + field_name, field_type, field_value, field_required = get_field_properties(extra_field) config = _field_config.pop(field_name, {}) frontend_node = add_new_custom_field( frontend_node, @@ -232,17 +216,13 @@ def add_extra_fields(frontend_node, field_config, function_args): field_required, config, ) - if "kwargs" in function_args_names and not all( - key in function_args_names for key in field_config.keys() - ): + if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config.keys()): for field_name, field_config in _field_config.copy().items(): if "name" not in field_config or field_name == "code": continue config = _field_config.get(field_name, {}) config = config.model_dump() if isinstance(config, BaseModel) else config - field_name, field_type, field_value, field_required = get_field_properties( - extra_field=config - ) + field_name, field_type, field_value, field_required = get_field_properties(extra_field=config) frontend_node = add_new_custom_field( frontend_node, field_name, @@ -278,9 +258,7 @@ def run_build_config( raise HTTPException( status_code=400, detail={ - "error": ( - "Invalid type convertion. Please check your code and try again." - ), + "error": ("Invalid type convertion. Please check your code and try again."), "traceback": traceback.format_exc(), }, ) from exc @@ -360,16 +338,10 @@ def build_custom_component_template( add_extra_fields(frontend_node, field_config, entrypoint_args) - frontend_node = add_code_field( - frontend_node, custom_component.code, field_config.get("code", {}) - ) + frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {})) - add_base_classes( - frontend_node, custom_component.get_function_entrypoint_return_type - ) - add_output_types( - frontend_node, custom_component.get_function_entrypoint_return_type - ) + add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type) + add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type) reorder_fields(frontend_node, custom_instance._get_field_order()) @@ -380,9 +352,7 @@ def build_custom_component_template( raise HTTPException( status_code=400, detail={ - "error": ( - f"Something went wrong while building the custom component. Hints: {str(exc)}" - ), + "error": (f"Something went wrong while building the custom component. Hints: {str(exc)}"), "traceback": traceback.format_exc(), }, ) from exc @@ -418,9 +388,7 @@ def build_custom_components(components_paths: List[str]): custom_component_dict = build_custom_component_list_from_path(path_str) if custom_component_dict: category = next(iter(custom_component_dict)) - logger.info( - f"Loading {len(custom_component_dict[category])} component(s) from category {category}" - ) + logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}") custom_components_from_file = merge_nested_dicts_with_renaming( custom_components_from_file, custom_component_dict ) @@ -455,9 +423,7 @@ def update_field_dict( build_config = dd_build_config except Exception as exc: logger.error(f"Error while running update_build_config: {str(exc)}") - raise UpdateBuildConfigError( - f"Error while running update_build_config: {str(exc)}" - ) from exc + raise UpdateBuildConfigError(f"Error while running update_build_config: {str(exc)}") from exc # Let's check if "range_spec" is a RangeSpec object if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec):