diff --git a/src/backend/base/langflow/graph/graph/constants.py b/src/backend/base/langflow/graph/graph/constants.py index 8f5840524..ca04e81c6 100644 --- a/src/backend/base/langflow/graph/graph/constants.py +++ b/src/backend/base/langflow/graph/graph/constants.py @@ -21,6 +21,7 @@ class VertexTypesDict(LazyLoadDictBase): def get_type_dict(self): return { **{t: types.CustomComponentVertex for t in ["CustomComponent"]}, + **{t: types.ComponentVertex for t in ["Component"]}, **{t: types.InterfaceVertex for t in CHAT_COMPONENTS}, } diff --git a/src/backend/base/langflow/graph/vertex/types.py b/src/backend/base/langflow/graph/vertex/types.py index 590c38c24..79eafd8e5 100644 --- a/src/backend/base/langflow/graph/vertex/types.py +++ b/src/backend/base/langflow/graph/vertex/types.py @@ -24,6 +24,15 @@ class CustomComponentVertex(Vertex): return self.artifacts["repr"] or super()._built_object_repr() +class ComponentVertex(Vertex): + def __init__(self, data: Dict, graph): + super().__init__(data, graph=graph, base_type="component") + + def _built_object_repr(self): + if self.artifacts and "repr" in self.artifacts: + return self.artifacts["repr"] or super()._built_object_repr() + + class InterfaceVertex(Vertex): def __init__(self, data: Dict, graph): super().__init__(data, graph=graph, base_type="custom_components", is_task=True) diff --git a/src/backend/base/langflow/interface/initialize/loading.py b/src/backend/base/langflow/interface/initialize/loading.py index 098ff8a33..239bf5cc3 100644 --- a/src/backend/base/langflow/interface/initialize/loading.py +++ b/src/backend/base/langflow/interface/initialize/loading.py @@ -27,10 +27,25 @@ async def instantiate_class( params = convert_params_to_sets(params) params = convert_kwargs(params) logger.debug(f"Instantiating {vertex_type} of type {base_type}") + if not base_type: raise ValueError("No base type provided for vertex") + + params_copy = params.copy() + class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code")) + custom_component: "CustomComponent" = class_object( + user_id=user_id, + parameters=params_copy, + vertex=vertex, + selected_output_type=vertex.selected_output_type, + ) + params_copy = update_params_with_load_from_db_fields( + custom_component, params_copy, vertex.load_from_db_fields, fallback_to_env_vars + ) if base_type == "custom_components": - return await instantiate_custom_component(params, user_id, vertex, fallback_to_env_vars=fallback_to_env_vars) + return await build_custom_component(params=params, custom_component=custom_component) + elif base_type == "component": + return await build_component(params=params, custom_component=custom_component) else: raise ValueError(f"Base type {base_type} not found.") @@ -94,26 +109,37 @@ def update_params_with_load_from_db_fields( return params -async def instantiate_custom_component( - params: dict, user_id: str, vertex: "Vertex", fallback_to_env_vars: bool = False +async def build_component( + params: dict, + custom_component: "CustomComponent", + vertex: "Vertex", ): - params_copy = params.copy() - class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code")) - custom_component: "CustomComponent" = class_object( - user_id=user_id, - parameters=params_copy, - vertex=vertex, - selected_output_type=vertex.selected_output_type, - ) - params_copy = update_params_with_load_from_db_fields( - custom_component, params_copy, vertex.load_from_db_fields, fallback_to_env_vars - ) - # Now set the params as attributes of the custom_component - custom_component.set_attributes(params_copy) + custom_component.set_attributes(params) - if "retriever" in params_copy and hasattr(params_copy["retriever"], "as_retriever"): - params_copy["retriever"] = params_copy["retriever"].as_retriever() + build_result = {} + if hasattr(custom_component, "outputs"): + for output in custom_component.outputs: + # Build the output if it's connected to some other vertex + # or if it's not connected to any vertex + if not vertex.edges or output.name in vertex.edges: + method: Callable | Awaitable = getattr(custom_component, output.method) + result = method() + # If the method is asynchronous, we need to await it + if inspect.iscoroutinefunction(method): + result = await result + build_result[output.name] = result + custom_repr = custom_component.custom_repr() + if custom_repr is None and isinstance(build_result, (dict, Record, str)): + custom_repr = build_result + if not isinstance(custom_repr, str): + custom_repr = str(custom_repr) + return custom_component, build_result, {"repr": custom_repr} + + +async def build_custom_component(params: dict, custom_component: "CustomComponent"): + if "retriever" in params and hasattr(params["retriever"], "as_retriever"): + params["retriever"] = params["retriever"].as_retriever() # Determine if the build method is asynchronous is_async = inspect.iscoroutinefunction(custom_component.build) @@ -124,22 +150,12 @@ async def instantiate_custom_component( # the methods don't require any params because they are already set in the custom_component # so we can just call them - if hasattr(custom_component, "outputs"): - for output in custom_component.outputs: - if output.name in vertex.edges: - method: Callable | Awaitable = getattr(custom_component, output.method) - result = method() - # If the method is asynchronous, we need to await it - if inspect.iscoroutinefunction(method): - result = await result - vertex.add_result(output.name, result) - if is_async: # Await the build method directly if it's async - build_result = await custom_component.build(**params_copy) + build_result = await custom_component.build(**params) else: # Call the build method directly if it's sync - build_result = custom_component.build(**params_copy) + build_result = custom_component.build(**params) custom_repr = custom_component.custom_repr() if custom_repr is None and isinstance(build_result, (dict, Record, str)): custom_repr = build_result diff --git a/src/backend/base/langflow/legacy_custom/customs.py b/src/backend/base/langflow/legacy_custom/customs.py index 26e5e33fa..e4090135e 100644 --- a/src/backend/base/langflow/legacy_custom/customs.py +++ b/src/backend/base/langflow/legacy_custom/customs.py @@ -5,6 +5,9 @@ CUSTOM_NODES: dict[str, dict[str, frontend_node.base.FrontendNode]] = { "custom_components": { "CustomComponent": frontend_node.custom_components.CustomComponentFrontendNode(), }, + "component": { + "Component": frontend_node.custom_components.ComponentFrontendNode(), + }, } diff --git a/src/backend/base/langflow/template/frontend_node/custom_components.py b/src/backend/base/langflow/template/frontend_node/custom_components.py index 6969f8a73..2fd1e9cdf 100644 --- a/src/backend/base/langflow/template/frontend_node/custom_components.py +++ b/src/backend/base/langflow/template/frontend_node/custom_components.py @@ -67,3 +67,28 @@ class CustomComponentFrontendNode(FrontendNode): ) description: Optional[str] = None base_classes: list[str] = [] + + +class ComponentFrontendNode(FrontendNode): + _format_template: bool = False + name: str = "Component" + display_name: Optional[str] = "Component" + beta: bool = False + template: Template = Template( + type_name="Component", + fields=[ + Input( + field_type="code", + required=True, + placeholder="", + is_list=False, + show=True, + value=DEFAULT_CUSTOM_COMPONENT_CODE, + name="code", + advanced=False, + dynamic=True, + ) + ], + ) + description: Optional[str] = None + base_classes: list[str] = [] diff --git a/tests/data/component_multiple_outputs.py b/tests/data/component_multiple_outputs.py index 7a01fafba..26fe13acd 100644 --- a/tests/data/component_multiple_outputs.py +++ b/tests/data/component_multiple_outputs.py @@ -8,7 +8,7 @@ class MultipleOutputsComponent(CustomComponent): Input(display_name="Number", name="number", field_type=int), ] outputs = [ - Output(display_name="Certain Output", method="certain_output", name="certain_output"), + Output(name="Certain Output", method="certain_output"), Output(name="Other Output", method="other_output"), ] diff --git a/tests/data/component_nested_call.py b/tests/data/component_nested_call.py new file mode 100644 index 000000000..204ff169c --- /dev/null +++ b/tests/data/component_nested_call.py @@ -0,0 +1,20 @@ +from langflow.custom import CustomComponent +from langflow.template.field.base import Input, Output +from random import randint + + +class MultipleOutputsComponent(CustomComponent): + inputs = [ + Input(display_name="Input", name="input", field_type=str), + Input(display_name="Number", name="number", field_type=int), + ] + outputs = [ + Output(name="Certain Output", method="certain_output"), + Output(name="Other Output", method="other_output"), + ] + + def certain_output(self) -> int: + return randint(0, self.number) + + def other_output(self) -> int: + return self.certain_output()