diff --git a/src/backend/langflow/api/utils.py b/src/backend/langflow/api/utils.py index a384ccbbf..8e2c3b227 100644 --- a/src/backend/langflow/api/utils.py +++ b/src/backend/langflow/api/utils.py @@ -3,12 +3,9 @@ from pathlib import Path from typing import TYPE_CHECKING, List from fastapi import HTTPException -from langchain_core.documents import Document -from langflow.services.store.schema import StoreComponentCreate -from pydantic import BaseModel - from langflow.services.store.schema import StoreComponentCreate from langflow.services.store.utils import get_lf_version_from_pypi +from platformdirs import user_cache_dir if TYPE_CHECKING: from langflow.services.database.models.flow.model import Flow @@ -173,37 +170,19 @@ async def check_langflow_version(component: StoreComponentCreate): ) -def format_elapsed_time(elapsed_time: float) -> str: - """Format elapsed time to a human-readable format coming from perf_counter(). - - - Less than 1 second: returns milliseconds - - Less than 1 minute: returns seconds rounded to 2 decimals - - 1 minute or more: returns minutes and seconds - """ +def format_elapsed_time(elapsed_time) -> str: + # Format elapsed time to human readable format coming from + # perf_counter() + # If the elapsed time is less than 1 second, return ms + # If the elapsed time is less than 1 minute, return seconds rounded to 2 decimals + time_str = "" if elapsed_time < 1: - milliseconds = int(round(elapsed_time * 1000)) - return f"{milliseconds} ms" + elapsed_time = int(round(elapsed_time * 1000)) + time_str = f"{elapsed_time} ms" elif elapsed_time < 60: - seconds = round(elapsed_time, 2) - unit = "second" if seconds == 1 else "seconds" - return f"{seconds} {unit}" + elapsed_time = round(elapsed_time, 2) + time_str = f"{elapsed_time} seconds" else: - minutes = int(elapsed_time // 60) - seconds = round(elapsed_time % 60, 2) - minutes_unit = "minute" if minutes == 1 else "minutes" - seconds_unit = "second" if seconds == 1 else "seconds" - return f"{minutes} {minutes_unit}, {seconds} {seconds_unit}" - - -def serialize_field(value): - """Unified serialization function for handling both BaseModel and Document types, - including handling lists of these types.""" - if isinstance(value, (list, tuple)): - return [serialize_field(v) for v in value] - elif isinstance(value, Document): - return value.to_json() - elif isinstance(value, BaseModel): - return value.model_dump() - elif isinstance(value, str): - return {"result": value} - return value + elapsed_time = round(elapsed_time / 60, 2) + time_str = f"{elapsed_time} minutes" + return time_str diff --git a/src/backend/langflow/interface/custom/custom_component/component.py b/src/backend/langflow/interface/custom/custom_component/component.py index 594ec982f..357a04c3d 100644 --- a/src/backend/langflow/interface/custom/custom_component/component.py +++ b/src/backend/langflow/interface/custom/custom_component/component.py @@ -1,4 +1,3 @@ -import ast import operator import warnings from typing import Any, ClassVar, Optional @@ -7,6 +6,7 @@ from cachetools import TTLCache, cachedmethod from fastapi import HTTPException from langflow.interface.custom.code_parser import CodeParser +from langflow.interface.custom.eval import eval_custom_component_code from langflow.utils import validate @@ -63,26 +63,29 @@ class Component: return validate.create_function(self.code, self._function_entrypoint_name) - def build_template_config(self, attributes) -> dict: + def getattr_return_str(self, component, attribute): + attribute = getattr(component, attribute) + return str(attribute) if attribute else "" + + def build_template_config(self) -> dict: + if not self.code: + return {} + + cc_class = eval_custom_component_code(self.code) + component_instance = cc_class() template_config = {} + attributes_func_mapping = { + "display_name": self.getattr_return_str, + "description": self.getattr_return_str, + "beta": self.getattr_return_str, + "documentation": self.getattr_return_str, + } - for item in attributes: - item_name = item.get("name") + for attribute, func in attributes_func_mapping.items(): + if hasattr(component_instance, attribute): + template_config[attribute] = func(component_instance, attribute) - if item_value := item.get("value"): - if "display_name" in item_name: - template_config["display_name"] = ast.literal_eval(item_value) - - elif "description" in item_name: - template_config["description"] = ast.literal_eval(item_value) - - elif "beta" in item_name: - template_config["beta"] = ast.literal_eval(item_value) - - elif "documentation" in item_name: - template_config["documentation"] = ast.literal_eval(item_value) - - return template_config + return template_config def build(self, *args: Any, **kwargs: Any) -> Any: raise NotImplementedError diff --git a/src/backend/langflow/interface/custom/custom_component/custom_component.py b/src/backend/langflow/interface/custom/custom_component/custom_component.py index 542ebe75e..0021de41d 100644 --- a/src/backend/langflow/interface/custom/custom_component/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component/custom_component.py @@ -5,6 +5,7 @@ from uuid import UUID import yaml from cachetools import TTLCache, cachedmethod from fastapi import HTTPException + from langflow.interface.custom.code_parser.utils import ( extract_inner_type_from_generic_alias, extract_union_types_from_generic_alias, @@ -137,20 +138,6 @@ class CustomComponent(Component): def template_config(self): return self.build_template_config() - def build_template_config(self): - if not self.code: - return {} - - attributes = [ - main_class["attributes"] - for main_class in self.tree.get("classes", []) - if main_class["name"] == self.get_main_class_name - ] - # Get just the first item - attributes = next(iter(attributes), []) - - return super().build_template_config(attributes) - @property def keys(self): def get_credential(name: str): diff --git a/src/backend/langflow/interface/custom/eval.py b/src/backend/langflow/interface/custom/eval.py new file mode 100644 index 000000000..b36f10d92 --- /dev/null +++ b/src/backend/langflow/interface/custom/eval.py @@ -0,0 +1,12 @@ +from typing import TYPE_CHECKING, Type + +from langflow.utils import validate + +if TYPE_CHECKING: + from langflow.interface.custom.custom_component import CustomComponent + + +def eval_custom_component_code(code: str) -> Type["CustomComponent"]: + """Evaluate custom component code""" + class_name = validate.extract_class_name(code) + return validate.create_class(code, class_name) diff --git a/src/backend/langflow/interface/custom/utils.py b/src/backend/langflow/interface/custom/utils.py index 5f901bedf..1ad68edb5 100644 --- a/src/backend/langflow/interface/custom/utils.py +++ b/src/backend/langflow/interface/custom/utils.py @@ -7,6 +7,8 @@ from typing import Any, Dict, List, Optional, Union from uuid import UUID from fastapi import HTTPException +from loguru import logger + from langflow.field_typing.range_spec import RangeSpec from langflow.interface.custom.code_parser.utils import extract_inner_type from langflow.interface.custom.custom_component import CustomComponent @@ -15,11 +17,11 @@ from langflow.interface.custom.directory_reader.utils import ( determine_component_name, merge_nested_dicts_with_renaming, ) -from langflow.interface.importing.utils import eval_custom_component_code +from langflow.interface.custom.eval import eval_custom_component_code from langflow.template.field.base import TemplateField from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode +from langflow.utils import validate from langflow.utils.util import get_base_classes -from loguru import logger def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]): @@ -370,3 +372,10 @@ def build_component(component): component_template = create_component_template(component) logger.debug(f"Building component: {component_name, component.get('output_types')}") return component_name, component_template + + +def get_function(code): + """Get the function""" + function_name = validate.extract_function_name(code) + + return validate.create_function(code, function_name) diff --git a/src/backend/langflow/interface/importing/utils.py b/src/backend/langflow/interface/importing/utils.py index 9d7305f16..62e1b5e42 100644 --- a/src/backend/langflow/interface/importing/utils.py +++ b/src/backend/langflow/interface/importing/utils.py @@ -9,9 +9,9 @@ from langchain.chains.base import Chain from langchain.prompts import PromptTemplate from langchain.tools import BaseTool from langchain_core.language_models.chat_models import BaseChatModel + from langflow.interface.custom.custom_component import CustomComponent from langflow.interface.wrappers.base import wrapper_creator -from langflow.utils import validate def import_module(module_path: str) -> Any: @@ -171,16 +171,3 @@ def import_utility(utility: str) -> Any: if utility == "SQLDatabase": return import_class(f"langchain_community.sql_database.{utility}") return import_class(f"langchain_community.utilities.{utility}") - - -def get_function(code): - """Get the function""" - function_name = validate.extract_function_name(code) - - return validate.create_function(code, function_name) - - -def eval_custom_component_code(code: str) -> Type[CustomComponent]: - """Evaluate custom component code""" - class_name = validate.extract_class_name(code) - return validate.create_class(code, class_name) diff --git a/src/backend/langflow/interface/initialize/loading.py b/src/backend/langflow/interface/initialize/loading.py index 804c07339..37214369c 100644 --- a/src/backend/langflow/interface/initialize/loading.py +++ b/src/backend/langflow/interface/initialize/loading.py @@ -14,8 +14,10 @@ from langchain_core.documents import Document from loguru import logger from pydantic import ValidationError +from langflow.interface.custom.eval import eval_custom_component_code +from langflow.interface.custom.utils import get_function from langflow.interface.custom_lists import CUSTOM_NODES -from langflow.interface.importing.utils import eval_custom_component_code, get_function, import_by_type +from langflow.interface.importing.utils import import_by_type from langflow.interface.initialize.llm import initialize_vertexai from langflow.interface.initialize.utils import handle_format_kwargs, handle_node_type, handle_partial_variables from langflow.interface.initialize.vector_store import vecstore_initializer diff --git a/src/backend/langflow/interface/tools/custom.py b/src/backend/langflow/interface/tools/custom.py index 73f5842df..6ba8cac13 100644 --- a/src/backend/langflow/interface/tools/custom.py +++ b/src/backend/langflow/interface/tools/custom.py @@ -1,10 +1,10 @@ from typing import Callable, Optional -from langflow.interface.importing.utils import get_function +from langchain.agents.tools import Tool from pydantic.v1 import BaseModel, validator +from langflow.interface.custom.utils import get_function from langflow.utils import validate -from langchain.agents.tools import Tool class Function(BaseModel):