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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] [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/78] 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 86bba19e953f6ba79d043311ebe97435745d9ba3 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:09:06 -0300 Subject: [PATCH 15/78] refactor: update BaseInputMixin to handle invalid field types gracefully The `BaseInputMixin` class in `input_mixin.py` has been updated to handle invalid field types gracefully. Instead of raising an exception, it now returns `FieldTypes.OTHER` for any invalid field type. This change improves the robustness and reliability of the codebase. --- src/backend/base/langflow/inputs/input_mixin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index fe7f54f5674b..ac446ac3b901 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -78,9 +78,10 @@ def to_dict(self): @field_validator("field_type", mode="before") @classmethod def validate_field_type(cls, v): - if v not in FieldTypes: + try: + return FieldTypes(v) + except ValueError: return FieldTypes.OTHER - return FieldTypes(v) @model_serializer(mode="wrap") def serialize_model(self, handler): From d495989b7dcaddc292ff458bf216af438150ad23 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:09:27 -0300 Subject: [PATCH 16/78] refactor: update file_types field alias in FileMixin The `file_types` field in the `FileMixin` class of `input_mixin.py` has been updated to use the `alias` parameter instead of `serialization_alias`. This change ensures consistency and improves the clarity of the codebase. --- src/backend/base/langflow/inputs/input_mixin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index ac446ac3b901..9b20a8b8f4cd 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -102,7 +102,7 @@ class MetadataTraceMixin(BaseModel): # Mixin for input fields that can be listable class ListableInputMixin(BaseModel): - is_list: bool = Field(default=False, serialization_alias="list") + is_list: bool = Field(default=False, alias="list") # Specific mixin for fields needing database interaction @@ -113,7 +113,7 @@ class DatabaseLoadMixin(BaseModel): # Specific mixin for fields needing file interaction class FileMixin(BaseModel): file_path: Optional[str] = Field(default="") - file_types: list[str] = Field(default=[], serialization_alias="fileTypes") + file_types: list[str] = Field(default=[], alias="fileTypes") @field_validator("file_types") @classmethod From da5889209ab6302eb2f5ce43b926f0207af6af39 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:09:58 -0300 Subject: [PATCH 17/78] refactor(inputs): update field_type declarations in various input classes to use SerializableFieldTypes enum for better type safety and clarity --- .../base/langflow/inputs/input_mixin.py | 2 +- src/backend/base/langflow/inputs/inputs.py | 54 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index 9b20a8b8f4cd..7dfa4f966150 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -29,7 +29,7 @@ class FieldTypes(str, Enum): class BaseInputMixin(BaseModel, validate_assignment=True): model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") - field_type: Optional[SerializableFieldTypes] = Field(default=FieldTypes.TEXT) + field_type: SerializableFieldTypes = Field(default=FieldTypes.TEXT) required: bool = False """Specifies if the field is required. Defaults to False.""" diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index c8023775f24a..452dd28f2e9a 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -24,7 +24,7 @@ class TableInput(BaseInputMixin, MetadataTraceMixin, TableMixin, ListableInputMixin): - field_type: Optional[SerializableFieldTypes] = FieldTypes.TABLE + field_type: SerializableFieldTypes = FieldTypes.TABLE is_list: bool = True @field_validator("value") @@ -50,11 +50,11 @@ class HandleInput(BaseInputMixin, ListableInputMixin, MetadataTraceMixin): Attributes: input_types (list[str]): A list of input types. - field_type (Optional[SerializableFieldTypes]): The field type of the input. + field_type (SerializableFieldTypes): The field type of the input. """ input_types: list[str] = Field(default_factory=list) - field_type: Optional[SerializableFieldTypes] = FieldTypes.OTHER + field_type: SerializableFieldTypes = FieldTypes.OTHER class DataInput(HandleInput, InputTraceMixin): @@ -69,12 +69,12 @@ class DataInput(HandleInput, InputTraceMixin): class PromptInput(BaseInputMixin, ListableInputMixin, InputTraceMixin): - field_type: Optional[SerializableFieldTypes] = FieldTypes.PROMPT + field_type: SerializableFieldTypes = FieldTypes.PROMPT # Applying mixins to a specific input type class StrInput(BaseInputMixin, ListableInputMixin, DatabaseLoadMixin, MetadataTraceMixin): - field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT + field_type: SerializableFieldTypes = FieldTypes.TEXT load_from_db: CoalesceBool = False """Defines if the field will allow the user to open a text editor. Default is False.""" @@ -190,11 +190,11 @@ class MultilineInput(MessageTextInput, MultilineMixin, InputTraceMixin): Represents a multiline input field. Attributes: - field_type (Optional[SerializableFieldTypes]): The type of the field. Defaults to FieldTypes.TEXT. + field_type (SerializableFieldTypes): The type of the field. Defaults to FieldTypes.TEXT. multiline (CoalesceBool): Indicates whether the input field should support multiple lines. Defaults to True. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT + field_type: SerializableFieldTypes = FieldTypes.TEXT multiline: CoalesceBool = True @@ -203,11 +203,11 @@ class MultilineSecretInput(MessageTextInput, MultilineMixin, InputTraceMixin): Represents a multiline input field. Attributes: - field_type (Optional[SerializableFieldTypes]): The type of the field. Defaults to FieldTypes.TEXT. + field_type (SerializableFieldTypes): The type of the field. Defaults to FieldTypes.TEXT. multiline (CoalesceBool): Indicates whether the input field should support multiple lines. Defaults to True. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.PASSWORD + field_type: SerializableFieldTypes = FieldTypes.PASSWORD multiline: CoalesceBool = True password: CoalesceBool = Field(default=True) @@ -219,12 +219,12 @@ class SecretStrInput(BaseInputMixin, DatabaseLoadMixin): This class inherits from `BaseInputMixin` and `DatabaseLoadMixin`. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to `FieldTypes.PASSWORD`. + field_type (SerializableFieldTypes): The field type of the input. Defaults to `FieldTypes.PASSWORD`. password (CoalesceBool): A boolean indicating whether the input is a password. Defaults to `True`. input_types (list[str]): A list of input types associated with this input. Defaults to an empty list. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.PASSWORD + field_type: SerializableFieldTypes = FieldTypes.PASSWORD password: CoalesceBool = Field(default=True) input_types: list[str] = [] load_from_db: CoalesceBool = True @@ -238,10 +238,10 @@ class IntInput(BaseInputMixin, ListableInputMixin, RangeMixin, MetadataTraceMixi It inherits from the `BaseInputMixin`, `ListableInputMixin`, and `RangeMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.INTEGER. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.INTEGER. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.INTEGER + field_type: SerializableFieldTypes = FieldTypes.INTEGER class FloatInput(BaseInputMixin, ListableInputMixin, RangeMixin, MetadataTraceMixin): @@ -252,10 +252,10 @@ class FloatInput(BaseInputMixin, ListableInputMixin, RangeMixin, MetadataTraceMi It inherits from the `BaseInputMixin`, `ListableInputMixin`, and `RangeMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.FLOAT. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.FLOAT. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.FLOAT + field_type: SerializableFieldTypes = FieldTypes.FLOAT class BoolInput(BaseInputMixin, ListableInputMixin, MetadataTraceMixin): @@ -266,11 +266,11 @@ class BoolInput(BaseInputMixin, ListableInputMixin, MetadataTraceMixin): It inherits from the `BaseInputMixin` and `ListableInputMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.BOOLEAN. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.BOOLEAN. value (CoalesceBool): The value of the boolean input. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.BOOLEAN + field_type: SerializableFieldTypes = FieldTypes.BOOLEAN value: CoalesceBool = False @@ -282,11 +282,11 @@ class NestedDictInput(BaseInputMixin, ListableInputMixin, MetadataTraceMixin, In It inherits from the `BaseInputMixin` and `ListableInputMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.NESTED_DICT. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.NESTED_DICT. value (Optional[dict]): The value of the input. Defaults to an empty dictionary. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.NESTED_DICT + field_type: SerializableFieldTypes = FieldTypes.NESTED_DICT value: Optional[dict | Data] = {} @@ -298,11 +298,11 @@ class DictInput(BaseInputMixin, ListableInputMixin, InputTraceMixin): It inherits from the `BaseInputMixin` and `ListableInputMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.DICT. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.DICT. value (Optional[dict]): The value of the dictionary input. Defaults to an empty dictionary. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.DICT + field_type: SerializableFieldTypes = FieldTypes.DICT value: Optional[dict] = {} @@ -314,12 +314,12 @@ class DropdownInput(BaseInputMixin, DropDownMixin, MetadataTraceMixin): It inherits from the `BaseInputMixin` and `DropDownMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.TEXT. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.TEXT. options (Optional[Union[list[str], Callable]]): List of options for the field. Default is None. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT + field_type: SerializableFieldTypes = FieldTypes.TEXT options: list[str] = Field(default_factory=list) combobox: CoalesceBool = False @@ -332,12 +332,12 @@ class MultiselectInput(BaseInputMixin, ListableInputMixin, DropDownMixin, Metada It inherits from the `BaseInputMixin`, `ListableInputMixin` and `DropDownMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.TEXT. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.TEXT. options (Optional[Union[list[str], Callable]]): List of options for the field. Only used when is_list=True. Default is None. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT + field_type: SerializableFieldTypes = FieldTypes.TEXT options: list[str] = Field(default_factory=list) is_list: bool = Field(default=True, serialization_alias="list") combobox: CoalesceBool = False @@ -362,10 +362,10 @@ class FileInput(BaseInputMixin, ListableInputMixin, FileMixin, MetadataTraceMixi It inherits from the `BaseInputMixin`, `ListableInputMixin`, and `FileMixin` classes. Attributes: - field_type (Optional[SerializableFieldTypes]): The field type of the input. Defaults to FieldTypes.FILE. + field_type (SerializableFieldTypes): The field type of the input. Defaults to FieldTypes.FILE. """ - field_type: Optional[SerializableFieldTypes] = FieldTypes.FILE + field_type: SerializableFieldTypes = FieldTypes.FILE InputTypes = Union[ From 62c9ec485907d99e08a11ca7ddaebdaa583d3fe3 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:11:03 -0300 Subject: [PATCH 18/78] refactor(inputs): convert dict to Message object in _validate_value method --- src/backend/base/langflow/inputs/inputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index 452dd28f2e9a..f67e26060020 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -129,6 +129,8 @@ class MessageInput(StrInput, InputTraceMixin): @staticmethod def _validate_value(v: Any, _info): # If v is a instance of Message, then its fine + if isinstance(v, dict): + return Message(**v) if isinstance(v, Message): return v if isinstance(v, str): From 1d6491a26cf1ef35c1e890aca6a01d2ce77a0c88 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:12:16 -0300 Subject: [PATCH 19/78] refactor(inputs): convert dict to Message object in _validate_value method --- src/backend/base/langflow/inputs/inputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index f67e26060020..7466b3aabc4f 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -166,6 +166,8 @@ def _validate_value(v: Any, _info): ValueError: If the value is not of a valid type or if the input is missing a required key. """ value: str | AsyncIterator | Iterator | None = None + if isinstance(v, dict): + v = Message(**v) if isinstance(v, str): value = v elif isinstance(v, Message): From 5657101e8a751ce979bc663ac8b3a96a58161328 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:21:29 -0300 Subject: [PATCH 20/78] refactor(inputs): update model_config in BaseInputMixin to enable populating by name The `model_config` attribute in the `BaseInputMixin` class of `input_mixin.py` has been updated to include the `populate_by_name=True` parameter. This change allows the model configuration to be populated by name, improving the flexibility and usability of the codebase. --- src/backend/base/langflow/inputs/input_mixin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index 7dfa4f966150..5b1f112d0fcf 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -27,7 +27,7 @@ class FieldTypes(str, Enum): # Base mixin for common input field attributes and methods class BaseInputMixin(BaseModel, validate_assignment=True): - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid", populate_by_name=True) field_type: SerializableFieldTypes = Field(default=FieldTypes.TEXT) 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 21/78] 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 5ce27d2efd03715563ec805348770fa58e8b7a4d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:27:00 -0300 Subject: [PATCH 22/78] refactor(component): add get_input and get_output methods for easier access to input and output values The `Component` class in `component.py` has been updated to include the `get_input` and `get_output` methods. These methods allow for easier retrieval of input and output values by name, improving the usability and readability of the codebase. --- .../custom/custom_component/component.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index a5373b449fdc..00513cdca6e5 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -89,6 +89,34 @@ def map_outputs(self, outputs: List[Output]): raise ValueError("Output name cannot be None.") self._outputs[output.name] = output + def get_input(self, name: str) -> Any: + """ + Retrieves the value of the input with the specified name. + Args: + name (str): The name of the input. + Returns: + Any: The value of the input. + Raises: + ValueError: If the input with the specified name is not found. + """ + if name in self._inputs: + return self._inputs[name] + raise ValueError(f"Input {name} not found in {self.__class__.__name__}") + + def get_output(self, name: str) -> Any: + """ + Retrieves the output with the specified name. + Args: + name (str): The name of the output to retrieve. + Returns: + Any: The output value. + Raises: + ValueError: If the output with the specified name is not found. + """ + if name in self._outputs: + return self._outputs[name] + raise ValueError(f"Output {name} not found in {self.__class__.__name__}") + def validate(self, params: dict): self._validate_inputs(params) self._validate_outputs() From aab3fd62f8b140d222937b2218e5fa7aa83803a4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:27:05 -0300 Subject: [PATCH 23/78] refactor(vertex): add get_input and get_output methods for easier access to input and output values --- src/backend/base/langflow/graph/vertex/types.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/vertex/types.py b/src/backend/base/langflow/graph/vertex/types.py index c36487dd9123..2078e341c081 100644 --- a/src/backend/base/langflow/graph/vertex/types.py +++ b/src/backend/base/langflow/graph/vertex/types.py @@ -9,11 +9,12 @@ from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes, ResultData from langflow.graph.utils import UnbuiltObject, log_transaction, log_vertex_build, serialize_field from langflow.graph.vertex.base import Vertex +from langflow.inputs.inputs import InputTypes from langflow.schema import Data from langflow.schema.artifact import ArtifactType from langflow.schema.message import Message from langflow.schema.schema import INPUT_FIELD_NAME -from langflow.template.field.base import UNDEFINED +from langflow.template.field.base import UNDEFINED, Output from langflow.utils.schemas import ChatOutputResponse, DataOutputResponse from langflow.utils.util import unescape_string @@ -57,6 +58,16 @@ def _update_built_object_and_artifacts(self, result): for key, value in self._built_object.items(): self.add_result(key, value) + def get_input(self, name: str) -> InputTypes: + if self._custom_component is None: + raise ValueError(f"Vertex {self.id} does not have a component instance.") + return self._custom_component.get_input(name) + + def get_output(self, name: str) -> Output: + if self._custom_component is None: + raise ValueError(f"Vertex {self.id} does not have a component instance.") + return self._custom_component.get_output(name) + def get_edge_with_target(self, target_id: str) -> Generator["ContractEdge", None, None]: """ Get the edge with the target id. From 62acf55ffaeaa8d21f5bf289c7164f18db74794e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:33:01 -0300 Subject: [PATCH 24/78] refactor(component): add set_output_value method for easier modification of output values The `Component` class in `component.py` has been updated to include the `set_output_value` method. This method allows for easier modification of output values by name, improving the usability and flexibility of the codebase. --- .../base/langflow/custom/custom_component/component.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 00513cdca6e5..4af5f65b8d39 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -117,6 +117,12 @@ def get_output(self, name: str) -> Any: return self._outputs[name] raise ValueError(f"Output {name} not found in {self.__class__.__name__}") + def set_output_value(self, name: str, value: Any): + if name in self._outputs: + self._outputs[name].value = value + else: + raise ValueError(f"Output {name} not found in {self.__class__.__name__}") + def validate(self, params: dict): self._validate_inputs(params) self._validate_outputs() From efd1592fa267e585e64c59aba62a0efc3f91d5ad Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:37:18 -0300 Subject: [PATCH 25/78] feat: add run_until_complete and run_in_thread functions for handling asyncio tasks The `async_helpers.py` file in the `src/backend/base/langflow/utils` directory has been added. This file includes the `run_until_complete` and `run_in_thread` functions, which provide a way to handle asyncio tasks in different scenarios. The `run_until_complete` function checks if an event loop is already running and either runs the coroutine in a separate event loop in a new thread or creates a new event loop and runs the coroutine. The `run_in_thread` function runs the coroutine in a separate thread and returns the result or raises an exception if one occurs. These functions improve the flexibility and usability of the codebase. --- .../base/langflow/utils/async_helpers.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/backend/base/langflow/utils/async_helpers.py diff --git a/src/backend/base/langflow/utils/async_helpers.py b/src/backend/base/langflow/utils/async_helpers.py new file mode 100644 index 000000000000..25ce544510af --- /dev/null +++ b/src/backend/base/langflow/utils/async_helpers.py @@ -0,0 +1,35 @@ +import asyncio +import threading + + +def run_until_complete(coro): + try: + loop = asyncio.get_event_loop() + if loop.is_running(): + # Run the coroutine in a separate event loop in a new thread + return run_in_thread(coro) + else: + return loop.run_until_complete(coro) + except RuntimeError: + # If there's no event loop, create a new one and run the coroutine + return asyncio.run(coro) + + +def run_in_thread(coro): + result = None + exception = None + + def target(): + nonlocal result, exception + try: + result = asyncio.run(coro) + except Exception as e: + exception = e + + thread = threading.Thread(target=target) + thread.start() + thread.join() + + if exception: + raise exception + return result From fc54f356abfbb662152dcd52ff0e8b33132d1228 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:38:24 -0300 Subject: [PATCH 26/78] refactor(component): add _edges attribute to Component class for managing edges The `Component` class in `component.py` has been updated to include the `_edges` attribute. This attribute is a list of `EdgeData` objects and is used for managing edges in the component. This change improves the functionality and organization of the codebase. --- src/backend/base/langflow/custom/custom_component/component.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 4af5f65b8d39..1abfbe5b0522 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -6,6 +6,7 @@ import yaml from pydantic import BaseModel +from langflow.graph.edge.schema import EdgeData from langflow.helpers.custom import format_type from langflow.inputs.inputs import InputTypes from langflow.schema.artifact import get_artifact_type, post_process_raw @@ -40,6 +41,7 @@ def __init__(self, **kwargs): self._results: dict[str, Any] = {} self._attributes: dict[str, Any] = {} self._parameters = inputs or {} + self._edges: list[EdgeData] = [] self._components: list[Component] = [] self.set_attributes(self._parameters) self._output_logs = {} From 1954edf9f962ff8916cf893a71d8bcfaf69a6959 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:39:51 -0300 Subject: [PATCH 27/78] fix(component.py): fix conditional statement to check if self._vertex is not None before accessing its attributes --- .../custom/custom_component/component.py | 22 +++++++++++++++---- 1 file changed, 18 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 1abfbe5b0522..c276cdc3fc49 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -57,6 +57,7 @@ def __init__(self, **kwargs): self.map_inputs(self.inputs) if self.outputs is not None: self.map_outputs(self.outputs) + # Set output types self._set_output_types() def __getattr__(self, name: str) -> Any: @@ -94,10 +95,13 @@ def map_outputs(self, outputs: List[Output]): def get_input(self, name: str) -> Any: """ Retrieves the value of the input with the specified name. + Args: name (str): The name of the input. + Returns: Any: The value of the input. + Raises: ValueError: If the input with the specified name is not found. """ @@ -108,10 +112,13 @@ def get_input(self, name: str) -> Any: def get_output(self, name: str) -> Any: """ Retrieves the output with the specified name. + Args: name (str): The name of the output to retrieve. + Returns: Any: The output value. + Raises: ValueError: If the output with the specified name is not found. """ @@ -161,6 +168,7 @@ def _validate_inputs(self, params: dict): continue input_ = self._inputs[key] # BaseInputMixin has a `validate_assignment=True` + input_.value = value params[input_.name] = input_.value @@ -168,7 +176,7 @@ def set_attributes(self, params: dict): self._validate_inputs(params) _attributes = {} for key, value in params.items(): - if key in self.__dict__: + if key in self.__dict__ and value != getattr(self, key): raise ValueError( f"{self.__class__.__name__} defines an input parameter named '{key}' " f"that is a reserved word and cannot be used." @@ -222,11 +230,16 @@ async def _build_results(self): _results = {} _artifacts = {} if hasattr(self, "outputs"): - self._set_outputs(self._vertex.outputs) + if self._vertex: + 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 + or 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) @@ -238,7 +251,8 @@ async def _build_results(self): if inspect.iscoroutinefunction(method): result = await result if ( - isinstance(result, Message) + self._vertex is not None + and isinstance(result, Message) and result.flow_id is None and self._vertex.graph.flow_id is not None ): From 25b4dc536308222e1ee63065ccac72b3e8100eb8 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:40:00 -0300 Subject: [PATCH 28/78] refactor(component): add _get_fallback_input method for handling fallback input The `Component` class in `component.py` has been updated to include the `_get_fallback_input` method. This method returns an `Input` object with the provided keyword arguments, which is used as a fallback input when needed. This change improves the flexibility and readability of the codebase. --- src/backend/base/langflow/custom/custom_component/component.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index c276cdc3fc49..9ca90eca4f1f 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -330,3 +330,6 @@ def _get_field_order(self): def build(self, **kwargs): self.set_attributes(kwargs) + + def _get_fallback_input(self, **kwargs): + return Input(**kwargs) From 213a80380967dd2cfcb323744c3fa995c4738f1e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:40:21 -0300 Subject: [PATCH 29/78] refactor(component): add TYPE_CHECKING import for Vertex in component.py --- .../base/langflow/custom/custom_component/component.py | 5 ++++- 1 file changed, 4 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 9ca90eca4f1f..e6bc3acb6df4 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -1,5 +1,5 @@ import inspect -from typing import Any, Callable, ClassVar, List, Optional, Union, get_type_hints +from typing import TYPE_CHECKING, Any, Callable, ClassVar, List, Optional, Union, get_type_hints from uuid import UUID import nanoid # type: ignore @@ -17,6 +17,9 @@ from .custom_component import CustomComponent +if TYPE_CHECKING: + from langflow.graph.vertex.base import Vertex + BACKWARDS_COMPATIBLE_ATTRIBUTES = ["user_id", "vertex", "tracing_service"] From 5ab8fedb4f04f2f0386dad8e3fec2534669b2a52 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:41:08 -0300 Subject: [PATCH 30/78] refactor(component): add _map_parameters_on_frontend_node and _map_parameters_on_template and other methods The `Component` class in `component.py` has been refactored to include the `_map_parameters_on_frontend_node` and `_map_parameters_on_template` methods. These methods are responsible for mapping the parameters of the component onto the frontend node and template, respectively. This change improves the organization and maintainability of the codebase. --- .../custom/custom_component/component.py | 54 ++++++++++++++++--- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index e6bc3acb6df4..d1c95ddbf107 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -145,12 +145,6 @@ def _set_output_types(self): 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 @@ -164,6 +158,54 @@ def _validate_outputs(self): # Raise Error if some rule isn't met pass + def _map_parameters_on_frontend_node(self, frontend_node: ComponentFrontendNode): + for name, value in self._parameters.items(): + frontend_node.set_field_value_in_template(name, value) + + def _map_parameters_on_template(self, template: dict): + for name, value in self._parameters.items(): + template[name]["value"] = value + + 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 _update_template(self, frontend_node: dict): + return frontend_node + + def to_frontend_node(self): + #! This part here is clunky but we need it like this for + #! backwards compatibility. We can change how prompt component + #! works and then update this later + field_config = self.get_template_config(self) + frontend_node = ComponentFrontendNode.from_inputs(**field_config) + self._map_parameters_on_frontend_node(frontend_node) + + frontend_node_dict = frontend_node.to_dict(keep_name=False) + frontend_node_dict = self._update_template(frontend_node_dict) + self._map_parameters_on_template(frontend_node_dict["template"]) + + frontend_node = ComponentFrontendNode.from_dict(frontend_node_dict) + + for output in frontend_node.outputs: + if output.types: + continue + return_types = self._get_method_return_type(output.method) + output.add_types(return_types) + output.set_selected() + + frontend_node.validate_component() + frontend_node.set_base_classes_from_outputs() + data = { + "data": { + "node": frontend_node.to_dict(keep_name=False), + "type": self.__class__.__name__, + } + } + return data + def _validate_inputs(self, params: dict): # Params keys are the `name` attribute of the Input objects for key, value in params.copy().items(): From ea8ac9f8b846dfb094aebee3903935e353c72fc0 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:41:54 -0300 Subject: [PATCH 31/78] refactor(component): Add map_inputs and map_outputs methods for mapping inputs and outputs The `Component` class in `component.py` has been updated to include the `map_inputs` and `map_outputs` methods. These methods allow for mapping the given inputs and outputs to the component, improving the functionality and organization of the codebase. --- .../custom/custom_component/component.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index d1c95ddbf107..6ed38a268d25 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -135,7 +135,53 @@ def set_output_value(self, name: str, value: Any): else: raise ValueError(f"Output {name} not found in {self.__class__.__name__}") + 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 map_inputs(self, inputs: List[InputTypes]): + """ + Maps the given inputs to the component. + + Args: + inputs (List[InputTypes]): A list of InputTypes objects representing the inputs. + + Raises: + ValueError: If the input name is None. + + """ + self.inputs = inputs + for input_ in inputs: + if input_.name is None: + raise ValueError("Input name cannot be None.") + self._inputs[input_.name] = input_ + def validate(self, params: dict): + """ + Validates the component parameters. + + Args: + params (dict): A dictionary containing the component parameters. + + Raises: + ValueError: If the inputs are not valid. + ValueError: If the outputs are not valid. + """ self._validate_inputs(params) self._validate_outputs() From 13c63f0afdb3afc4eb7443edff222c61ad305ab5 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:43:21 -0300 Subject: [PATCH 32/78] refactor(component): Add Input, Output, and ComponentFrontendNode imports and run_until_complete function This commit refactors the `component.py` file in the `src/backend/base/langflow/custom/custom_component` directory. It adds the `Input`, `Output`, and `ComponentFrontendNode` imports, as well as the `run_until_complete` function from the `async_helpers.py` file. These changes improve the functionality and organization of the codebase. --- .../base/langflow/custom/custom_component/component.py | 4 +++- 1 file changed, 3 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 6ed38a268d25..a10784f99359 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -13,7 +13,9 @@ from langflow.schema.data import Data from langflow.schema.message import Message from langflow.services.tracing.schema import Log -from langflow.template.field.base import UNDEFINED, Output +from langflow.template.field.base import UNDEFINED, Input, Output +from langflow.template.frontend_node.custom_components import ComponentFrontendNode +from langflow.utils.async_helpers import run_until_complete from .custom_component import CustomComponent From 564af7ecbc89db047de82fe843a998bd1bb104a0 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:44:08 -0300 Subject: [PATCH 33/78] refactor(component): Add map_inputs and map_outputs methods for mapping inputs and outputs --- .../custom/custom_component/component.py | 65 ++++++++++++------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index a10784f99359..287c97887f5d 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -65,37 +65,54 @@ def __init__(self, **kwargs): # Set output types self._set_output_types() - def __getattr__(self, name: str) -> Any: - if "_attributes" in self.__dict__ and name in self.__dict__["_attributes"]: - 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 set(self, **kwargs): + """ + Connects the component to other components or sets parameters and attributes. - def map_inputs(self, inputs: List[InputTypes]): - self.inputs = inputs - for input_ in inputs: - if input_.name is None: - raise ValueError("Input name cannot be None.") - self._inputs[input_.name] = input_ + Args: + **kwargs: Keyword arguments representing the connections, parameters, and attributes. - def map_outputs(self, outputs: List[Output]): + Returns: + None + + Raises: + KeyError: If the specified input name does not exist. """ - Maps the given list of outputs to the component. + for key, value in kwargs.items(): + self._process_connection_or_parameter(key, value) + + def list_inputs(self): + """ + Returns a list of input names. + """ + return [_input.name for _input in self.inputs] + + def list_outputs(self): + """ + Returns a list of output names. + """ + return [_output.name for _output in self.outputs] + + async def run(self): + """ + Executes the component's logic and returns the result. + + Returns: + The result of executing the component's logic. + """ + return await self._run() + + def set_vertex(self, vertex: "Vertex"): + """ + Sets the vertex for the component. + Args: - outputs (List[Output]): The list of outputs to be mapped. - Raises: - ValueError: If the output name is None. + vertex (Vertex): The vertex to set. + 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 + self._vertex = vertex def get_input(self, name: str) -> Any: """ From f32419926d2f03a4c65a8326bf54869d7b22fb34 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:44:24 -0300 Subject: [PATCH 34/78] refactor(component): Add _process_connection_or_parameter method for handling connections and parameters The `Component` class in `component.py` has been updated to include the `_process_connection_or_parameter` method. This method is responsible for handling connections and parameters based on the provided key and value. It checks if the value is callable and connects it to the component, otherwise it sets the parameter or attribute. This change improves the functionality and organization of the codebase. --- .../custom/custom_component/component.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 287c97887f5d..c734ad2e27fc 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -219,6 +219,93 @@ def _get_output_by_method(self, method: Callable): raise ValueError(f"Output with method {method_name} not found") return output + def _process_connection_or_parameter(self, key, value): + _input = self._get_or_create_input(key) + if callable(value): + self._connect_to_component(key, value, _input) + else: + self._set_parameter_or_attribute(key, value) + + def _get_or_create_input(self, key): + try: + return self._inputs[key] + except KeyError: + _input = self._get_fallback_input(name=key, display_name=key) + self._inputs[key] = _input + self.inputs.append(_input) + return _input + + def _connect_to_component(self, key, value, _input): + component = value.__self__ + self._components.append(component) + output = component._get_output_by_method(value) + self._add_edge(component, key, output, _input) + + def _add_edge(self, component, key, output, _input): + self._edges.append( + { + "source": component._id, + "target": self._id, + "data": { + "sourceHandle": { + "dataType": self.name, + "id": component._id, + "name": output.name, + "output_types": output.types, + }, + "targetHandle": { + "fieldName": key, + "id": self._id, + "inputTypes": _input.input_types, + "type": _input.field_type, + }, + }, + } + ) + + def _set_parameter_or_attribute(self, key, value): + self._parameters[key] = value + self._attributes[key] = value + + def __call__(self, **kwargs): + self.set(**kwargs) + + return run_until_complete(self.run()) + + async def _run(self): + # Resolve callable inputs + for key, _input in self._inputs.items(): + if callable(_input.value): + result = _input.value() + if inspect.iscoroutine(result): + result = await result + self._inputs[key].value = result + + self.set_attributes({}) + + return await self.build_results() + + def __getattr__(self, name: str) -> Any: + if "_attributes" in self.__dict__ and name in self.__dict__["_attributes"]: + 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 _set_input_value(self, name: str, value: Any): + if name in self._inputs: + input_value = self._inputs[name].value + if callable(input_value): + raise ValueError( + f"Input {name} is connected to {input_value.__self__.display_name}.{input_value.__name__}" + ) + self._inputs[name].value = value + self._attributes[name] = value + else: + raise ValueError(f"Input {name} not found in {self.__class__.__name__}") + def _validate_outputs(self): # Raise Error if some rule isn't met pass From e082ce6aaff1a1a30c4b40d6cb17cd866ffdb762 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:45:27 -0300 Subject: [PATCH 35/78] refactor(frontend_node): Add set_field_value_in_template method for updating field values The `FrontendNode` class in `base.py` has been updated to include the `set_field_value_in_template` method. This method allows for updating the value of a specific field in the template of the frontend node. It iterates through the fields and sets the value of the field with the provided name. This change improves the flexibility and functionality of the codebase. --- src/backend/base/langflow/template/frontend_node/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/base/langflow/template/frontend_node/base.py b/src/backend/base/langflow/template/frontend_node/base.py index 9aeb0b97dd9b..632598c42cbc 100644 --- a/src/backend/base/langflow/template/frontend_node/base.py +++ b/src/backend/base/langflow/template/frontend_node/base.py @@ -173,3 +173,9 @@ def from_inputs(cls, **kwargs): template = Template(type_name="Component", fields=inputs) kwargs["template"] = template return cls(**kwargs) + + def set_field_value_in_template(self, field_name, value): + for field in self.template.fields: + if field.name == field_name: + field.value = value + break From 810855596338e168ccf0ec85cbd3b2d6c73f97d4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:49:05 -0300 Subject: [PATCH 36/78] refactor(inputs): Add DefaultPromptField class for default prompt inputs The `inputs.py` file in the `src/backend/base/langflow/inputs` directory has been refactored to include the `DefaultPromptField` class. This class represents a default prompt input with customizable properties such as name, display name, field type, advanced flag, multiline flag, input types, and value. This change improves the flexibility and functionality of the codebase. --- src/backend/base/langflow/inputs/inputs.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index 7466b3aabc4f..a9b5b2a5490f 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -6,6 +6,7 @@ from langflow.inputs.validators import CoalesceBool from langflow.schema.data import Data from langflow.schema.message import Message +from langflow.template.field.base import Input from .input_mixin import ( BaseInputMixin, @@ -57,7 +58,7 @@ class HandleInput(BaseInputMixin, ListableInputMixin, MetadataTraceMixin): field_type: SerializableFieldTypes = FieldTypes.OTHER -class DataInput(HandleInput, InputTraceMixin): +class DataInput(HandleInput, InputTraceMixin, ListableInputMixin): """ Represents an Input that has a Handle that receives a Data object. @@ -372,7 +373,23 @@ class FileInput(BaseInputMixin, ListableInputMixin, FileMixin, MetadataTraceMixi field_type: SerializableFieldTypes = FieldTypes.FILE +DEFAULT_PROMPT_INTUT_TYPES = ["Message", "Text"] + + +class DefaultPromptField(Input): + name: str + display_name: Optional[str] = None + field_type: str = "str" + + advanced: bool = False + multiline: bool = True + input_types: list[str] = DEFAULT_PROMPT_INTUT_TYPES + value: str = "" # Set the value to empty string + + InputTypes = Union[ + Input, + DefaultPromptField, BoolInput, DataInput, DictInput, @@ -398,6 +415,9 @@ class FileInput(BaseInputMixin, ListableInputMixin, FileMixin, MetadataTraceMixi def _instantiate_input(input_type: str, data: dict) -> InputTypes: input_type_class = InputTypesMap.get(input_type) + if "type" in data: + # Replate with field_type + data["field_type"] = data.pop("type") if input_type_class: return input_type_class(**data) else: From 3d76d6cbcd2997e6ae89606b55172cff5e031893 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:49:25 -0300 Subject: [PATCH 37/78] feat: Add Template.from_dict method for creating Template objects from dictionaries This commit adds the `from_dict` class method to the `Template` class in `base.py`. This method allows for creating `Template` objects from dictionaries by converting the dictionary keys and values into the appropriate `Template` attributes. This change improves the flexibility and functionality of the codebase. --- .../base/langflow/template/template/base.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/template/template/base.py b/src/backend/base/langflow/template/template/base.py index 7c0c7fa0faf8..d0372b0b6547 100644 --- a/src/backend/base/langflow/template/template/base.py +++ b/src/backend/base/langflow/template/template/base.py @@ -2,7 +2,7 @@ from pydantic import BaseModel, Field, model_serializer -from langflow.inputs.inputs import InputTypes +from langflow.inputs.inputs import InputTypes, _instantiate_input from langflow.template.field.base import Input from langflow.utils.constants import DIRECT_TYPES @@ -35,6 +35,28 @@ def serialize_model(self, handler): return result + @classmethod + def from_dict(cls, data: dict) -> "Template": + for key, value in data.copy().items(): + if key == "_type": + data["type_name"] = value + del data[key] + else: + value["name"] = key + if "fields" not in data: + data["fields"] = [] + input_type = value.pop("_input_type", None) + if input_type: + try: + _input = _instantiate_input(input_type, value) + except Exception as e: + raise ValueError(f"Error instantiating input {input_type}: {e}") + else: + _input = Input(**value) + + data["fields"].append(_input) + return cls(**data) + # For backwards compatibility def to_dict(self, format_field_func=None): self.process_fields(format_field_func) From 24529352e3de53ffd8d3088dbbbdc87df4ad358e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 14:49:30 -0300 Subject: [PATCH 38/78] refactor(frontend_node): Add from_dict method for creating FrontendNode objects from dictionaries --- src/backend/base/langflow/template/frontend_node/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/base/langflow/template/frontend_node/base.py b/src/backend/base/langflow/template/frontend_node/base.py index 632598c42cbc..199233ee9b60 100644 --- a/src/backend/base/langflow/template/frontend_node/base.py +++ b/src/backend/base/langflow/template/frontend_node/base.py @@ -89,6 +89,12 @@ def serialize_model(self, handler): return {name: result} + @classmethod + def from_dict(cls, data: dict) -> "FrontendNode": + if "template" in data: + data["template"] = Template.from_dict(data["template"]) + return cls(**data) + # For backwards compatibility def to_dict(self, keep_name=True) -> dict: """Returns a dict representation of the frontend node.""" 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 39/78] 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 f1e03fa2d97d062df8d95c141e2ed2e33a59602e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:44:20 -0300 Subject: [PATCH 40/78] refactor(graph): Add EdgeData import and update add_nodes_and_edges method signature The `Graph` class in `base.py` has been updated to include the `EdgeData` import and modify the signature of the `add_nodes_and_edges` method. The `add_nodes_and_edges` method now accepts a list of dictionaries representing `EdgeData` objects instead of a list of dictionaries with string keys and values. This change improves the type safety and clarity of the codebase. --- src/backend/base/langflow/graph/graph/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 35db271c272c..bc9278256ebb 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -10,6 +10,7 @@ from langflow.exceptions.component import ComponentBuildException from langflow.graph.edge.base import ContractEdge +from langflow.graph.edge.schema import EdgeData from langflow.graph.graph.constants import lazy_load_vertex_dict from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager from langflow.graph.graph.state_manager import GraphStateManager @@ -73,7 +74,7 @@ def __init__( logger.error(f"Error getting tracing service: {exc}") self.tracing_service = None - def add_nodes_and_edges(self, nodes: List[Dict], edges: List[Dict[str, str]]): + def add_nodes_and_edges(self, nodes: List[Dict], edges: List[EdgeData]): self._vertices = nodes self._edges = edges self.raw_graph_data = {"nodes": nodes, "edges": edges} From 939ae63b9ecfd1ab90c48331d264d5cf6a8d1253 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:44:28 -0300 Subject: [PATCH 41/78] refactor(graph): Add first_layer property to Graph class The `Graph` class in `base.py` has been updated to include the `first_layer` property. This property returns the first layer of the graph and throws a `ValueError` if the graph is not prepared. This change improves the functionality and organization of the codebase. --- src/backend/base/langflow/graph/graph/base.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index bc9278256ebb..8a6a305c68ce 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -221,6 +221,12 @@ def validate_stream(self): "are connected and both have stream or streaming set to True" ) + @property + def first_layer(self): + if self._first_layer is None: + raise ValueError("Graph not prepared. Call prepare() first.") + return self._first_layer + @property def run_id(self): """ From 91276c646130f70ac0553a294d8343dc3b2e79a2 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:44:37 -0300 Subject: [PATCH 42/78] refactor(graph): Update Graph class instantiation in base.py The `Graph` class in `base.py` has been updated to use keyword arguments when instantiating the class. This change improves the readability and maintainability of the codebase. --- src/backend/base/langflow/graph/graph/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 8a6a305c68ce..1888aab2d702 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -653,7 +653,7 @@ def from_payload( try: vertices = payload["nodes"] edges = payload["edges"] - graph = cls(flow_id, flow_name, user_id) + graph = cls(flow_id=flow_id, flow_name=flow_name, user_id=user_id) graph.add_nodes_and_edges(vertices, edges) return graph except KeyError as exc: From 805f9fee2738e8325fcf97ce0bdfff20f18c5384 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:44:44 -0300 Subject: [PATCH 43/78] refactor(graph): Add prepare method to Graph class The `Graph` class in `base.py` has been updated to include the `prepare` method. This method prepares the graph for execution by validating the stream, building edges, and sorting vertices. It also adds the first layer of vertices to the run manager and sets the run queue. This change improves the functionality and organization of the codebase. --- src/backend/base/langflow/graph/graph/base.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 1888aab2d702..13bfa6447181 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1224,6 +1224,26 @@ def _create_vertex(self, vertex: dict): vertex_instance.set_top_level(self.top_level_vertices) return vertex_instance + def prepare(self, stop_component_id: Optional[str] = None, start_component_id: Optional[str] = None): + if stop_component_id and start_component_id: + raise ValueError("You can only provide one of stop_component_id or start_component_id") + self.validate_stream() + self.edges = self._build_edges() + if stop_component_id or start_component_id: + try: + first_layer = self.sort_vertices(stop_component_id, start_component_id) + except Exception as exc: + logger.error(exc) + first_layer = self.sort_vertices() + else: + first_layer = self.sort_vertices() + + for vertex_id in first_layer: + self.run_manager.add_to_vertices_being_run(vertex_id) + self._run_queue = deque(first_layer) + self._prepared = True + return self + def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]: """Returns the children of a vertex based on the vertex type.""" children = [] From b706aeaf70507c7ccd3352ca25df67e64f759cf5 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:44:52 -0300 Subject: [PATCH 44/78] refactor(graph): Improve graph preparation in retrieve_vertices_order function The `retrieve_vertices_order` function in `chat.py` has been updated to improve the graph preparation process. Instead of manually sorting vertices and adding them to the run manager, the function now calls the `prepare` method of the `Graph` class. This method validates the stream, builds edges, and sets the first layer of vertices. This change improves the functionality and organization of the codebase. --- src/backend/base/langflow/api/v1/chat.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/backend/base/langflow/api/v1/chat.py b/src/backend/base/langflow/api/v1/chat.py index 8121e8889295..dc5a604e56c7 100644 --- a/src/backend/base/langflow/api/v1/chat.py +++ b/src/backend/base/langflow/api/v1/chat.py @@ -95,18 +95,7 @@ async def retrieve_vertices_order( graph = await build_and_cache_graph_from_data( flow_id=flow_id_str, graph_data=data.model_dump(), chat_service=chat_service ) - graph.validate_stream() - if stop_component_id or start_component_id: - try: - first_layer = graph.sort_vertices(stop_component_id, start_component_id) - except Exception as exc: - logger.error(exc) - first_layer = graph.sort_vertices() - else: - first_layer = graph.sort_vertices() - - for vertex_id in first_layer: - graph.run_manager.add_to_vertices_being_run(vertex_id) + graph = graph.prepare(stop_component_id, start_component_id) # Now vertices is a list of lists # We need to get the id of each vertex @@ -122,7 +111,7 @@ async def retrieve_vertices_order( playgroundSuccess=True, ), ) - return VerticesOrderResponse(ids=first_layer, run_id=graph._run_id, vertices_to_run=vertices_to_run) + return VerticesOrderResponse(ids=graph.first_layer, run_id=graph.run_id, vertices_to_run=vertices_to_run) except Exception as exc: background_tasks.add_task( telemetry_service.log_package_playground, From 4b04dd74b678f7f7a956e90151c1e0786d6f8be6 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:54:25 -0300 Subject: [PATCH 45/78] refactor: Add GetCache and SetCache protocols for caching functionality --- src/backend/base/langflow/services/chat/schema.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/backend/base/langflow/services/chat/schema.py diff --git a/src/backend/base/langflow/services/chat/schema.py b/src/backend/base/langflow/services/chat/schema.py new file mode 100644 index 000000000000..51cf32e225cb --- /dev/null +++ b/src/backend/base/langflow/services/chat/schema.py @@ -0,0 +1,10 @@ +import asyncio +from typing import Any, Protocol + + +class GetCache(Protocol): + async def __call__(self, key: str, lock: asyncio.Lock | None = None) -> Any: ... + + +class SetCache(Protocol): + async def __call__(self, key: str, data: Any, lock: asyncio.Lock | None = None) -> bool: ... From 2d9a64119b9f728befab28e1d1666bae294ac5b8 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:54:54 -0300 Subject: [PATCH 46/78] refactor(graph): Add VertexBuildResult class for representing vertex build results --- src/backend/base/langflow/graph/graph/schema.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/backend/base/langflow/graph/graph/schema.py diff --git a/src/backend/base/langflow/graph/graph/schema.py b/src/backend/base/langflow/graph/graph/schema.py new file mode 100644 index 000000000000..30d67255fd96 --- /dev/null +++ b/src/backend/base/langflow/graph/graph/schema.py @@ -0,0 +1,13 @@ +from typing import TYPE_CHECKING, NamedTuple + +if TYPE_CHECKING: + from langflow.graph.schema import ResultData + from langflow.graph.vertex.base import Vertex + + +class VertexBuildResult(NamedTuple): + result_dict: "ResultData" + params: str + valid: bool + artifacts: dict + vertex: "Vertex" From edbbad16c593bdaaf544c2278ab5aa6b2b63afbf Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:55:31 -0300 Subject: [PATCH 47/78] refactor(chat.py, base.py): update build_vertex method in chat.py and base.py --- src/backend/base/langflow/api/v1/chat.py | 15 ++++++------ src/backend/base/langflow/graph/graph/base.py | 23 ++++++++++++------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/backend/base/langflow/api/v1/chat.py b/src/backend/base/langflow/api/v1/chat.py index dc5a604e56c7..9c0fc881a4e6 100644 --- a/src/backend/base/langflow/api/v1/chat.py +++ b/src/backend/base/langflow/api/v1/chat.py @@ -178,19 +178,18 @@ async def build_vertex( try: lock = chat_service._async_cache_locks[flow_id_str] - ( - result_dict, - params, - valid, - artifacts, - vertex, - ) = await graph.build_vertex( - chat_service=chat_service, + vertex_build_result = await graph.build_vertex( vertex_id=vertex_id, user_id=current_user.id, inputs_dict=inputs.model_dump() if inputs else {}, files=files, + get_cache=chat_service.get_cache, + set_cache=chat_service.set_cache, ) + result_dict = vertex_build_result.result_dict + params = vertex_build_result.params + valid = vertex_build_result.valid + artifacts = vertex_build_result.artifacts next_runnable_vertices = await graph.get_next_runnable_vertices(lock, vertex=vertex, cache=False) top_level_vertices = graph.get_top_level_vertices(next_runnable_vertices) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 13bfa6447181..2cfa122d6de3 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -13,6 +13,7 @@ from langflow.graph.edge.schema import EdgeData from langflow.graph.graph.constants import lazy_load_vertex_dict from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager +from langflow.graph.graph.schema import VertexBuildResult from langflow.graph.graph.state_manager import GraphStateManager from langflow.graph.graph.utils import find_start_component_id, process_flow, sort_up_to_vertex from langflow.graph.schema import InterfaceComponentTypes, RunOutputs @@ -21,7 +22,7 @@ from langflow.schema import Data from langflow.schema.schema import INPUT_FIELD_NAME, InputType from langflow.services.cache.utils import CacheMiss -from langflow.services.chat.service import ChatService +from langflow.services.chat.schema import GetCache, SetCache from langflow.services.deps import get_chat_service, get_tracing_service if TYPE_CHECKING: @@ -858,13 +859,14 @@ def get_root_of_group_node(self, vertex_id: str) -> Vertex: async def build_vertex( self, - chat_service: ChatService, vertex_id: str, + get_cache: GetCache, + set_cache: SetCache, inputs_dict: Optional[Dict[str, str]] = None, files: Optional[list[str]] = None, user_id: Optional[str] = None, fallback_to_env_vars: bool = False, - ): + ) -> VertexBuildResult: """ Builds a vertex in the graph. @@ -888,12 +890,12 @@ async def build_vertex( params = "" if vertex.frozen: # Check the cache for the vertex - cached_result = await chat_service.get_cache(key=vertex.id) + cached_result = await get_cache(key=vertex.id) if isinstance(cached_result, CacheMiss): await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - await chat_service.set_cache(key=vertex.id, data=vertex) + await set_cache(key=vertex.id, data=vertex) else: cached_vertex = cached_result["result"] # Now set update the vertex with the cached vertex @@ -910,7 +912,7 @@ async def build_vertex( await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - await chat_service.set_cache(key=vertex.id, data=vertex) + await set_cache(key=vertex.id, data=vertex) if vertex.result is not None: params = f"{vertex._built_object_repr()}{params}" @@ -919,7 +921,11 @@ async def build_vertex( artifacts = vertex.artifacts else: raise ValueError(f"No result found for vertex {vertex_id}") - return result_dict, params, valid, artifacts, vertex + + vertex_build_result = VertexBuildResult( + result_dict=result_dict, params=params, valid=valid, artifacts=artifacts, vertex=vertex + ) + return vertex_build_result except Exception as exc: if not isinstance(exc, ComponentBuildException): logger.exception(f"Error building Component: \n\n{exc}") @@ -973,11 +979,12 @@ async def process(self, fallback_to_env_vars: bool, start_component_id: Optional vertex = self.get_vertex(vertex_id) task = asyncio.create_task( self.build_vertex( - chat_service=chat_service, vertex_id=vertex_id, user_id=self.user_id, inputs_dict={}, fallback_to_env_vars=fallback_to_env_vars, + get_cache=chat_service.get_cache, + set_cache=chat_service.set_cache, ), name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}", ) From 92da5048e49b3a0d1464d36b33ae2a04b0bd8add Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 15:58:53 -0300 Subject: [PATCH 48/78] refactor(graph): Update Edge and ContractEdge constructors to use EdgeData type The constructors of the `Edge` and `ContractEdge` classes in `base.py` have been updated to use the `EdgeData` type for the `edge` and `raw_edge` parameters, respectively. This change improves the type safety and clarity of the codebase. --- src/backend/base/langflow/graph/edge/base.py | 9 +++++---- src/backend/base/langflow/graph/graph/base.py | 3 +-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index 78ad68d45a3f..1cd003b658b8 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -3,6 +3,7 @@ from loguru import logger from pydantic import BaseModel, Field, field_validator +from langflow.graph.edge.schema import EdgeData from langflow.schema.schema import INPUT_FIELD_NAME if TYPE_CHECKING: @@ -36,7 +37,7 @@ class TargetHandle(BaseModel): class Edge: - def __init__(self, source: "Vertex", target: "Vertex", edge: dict): + def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): self.source_id: str = source.id if source else "" self.target_id: str = target.id if target else "" if data := edge.get("data", {}): @@ -50,8 +51,8 @@ def __init__(self, source: "Vertex", target: "Vertex", edge: dict): else: # Logging here because this is a breaking change logger.error("Edge data is empty") - self._source_handle = edge.get("sourceHandle", "") - self._target_handle = edge.get("targetHandle", "") + self._source_handle = edge.get("sourceHandle", "") # type: ignore + self._target_handle = edge.get("targetHandle", "") # type: ignore # 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD' # target_param is documents self.target_param = self._target_handle.split("|")[1] @@ -182,7 +183,7 @@ def __eq__(self, __o: object) -> bool: class ContractEdge(Edge): - def __init__(self, source: "Vertex", target: "Vertex", raw_edge: dict): + def __init__(self, source: "Vertex", target: "Vertex", raw_edge: EdgeData): super().__init__(source, target, raw_edge) self.is_fulfilled = False # Whether the contract has been fulfilled. self.result: Any = None diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 2cfa122d6de3..f027dc281e41 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -93,8 +93,7 @@ def add_nodes_and_edges(self, nodes: List[Dict], edges: List[EdgeData]): def add_node(self, node: dict): self._vertices.append(node) - # TODO: Create a TypedDict to represente the edge - def add_edge(self, edge: dict): + def add_edge(self, edge: EdgeData): self._edges.append(edge) def initialize(self): 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 49/78] 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 50/78] 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 4b33c218cf902dd4106fc2c2486bdd2bd1eb8e80 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 16:20:51 -0300 Subject: [PATCH 51/78] refactor(base): Update target_param assignment in Edge class The `target_param` assignment in the `Edge` class of `base.py` has been updated to use the `cast` function for type hinting. This change improves the type safety and clarity of the codebase. --- src/backend/base/langflow/graph/edge/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index 1cd003b658b8..d7ae82de4d02 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional, cast from loguru import logger from pydantic import BaseModel, Field, field_validator @@ -55,7 +55,7 @@ def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): self._target_handle = edge.get("targetHandle", "") # type: ignore # 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD' # target_param is documents - self.target_param = self._target_handle.split("|")[1] + self.target_param = cast(str, self._target_handle).split("|")[1] # Validate in __init__ to fail fast self.validate_edge(source, target) From 0de9b8bf114bc325cd85d3c21c0bdb3c6990ed0f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 16:37:42 -0300 Subject: [PATCH 52/78] refactor(base): Add check for existing type in add_types method --- src/backend/base/langflow/template/field/base.py | 5 ++++- 1 file changed, 4 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..97b6d6a816f9 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -1,5 +1,6 @@ from enum import Enum -from typing import Any, Callable, GenericAlias, Optional, Union, _GenericAlias, _UnionGenericAlias # type: ignore +from typing import Optional # type: ignore +from typing import Any, Callable, GenericAlias, Union, _GenericAlias, _UnionGenericAlias from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer, model_validator @@ -182,6 +183,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 8cc7700d86cea6c5cb5405a288b00475647fda0f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 31 Jul 2024 18:17:39 -0300 Subject: [PATCH 53/78] 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 54/78] 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 55/78] 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 56/78] 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 57/78] 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 58/78] 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 59/78] 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 60/78] 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 From ea8ae0d17ea8be210e7b8cb571ab590abb31fb43 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 10:39:37 -0300 Subject: [PATCH 61/78] refactor(graph): update ContractEdge to use EdgeData for raw_edge parameter, enhancing type consistency across edges --- src/backend/base/langflow/graph/edge/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index 78ad68d45a3f..e99685933b27 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -3,6 +3,7 @@ from loguru import logger from pydantic import BaseModel, Field, field_validator +from langflow.graph.edge.schema import EdgeData from langflow.schema.schema import INPUT_FIELD_NAME if TYPE_CHECKING: @@ -182,7 +183,7 @@ def __eq__(self, __o: object) -> bool: class ContractEdge(Edge): - def __init__(self, source: "Vertex", target: "Vertex", raw_edge: dict): + def __init__(self, source: "Vertex", target: "Vertex", raw_edge: EdgeData): super().__init__(source, target, raw_edge) self.is_fulfilled = False # Whether the contract has been fulfilled. self.result: Any = None From 9b5338382b6fe40d68ec9e8756cc2f065e319705 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 10:39:47 -0300 Subject: [PATCH 62/78] refactor(graph): update add_edge method to use EdgeData type, improving type safety and consistency in graph edges --- src/backend/base/langflow/graph/graph/base.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 13bfa6447181..0af323153582 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -6,8 +6,6 @@ from itertools import chain from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, Type, Union -from loguru import logger - from langflow.exceptions.component import ComponentBuildException from langflow.graph.edge.base import ContractEdge from langflow.graph.edge.schema import EdgeData @@ -23,6 +21,7 @@ from langflow.services.cache.utils import CacheMiss from langflow.services.chat.service import ChatService from langflow.services.deps import get_chat_service, get_tracing_service +from loguru import logger if TYPE_CHECKING: from langflow.graph.schema import ResultData @@ -92,8 +91,7 @@ def add_nodes_and_edges(self, nodes: List[Dict], edges: List[EdgeData]): def add_node(self, node: dict): self._vertices.append(node) - # TODO: Create a TypedDict to represente the edge - def add_edge(self, edge: dict): + def add_edge(self, edge: EdgeData): self._edges.append(edge) def initialize(self): From 5ae0a5c061b20b2679373e1b0865d5ee99c21f2a Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 11:00:02 -0300 Subject: [PATCH 63/78] chore(dependencies): bump mypy version from 1.10.0 to 1.11.0 for improved type checking capabilities --- poetry.lock | 7 +++++-- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index fd5d0bdeae02..b71793db0428 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5988,7 +5988,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] [[package]] @@ -6912,6 +6912,7 @@ optional = false python-versions = ">=3.9" files = [ {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, + {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, @@ -6932,6 +6933,7 @@ files = [ {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, + {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, @@ -8644,6 +8646,7 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -11991,4 +11994,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.13" -content-hash = "3acb8b0235dcb5d33db0362bd5cf8a5dfcfb9cc198887dd2570b81c39a0fe46c" +content-hash = "b0b58a9883d3eacc262701a7938ff839365386ba5e155dd2582986501d2b5d7c" diff --git a/pyproject.toml b/pyproject.toml index 7e60cb89b9f8..538e1e46b489 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ composio-langchain = "^0.3.28" [tool.poetry.group.dev.dependencies] types-redis = "^4.6.0.5" ipykernel = "^6.29.0" -mypy = "^1.10.0" +mypy = "^1.11.0" ruff = "^0.4.5" httpx = "*" pytest = "^8.2.0" From 7801717c2e03e22a90cc005ce1b4c6f559f9e69c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 11:07:29 -0300 Subject: [PATCH 64/78] refactor: update Edge class to use EdgeData type for raw_edge parameter The Edge class in base.py has been updated to use the EdgeData type for the raw_edge parameter in the __init__ method. This change improves type safety and consistency in graph edges. --- src/backend/base/langflow/graph/edge/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index e99685933b27..f981cf78d727 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, List, Optional +from typing import TYPE_CHECKING, Any, List, Optional, cast from loguru import logger from pydantic import BaseModel, Field, field_validator @@ -37,7 +37,7 @@ class TargetHandle(BaseModel): class Edge: - def __init__(self, source: "Vertex", target: "Vertex", edge: dict): + def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): self.source_id: str = source.id if source else "" self.target_id: str = target.id if target else "" if data := edge.get("data", {}): @@ -51,11 +51,11 @@ def __init__(self, source: "Vertex", target: "Vertex", edge: dict): else: # Logging here because this is a breaking change logger.error("Edge data is empty") - self._source_handle = edge.get("sourceHandle", "") - self._target_handle = edge.get("targetHandle", "") + self._source_handle = edge.get("sourceHandle", "") # type: ignore + self._target_handle = edge.get("targetHandle", "") # type: ignore # 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD' # target_param is documents - self.target_param = self._target_handle.split("|")[1] + self.target_param = cast(str, self._target_handle.split("|")[1]) # Validate in __init__ to fail fast self.validate_edge(source, target) From b7ed091b675428fae6b902ade31ffad728def64e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 11:51:41 -0300 Subject: [PATCH 65/78] refactor: update follow_imports to "skip" in mypy configuration --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 538e1e46b489..b9a26706fd12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -193,7 +193,7 @@ line-length = 120 [tool.mypy] plugins = ["pydantic.mypy"] -follow_imports = "silent" +follow_imports = "skip" disable_error_code = ["type-var"] [build-system] From 0bb7ae86ae594e3a63efe6685897d6c78c9ab9ba Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 11:52:03 -0300 Subject: [PATCH 66/78] fix: add type ignore comment for return data in SelfQueryRetriever.py to resolve mypy warning --- .../base/langflow/components/retrievers/SelfQueryRetriever.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/components/retrievers/SelfQueryRetriever.py b/src/backend/base/langflow/components/retrievers/SelfQueryRetriever.py index 2a753b30e4cd..149c7325c853 100644 --- a/src/backend/base/langflow/components/retrievers/SelfQueryRetriever.py +++ b/src/backend/base/langflow/components/retrievers/SelfQueryRetriever.py @@ -67,4 +67,4 @@ def build( documents = self_query_retriever.invoke(input=input_text, config={"callbacks": self.get_langchain_callbacks()}) data = [Data.from_document(document) for document in documents] self.status = data - return data + return data # type: ignore From 82aa1bf8eac18ef2035b2f23b7ff6bbf1ec1dcde Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 12:07:52 -0300 Subject: [PATCH 67/78] chore: update mypy configuration to include namespace_packages and ignore missing imports in pyproject.toml files --- pyproject.toml | 3 +++ src/backend/base/pyproject.toml | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b9a26706fd12..2553900dcc4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -195,6 +195,9 @@ line-length = 120 plugins = ["pydantic.mypy"] follow_imports = "skip" disable_error_code = ["type-var"] +namespace_packages = true +mypy_path = "langflow" +ignore_missing_imports = true [build-system] requires = ["poetry-core"] diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index 9013a49e383b..b6c1c3eb67ce 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -96,11 +96,12 @@ log_cli = true markers = ["async_test"] [tool.mypy] +plugins = ["pydantic.mypy"] +follow_imports = "skip" +disable_error_code = ["type-var"] namespace_packages = true mypy_path = "langflow" ignore_missing_imports = true -disable_error_code = ["type-var"] - [tool.ruff] exclude = ["src/backend/langflow/alembic/*"] From d384bdee0a63258a80178d994f39f98ea817927d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 12:08:01 -0300 Subject: [PATCH 68/78] fix: add type ignore comment for target_param in base.py to resolve mypy warning about split method usage --- src/backend/base/langflow/graph/edge/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/edge/base.py b/src/backend/base/langflow/graph/edge/base.py index f981cf78d727..a59eea256b8e 100644 --- a/src/backend/base/langflow/graph/edge/base.py +++ b/src/backend/base/langflow/graph/edge/base.py @@ -55,7 +55,7 @@ def __init__(self, source: "Vertex", target: "Vertex", edge: EdgeData): self._target_handle = edge.get("targetHandle", "") # type: ignore # 'BaseLoader;BaseOutputParser|documents|PromptTemplate-zmTlD' # target_param is documents - self.target_param = cast(str, self._target_handle.split("|")[1]) + self.target_param = cast(str, self._target_handle.split("|")[1]) # type: ignore # Validate in __init__ to fail fast self.validate_edge(source, target) From 2f5a6171f135a2fd8608261c29eb19dbb1b60758 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 12:08:33 -0300 Subject: [PATCH 69/78] fix: add type ignore comments for various classes to resolve mypy warnings in input_mixin, database models, and cache service files --- src/backend/base/langflow/inputs/input_mixin.py | 2 +- src/backend/base/langflow/processing/process.py | 15 ++++++++------- .../base/langflow/services/cache/service.py | 6 +++--- .../services/database/models/flow/model.py | 2 +- .../services/database/models/folder/model.py | 2 +- .../services/database/models/message/model.py | 2 +- .../base/langflow/services/database/service.py | 4 ++-- .../langflow/services/plugins/langfuse_plugin.py | 7 +++++-- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index 5b1f112d0fcf..f772a256ac0d 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -26,7 +26,7 @@ class FieldTypes(str, Enum): # Base mixin for common input field attributes and methods -class BaseInputMixin(BaseModel, validate_assignment=True): +class BaseInputMixin(BaseModel, validate_assignment=True): # type: ignore model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid", populate_by_name=True) field_type: SerializableFieldTypes = Field(default=FieldTypes.TEXT) diff --git a/src/backend/base/langflow/processing/process.py b/src/backend/base/langflow/processing/process.py index 60f11b38863f..fbbc1b9c86c2 100644 --- a/src/backend/base/langflow/processing/process.py +++ b/src/backend/base/langflow/processing/process.py @@ -1,4 +1,7 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, cast + +from loguru import logger +from pydantic import BaseModel from langflow.graph.graph.base import Graph from langflow.graph.schema import RunOutputs @@ -6,8 +9,6 @@ from langflow.schema.graph import InputValue, Tweaks from langflow.schema.schema import INPUT_FIELD_NAME from langflow.services.deps import get_settings_service -from loguru import logger -from pydantic import BaseModel if TYPE_CHECKING: from langflow.api.v1.schemas import InputValueRequest @@ -112,7 +113,7 @@ def run_graph( def validate_input( - graph_data: Dict[str, Any], tweaks: Union["Tweaks", Dict[str, Dict[str, Any]]] + graph_data: Dict[str, Any], tweaks: Union["Tweaks", Dict[str, str | Dict[str, Any]]] ) -> List[Dict[str, Any]]: if not isinstance(graph_data, dict) or not isinstance(tweaks, dict): raise ValueError("graph_data and tweaks should be dictionaries") @@ -162,12 +163,12 @@ def process_tweaks( """ tweaks_dict = {} if not isinstance(tweaks, dict): - tweaks_dict = tweaks.model_dump() + tweaks_dict = cast(Dict[str, Any], tweaks.model_dump()) else: tweaks_dict = tweaks if "stream" not in tweaks_dict: - tweaks_dict["stream"] = stream - nodes = validate_input(graph_data, tweaks_dict) + tweaks_dict |= {"stream": stream} + nodes = validate_input(graph_data, cast(Dict[str, str | Dict[str, Any]], tweaks_dict)) nodes_map = {node.get("id"): node for node in nodes} nodes_display_name_map = {node.get("data", {}).get("node", {}).get("display_name"): node for node in nodes} diff --git a/src/backend/base/langflow/services/cache/service.py b/src/backend/base/langflow/services/cache/service.py index 26aaa0978e9c..3d4131c239c1 100644 --- a/src/backend/base/langflow/services/cache/service.py +++ b/src/backend/base/langflow/services/cache/service.py @@ -13,7 +13,7 @@ CACHE_MISS = CacheMiss() -class ThreadingInMemoryCache(CacheService, Generic[LockType]): +class ThreadingInMemoryCache(CacheService, Generic[LockType]): # type: ignore """ A simple in-memory cache using an OrderedDict. @@ -181,7 +181,7 @@ def __repr__(self): return f"InMemoryCache(max_size={self.max_size}, expiration_time={self.expiration_time})" -class RedisCache(AsyncBaseCacheService, Generic[LockType]): +class RedisCache(AsyncBaseCacheService, Generic[LockType]): # type: ignore """ A Redis-based cache implementation. @@ -331,7 +331,7 @@ def __repr__(self): return f"RedisCache(expiration_time={self.expiration_time})" -class AsyncInMemoryCache(AsyncBaseCacheService, Generic[AsyncLockType]): +class AsyncInMemoryCache(AsyncBaseCacheService, Generic[AsyncLockType]): # type: ignore def __init__(self, max_size=None, expiration_time=3600): self.cache = OrderedDict() diff --git a/src/backend/base/langflow/services/database/models/flow/model.py b/src/backend/base/langflow/services/database/models/flow/model.py index b23f16a6eace..22c2d2f904df 100644 --- a/src/backend/base/langflow/services/database/models/flow/model.py +++ b/src/backend/base/langflow/services/database/models/flow/model.py @@ -137,7 +137,7 @@ def validate_dt(cls, v): return datetime.fromisoformat(v) -class Flow(FlowBase, table=True): +class Flow(FlowBase, table=True): # type: ignore id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) data: Optional[Dict] = Field(default=None, sa_column=Column(JSON)) user_id: Optional[UUID] = Field(index=True, foreign_key="user.id", nullable=True) diff --git a/src/backend/base/langflow/services/database/models/folder/model.py b/src/backend/base/langflow/services/database/models/folder/model.py index dc2dfaa80355..73ba6a6e54cc 100644 --- a/src/backend/base/langflow/services/database/models/folder/model.py +++ b/src/backend/base/langflow/services/database/models/folder/model.py @@ -16,7 +16,7 @@ class FolderBase(SQLModel): description: Optional[str] = Field(default=None) -class Folder(FolderBase, table=True): +class Folder(FolderBase, table=True): # type: ignore id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True) parent_id: Optional[UUID] = Field(default=None, foreign_key="folder.id") diff --git a/src/backend/base/langflow/services/database/models/message/model.py b/src/backend/base/langflow/services/database/models/message/model.py index 7c0b9dc8f2b1..0f2f675beefc 100644 --- a/src/backend/base/langflow/services/database/models/message/model.py +++ b/src/backend/base/langflow/services/database/models/message/model.py @@ -47,7 +47,7 @@ def from_message(cls, message: "Message", flow_id: str | UUID | None = None): ) -class MessageTable(MessageBase, table=True): +class MessageTable(MessageBase, table=True): # type: ignore __tablename__ = "message" id: UUID = Field(default_factory=uuid4, primary_key=True) flow_id: Optional[UUID] = Field(default=None, foreign_key="flow.id") diff --git a/src/backend/base/langflow/services/database/service.py b/src/backend/base/langflow/services/database/service.py index 5dd6fe0555be..700e900d6c95 100644 --- a/src/backend/base/langflow/services/database/service.py +++ b/src/backend/base/langflow/services/database/service.py @@ -1,7 +1,7 @@ import time from datetime import datetime from pathlib import Path -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Type import sqlalchemy as sa from alembic import command, util @@ -133,7 +133,7 @@ def migrate_flows_if_auto_login(self): def check_schema_health(self) -> bool: inspector = inspect(self.engine) - model_mapping = { + model_mapping: dict[str, Type[SQLModel]] = { "flow": models.Flow, "user": models.User, "apikey": models.ApiKey, diff --git a/src/backend/base/langflow/services/plugins/langfuse_plugin.py b/src/backend/base/langflow/services/plugins/langfuse_plugin.py index ffc8139f3924..b65c27733568 100644 --- a/src/backend/base/langflow/services/plugins/langfuse_plugin.py +++ b/src/backend/base/langflow/services/plugins/langfuse_plugin.py @@ -1,13 +1,16 @@ -from typing import Optional +from typing import TYPE_CHECKING, Optional from loguru import logger from langflow.services.deps import get_settings_service from langflow.services.plugins.base import CallbackPlugin +if TYPE_CHECKING: + from langfuse import Langfuse # type: ignore + class LangfuseInstance: - _instance = None + _instance: Optional["Langfuse"] = None @classmethod def get(cls): From 0d595db2bdaec3a5be9fc7d4f0cc44f5251ec25c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 12:11:48 -0300 Subject: [PATCH 70/78] refactor: add first layer attribute to Graph class The Graph class in base.py has been updated to include a new attribute called `_first_layer`. This attribute is a list of strings and is initialized as an empty list. This change enhances the functionality of the Graph class by providing a way to store and access the first layer of vertices in the graph. --- src/backend/base/langflow/graph/graph/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 0af323153582..c91312bac5a3 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -6,6 +6,8 @@ from itertools import chain from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple, Type, Union +from loguru import logger + from langflow.exceptions.component import ComponentBuildException from langflow.graph.edge.base import ContractEdge from langflow.graph.edge.schema import EdgeData @@ -21,7 +23,6 @@ from langflow.services.cache.utils import CacheMiss from langflow.services.chat.service import ChatService from langflow.services.deps import get_chat_service, get_tracing_service -from loguru import logger if TYPE_CHECKING: from langflow.graph.schema import ResultData @@ -67,6 +68,7 @@ def __init__( self.vertices: List[Vertex] = [] self.run_manager = RunnableVerticesManager() self.state_manager = GraphStateManager() + self._first_layer: List[str] = [] try: self.tracing_service: "TracingService" | None = get_tracing_service() except Exception as exc: From f92b694ec5b8e6b0b474c75f70dd8e1c5b59b98e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 12:20:56 -0300 Subject: [PATCH 71/78] refactor: store first layer in Graph class with _first_layer attribute for improved access to vertices being run --- src/backend/base/langflow/graph/graph/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index c91312bac5a3..d3e5c544e37e 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1240,6 +1240,7 @@ def prepare(self, stop_component_id: Optional[str] = None, start_component_id: O for vertex_id in first_layer: self.run_manager.add_to_vertices_being_run(vertex_id) + self._first_layer = first_layer self._run_queue = deque(first_layer) self._prepared = True return self From ff7813bbefb712f7b821031fe7b36c6542d63a61 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 12:33:50 -0300 Subject: [PATCH 72/78] fix: add type ignore comments to database model classes to resolve mypy type checking warnings --- .../base/langflow/services/database/models/api_key/model.py | 2 +- .../langflow/services/database/models/transactions/model.py | 2 +- .../base/langflow/services/database/models/user/model.py | 2 +- .../base/langflow/services/database/models/variable/model.py | 2 +- .../langflow/services/database/models/vertex_builds/model.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/base/langflow/services/database/models/api_key/model.py b/src/backend/base/langflow/services/database/models/api_key/model.py index 157b08b32456..1a0baaef59f7 100644 --- a/src/backend/base/langflow/services/database/models/api_key/model.py +++ b/src/backend/base/langflow/services/database/models/api_key/model.py @@ -20,7 +20,7 @@ class ApiKeyBase(SQLModel): is_active: bool = Field(default=True) -class ApiKey(ApiKeyBase, table=True): +class ApiKey(ApiKeyBase, table=True): # type: ignore id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) created_at: Optional[datetime] = Field( default=None, sa_column=Column(DateTime(timezone=True), server_default=func.now(), nullable=False) diff --git a/src/backend/base/langflow/services/database/models/transactions/model.py b/src/backend/base/langflow/services/database/models/transactions/model.py index b07eba15bee8..2b8978d80dc8 100644 --- a/src/backend/base/langflow/services/database/models/transactions/model.py +++ b/src/backend/base/langflow/services/database/models/transactions/model.py @@ -33,7 +33,7 @@ def validate_flow_id(cls, value): return value -class TransactionTable(TransactionBase, table=True): +class TransactionTable(TransactionBase, table=True): # type: ignore __tablename__ = "transaction" id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True) flow: "Flow" = Relationship(back_populates="transactions") diff --git a/src/backend/base/langflow/services/database/models/user/model.py b/src/backend/base/langflow/services/database/models/user/model.py index dfc978c273d7..0f71cfb4fa23 100644 --- a/src/backend/base/langflow/services/database/models/user/model.py +++ b/src/backend/base/langflow/services/database/models/user/model.py @@ -11,7 +11,7 @@ from langflow.services.database.models.folder import Folder -class User(SQLModel, table=True): +class User(SQLModel, table=True): # type: ignore id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) username: str = Field(index=True, unique=True) password: str = Field() diff --git a/src/backend/base/langflow/services/database/models/variable/model.py b/src/backend/base/langflow/services/database/models/variable/model.py index 1344dc9c32cc..376c1c92ee74 100644 --- a/src/backend/base/langflow/services/database/models/variable/model.py +++ b/src/backend/base/langflow/services/database/models/variable/model.py @@ -19,7 +19,7 @@ class VariableBase(SQLModel): type: Optional[str] = Field(None, description="Type of the variable") -class Variable(VariableBase, table=True): +class Variable(VariableBase, table=True): # type: ignore id: Optional[UUID] = Field( default_factory=uuid4, primary_key=True, diff --git a/src/backend/base/langflow/services/database/models/vertex_builds/model.py b/src/backend/base/langflow/services/database/models/vertex_builds/model.py index c32cefc8dca1..78d582fe8587 100644 --- a/src/backend/base/langflow/services/database/models/vertex_builds/model.py +++ b/src/backend/base/langflow/services/database/models/vertex_builds/model.py @@ -33,7 +33,7 @@ def validate_flow_id(cls, value): return value -class VertexBuildTable(VertexBuildBase, table=True): +class VertexBuildTable(VertexBuildBase, table=True): # type: ignore __tablename__ = "vertex_build" build_id: Optional[UUID] = Field(default_factory=uuid4, primary_key=True) flow: "Flow" = Relationship(back_populates="vertex_builds") From 10f5dc11b7cf7bfa9d31f124b5dfb0c2161e6bcd Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 16:40:17 -0300 Subject: [PATCH 73/78] fix: update build_vertex call to include cache methods from chat_service for improved functionality --- src/backend/base/langflow/api/v1/chat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/api/v1/chat.py b/src/backend/base/langflow/api/v1/chat.py index 1b6e340fcfdf..01ba21758f6f 100644 --- a/src/backend/base/langflow/api/v1/chat.py +++ b/src/backend/base/langflow/api/v1/chat.py @@ -221,7 +221,8 @@ async def _build_vertex(vertex_id: str, graph: "Graph") -> VertexBuildResponse: artifacts, vertex, ) = await graph.build_vertex( - chat_service=chat_service, + get_cache=chat_service.get_cache, + set_cache=chat_service.set_cache, vertex_id=vertex_id, user_id=current_user.id, inputs_dict=inputs.model_dump() if inputs else {}, From aa97e3ea3cb9f2c8dd9ef24e8d7cd09d644f1efe Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 17:08:19 -0300 Subject: [PATCH 74/78] fix(graph): update build_vertex to handle optional cache methods for better stability and error handling --- src/backend/base/langflow/graph/graph/base.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 6501e49ea06b..0ae980fff378 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -860,8 +860,8 @@ def get_root_of_group_node(self, vertex_id: str) -> Vertex: async def build_vertex( self, vertex_id: str, - get_cache: GetCache, - set_cache: SetCache, + get_cache: GetCache | None = None, + set_cache: SetCache | None = None, inputs_dict: Optional[Dict[str, str]] = None, files: Optional[list[str]] = None, user_id: Optional[str] = None, @@ -890,7 +890,10 @@ async def build_vertex( params = "" if vertex.frozen: # Check the cache for the vertex - cached_result = await get_cache(key=vertex.id) + if get_cache: + cached_result = await get_cache(key=vertex.id) + else: + cached_result = None if isinstance(cached_result, CacheMiss): await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files @@ -913,8 +916,8 @@ async def build_vertex( await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - if chat_service: - await chat_service.set_cache(key=vertex.id, data=vertex) + if set_cache: + await set_cache(key=vertex.id, data=vertex) else: await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files From b8b3f3194fc918dcd90f62fc4728f3c6503cf215 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 17:33:14 -0300 Subject: [PATCH 75/78] fix(graph): handle optional caching in build_vertex to prevent unnecessary cache calls and improve stability --- src/backend/base/langflow/graph/graph/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 0ae980fff378..760af50e4171 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -898,7 +898,8 @@ async def build_vertex( await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - await set_cache(key=vertex.id, data=vertex) + if set_cache: + await set_cache(key=vertex.id, data=vertex) else: cached_result = None if cached_result and not isinstance(cached_result, CacheMiss): @@ -922,7 +923,8 @@ async def build_vertex( await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - await set_cache(key=vertex.id, data=vertex) + if set_cache: + await set_cache(key=vertex.id, data=vertex) if vertex.result is not None: params = f"{vertex._built_object_repr()}{params}" From 1c3e558b9a4924a6fcbf12c0801e9b568d31c58e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 17:51:12 -0300 Subject: [PATCH 76/78] fix(graph): refine cache handling in build_vertex to ensure checks for optional caching are properly evaluated --- src/backend/base/langflow/graph/graph/base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 760af50e4171..52eec004ba50 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -890,7 +890,7 @@ async def build_vertex( params = "" if vertex.frozen: # Check the cache for the vertex - if get_cache: + if get_cache is not None: cached_result = await get_cache(key=vertex.id) else: cached_result = None @@ -898,7 +898,7 @@ async def build_vertex( await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - if set_cache: + if set_cache is not None: await set_cache(key=vertex.id, data=vertex) else: cached_result = None @@ -917,13 +917,13 @@ async def build_vertex( await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - if set_cache: + if set_cache is not None: await set_cache(key=vertex.id, data=vertex) else: await vertex.build( user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files ) - if set_cache: + if set_cache is not None: await set_cache(key=vertex.id, data=vertex) if vertex.result is not None: From 9cf4f66e54cbc6f7d1bdd02a9cd3e8c693a1a02b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 17:52:29 -0300 Subject: [PATCH 77/78] fix(graph): streamline build_vertex result handling in chat.py for improved clarity and maintainability --- src/backend/base/langflow/api/v1/chat.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/backend/base/langflow/api/v1/chat.py b/src/backend/base/langflow/api/v1/chat.py index 01ba21758f6f..a4b17c15e99c 100644 --- a/src/backend/base/langflow/api/v1/chat.py +++ b/src/backend/base/langflow/api/v1/chat.py @@ -214,20 +214,18 @@ async def _build_vertex(vertex_id: str, graph: "Graph") -> VertexBuildResponse: vertex = graph.get_vertex(vertex_id) try: lock = chat_service._async_cache_locks[flow_id_str] - ( - result_dict, - params, - valid, - artifacts, - vertex, - ) = await graph.build_vertex( - get_cache=chat_service.get_cache, - set_cache=chat_service.set_cache, + vertex_build_result = await graph.build_vertex( vertex_id=vertex_id, user_id=current_user.id, inputs_dict=inputs.model_dump() if inputs else {}, files=files, + get_cache=chat_service.get_cache, + set_cache=chat_service.set_cache, ) + result_dict = vertex_build_result.result_dict + params = vertex_build_result.params + valid = vertex_build_result.valid + artifacts = vertex_build_result.artifacts next_runnable_vertices = await graph.get_next_runnable_vertices(lock, vertex=vertex, cache=False) top_level_vertices = graph.get_top_level_vertices(next_runnable_vertices) From 0c2efcd1e92b884fa7c4d12e58f65d469278070a Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Fri, 2 Aug 2024 18:16:20 -0300 Subject: [PATCH 78/78] fix(graph): remove redundant cached_result assignment in build_vertex for cleaner cache handling and improved performance --- src/backend/base/langflow/graph/graph/base.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index 52eec004ba50..54c525137468 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -900,8 +900,6 @@ async def build_vertex( ) if set_cache is not None: await set_cache(key=vertex.id, data=vertex) - else: - cached_result = None if cached_result and not isinstance(cached_result, CacheMiss): cached_vertex = cached_result["result"] # Now set update the vertex with the cached vertex