From 58c43695469963b7d0e73e508d75eeb41f47b3bb Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:32:02 -0300 Subject: [PATCH 01/26] refactor: update code references to use _code instead of code --- src/backend/base/langflow/api/v1/endpoints.py | 4 +- .../custom/custom_component/base_component.py | 11 ++-- .../custom_component/custom_component.py | 10 ++-- .../directory_reader/directory_reader.py | 2 +- src/backend/base/langflow/custom/utils.py | 18 +++---- .../tests/unit/test_custom_component.py | 50 +++++++++---------- .../tests/unit/test_helper_components.py | 2 +- 7 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/backend/base/langflow/api/v1/endpoints.py b/src/backend/base/langflow/api/v1/endpoints.py index fadd1d7b313a..296bfbba264c 100644 --- a/src/backend/base/langflow/api/v1/endpoints.py +++ b/src/backend/base/langflow/api/v1/endpoints.py @@ -552,7 +552,7 @@ async def custom_component( raw_code: CustomComponentRequest, user: User = Depends(get_current_active_user), ): - component = Component(code=raw_code.code) + component = Component(_code=raw_code.code) built_frontend_node, component_instance = build_custom_component_template(component, user_id=user.id) if raw_code.frontend_node is not None: @@ -582,7 +582,7 @@ async def custom_component_update( """ try: - component = Component(code=code_request.code) + component = Component(_code=code_request.code) component_node, cc_instance = build_custom_component_template( component, diff --git a/src/backend/base/langflow/custom/custom_component/base_component.py b/src/backend/base/langflow/custom/custom_component/base_component.py index 098942dd4182..ce1c5c445b44 100644 --- a/src/backend/base/langflow/custom/custom_component/base_component.py +++ b/src/backend/base/langflow/custom/custom_component/base_component.py @@ -23,7 +23,8 @@ class BaseComponent: ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided." ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided." - code: Optional[str] = None + _code: Optional[str] = None + """The code of the component. Defaults to None.""" _function_entrypoint_name: str = "build" field_config: dict = {} _user_id: Optional[str] @@ -47,7 +48,7 @@ def get_code_tree(self, code: str): return parser.parse_code() def get_function(self): - if not self.code: + if not self._code: raise ComponentCodeNullError( status_code=400, detail={"error": self.ERROR_CODE_NULL, "traceback": ""}, @@ -62,7 +63,7 @@ def get_function(self): }, ) - return validate.create_function(self.code, self._function_entrypoint_name) + return validate.create_function(self._code, self._function_entrypoint_name) def build_template_config(self) -> dict: """ @@ -71,10 +72,10 @@ def build_template_config(self) -> dict: Returns: A dictionary representing the template configuration. """ - if not self.code: + if not self._code: return {} - cc_class = eval_custom_component_code(self.code) + cc_class = eval_custom_component_code(self._code) component_instance = cc_class() template_config = {} diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index c5e10932f724..95c10022f033 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -64,8 +64,6 @@ class CustomComponent(BaseComponent): is_output: Optional[bool] = None """The output state of the component. Defaults to None. If True, the component must have a field named 'input_value'.""" - code: Optional[str] = None - """The code of the component. Defaults to None.""" field_config: dict = {} """The field configuration of the component. Defaults to an empty dictionary.""" field_order: Optional[List[str]] = None @@ -226,7 +224,7 @@ def tree(self): Returns: dict: The code tree of the custom component. """ - return self.get_code_tree(self.code or "") + return self.get_code_tree(self._code or "") def to_data(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False) -> List[Data]: """ @@ -326,7 +324,7 @@ def get_method(self, method_name: str): Returns: dict: The build method for the custom component. """ - if not self.code: + if not self._code: return {} component_classes = [ @@ -379,7 +377,7 @@ def get_main_class_name(self): Returns: str: The main class name of the custom component. """ - if not self.code: + if not self._code: return "" base_name = self.code_class_base_inheritance @@ -468,7 +466,7 @@ def get_function(self): Returns: Callable: The function associated with the custom component. """ - return validate.create_function(self.code, self.function_entrypoint_name) + return validate.create_function(self._code, self.function_entrypoint_name) async def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> "Graph": if not self._user_id: diff --git a/src/backend/base/langflow/custom/directory_reader/directory_reader.py b/src/backend/base/langflow/custom/directory_reader/directory_reader.py index 1fb4ca93e5a8..18f39b5eb039 100644 --- a/src/backend/base/langflow/custom/directory_reader/directory_reader.py +++ b/src/backend/base/langflow/custom/directory_reader/directory_reader.py @@ -373,7 +373,7 @@ def get_output_types_from_code(code: str) -> list: """ Get the output types from the code. """ - custom_component = CustomComponent(code=code) + custom_component = CustomComponent(_code=code) types_list = custom_component.get_function_entrypoint_return_type # Get the name of types classes diff --git a/src/backend/base/langflow/custom/utils.py b/src/backend/base/langflow/custom/utils.py index d721bc275dc8..84aef23994ff 100644 --- a/src/backend/base/langflow/custom/utils.py +++ b/src/backend/base/langflow/custom/utils.py @@ -266,10 +266,10 @@ def run_build_inputs( def get_component_instance(custom_component: CustomComponent, user_id: Optional[Union[str, UUID]] = None): try: - if custom_component.code is None: + if custom_component._code is None: raise ValueError("Code is None") - elif isinstance(custom_component.code, str): - custom_class = eval_custom_component_code(custom_component.code) + elif isinstance(custom_component._code, str): + custom_class = eval_custom_component_code(custom_component._code) else: raise ValueError("Invalid code type") except Exception as exc: @@ -300,10 +300,10 @@ def run_build_config( """Build the field configuration for a custom component""" try: - if custom_component.code is None: + if custom_component._code is None: raise ValueError("Code is None") - elif isinstance(custom_component.code, str): - custom_class = eval_custom_component_code(custom_component.code) + elif isinstance(custom_component._code, str): + custom_class = eval_custom_component_code(custom_component._code) else: raise ValueError("Invalid code type") except Exception as exc: @@ -363,7 +363,7 @@ def build_custom_component_template_from_inputs( # The List of Inputs fills the role of the build_config and the entrypoint_args field_config = custom_component.template_config frontend_node = ComponentFrontendNode.from_inputs(**field_config) - 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", {})) # But we now need to calculate the return_type of the methods in the outputs for output in frontend_node.outputs: if output.types: @@ -407,7 +407,7 @@ 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) @@ -432,7 +432,7 @@ def create_component_template(component): component_code = component["code"] component_output_types = component["output_types"] - component_extractor = Component(code=component_code) + component_extractor = Component(_code=component_code) component_template, component_instance = build_custom_component_template(component_extractor) if not component_template["output_types"] and component_output_types: diff --git a/src/backend/tests/unit/test_custom_component.py b/src/backend/tests/unit/test_custom_component.py index a0e5525cb57a..d582f2933450 100644 --- a/src/backend/tests/unit/test_custom_component.py +++ b/src/backend/tests/unit/test_custom_component.py @@ -16,7 +16,7 @@ def code_component_with_multiple_outputs(): with open("src/backend/tests/data/component_multiple_outputs.py", "r") as f: code = f.read() - return Component(code=code) + return Component(_code=code) code_default = """ @@ -72,8 +72,8 @@ def test_component_init(): """ Test the initialization of the Component class. """ - component = BaseComponent(code=code_default, function_entrypoint_name="build") - assert component.code == code_default + component = BaseComponent(_code=code_default, function_entrypoint_name="build") + assert component._code == code_default assert component.function_entrypoint_name == "build" @@ -81,8 +81,8 @@ def test_component_get_code_tree(): """ Test the get_code_tree method of the Component class. """ - component = BaseComponent(code=code_default, function_entrypoint_name="build") - tree = component.get_code_tree(component.code) + component = BaseComponent(_code=code_default, function_entrypoint_name="build") + tree = component.get_code_tree(component._code) assert "imports" in tree @@ -91,7 +91,7 @@ def test_component_code_null_error(): Test the get_function method raises the ComponentCodeNullError when the code is empty. """ - component = BaseComponent(code="", function_entrypoint_name="") + component = BaseComponent(_code="", function_entrypoint_name="") with pytest.raises(ComponentCodeNullError): component.get_function() @@ -102,8 +102,8 @@ def test_custom_component_init(): """ function_entrypoint_name = "build" - custom_component = CustomComponent(code=code_default, function_entrypoint_name=function_entrypoint_name) - assert custom_component.code == code_default + custom_component = CustomComponent(_code=code_default, function_entrypoint_name=function_entrypoint_name) + assert custom_component._code == code_default assert custom_component.function_entrypoint_name == function_entrypoint_name @@ -111,7 +111,7 @@ def test_custom_component_build_template_config(): """ Test the build_template_config property of the CustomComponent class. """ - custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") + custom_component = CustomComponent(_code=code_default, function_entrypoint_name="build") config = custom_component.build_template_config() assert isinstance(config, dict) @@ -120,7 +120,7 @@ def test_custom_component_get_function(): """ Test the get_function property of the CustomComponent class. """ - custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build") + custom_component = CustomComponent(_code="def build(): pass", function_entrypoint_name="build") my_function = custom_component.get_function() assert isinstance(my_function, types.FunctionType) @@ -195,7 +195,7 @@ def test_component_get_function_valid(): Test the get_function method of the Component class with valid code and function_entrypoint_name. """ - component = BaseComponent(code="def build(): pass", function_entrypoint_name="build") + component = BaseComponent(_code="def build(): pass", function_entrypoint_name="build") my_function = component.get_function() assert callable(my_function) @@ -205,7 +205,7 @@ def test_custom_component_get_function_entrypoint_args(): Test the get_function_entrypoint_args property of the CustomComponent class. """ - custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") + custom_component = CustomComponent(_code=code_default, function_entrypoint_name="build") args = custom_component.get_function_entrypoint_args assert len(args) == 3 assert args[0]["name"] == "self" @@ -219,7 +219,7 @@ def test_custom_component_get_function_entrypoint_return_type(): property of the CustomComponent class. """ - custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") + custom_component = CustomComponent(_code=code_default, function_entrypoint_name="build") return_type = custom_component.get_function_entrypoint_return_type assert return_type == [Document] @@ -228,7 +228,7 @@ def test_custom_component_get_main_class_name(): """ Test the get_main_class_name property of the CustomComponent class. """ - custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") + custom_component = CustomComponent(_code=code_default, function_entrypoint_name="build") class_name = custom_component.get_main_class_name assert class_name == "YourComponent" @@ -238,7 +238,7 @@ def test_custom_component_get_function_valid(): Test the get_function property of the CustomComponent class with valid code and function_entrypoint_name. """ - custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build") + custom_component = CustomComponent(_code="def build(): pass", function_entrypoint_name="build") my_function = custom_component.get_function assert callable(my_function) @@ -352,9 +352,9 @@ def test_component_get_code_tree_syntax_error(): Test the get_code_tree method of the Component class raises the CodeSyntaxError when given incorrect syntax. """ - component = BaseComponent(code="import os as", function_entrypoint_name="build") + component = BaseComponent(_code="import os as", function_entrypoint_name="build") with pytest.raises(CodeSyntaxError): - component.get_code_tree(component.code) + component.get_code_tree(component._code) def test_custom_component_class_template_validation_no_code(): @@ -362,7 +362,7 @@ def test_custom_component_class_template_validation_no_code(): Test the _class_template_validation method of the CustomComponent class raises the HTTPException when the code is None. """ - custom_component = CustomComponent(code=None, function_entrypoint_name="build") + custom_component = CustomComponent(_code=None, function_entrypoint_name="build") with pytest.raises(TypeError): custom_component.get_function() @@ -372,9 +372,9 @@ def test_custom_component_get_code_tree_syntax_error(): Test the get_code_tree method of the CustomComponent class raises the CodeSyntaxError when given incorrect syntax. """ - custom_component = CustomComponent(code="import os as", function_entrypoint_name="build") + custom_component = CustomComponent(_code="import os as", function_entrypoint_name="build") with pytest.raises(CodeSyntaxError): - custom_component.get_code_tree(custom_component.code) + custom_component.get_code_tree(custom_component._code) def test_custom_component_get_function_entrypoint_args_no_args(): @@ -387,7 +387,7 @@ class MyMainClass(CustomComponent): def build(): pass""" - custom_component = CustomComponent(code=my_code, function_entrypoint_name="build") + custom_component = CustomComponent(_code=my_code, function_entrypoint_name="build") args = custom_component.get_function_entrypoint_args assert len(args) == 0 @@ -402,7 +402,7 @@ class MyClass(CustomComponent): def build(): pass""" - custom_component = CustomComponent(code=my_code, function_entrypoint_name="build") + custom_component = CustomComponent(_code=my_code, function_entrypoint_name="build") return_type = custom_component.get_function_entrypoint_return_type assert return_type == [] @@ -416,7 +416,7 @@ def test_custom_component_get_main_class_name_no_main_class(): def build(): pass""" - custom_component = CustomComponent(code=my_code, function_entrypoint_name="build") + custom_component = CustomComponent(_code=my_code, function_entrypoint_name="build") class_name = custom_component.get_main_class_name assert class_name == "" @@ -426,13 +426,13 @@ def test_custom_component_build_not_implemented(): Test the build method of the CustomComponent class raises the NotImplementedError. """ - custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build") + custom_component = CustomComponent(_code="def build(): pass", function_entrypoint_name="build") with pytest.raises(NotImplementedError): custom_component.build() def test_build_config_no_code(): - component = CustomComponent(code=None) + component = CustomComponent(_code=None) assert component.get_function_entrypoint_args == [] assert component.get_function_entrypoint_return_type == [] diff --git a/src/backend/tests/unit/test_helper_components.py b/src/backend/tests/unit/test_helper_components.py index dc07c583549c..75395b8620f5 100644 --- a/src/backend/tests/unit/test_helper_components.py +++ b/src/backend/tests/unit/test_helper_components.py @@ -32,7 +32,7 @@ def test_uuid_generator_component(): # Arrange uuid_generator_component = helpers.IDGeneratorComponent() - uuid_generator_component.code = open(helpers.IDGenerator.__file__, "r").read() + uuid_generator_component._code = open(helpers.IDGenerator.__file__, "r").read() frontend_node, _ = build_custom_component_template(uuid_generator_component) From 1f96febbb9dede439e000c99d42313d5ad18dfd1 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:39:04 -0300 Subject: [PATCH 02/26] refactor: add backwards compatible attributes to Component class --- .../base/langflow/custom/custom_component/component.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 2aafd92109ce..b3bef4fd348e 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -14,6 +14,8 @@ from .custom_component import CustomComponent +BACKWARDS_COMPATIBLE_ATTRIBUTES = ["user_id", "vertex", "tracing_service"] + class Component(CustomComponent): inputs: List[InputTypes] = [] @@ -39,6 +41,8 @@ def __getattr__(self, name: str) -> Any: return self.__dict__["_attributes"][name] if "_inputs" in self.__dict__ and name in self.__dict__["_inputs"]: return self.__dict__["_inputs"][name].value + if name in BACKWARDS_COMPATIBLE_ATTRIBUTES: + return self.__dict__[f"_{name}"] raise AttributeError(f"{name} not found in {self.__class__.__name__}") def map_inputs(self, inputs: List[InputTypes]): From 290f685d4edfd02290a9cc5bee2f31a456a8835a Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:40:29 -0300 Subject: [PATCH 03/26] refactor: update Component constructor to pass config params with underscore Refactored the `Component` class in `component.py` to handle inputs and outputs. Added a new method `map_outputs` to map a list of outputs to the component. Also updated the `__init__` method to properly initialize the inputs, outputs, and other attributes. This change improves the flexibility and extensibility of the `Component` class. Co-authored-by: Gabriel Luiz Freitas Almeida --- .../custom/custom_component/component.py | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index b3bef4fd348e..1134918553df 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -23,18 +23,36 @@ class Component(CustomComponent): code_class_base_inheritance: ClassVar[str] = "Component" _output_logs: dict[str, Log] = {} - def __init__(self, **data): + def __init__(self, **kwargs): + # if key starts with _ it is a config + # else it is an input + inputs = {} + config = {} + for key, value in kwargs.items(): + if key.startswith("_"): + config[key] = value + else: + inputs[key] = value self._inputs: dict[str, InputTypes] = {} + self._outputs: dict[str, Output] = {} self._results: dict[str, Any] = {} self._attributes: dict[str, Any] = {} - self._parameters: dict[str, Any] = {} + self._parameters = inputs or {} + self._components: list[Component] = [] + self.set_attributes(self._parameters) self._output_logs = {} - super().__init__(**data) + config = config or {} + if "_id" not in config: + config |= {"_id": f"{self.__class__.__name__}-{nanoid.generate(size=5)}"} + super().__init__(**config) + if hasattr(self, "_trace_type"): + self.trace_type = self._trace_type if not hasattr(self, "trace_type"): self.trace_type = "chain" if self.inputs is not None: self.map_inputs(self.inputs) - self.set_attributes(self._parameters) + if self.outputs is not None: + self.map_outputs(self.outputs) def __getattr__(self, name: str) -> Any: if "_attributes" in self.__dict__ and name in self.__dict__["_attributes"]: @@ -52,6 +70,22 @@ def map_inputs(self, inputs: List[InputTypes]): raise ValueError("Input name cannot be None.") self._inputs[input_.name] = input_ + def map_outputs(self, outputs: List[Output]): + """ + Maps the given list of outputs to the component. + Args: + outputs (List[Output]): The list of outputs to be mapped. + Raises: + ValueError: If the output name is None. + Returns: + None + """ + self.outputs = outputs + for output in outputs: + if output.name is None: + raise ValueError("Output name cannot be None.") + self._outputs[output.name] = output + def validate(self, params: dict): self._validate_inputs(params) self._validate_outputs() From ca1aa328227835c66357d191c7ff6c862d854112 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:42:32 -0300 Subject: [PATCH 04/26] refactor: change attribute to use underscore --- .../base/langflow/base/agents/crewai/crew.py | 6 +-- .../base/langflow/base/models/model.py | 6 +-- .../langflow/components/prototypes/Listen.py | 6 +-- .../langflow/components/prototypes/Notify.py | 6 +-- .../custom/custom_component/component.py | 19 ++++---- .../custom_component/custom_component.py | 44 ++++++++----------- .../base/langflow/services/tracing/service.py | 6 +-- 7 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/backend/base/langflow/base/agents/crewai/crew.py b/src/backend/base/langflow/base/agents/crewai/crew.py index 8326c3965ef0..359b87591fd4 100644 --- a/src/backend/base/langflow/base/agents/crewai/crew.py +++ b/src/backend/base/langflow/base/agents/crewai/crew.py @@ -51,8 +51,8 @@ def get_task_callback( self, ) -> Callable: def task_callback(task_output: TaskOutput): - if self.vertex: - vertex_id = self.vertex.id + if self._vertex: + vertex_id = self._vertex.id else: vertex_id = self.display_name or self.__class__.__name__ self.log(task_output.model_dump(), name=f"Task (Agent: {task_output.agent}) - {vertex_id}") @@ -63,7 +63,7 @@ def get_step_callback( self, ) -> Callable: def step_callback(agent_output: Union[AgentFinish, List[Tuple[AgentAction, str]]]): - _id = self.vertex.id if self.vertex else self.display_name + _id = self._vertex.id if self._vertex else self.display_name if isinstance(agent_output, AgentFinish): messages = agent_output.messages self.log(cast(dict, messages[0].to_json()), name=f"Finish (Agent: {_id})") diff --git a/src/backend/base/langflow/base/models/model.py b/src/backend/base/langflow/base/models/model.py index 6a2aeda1d534..9a1057c62768 100644 --- a/src/backend/base/langflow/base/models/model.py +++ b/src/backend/base/langflow/base/models/model.py @@ -1,7 +1,7 @@ import json import warnings from abc import abstractmethod -from typing import Optional, Union, List +from typing import List, Optional, Union from langchain_core.language_models.llms import LLM from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage @@ -10,7 +10,7 @@ from langflow.custom import Component from langflow.field_typing import LanguageModel from langflow.inputs import MessageInput, MessageTextInput -from langflow.inputs.inputs import InputTypes, BoolInput +from langflow.inputs.inputs import BoolInput, InputTypes from langflow.schema.message import Message from langflow.template.field.base import Output @@ -164,7 +164,7 @@ def get_chat_result( inputs: Union[list, dict] = messages or {} try: runnable = runnable.with_config( # type: ignore - {"run_name": self.display_name, "project_name": self.tracing_service.project_name} # type: ignore + {"run_name": self.display_name, "project_name": self._tracing_service.project_name} # type: ignore ) if stream: return runnable.stream(inputs) # type: ignore diff --git a/src/backend/base/langflow/components/prototypes/Listen.py b/src/backend/base/langflow/components/prototypes/Listen.py index 6e5de723a87a..e75ec070b8ac 100644 --- a/src/backend/base/langflow/components/prototypes/Listen.py +++ b/src/backend/base/langflow/components/prototypes/Listen.py @@ -23,6 +23,6 @@ def build(self, name: str) -> Data: return state def _set_successors_ids(self): - self.vertex.is_state = True - successors = self.vertex.graph.successor_map.get(self.vertex.id, []) - return successors + self.vertex.graph.activated_vertices + self._vertex.is_state = True + successors = self._vertex.graph.successor_map.get(self._vertex.id, []) + return successors + self._vertex.graph.activated_vertices diff --git a/src/backend/base/langflow/components/prototypes/Notify.py b/src/backend/base/langflow/components/prototypes/Notify.py index b83331e3a914..72287a25509b 100644 --- a/src/backend/base/langflow/components/prototypes/Notify.py +++ b/src/backend/base/langflow/components/prototypes/Notify.py @@ -43,6 +43,6 @@ def build(self, name: str, data: Optional[Data] = None, append: bool = False) -> return data def _set_successors_ids(self): - self.vertex.is_state = True - successors = self.vertex.graph.successor_map.get(self.vertex.id, []) - return successors + self.vertex.graph.activated_vertices + self._vertex.is_state = True + successors = self._vertex.graph.successor_map.get(self._vertex.id, []) + return successors + self._vertex.graph.activated_vertices diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 1134918553df..b51552c791ef 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -2,6 +2,7 @@ from typing import Any, Callable, ClassVar, List, Optional, Union from uuid import UUID +import nanoid import yaml from pydantic import BaseModel @@ -144,9 +145,9 @@ def get_trace_as_metadata(self): async def _build_with_tracing(self): inputs = self.get_trace_as_inputs() metadata = self.get_trace_as_metadata() - async with self.tracing_service.trace_context(self, self.trace_name, inputs, metadata): + async with self._tracing_service.trace_context(self, self.trace_name, inputs, metadata): _results, _artifacts = await self._build_results() - self.tracing_service.set_outputs(self.trace_name, _results) + self._tracing_service.set_outputs(self.trace_name, _results) return _results, _artifacts @@ -154,7 +155,7 @@ async def _build_without_tracing(self): return await self._build_results() async def build_results(self): - if self.tracing_service: + if self._tracing_service: return await self._build_with_tracing() return await self._build_without_tracing() @@ -162,11 +163,11 @@ async def _build_results(self): _results = {} _artifacts = {} if hasattr(self, "outputs"): - self._set_outputs(self.vertex.outputs) + self._set_outputs(self._vertex.outputs) for output in self.outputs: # Build the output if it's connected to some other vertex # or if it's not connected to any vertex - if not self.vertex.outgoing_edges or output.name in self.vertex.edges_source_names: + if not self._vertex.outgoing_edges or output.name in self._vertex.edges_source_names: if output.method is None: raise ValueError(f"Output {output.name} does not have a method defined.") method: Callable = getattr(self, output.method) @@ -180,9 +181,9 @@ async def _build_results(self): if ( isinstance(result, Message) and result.flow_id is None - and self.vertex.graph.flow_id is not None + and self._vertex.graph.flow_id is not None ): - result.set_flow_id(self.vertex.graph.flow_id) + result.set_flow_id(self._vertex.graph.flow_id) _results[output.name] = result output.value = result custom_repr = self.custom_repr() @@ -214,8 +215,8 @@ async def _build_results(self): self._logs = [] self._artifacts = _artifacts self._results = _results - if self.tracing_service: - self.tracing_service.set_outputs(self.trace_name, _results) + if self._tracing_service: + self._tracing_service.set_outputs(self.trace_name, _results) return _results, _artifacts def custom_repr(self): diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index 95c10022f033..a0452ca9e040 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -14,7 +14,7 @@ from langflow.schema.dotdict import dotdict from langflow.schema.log import LoggableType from langflow.schema.schema import OutputValue -from langflow.services.deps import get_storage_service, get_tracing_service, get_variable_service, session_scope +from langflow.services.deps import get_storage_service, get_variable_service, session_scope from langflow.services.storage.service import StorageService from langflow.services.tracing.schema import Log from langflow.template.utils import update_frontend_node_with_template_values @@ -72,20 +72,20 @@ class CustomComponent(BaseComponent): """The default frozen state of the component. Defaults to False.""" build_parameters: Optional[dict] = None """The build parameters of the component. Defaults to None.""" - vertex: Optional["Vertex"] = None + _vertex: Optional["Vertex"] = None """The edge target parameter of the component. Defaults to None.""" code_class_base_inheritance: ClassVar[str] = "CustomComponent" function_entrypoint_name: ClassVar[str] = "build" function: Optional[Callable] = None repr_value: Optional[Any] = "" - user_id: Optional[Union[UUID, str]] = None + _user_id: Optional[Union[UUID, str]] = None status: Optional[Any] = None """The status of the component. This is displayed on the frontend. Defaults to None.""" _flows_data: Optional[List[Data]] = None _outputs: List[OutputValue] = [] _logs: List[Log] = [] _output_logs: dict[str, Log] = {} - tracing_service: Optional["TracingService"] = None + _tracing_service: Optional["TracingService"] = None def set_attributes(self, parameters: dict): pass @@ -94,51 +94,43 @@ def set_parameters(self, parameters: dict): self._parameters = parameters self.set_attributes(self._parameters) - @classmethod - def initialize(cls, **kwargs): - user_id = kwargs.pop("user_id", None) - vertex = kwargs.pop("vertex", None) - tracing_service = kwargs.pop("tracing_service", get_tracing_service()) - params_copy = kwargs.copy() - return cls(user_id=user_id, _parameters=params_copy, vertex=vertex, tracing_service=tracing_service) - @property def trace_name(self): - return f"{self.display_name} ({self.vertex.id})" + return f"{self.display_name} ({self._vertex.id})" def update_state(self, name: str, value: Any): - if not self.vertex: + if not self._vertex: raise ValueError("Vertex is not set") try: - self.vertex.graph.update_state(name=name, record=value, caller=self.vertex.id) + self._vertex.graph.update_state(name=name, record=value, caller=self._vertex.id) except Exception as e: raise ValueError(f"Error updating state: {e}") def stop(self, output_name: str | None = None): - if not output_name and self.vertex and len(self.vertex.outputs) == 1: - output_name = self.vertex.outputs[0]["name"] + if not output_name and self._vertex and len(self._vertex.outputs) == 1: + output_name = self._vertex.outputs[0]["name"] elif not output_name: raise ValueError("You must specify an output name to call stop") - if not self.vertex: + if not self._vertex: raise ValueError("Vertex is not set") try: - self.graph.mark_branch(vertex_id=self.vertex.id, output_name=output_name, state="INACTIVE") + self.graph.mark_branch(vertex_id=self._vertex.id, output_name=output_name, state="INACTIVE") except Exception as e: raise ValueError(f"Error stopping {self.display_name}: {e}") def append_state(self, name: str, value: Any): - if not self.vertex: + if not self._vertex: raise ValueError("Vertex is not set") try: - self.vertex.graph.append_state(name=name, record=value, caller=self.vertex.id) + self._vertex.graph.append_state(name=name, record=value, caller=self._vertex.id) except Exception as e: raise ValueError(f"Error appending state: {e}") def get_state(self, name: str): - if not self.vertex: + if not self._vertex: raise ValueError("Vertex is not set") try: - return self.vertex.graph.get_state(name=name) + return self._vertex.graph.get_state(name=name) except Exception as e: raise ValueError(f"Error getting state: {e}") @@ -176,7 +168,7 @@ def get_full_path(self, path: str) -> str: @property def graph(self): - return self.vertex.graph + return self._vertex.graph def _get_field_order(self): return self.field_order or list(self.field_config.keys()) @@ -522,8 +514,8 @@ def log(self, message: LoggableType | list[LoggableType], name: Optional[str] = name = f"Log {len(self._logs) + 1}" log = Log(message=message, type=get_artifact_type(message), name=name) self._logs.append(log) - if self.tracing_service and self.vertex: - self.tracing_service.add_log(trace_name=self.trace_name, log=log) + if self._tracing_service and self._vertex: + self._tracing_service.add_log(trace_name=self.trace_name, log=log) def post_code_processing(self, new_frontend_node: dict, current_frontend_node: dict): """ diff --git a/src/backend/base/langflow/services/tracing/service.py b/src/backend/base/langflow/services/tracing/service.py index feb74e3a2344..6a3b7ac987c3 100644 --- a/src/backend/base/langflow/services/tracing/service.py +++ b/src/backend/base/langflow/services/tracing/service.py @@ -194,8 +194,8 @@ async def trace_context( metadata: Optional[Dict[str, Any]] = None, ): trace_id = trace_name - if component.vertex: - trace_id = component.vertex.id + if component._vertex: + trace_id = component._vertex.id trace_type = component.trace_type self._start_traces( trace_id, @@ -203,7 +203,7 @@ async def trace_context( trace_type, self._cleanup_inputs(inputs), metadata, - component.vertex, + component._vertex, ) try: yield self From 81391c57687dcb357018871ba6f89f622a3644a2 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:42:41 -0300 Subject: [PATCH 05/26] refactor: update CustomComponent initialization parameters Refactored the `instantiate_class` function in `loading.py` to update the initialization parameters for the `CustomComponent` class. Changed the parameter names from `user_id`, `parameters`, `vertex`, and `tracing_service` to `_user_id`, `_parameters`, `_vertex`, and `_tracing_service` respectively. This change ensures consistency and improves code readability. Co-authored-by: Gabriel Luiz Freitas Almeida --- .../langflow/interface/initialize/loading.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/backend/base/langflow/interface/initialize/loading.py b/src/backend/base/langflow/interface/initialize/loading.py index f090febf3a8a..ea2858100ac4 100644 --- a/src/backend/base/langflow/interface/initialize/loading.py +++ b/src/backend/base/langflow/interface/initialize/loading.py @@ -33,11 +33,11 @@ async def instantiate_class( custom_params = get_params(vertex.params) code = custom_params.pop("code") class_object: Type["CustomComponent" | "Component"] = eval_custom_component_code(code) - custom_component: "CustomComponent" | "Component" = class_object.initialize( - user_id=user_id, - parameters=custom_params, - vertex=vertex, - tracing_service=get_tracing_service(), + custom_component: "CustomComponent" | "Component" = class_object( + _user_id=user_id, + _parameters=custom_params, + _vertex=vertex, + _tracing_service=get_tracing_service(), ) return custom_component, custom_params @@ -186,9 +186,9 @@ async def build_custom_component(params: dict, custom_component: "CustomComponen raw = post_process_raw(raw, artifact_type) artifact = {"repr": custom_repr, "raw": raw, "type": artifact_type} - if custom_component.vertex is not None: - custom_component._artifacts = {custom_component.vertex.outputs[0].get("name"): artifact} - custom_component._results = {custom_component.vertex.outputs[0].get("name"): build_result} + if custom_component._vertex is not None: + custom_component._artifacts = {custom_component._vertex.outputs[0].get("name"): artifact} + custom_component._results = {custom_component._vertex.outputs[0].get("name"): build_result} return custom_component, build_result, artifact raise ValueError("Custom component does not have a vertex") From 6d01edad358d5b7d9ec6c470540ab3e0d2ef011f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:47:34 -0300 Subject: [PATCH 06/26] refactor: update BaseComponent to accept UUID for _user_id Updated the `BaseComponent` class in `base_component.py` to accept a `UUID` type for the `_user_id` attribute. This change improves the type safety and ensures consistency with the usage of `_user_id` throughout the codebase. --- .../base/langflow/custom/custom_component/base_component.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/custom_component/base_component.py b/src/backend/base/langflow/custom/custom_component/base_component.py index ce1c5c445b44..9e3342970bcf 100644 --- a/src/backend/base/langflow/custom/custom_component/base_component.py +++ b/src/backend/base/langflow/custom/custom_component/base_component.py @@ -1,6 +1,7 @@ import operator import warnings from typing import Any, ClassVar, Optional +from uuid import UUID from cachetools import TTLCache, cachedmethod from fastapi import HTTPException @@ -27,7 +28,7 @@ class BaseComponent: """The code of the component. Defaults to None.""" _function_entrypoint_name: str = "build" field_config: dict = {} - _user_id: Optional[str] + _user_id: Optional[str | UUID] def __init__(self, **data): self.cache = TTLCache(maxsize=1024, ttl=60) From 49349ff86c10d83ebe48d12085613348a38f5218 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:47:40 -0300 Subject: [PATCH 07/26] refactor: import nanoid with type annotation The `nanoid` import in `component.py` has been updated to include a type annotation `# type: ignore`. This change ensures that the type checker ignores any errors related to the `nanoid` import. --- src/backend/base/langflow/custom/custom_component/component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index b51552c791ef..07a73ab87964 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -2,7 +2,7 @@ from typing import Any, Callable, ClassVar, List, Optional, Union from uuid import UUID -import nanoid +import nanoid # type: ignore import yaml from pydantic import BaseModel From 1e2f5175d1ee2b933c5eb7e97cdb3725fe32c723 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:47:55 -0300 Subject: [PATCH 08/26] fix(custom_component.py): convert _user_id to string before passing to functions to ensure compatibility with function signatures --- .../langflow/custom/custom_component/custom_component.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index a0452ca9e040..c341762744f6 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -1,4 +1,5 @@ from pathlib import Path +from turtle import st from typing import TYPE_CHECKING, Any, Callable, ClassVar, List, Optional, Sequence, Union from uuid import UUID @@ -78,7 +79,6 @@ class CustomComponent(BaseComponent): function_entrypoint_name: ClassVar[str] = "build" function: Optional[Callable] = None repr_value: Optional[Any] = "" - _user_id: Optional[Union[UUID, str]] = None status: Optional[Any] = None """The status of the component. This is displayed on the frontend. Defaults to None.""" _flows_data: Optional[List[Data]] = None @@ -463,7 +463,7 @@ def get_function(self): async def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> "Graph": if not self._user_id: raise ValueError("Session is invalid") - return await load_flow(user_id=self._user_id, flow_id=flow_id, tweaks=tweaks) + return await load_flow(user_id=str(self._user_id), flow_id=flow_id, tweaks=tweaks) async def run_flow( self, @@ -479,14 +479,14 @@ async def run_flow( flow_id=flow_id, flow_name=flow_name, tweaks=tweaks, - user_id=self._user_id, + user_id=str(self._user_id), ) def list_flows(self) -> List[Data]: if not self._user_id: raise ValueError("Session is invalid") try: - return list_flows(user_id=self._user_id) + return list_flows(user_id=str(self._user_id)) except Exception as e: raise ValueError(f"Error listing flows: {e}") From fde96870bf549d7c1dd54f25ea0a428b83cef0b4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:50:30 -0300 Subject: [PATCH 09/26] feat(component.py): add method to set output types based on method return type to improve type checking and validation in custom components --- .../custom/custom_component/component.py | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 07a73ab87964..a5373b449fdc 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -1,11 +1,12 @@ import inspect -from typing import Any, Callable, ClassVar, List, Optional, Union +from typing import Any, Callable, ClassVar, List, Optional, Union, get_type_hints from uuid import UUID import nanoid # type: ignore import yaml from pydantic import BaseModel +from langflow.helpers.custom import format_type from langflow.inputs.inputs import InputTypes from langflow.schema.artifact import get_artifact_type, post_process_raw from langflow.schema.data import Data @@ -54,6 +55,7 @@ def __init__(self, **kwargs): self.map_inputs(self.inputs) if self.outputs is not None: self.map_outputs(self.outputs) + self._set_output_types() def __getattr__(self, name: str) -> Any: if "_attributes" in self.__dict__ and name in self.__dict__["_attributes"]: @@ -91,6 +93,27 @@ def validate(self, params: dict): self._validate_inputs(params) self._validate_outputs() + def _set_output_types(self): + for output in self.outputs: + return_types = self._get_method_return_type(output.method) + output.add_types(return_types) + output.set_selected() + + def _get_method_return_type(self, method_name: str) -> List[str]: + method = getattr(self, method_name) + return_type = get_type_hints(method)["return"] + extracted_return_types = self._extract_return_type(return_type) + return [format_type(extracted_return_type) for extracted_return_type in extracted_return_types] + + def _get_output_by_method(self, method: Callable): + # method is a callable and output.method is a string + # we need to find the output that has the same method + output = next((output for output in self.outputs if output.method == method.__name__), None) + if output is None: + method_name = method.__name__ if hasattr(method, "__name__") else str(method) + raise ValueError(f"Output with method {method_name} not found") + return output + def _validate_outputs(self): # Raise Error if some rule isn't met pass From 7f679a4de4e3182f1c82c5a9a98120d0ced4dcdd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:55:16 -0300 Subject: [PATCH 10/26] refactor: extract method to get method return type in CustomComponent --- .../custom/custom_component/custom_component.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index c341762744f6..b73cca405d3d 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -1,7 +1,5 @@ from pathlib import Path -from turtle import st from typing import TYPE_CHECKING, Any, Callable, ClassVar, List, Optional, Sequence, Union -from uuid import UUID import yaml from cachetools import TTLCache @@ -269,6 +267,14 @@ def to_data(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bo return data_objects + def get_method_return_type(self, method_name: str): + build_method = self.get_method(method_name) + if not build_method or not build_method.get("has_return"): + return [] + return_type = build_method["return_type"] + + return self._extract_return_type(return_type) + def create_references_from_data(self, data: List[Data], include_data: bool = False) -> str: """ Create references from a list of data. @@ -341,12 +347,7 @@ def get_function_entrypoint_return_type(self) -> List[Any]: """ return self.get_method_return_type(self.function_entrypoint_name) - def get_method_return_type(self, method_name: str): - build_method = self.get_method(method_name) - if not build_method or not build_method.get("has_return"): - return [] - return_type = build_method["return_type"] - + def _extract_return_type(self, return_type: str): if hasattr(return_type, "__origin__") and return_type.__origin__ in [ list, List, From 485e5e238474a2a5b163eac38cc68c0ddb42e87b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:58:29 -0300 Subject: [PATCH 11/26] refactor(utils.py): refactor code to use _user_id instead of user_id for consistency and clarity perf(utils.py): optimize code by reusing cc_instance instead of calling get_component_instance multiple times --- src/backend/base/langflow/custom/utils.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/backend/base/langflow/custom/utils.py b/src/backend/base/langflow/custom/utils.py index 84aef23994ff..498fd8a1ab6a 100644 --- a/src/backend/base/langflow/custom/utils.py +++ b/src/backend/base/langflow/custom/utils.py @@ -283,7 +283,7 @@ def get_component_instance(custom_component: CustomComponent, user_id: Optional[ ) from exc try: - custom_instance = custom_class(user_id=user_id) + custom_instance = custom_class(_user_id=user_id) return custom_instance except Exception as exc: logger.error(f"Error while instantiating custom component: {str(exc)}") @@ -317,7 +317,7 @@ def run_build_config( ) from exc try: - custom_instance = custom_class(user_id=user_id) + custom_instance = custom_class(_user_id=user_id) build_config: Dict = custom_instance.build_config() for field_name, field in build_config.copy().items(): @@ -361,14 +361,15 @@ def build_custom_component_template_from_inputs( custom_component: Union[Component, CustomComponent], user_id: Optional[Union[str, UUID]] = None ): # The List of Inputs fills the role of the build_config and the entrypoint_args - field_config = custom_component.template_config + cc_instance = get_component_instance(custom_component, user_id=user_id) + field_config = cc_instance.get_template_config(cc_instance) frontend_node = ComponentFrontendNode.from_inputs(**field_config) frontend_node = add_code_field(frontend_node, custom_component._code, field_config.get("code", {})) # But we now need to calculate the return_type of the methods in the outputs for output in frontend_node.outputs: if output.types: continue - return_types = custom_component.get_method_return_type(output.method) + return_types = cc_instance.get_method_return_type(output.method) return_types = [format_type(return_type) for return_type in return_types] output.add_types(return_types) output.set_selected() @@ -376,8 +377,8 @@ def build_custom_component_template_from_inputs( frontend_node.validate_component() # ! This should be removed when we have a better way to handle this frontend_node.set_base_classes_from_outputs() - reorder_fields(frontend_node, custom_component._get_field_order()) - cc_instance = get_component_instance(custom_component, user_id=user_id) + reorder_fields(frontend_node, cc_instance._get_field_order()) + return frontend_node.to_dict(add_name=False), cc_instance @@ -414,7 +415,7 @@ def build_custom_component_template( reorder_fields(frontend_node, custom_instance._get_field_order()) - return frontend_node.to_dict(add_name=False), custom_instance + return frontend_node.to_dict(keep_name=False), custom_instance except Exception as exc: if isinstance(exc, HTTPException): raise exc From 2c389dc9d18ad78c19fd91a4c395929de71e878e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 13:59:48 -0300 Subject: [PATCH 12/26] refactor(utils.py, base.py): change parameter name 'add_name' to 'keep_name' for clarity and consistency in codebase --- src/backend/base/langflow/custom/utils.py | 4 ++-- src/backend/base/langflow/template/frontend_node/base.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/custom/utils.py b/src/backend/base/langflow/custom/utils.py index 84aef23994ff..b5f31a551a67 100644 --- a/src/backend/base/langflow/custom/utils.py +++ b/src/backend/base/langflow/custom/utils.py @@ -378,7 +378,7 @@ def build_custom_component_template_from_inputs( frontend_node.set_base_classes_from_outputs() reorder_fields(frontend_node, custom_component._get_field_order()) cc_instance = get_component_instance(custom_component, user_id=user_id) - return frontend_node.to_dict(add_name=False), cc_instance + return frontend_node.to_dict(keep_name=False), cc_instance def build_custom_component_template( @@ -414,7 +414,7 @@ def build_custom_component_template( reorder_fields(frontend_node, custom_instance._get_field_order()) - return frontend_node.to_dict(add_name=False), custom_instance + return frontend_node.to_dict(keep_name=False), custom_instance except Exception as exc: if isinstance(exc, HTTPException): raise exc diff --git a/src/backend/base/langflow/template/frontend_node/base.py b/src/backend/base/langflow/template/frontend_node/base.py index 77b6d1f69ae5..9aeb0b97dd9b 100644 --- a/src/backend/base/langflow/template/frontend_node/base.py +++ b/src/backend/base/langflow/template/frontend_node/base.py @@ -90,10 +90,10 @@ def serialize_model(self, handler): return {name: result} # For backwards compatibility - def to_dict(self, add_name=True) -> dict: + def to_dict(self, keep_name=True) -> dict: """Returns a dict representation of the frontend node.""" dump = self.model_dump(by_alias=True, exclude_none=True) - if not add_name: + if not keep_name: return dump.pop(self.name) return dump From 53255418ea1b16b8167aaf5f12a5516e8641c68f Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 31 Jul 2024 17:01:09 +0000 Subject: [PATCH 13/26] [autofix.ci] apply automated fixes --- .../base/langflow/custom/custom_component/custom_component.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index c341762744f6..14b5240f65ed 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -1,7 +1,5 @@ from pathlib import Path -from turtle import st from typing import TYPE_CHECKING, Any, Callable, ClassVar, List, Optional, Sequence, Union -from uuid import UUID import yaml from cachetools import TTLCache From 4a455db4cbcef5cf314ffde950f1d842dbc8dd94 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:04:51 -0300 Subject: [PATCH 14/26] refactor: update schema.py to include Edge related typres The `schema.py` file in the `src/backend/base/langflow/graph/edge` directory has been updated to include the `TargetHandle` and `SourceHandle` models. These models define the structure and attributes of the target and source handles used in the edge data. This change improves the clarity and consistency of the codebase. --- .../base/langflow/graph/edge/schema.py | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/schema.py b/src/backend/base/langflow/graph/edge/schema.py index 628073d1326d..6d9c95dd15e5 100644 --- a/src/backend/base/langflow/graph/edge/schema.py +++ b/src/backend/base/langflow/graph/edge/schema.py @@ -1,5 +1,7 @@ -from typing import Optional, Any, List -from pydantic import BaseModel +from typing import Any, List, Optional + +from pydantic import BaseModel, Field, field_validator +from typing_extensions import TypedDict class ResultPair(BaseModel): @@ -32,3 +34,55 @@ def format(self, sep: str = "\n") -> str: for result_pair in self.result_pairs[:-1] ] ) + + +class TargetHandle(BaseModel): + fieldName: str = Field(..., description="Field name for the target handle.") + id: str = Field(..., description="Unique identifier for the target handle.") + inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.") + type: str = Field(..., description="Type of the target handle.") + + +class SourceHandle(BaseModel): + baseClasses: list[str] = Field(default_factory=list, description="List of base classes for the source handle.") + dataType: str = Field(..., description="Data type for the source handle.") + id: str = Field(..., description="Unique identifier for the source handle.") + name: Optional[str] = Field(None, description="Name of the source handle.") + output_types: List[str] = Field(default_factory=list, description="List of output types for the source handle.") + + @field_validator("name", mode="before") + @classmethod + def validate_name(cls, v, _info): + if _info.data["dataType"] == "GroupNode": + # 'OpenAIModel-u4iGV_text_output' + splits = v.split("_", 1) + if len(splits) != 2: + raise ValueError(f"Invalid source handle name {v}") + v = splits[1] + return v + + +class SourceHandleDict(TypedDict, total=False): + baseClasses: list[str] + dataType: str + id: str + name: Optional[str] + output_types: List[str] + + +class TargetHandleDict(TypedDict): + fieldName: str + id: str + inputTypes: Optional[List[str]] + type: str + + +class EdgeDataDetails(TypedDict): + sourceHandle: SourceHandleDict + targetHandle: TargetHandleDict + + +class EdgeData(TypedDict, total=False): + source: str + target: str + data: EdgeDataDetails From 1fe81266eb391352f66b6003d51ae6fa1582c602 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:24:39 -0300 Subject: [PATCH 15/26] refactor: update _extract_return_type method in CustomComponent to accept Any type The _extract_return_type method in CustomComponent has been updated to accept the Any type as the return_type parameter. This change improves the flexibility and compatibility of the method, allowing it to handle a wider range of return types. --- .../base/langflow/custom/custom_component/custom_component.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index b73cca405d3d..135ae350b241 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -347,7 +347,7 @@ def get_function_entrypoint_return_type(self) -> List[Any]: """ return self.get_method_return_type(self.function_entrypoint_name) - def _extract_return_type(self, return_type: str): + def _extract_return_type(self, return_type: Any): if hasattr(return_type, "__origin__") and return_type.__origin__ in [ list, List, From 6ab4c35e50bbc4bb33833eeba7b9d78049bcfb7a Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:29:11 -0300 Subject: [PATCH 16/26] refactor: update BaseComponent to use get_template_config method Refactored the `BaseComponent` class in `base_component.py` to use the `get_template_config` method instead of duplicating the code. This change improves code readability and reduces redundancy. --- .../custom/custom_component/base_component.py | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/base_component.py b/src/backend/base/langflow/custom/custom_component/base_component.py index 9e3342970bcf..7ccfc712da8c 100644 --- a/src/backend/base/langflow/custom/custom_component/base_component.py +++ b/src/backend/base/langflow/custom/custom_component/base_component.py @@ -28,7 +28,7 @@ class BaseComponent: """The code of the component. Defaults to None.""" _function_entrypoint_name: str = "build" field_config: dict = {} - _user_id: Optional[str | UUID] + _user_id: Optional[str | UUID] = None def __init__(self, **data): self.cache = TTLCache(maxsize=1024, ttl=60) @@ -39,7 +39,7 @@ def __init__(self, **data): setattr(self, key, value) def __setattr__(self, key, value): - if key == "_user_id" and hasattr(self, "_user_id"): + if key == "_user_id" and hasattr(self, "_user_id") and getattr(self, "_user_id") is not None: warnings.warn("user_id is immutable and cannot be changed.") super().__setattr__(key, value) @@ -66,23 +66,16 @@ def get_function(self): return validate.create_function(self._code, self._function_entrypoint_name) - def build_template_config(self) -> dict: + @staticmethod + def get_template_config(component): """ - Builds the template configuration for the custom component. - - Returns: - A dictionary representing the template configuration. + Gets the template configuration for the custom component itself. """ - if not self._code: - return {} - - cc_class = eval_custom_component_code(self._code) - component_instance = cc_class() template_config = {} for attribute, func in ATTR_FUNC_MAPPING.items(): - if hasattr(component_instance, attribute): - value = getattr(component_instance, attribute) + if hasattr(component, attribute): + value = getattr(component, attribute) if value is not None: template_config[attribute] = func(value=value) @@ -92,5 +85,20 @@ def build_template_config(self) -> dict: return template_config + def build_template_config(self) -> dict: + """ + Builds the template configuration for the custom component. + + Returns: + A dictionary representing the template configuration. + """ + if not self._code: + return {} + + cc_class = eval_custom_component_code(self._code) + component_instance = cc_class() + template_config = self.get_template_config(component_instance) + return template_config + def build(self, *args: Any, **kwargs: Any) -> Any: raise NotImplementedError From 397893c1252f3b302f0034508e931f18d939c310 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 16:15:17 -0300 Subject: [PATCH 17/26] feat: add BaseModel class with model_config attribute A new `BaseModel` class has been added to the `base_model.py` file. This class extends the `PydanticBaseModel` and includes a `model_config` attribute of type `ConfigDict`. This change improves the codebase by providing a base model with a configuration dictionary for models. Co-authored-by: Gabriel Luiz Freitas Almeida --- src/backend/base/langflow/helpers/base_model.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/backend/base/langflow/helpers/base_model.py diff --git a/src/backend/base/langflow/helpers/base_model.py b/src/backend/base/langflow/helpers/base_model.py new file mode 100644 index 000000000000..c81fd99d2c61 --- /dev/null +++ b/src/backend/base/langflow/helpers/base_model.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel as PydanticBaseModel +from pydantic import ConfigDict + + +class BaseModel(PydanticBaseModel): + model_config = ConfigDict(populate_by_name=True) From b59a6cc896ef9eca5ac7fd95134d4259237a9d98 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 16:15:26 -0300 Subject: [PATCH 18/26] refactor: update langflow.graph.edge.schema.py Refactor the `langflow.graph.edge.schema.py` file to include the `TargetHandle` and `SourceHandle` models. This change improves the clarity and consistency of the codebase. Co-authored-by: Gabriel Luiz Freitas Almeida --- src/backend/base/langflow/graph/edge/schema.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/schema.py b/src/backend/base/langflow/graph/edge/schema.py index 6d9c95dd15e5..d8ae9963c18a 100644 --- a/src/backend/base/langflow/graph/edge/schema.py +++ b/src/backend/base/langflow/graph/edge/schema.py @@ -1,8 +1,10 @@ from typing import Any, List, Optional -from pydantic import BaseModel, Field, field_validator +from pydantic import Field, field_validator from typing_extensions import TypedDict +from langflow.helpers.base_model import BaseModel + class ResultPair(BaseModel): result: Any @@ -37,15 +39,19 @@ def format(self, sep: str = "\n") -> str: class TargetHandle(BaseModel): - fieldName: str = Field(..., description="Field name for the target handle.") + fieldName: str = Field(..., alias="fieldName", description="Field name for the target handle.") id: str = Field(..., description="Unique identifier for the target handle.") - inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.") + input_types: List[str] = Field( + default_factory=list, alias="inputTypes", description="List of input types for the target handle." + ) type: str = Field(..., description="Type of the target handle.") class SourceHandle(BaseModel): - baseClasses: list[str] = Field(default_factory=list, description="List of base classes for the source handle.") - dataType: str = Field(..., description="Data type for the source handle.") + base_classes: list[str] = Field( + default_factory=list, alias="baseClasses", description="List of base classes for the source handle." + ) + data_type: str = Field(..., alias="dataType", description="Data type for the source handle.") id: str = Field(..., description="Unique identifier for the source handle.") name: Optional[str] = Field(None, description="Name of the source handle.") output_types: List[str] = Field(default_factory=list, description="List of output types for the source handle.") @@ -53,7 +59,7 @@ class SourceHandle(BaseModel): @field_validator("name", mode="before") @classmethod def validate_name(cls, v, _info): - if _info.data["dataType"] == "GroupNode": + if _info.data["data_type"] == "GroupNode": # 'OpenAIModel-u4iGV_text_output' splits = v.split("_", 1) if len(splits) != 2: From 8cc7700d86cea6c5cb5405a288b00475647fda0f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 18:17:39 -0300 Subject: [PATCH 19/26] refactor: update build_custom_component_template to use add_name instead of keep_name Refactor the `build_custom_component_template` function in `utils.py` to use the `add_name` parameter instead of the deprecated `keep_name` parameter. This change ensures consistency with the updated method signature and improves code clarity. --- src/backend/base/langflow/custom/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/utils.py b/src/backend/base/langflow/custom/utils.py index 498fd8a1ab6a..5d8a9c664b37 100644 --- a/src/backend/base/langflow/custom/utils.py +++ b/src/backend/base/langflow/custom/utils.py @@ -415,7 +415,7 @@ def build_custom_component_template( reorder_fields(frontend_node, custom_instance._get_field_order()) - return frontend_node.to_dict(keep_name=False), custom_instance + return frontend_node.to_dict(add_name=False), custom_instance except Exception as exc: if isinstance(exc, HTTPException): raise exc From a88ff42eba77f20e1d82fa2c12c7d5ea07afbf2f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 18:45:35 -0300 Subject: [PATCH 20/26] feat(component.py): add method to set output types based on method return type to improve type checking and validation in custom components (#3115) * feat(component.py): add method to set output types based on method return type to improve type checking and validation in custom components * refactor: extract method to get method return type in CustomComponent * refactor: update _extract_return_type method in CustomComponent to accept Any type The _extract_return_type method in CustomComponent has been updated to accept the Any type as the return_type parameter. This change improves the flexibility and compatibility of the method, allowing it to handle a wider range of return types. --- .../custom/custom_component/component.py | 25 ++++++++++++++++++- .../custom_component/custom_component.py | 15 ++++++----- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 07a73ab87964..a5373b449fdc 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -1,11 +1,12 @@ import inspect -from typing import Any, Callable, ClassVar, List, Optional, Union +from typing import Any, Callable, ClassVar, List, Optional, Union, get_type_hints from uuid import UUID import nanoid # type: ignore import yaml from pydantic import BaseModel +from langflow.helpers.custom import format_type from langflow.inputs.inputs import InputTypes from langflow.schema.artifact import get_artifact_type, post_process_raw from langflow.schema.data import Data @@ -54,6 +55,7 @@ def __init__(self, **kwargs): self.map_inputs(self.inputs) if self.outputs is not None: self.map_outputs(self.outputs) + self._set_output_types() def __getattr__(self, name: str) -> Any: if "_attributes" in self.__dict__ and name in self.__dict__["_attributes"]: @@ -91,6 +93,27 @@ def validate(self, params: dict): self._validate_inputs(params) self._validate_outputs() + def _set_output_types(self): + for output in self.outputs: + return_types = self._get_method_return_type(output.method) + output.add_types(return_types) + output.set_selected() + + def _get_method_return_type(self, method_name: str) -> List[str]: + method = getattr(self, method_name) + return_type = get_type_hints(method)["return"] + extracted_return_types = self._extract_return_type(return_type) + return [format_type(extracted_return_type) for extracted_return_type in extracted_return_types] + + def _get_output_by_method(self, method: Callable): + # method is a callable and output.method is a string + # we need to find the output that has the same method + output = next((output for output in self.outputs if output.method == method.__name__), None) + if output is None: + method_name = method.__name__ if hasattr(method, "__name__") else str(method) + raise ValueError(f"Output with method {method_name} not found") + return output + def _validate_outputs(self): # Raise Error if some rule isn't met pass diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index 14b5240f65ed..135ae350b241 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -267,6 +267,14 @@ def to_data(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bo return data_objects + def get_method_return_type(self, method_name: str): + build_method = self.get_method(method_name) + if not build_method or not build_method.get("has_return"): + return [] + return_type = build_method["return_type"] + + return self._extract_return_type(return_type) + def create_references_from_data(self, data: List[Data], include_data: bool = False) -> str: """ Create references from a list of data. @@ -339,12 +347,7 @@ def get_function_entrypoint_return_type(self) -> List[Any]: """ return self.get_method_return_type(self.function_entrypoint_name) - def get_method_return_type(self, method_name: str): - build_method = self.get_method(method_name) - if not build_method or not build_method.get("has_return"): - return [] - return_type = build_method["return_type"] - + def _extract_return_type(self, return_type: Any): if hasattr(return_type, "__origin__") and return_type.__origin__ in [ list, List, From a8c356464008056532225bfda4415707d3499c55 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 18:46:10 -0300 Subject: [PATCH 21/26] refactor: add _template_config property to BaseComponent Add a new `_template_config` property to the `BaseComponent` class in `base_component.py`. This property is used to store the template configuration for the custom component. If the `_template_config` property is empty, it is populated by calling the `build_template_config` method. This change improves the efficiency of accessing the template configuration and ensures that it is only built when needed. --- .../base/langflow/custom/custom_component/base_component.py | 1 + .../base/langflow/custom/custom_component/custom_component.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/custom_component/base_component.py b/src/backend/base/langflow/custom/custom_component/base_component.py index 7ccfc712da8c..58d4daa46223 100644 --- a/src/backend/base/langflow/custom/custom_component/base_component.py +++ b/src/backend/base/langflow/custom/custom_component/base_component.py @@ -29,6 +29,7 @@ class BaseComponent: _function_entrypoint_name: str = "build" field_config: dict = {} _user_id: Optional[str | UUID] = None + _template_config: dict = {} def __init__(self, **data): self.cache = TTLCache(maxsize=1024, ttl=60) diff --git a/src/backend/base/langflow/custom/custom_component/custom_component.py b/src/backend/base/langflow/custom/custom_component/custom_component.py index 135ae350b241..d99fd6561ba1 100644 --- a/src/backend/base/langflow/custom/custom_component/custom_component.py +++ b/src/backend/base/langflow/custom/custom_component/custom_component.py @@ -394,7 +394,9 @@ def template_config(self): Returns: dict: The template configuration for the custom component. """ - return self.build_template_config() + if not self._template_config: + self._template_config = self.build_template_config() + return self._template_config @property def variables(self): From 1ed4812f093d544dccadb9f06f59a1937afdac85 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 18:51:23 -0300 Subject: [PATCH 22/26] refactor: add type checking for Output types in add_types method Improve type checking in the `add_types` method of the `Output` class in `base.py`. Check if the `type_` already exists in the `types` list before adding it. This change ensures that duplicate types are not added to the list. --- src/backend/base/langflow/template/field/base.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/template/field/base.py b/src/backend/base/langflow/template/field/base.py index e825762301cc..eca0dc805b1e 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -1,5 +1,13 @@ from enum import Enum -from typing import Any, Callable, GenericAlias, Optional, Union, _GenericAlias, _UnionGenericAlias # type: ignore +from typing import ( + Any, + Callable, + GenericAlias, + Optional, # type: ignore + Union, + _GenericAlias, + _UnionGenericAlias, +) from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer, model_validator @@ -182,6 +190,8 @@ def to_dict(self): def add_types(self, _type: list[Any]): for type_ in _type: + if type_ in self.types: + continue if self.types is None: self.types = [] self.types.append(type_) From 7acc1dd24fe6b9003c34018934bdf2231ec3484c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 18:58:06 -0300 Subject: [PATCH 23/26] update starter projects --- .../initial_setup/starter_projects/Complex Agent.json | 6 ++++-- .../initial_setup/starter_projects/Hierarchical Agent.json | 6 ++++-- .../initial_setup/starter_projects/Sequential Agent.json | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Complex Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Complex Agent.json index 628ba2045b72..08d6086151dd 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Complex Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Complex Agent.json @@ -4037,7 +4037,8 @@ "name": "api_run_model", "selected": "Data", "types": [ - "Data" + "Data", + "list" ], "value": "__UNDEFINED__" }, @@ -4048,7 +4049,8 @@ "name": "api_build_tool", "selected": "Tool", "types": [ - "Tool" + "Tool", + "Sequence" ], "value": "__UNDEFINED__" } diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Hierarchical Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Hierarchical Agent.json index 8a0e18ee230d..7a48edba9aeb 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Hierarchical Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Hierarchical Agent.json @@ -2615,7 +2615,8 @@ "name": "api_run_model", "selected": "Data", "types": [ - "Data" + "Data", + "list" ], "value": "__UNDEFINED__" }, @@ -2626,7 +2627,8 @@ "name": "api_build_tool", "selected": "Tool", "types": [ - "Tool" + "Tool", + "Sequence" ], "value": "__UNDEFINED__" } diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Agent.json b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Agent.json index b1a70afc6833..5364b52fd015 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Sequential Agent.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Sequential Agent.json @@ -2953,7 +2953,8 @@ "name": "api_run_model", "selected": "Data", "types": [ - "Data" + "Data", + "list" ], "value": "__UNDEFINED__" }, @@ -2964,7 +2965,8 @@ "name": "api_build_tool", "selected": "Tool", "types": [ - "Tool" + "Tool", + "Sequence" ], "value": "__UNDEFINED__" } From 4be549f43bde8399576cc1e4b8295b0348a2167e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 19:12:08 -0300 Subject: [PATCH 24/26] refactor: optimize imports in base.py Optimize imports in the `base.py` file by removing unused imports and organizing the remaining imports. This change improves code readability and reduces unnecessary clutter. --- src/backend/base/langflow/template/field/base.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/backend/base/langflow/template/field/base.py b/src/backend/base/langflow/template/field/base.py index eca0dc805b1e..6395e680af8e 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -1,13 +1,8 @@ from enum import Enum -from typing import ( - Any, - Callable, - GenericAlias, - Optional, # type: ignore - Union, - _GenericAlias, - _UnionGenericAlias, -) +from typing import GenericAlias # type: ignore +from typing import _GenericAlias # type: ignore +from typing import _UnionGenericAlias # type: ignore +from typing import Any, Callable, Optional, Union from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer, model_validator From 61bdbb1e574ed42a9facb314f94789d9d527b888 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 19:12:19 -0300 Subject: [PATCH 25/26] fix(base.py): fix condition to check if self.types is not None before checking if type_ is in self.types --- src/backend/base/langflow/template/field/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/template/field/base.py b/src/backend/base/langflow/template/field/base.py index 6395e680af8e..79ca2d3d9a63 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -185,7 +185,7 @@ def to_dict(self): def add_types(self, _type: list[Any]): for type_ in _type: - if type_ in self.types: + if self.types and type_ in self.types: continue if self.types is None: self.types = [] From cb4bb932edcfb460af94c50a3cfcba385053400e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 19:48:26 -0300 Subject: [PATCH 26/26] refactor: update build_custom_component_template to use add_name instead of keep_name --- src/backend/base/langflow/custom/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/custom/utils.py b/src/backend/base/langflow/custom/utils.py index f309ee970263..48beb3de7a02 100644 --- a/src/backend/base/langflow/custom/utils.py +++ b/src/backend/base/langflow/custom/utils.py @@ -415,7 +415,7 @@ def build_custom_component_template( reorder_fields(frontend_node, custom_instance._get_field_order()) - return frontend_node.to_dict(add_name=False), custom_instance + return frontend_node.to_dict(keep_name=False), custom_instance except Exception as exc: if isinstance(exc, HTTPException): raise exc