📝 (text.py): Rename CustomComponent to Component for better clarity and consistency

📝 (ChatInput.py, TextInput.py, ChatOutput.py, RecordsOutput.py, TextOutput.py): Refactor code to use Input and Output classes for defining inputs and outputs
📝 (CustomComponent.py): Update method to get the build method for custom components to consider classes inheriting from Component or CustomComponent
📝 (DirectoryReader.py): Remove check for missing build function as it is no longer necessary
📝 (base.py): Add properties to easily access outgoing edges, incoming edges, and source names of edges in a Vertex object

♻️ (types.py): Refactor code to improve readability and maintainability by adding type hinting and organizing imports
📝 (types.py): Add missing documentation and comments to clarify the purpose of methods and classes
♻️ (loading.py): Refactor code to remove redundant code and improve code structure for better maintainability
📝 (loading.py): Add comments to explain the purpose of functions and the flow of the code
♻️ (base.py): Refactor code to improve consistency and readability by updating class names and method calls to match the intended functionality
This commit is contained in:
ogabrielluiz 2024-06-03 13:49:03 -03:00
commit daedaae820
12 changed files with 192 additions and 82 deletions

View file

@ -1,12 +1,12 @@
from typing import Optional
from langflow.custom import CustomComponent
from langflow.custom import Component
from langflow.field_typing import Text
from langflow.helpers.record import records_to_text
from langflow.schema.schema import Record
class TextComponent(CustomComponent):
class TextComponent(Component):
display_name = "Text Component"
description = "Used to pass text to the next component."

View file

@ -21,7 +21,7 @@ class ChatInput(ChatComponent):
]
def text_response(self) -> Text:
result = self.message
result = self.input_value
if self.session_id and isinstance(result, (Record, str)):
self.store_message(result, self.session_id, self.sender, self.sender_name)
return result
@ -29,11 +29,12 @@ class ChatInput(ChatComponent):
def record_response(self) -> Record:
record = Record(
data={
"message": self.message,
"message": self.input_value,
"sender": self.sender,
"sender_name": self.sender_name,
"session_id": self.session_id,
}
},
text_key="message",
)
if self.session_id and isinstance(record, (Record, str)):
self.store_message(record, self.session_id, self.sender, self.sender_name)

View file

@ -1,7 +1,6 @@
from typing import Optional
from langflow.base.io.text import TextComponent
from langflow.field_typing import Text
from langflow.template import Input, Output
class TextInput(TextComponent):
@ -9,24 +8,26 @@ class TextInput(TextComponent):
description = "Get text inputs from the Playground."
icon = "type"
def build_config(self):
return {
"input_value": {
"display_name": "Value",
"input_types": ["Record", "Text"],
"info": "Text or Record to be passed as input.",
},
"record_template": {
"display_name": "Record Template",
"multiline": True,
"info": "Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.",
"advanced": True,
},
}
inputs = [
Input(
name="input_value",
type=str,
display_name="Value",
info="Text or Record to be passed as input.",
input_types=["Record", "Text"],
),
Input(
name="record_template",
type=str,
display_name="Record Template",
multiline=True,
info="Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.",
advanced=True,
),
]
outputs = [
Output(name="Text", method="text_response"),
]
def build(
self,
input_value: Optional[Text] = "",
record_template: Optional[str] = "",
) -> Text:
return super().build(input_value=input_value, record_template=record_template)
def text_response(self) -> Text:
return self.input_value if self.input_value else ""

View file

@ -1,8 +1,7 @@
from typing import Optional, Union
from langflow.base.io.chat import ChatComponent
from langflow.field_typing import Text
from langflow.schema import Record
from langflow.template import Input, Output
class ChatOutput(ChatComponent):
@ -10,20 +9,34 @@ class ChatOutput(ChatComponent):
description = "Display a chat message in the Playground."
icon = "ChatOutput"
def build(
self,
sender: Optional[str] = "Machine",
sender_name: Optional[str] = "AI",
input_value: Optional[str] = None,
session_id: Optional[str] = None,
return_record: Optional[bool] = False,
record_template: Optional[str] = "{text}",
) -> Union[Text, Record]:
return super().build_with_record(
sender=sender,
sender_name=sender_name,
input_value=input_value,
session_id=session_id,
return_record=return_record,
record_template=record_template or "",
inputs = [
Input(name="input_value", type=str, display_name="Message", multiline=True),
Input(name="sender", type=str, display_name="Sender Type", options=["Machine", "AI"]),
Input(name="sender_name", type=str, display_name="Sender Name"),
Input(name="session_id", type=str, display_name="Session ID"),
Input(name="record_template", type=str, display_name="Record Template", default="{text}"),
]
outputs = [
Output(name="Message", method="text_response"),
Output(name="Record", method="record_response"),
]
def text_response(self) -> Text:
result = self.input_value
if self.session_id and isinstance(result, (Record, str)):
self.store_message(result, self.session_id, self.sender, self.sender_name)
return result
def record_response(self) -> Record:
record = Record(
data={
"message": self.input_value,
"sender": self.sender,
"sender_name": self.sender_name,
"session_id": self.session_id,
"template": self.record_template or "",
}
)
if self.session_id and isinstance(record, (Record, str)):
self.store_message(record, self.session_id, self.sender, self.sender_name)
return record

View file

@ -1,10 +1,18 @@
from langflow.custom import CustomComponent
from langflow.custom import Component
from langflow.schema import Record
from langflow.template import Input, Output
class RecordsOutput(CustomComponent):
class RecordsOutput(Component):
display_name = "Records Output"
description = "Display Records as a Table"
def build(self, input_value: Record) -> Record:
return input_value
inputs = [
Input(name="input_value", type=Record, display_name="Record Input"),
]
outputs = [
Output(name="Record", method="record_response"),
]
def record_response(self) -> Record:
return self.input_value

View file

@ -1,7 +1,6 @@
from typing import Optional
from langflow.base.io.text import TextComponent
from langflow.field_typing import Text
from langflow.template import Input, Output
class TextOutput(TextComponent):
@ -9,20 +8,26 @@ class TextOutput(TextComponent):
description = "Display a text output in the Playground."
icon = "type"
def build_config(self):
return {
"input_value": {
"display_name": "Value",
"input_types": ["Record", "Text"],
"info": "Text or Record to be passed as output.",
},
"record_template": {
"display_name": "Record Template",
"multiline": True,
"info": "Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.",
"advanced": True,
},
}
inputs = [
Input(
name="input_value",
type=str,
display_name="Value",
info="Text or Record to be passed as output.",
input_types=["Record", "Text"],
),
Input(
name="record_template",
type=str,
display_name="Record Template",
multiline=True,
info="Template to convert Record to Text. If left empty, it will be dynamically set to the Record's text key.",
advanced=True,
),
]
outputs = [
Output(name="Text", method="text_response"),
]
def build(self, input_value: Optional[Text] = "", record_template: Optional[str] = "") -> Text:
return super().build(input_value=input_value, record_template=record_template)
def text_response(self) -> Text:
return self.input_value if self.input_value else ""

View file

@ -1,10 +1,9 @@
import operator
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, ClassVar, List, Optional, Sequence, Union
from uuid import UUID
import yaml
from cachetools import TTLCache, cachedmethod
from cachetools import TTLCache
from langchain_core.documents import Document
from pydantic import BaseModel
@ -299,7 +298,6 @@ class CustomComponent(BaseComponent):
arg["type"] = "Data"
return args
@cachedmethod(operator.attrgetter("cache"))
def get_method(self, method_name: str):
"""
Gets the build method for the custom component.
@ -310,7 +308,9 @@ class CustomComponent(BaseComponent):
if not self.code:
return {}
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
component_classes = [
cls for cls in self.tree["classes"] if "Component" in cls["bases"] or "CustomComponent" in cls["bases"]
]
if not component_classes:
return {}

View file

@ -301,8 +301,6 @@ class DirectoryReader:
return False, "Empty file"
elif not self.validate_code(file_content):
return False, "Syntax error"
elif not self.validate_build(file_content):
return False, "Missing build function"
elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
"Optional", file_content
):

View file

@ -136,6 +136,18 @@ class Vertex:
def edges(self) -> List["ContractEdge"]:
return self.graph.get_vertex_edges(self.id)
@property
def outgoing_edges(self) -> List["ContractEdge"]:
return [edge for edge in self.edges if edge.source_id == self.id]
@property
def incoming_edges(self) -> List["ContractEdge"]:
return [edge for edge in self.edges if edge.target_id == self.id]
@property
def edges_source_names(self) -> List[str]:
return {edge.source_handle.name for edge in self.edges}
@property
def predecessors(self) -> List["Vertex"]:
return self.graph.get_predecessors(self)

View file

@ -1,19 +1,24 @@
import json
from typing import AsyncIterator, Dict, Iterator, List
from typing import Any, AsyncIterator, Dict, Iterator, List
import yaml
from git import TYPE_CHECKING
from langchain_core.messages import AIMessage
from loguru import logger
from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes
from langflow.graph.utils import UnbuiltObject, serialize_field
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.utils import log_transaction
from langflow.schema import Record
from langflow.schema.schema import INPUT_FIELD_NAME
from langflow.services.monitor.utils import log_vertex_build
from langflow.utils.schemas import ChatOutputResponse, RecordOutputResponse
from langflow.utils.util import unescape_string
if TYPE_CHECKING:
from langflow.graph.edge.base import ContractEdge
class CustomComponentVertex(Vertex):
def __init__(self, data: Dict, graph):
@ -32,10 +37,65 @@ class ComponentVertex(Vertex):
if self.artifacts and "repr" in self.artifacts:
return self.artifacts["repr"] or super()._built_object_repr()
def _update_built_object_and_artifacts(self, result):
"""
Updates the built object and its artifacts.
"""
if isinstance(result, tuple):
if len(result) == 2:
self._built_object, self.artifacts = result
elif len(result) == 3:
self._custom_component, self._built_object, self.artifacts = result
else:
self._built_object = result
class InterfaceVertex(Vertex):
for key, value in self._built_object.items():
self.add_result(key, value)
def get_edge_with_target(self, target_id: str) -> "ContractEdge":
"""
Get the edge with the target id.
Args:
target_id: The target id of the edge.
Returns:
The edge with the target id.
"""
for edge in self.edges:
if edge.target_id == target_id:
return edge
return None
async def _get_result(self, requester: "Vertex") -> Any:
"""
Retrieves the result of the built component.
If the component has not been built yet, a ValueError is raised.
Returns:
The built result if use_result is True, else the built object.
"""
if not self._built:
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="error")
raise ValueError(f"Component {self.display_name} has not been built yet")
if requester is None:
raise ValueError("Requester Vertex is None")
edge = self.get_edge_with_target(requester.id)
if edge is None:
raise ValueError(f"Edge not found between {self.display_name} and {requester.display_name}")
result = self.results[edge.source_handle.name]
log_transaction(source=self, target=requester, flow_id=self.graph.flow_id, status="success")
return result
class InterfaceVertex(ComponentVertex):
def __init__(self, data: Dict, graph):
super().__init__(data, graph=graph, base_type="custom_components", is_task=True)
super().__init__(data, graph=graph)
self.steps = [self._build, self._run]
def build_stream_url(self):

View file

@ -4,13 +4,15 @@ import os
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Type
import orjson
import yaml
from loguru import logger
from pydantic import BaseModel
from langflow.custom.eval import eval_custom_component_code
from langflow.schema.schema import Record
if TYPE_CHECKING:
from langflow.custom import CustomComponent
from langflow.custom import Component, CustomComponent
from langflow.graph.vertex.base import Vertex
@ -32,6 +34,7 @@ async def instantiate_class(
raise ValueError("No base type provided for vertex")
params_copy = params.copy()
# Remove code from params
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
custom_component: "CustomComponent" = class_object(
user_id=user_id,
@ -43,9 +46,9 @@ async def instantiate_class(
custom_component, params_copy, vertex.load_from_db_fields, fallback_to_env_vars
)
if base_type == "custom_components":
return await build_custom_component(params=params, custom_component=custom_component)
return await build_custom_component(params=params_copy, custom_component=custom_component)
elif base_type == "component":
return await build_component(params=params, custom_component=custom_component)
return await build_component(params=params_copy, custom_component=custom_component, vertex=vertex)
else:
raise ValueError(f"Base type {base_type} not found.")
@ -111,7 +114,7 @@ def update_params_with_load_from_db_fields(
async def build_component(
params: dict,
custom_component: "CustomComponent",
custom_component: "Component",
vertex: "Vertex",
):
# Now set the params as attributes of the custom_component
@ -122,7 +125,7 @@ async def build_component(
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:
if not vertex.outgoing_edges or output.name in vertex.edges_source_names:
method: Callable | Awaitable = getattr(custom_component, output.method)
result = method()
# If the method is asynchronous, we need to await it
@ -130,6 +133,15 @@ async def build_component(
result = await result
build_result[output.name] = result
custom_repr = custom_component.custom_repr()
# ! Temporary REPR
# Since all are dict, yaml.dump them
if isinstance(build_result, dict):
_build_result = {
key: value.model_dump() if isinstance(value, BaseModel) else value for key, value in build_result.items()
}
custom_repr = yaml.dump(_build_result)
if custom_repr is None and isinstance(build_result, (dict, Record, str)):
custom_repr = build_result
if not isinstance(custom_repr, str):

View file

@ -124,6 +124,6 @@ class FrontendNode(BaseModel):
if "inputs" not in kwargs:
raise ValueError("Missing 'inputs' argument.")
inputs = kwargs.pop("inputs")
template = Template(type_name="CustomComponent", fields=inputs)
template = Template(type_name="Component", fields=inputs)
kwargs["template"] = template
return cls(**kwargs)