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):
|
def get_type_dict(self):
|
||||||
return {
|
return {
|
||||||
**{t: types.CustomComponentVertex for t in ["CustomComponent"]},
|
**{t: types.CustomComponentVertex for t in ["CustomComponent"]},
|
||||||
|
**{t: types.ComponentVertex for t in ["Component"]},
|
||||||
**{t: types.InterfaceVertex for t in CHAT_COMPONENTS},
|
**{t: types.InterfaceVertex for t in CHAT_COMPONENTS},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,15 @@ class CustomComponentVertex(Vertex):
|
||||||
return self.artifacts["repr"] or super()._built_object_repr()
|
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):
|
class InterfaceVertex(Vertex):
|
||||||
def __init__(self, data: Dict, graph):
|
def __init__(self, data: Dict, graph):
|
||||||
super().__init__(data, graph=graph, base_type="custom_components", is_task=True)
|
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_params_to_sets(params)
|
||||||
params = convert_kwargs(params)
|
params = convert_kwargs(params)
|
||||||
logger.debug(f"Instantiating {vertex_type} of type {base_type}")
|
logger.debug(f"Instantiating {vertex_type} of type {base_type}")
|
||||||
|
|
||||||
if not base_type:
|
if not base_type:
|
||||||
raise ValueError("No base type provided for vertex")
|
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":
|
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:
|
else:
|
||||||
raise ValueError(f"Base type {base_type} not found.")
|
raise ValueError(f"Base type {base_type} not found.")
|
||||||
|
|
||||||
|
|
@ -94,26 +109,37 @@ def update_params_with_load_from_db_fields(
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|
||||||
async def instantiate_custom_component(
|
async def build_component(
|
||||||
params: dict, user_id: str, vertex: "Vertex", fallback_to_env_vars: bool = False
|
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
|
# 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"):
|
build_result = {}
|
||||||
params_copy["retriever"] = params_copy["retriever"].as_retriever()
|
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
|
# Determine if the build method is asynchronous
|
||||||
is_async = inspect.iscoroutinefunction(custom_component.build)
|
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
|
# the methods don't require any params because they are already set in the custom_component
|
||||||
# so we can just call them
|
# 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:
|
if is_async:
|
||||||
# Await the build method directly if it's 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:
|
else:
|
||||||
# Call the build method directly if it's sync
|
# 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()
|
custom_repr = custom_component.custom_repr()
|
||||||
if custom_repr is None and isinstance(build_result, (dict, Record, str)):
|
if custom_repr is None and isinstance(build_result, (dict, Record, str)):
|
||||||
custom_repr = build_result
|
custom_repr = build_result
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ CUSTOM_NODES: dict[str, dict[str, frontend_node.base.FrontendNode]] = {
|
||||||
"custom_components": {
|
"custom_components": {
|
||||||
"CustomComponent": frontend_node.custom_components.CustomComponentFrontendNode(),
|
"CustomComponent": frontend_node.custom_components.CustomComponentFrontendNode(),
|
||||||
},
|
},
|
||||||
|
"component": {
|
||||||
|
"Component": frontend_node.custom_components.ComponentFrontendNode(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,3 +67,28 @@ class CustomComponentFrontendNode(FrontendNode):
|
||||||
)
|
)
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
base_classes: list[str] = []
|
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),
|
Input(display_name="Number", name="number", field_type=int),
|
||||||
]
|
]
|
||||||
outputs = [
|
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"),
|
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