feat: Add ComponentFrontendNode to CustomComponent
This commit adds the `ComponentFrontendNode` class to the `CustomComponent` module. The `ComponentFrontendNode` class defines a new frontend node for the `Component` type. It includes a template with a code input field. This change enhances the functionality and flexibility of the `CustomComponent` module.
This commit is contained in:
parent
b0432c3802
commit
b7de1ff3bd
7 changed files with 105 additions and 31 deletions
|
|
@ -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},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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] = []
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
]
|
||||
|
||||
|
|
|
|||
20
tests/data/component_nested_call.py
Normal file
20
tests/data/component_nested_call.py
Normal file
|
|
@ -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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue