📝 (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:
parent
7fb5644a87
commit
daedaae820
12 changed files with 192 additions and 82 deletions
|
|
@ -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."
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ""
|
||||
|
|
|
|||
|
|
@ -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 {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue