From 6ff8b01e9c3773afde11cc69dece26dd3fdda3e5 Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Fri, 14 Jun 2024 11:52:08 -0300 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20(inputs/=5F=5Finit=5F=5F.py):?= =?UTF-8?q?=20Add=20TextInput=20class=20to=20support=20text=20input=20type?= =?UTF-8?q?=20in=20langflow=20inputs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📝 (inputs/inputs.py): Add TextInput class with validation logic for different input types like Data, Message, and Text 📝 (schema/message.py): Add text_key attribute to Message class to specify the key for text data in the message object --- src/backend/base/langflow/inputs/__init__.py | 4 ++- src/backend/base/langflow/inputs/inputs.py | 30 ++++++++++++++++++-- src/backend/base/langflow/schema/message.py | 1 + 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/backend/base/langflow/inputs/__init__.py b/src/backend/base/langflow/inputs/__init__.py index 53927ca0f..6939c470c 100644 --- a/src/backend/base/langflow/inputs/__init__.py +++ b/src/backend/base/langflow/inputs/__init__.py @@ -4,13 +4,14 @@ from .inputs import ( DropdownInput, FileInput, FloatInput, + HandleInput, IntInput, MultilineInput, NestedDictInput, PromptInput, SecretStrInput, StrInput, - HandleInput, + TextInput, ) __all__ = [ @@ -26,4 +27,5 @@ __all__ = [ "PromptInput", "MultilineInput", "HandleInput", + "TextInput", ] diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index c8146b2c2..17d10d78c 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -1,8 +1,10 @@ -from typing import Callable, Optional, Union +from typing import Any, Callable, Optional, Union -from pydantic import Field, model_validator +from pydantic import Field, field_validator, model_validator from langflow.inputs.validators import StrictBoolean +from langflow.schema.data import Data +from langflow.schema.message import Message from .input_mixin import ( BaseInputMixin, @@ -39,6 +41,30 @@ class StrInput(BaseInputMixin, ListableInputMixin, DatabaseLoadMixin): # noqa: """Defines if the field will allow the user to open a text editor. Default is False.""" +class TextInput(StrInput): + input_types: list[str] = ["Data", "Message", "Text"] + + @field_validator("value") + @classmethod + def validate_value(cls, v: Any, _info): + if isinstance(v, str): + return v + elif isinstance(v, Message): + return v.text + elif isinstance(v, Data): + if v.text_key in v.data: + return v.data[v.text_key] + else: + keys = ", ".join(v.data.keys()) + input_name = _info.data["name"] + raise ValueError( + f"The input to '{input_name}' must contain the key '{v.text_key}'." + f"You can set `text_key` to one of the following keys: {keys} or set the value using another Component." + ) + else: + raise ValueError(f"Invalid input type {type(v)}") + + class MultilineInput(BaseInputMixin): field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT multiline: StrictBoolean = True diff --git a/src/backend/base/langflow/schema/message.py b/src/backend/base/langflow/schema/message.py index caa7d95bf..706d337aa 100644 --- a/src/backend/base/langflow/schema/message.py +++ b/src/backend/base/langflow/schema/message.py @@ -17,6 +17,7 @@ def _timestamp_to_str(timestamp: datetime) -> str: class Message(Data): model_config = ConfigDict(arbitrary_types_allowed=True) # Helper class to deal with image data + text_key: str = "text" text: Optional[str | AsyncIterator | Iterator] = Field(default="") sender: str sender_name: str From 2276d050fe483cc0cb883dff749336468e5805bf Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Fri, 14 Jun 2024 11:52:50 -0300 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=94=A7=20(component.py):=20Add=20inpu?= =?UTF-8?q?t=20validation=20to=20Component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📝 (component.py): Update imports and type annotations for better readability and maintainability 🔧 (component.py): Refactor map_inputs method to accept InputTypes and validate inputs in Component class ♻️ (component.py): Refactor _validate_inputs method to check if input is a class method 🔧 (util.py): Add is_class_method function to check if a function is a class method --- .../custom/custom_component/component.py | 42 ++++++++++++------- src/backend/base/langflow/utils/util.py | 7 ++++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index f13d8a0a2..74b5d190f 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -1,25 +1,17 @@ import inspect -from typing import ( - TYPE_CHECKING, - AsyncIterator, - Awaitable, - Callable, - ClassVar, - Generator, - Iterator, - List, - Optional, - Union, -) +from typing import AsyncIterator, Awaitable, Callable, ClassVar, Generator, Iterator, List, Optional, Union from uuid import UUID import yaml +from git import TYPE_CHECKING from loguru import logger from pydantic import BaseModel +from langflow.inputs.inputs import InputTypes from langflow.schema.artifact import get_artifact_type, post_process_raw from langflow.schema.data import Data from langflow.template.field.base import UNDEFINED, Input, Output +from langflow.utils.util import is_class_method from .custom_component import CustomComponent @@ -36,7 +28,6 @@ def recursive_serialize_or_str(obj): elif isinstance(obj, BaseModel): return {k: recursive_serialize_or_str(v) for k, v in obj.model_dump().items()} elif isinstance(obj, (AsyncIterator, Generator, Iterator)): - # Turn it into something readable that does not # contain memory addresses # without consuming the iterator # return list(obj) consumes the iterator @@ -49,13 +40,35 @@ def recursive_serialize_or_str(obj): class Component(CustomComponent): - inputs: Optional[List[Input]] = None + inputs: Optional[List[InputTypes]] = None outputs: Optional[List[Output]] = None code_class_base_inheritance: ClassVar[str] = "Component" _results: dict = {} _arguments: dict = {} + _inputs: dict[str, InputTypes] = {} + + def __init__(self, **data): + super().__init__(**data) + if self.inputs is not None: + self.map_inputs(self.inputs) + + def map_inputs(self, inputs: List[Input]): + self.inputs = inputs + for input_ in inputs: + self._inputs[input_.name] = input_ + + def _validate_inputs(self, params: dict): + # Params keys are the `name` attribute of the Input objects + for key, value in params.items(): + if key not in self._inputs: + raise ValueError(f"Input {key} not found in arguments") + input_ = self._inputs[key] + # validate_inputs must be a classmethod + if hasattr(input_, "validate_value") and is_class_method(func=input_.validate_value, cls=input_): + input_.validate_value(value) def set_attributes(self, params: dict): + self._validate_inputs(params) for key, value in params.items(): if key in self.__dict__: raise ValueError(f"Key {key} already exists in {self.__class__.__name__}") @@ -149,3 +162,4 @@ class Component(CustomComponent): return [field.name for field in inputs] except KeyError: return [] + return [] diff --git a/src/backend/base/langflow/utils/util.py b/src/backend/base/langflow/utils/util.py index cbf1d393b..d58fdc4f8 100644 --- a/src/backend/base/langflow/utils/util.py +++ b/src/backend/base/langflow/utils/util.py @@ -449,3 +449,10 @@ def update_settings( if not store: logger.debug("Setting store to False") settings_service.settings.update_settings(store=False) + + +def is_class_method(func, cls): + """ + Check if a function is a class method. + """ + return inspect.ismethod(func) and func.__self__ is cls.__class__ From 0172a84d6392c472e4a2371eba197c22279eb7da Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Fri, 14 Jun 2024 13:51:04 -0300 Subject: [PATCH 3/3] refactor: Rename TextInput to TextInputComponent for consistency --- src/backend/base/langflow/components/inputs/TextInput.py | 2 +- src/backend/base/langflow/components/inputs/__init__.py | 4 ++-- src/backend/base/langflow/components/outputs/TextOutput.py | 2 +- src/backend/base/langflow/components/outputs/__init__.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/backend/base/langflow/components/inputs/TextInput.py b/src/backend/base/langflow/components/inputs/TextInput.py index 0c759c4e1..d1c8a0c91 100644 --- a/src/backend/base/langflow/components/inputs/TextInput.py +++ b/src/backend/base/langflow/components/inputs/TextInput.py @@ -4,7 +4,7 @@ from langflow.inputs import MultilineInput, StrInput from langflow.template import Output -class TextInput(TextComponent): +class TextInputComponent(TextComponent): display_name = "Text Input" description = "Get text inputs from the Playground." icon = "type" diff --git a/src/backend/base/langflow/components/inputs/__init__.py b/src/backend/base/langflow/components/inputs/__init__.py index c8b4f1645..48fc9a189 100644 --- a/src/backend/base/langflow/components/inputs/__init__.py +++ b/src/backend/base/langflow/components/inputs/__init__.py @@ -1,4 +1,4 @@ from .ChatInput import ChatInput -from .TextInput import TextInput +from .TextInput import TextInputComponent -__all__ = ["ChatInput", "TextInput"] +__all__ = ["ChatInput", "TextInputComponent"] diff --git a/src/backend/base/langflow/components/outputs/TextOutput.py b/src/backend/base/langflow/components/outputs/TextOutput.py index e70cfa12b..f68d55b01 100644 --- a/src/backend/base/langflow/components/outputs/TextOutput.py +++ b/src/backend/base/langflow/components/outputs/TextOutput.py @@ -3,7 +3,7 @@ from langflow.field_typing import Text from langflow.template import Input, Output -class TextOutput(TextComponent): +class TextOutputComponent(TextComponent): display_name = "Text Output" description = "Display a text output in the Playground." icon = "type" diff --git a/src/backend/base/langflow/components/outputs/__init__.py b/src/backend/base/langflow/components/outputs/__init__.py index cf395bb86..35200e0fe 100644 --- a/src/backend/base/langflow/components/outputs/__init__.py +++ b/src/backend/base/langflow/components/outputs/__init__.py @@ -1,4 +1,4 @@ from .ChatOutput import ChatOutput -from .TextOutput import TextOutput +from .TextOutput import TextOutputComponent -__all__ = ["ChatOutput", "TextOutput"] +__all__ = ["ChatOutput", "TextOutputComponent"]