From 3862afc907cd065d6dd27e03d68ec7da767e339b Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Thu, 13 Jun 2024 16:29:00 -0300 Subject: [PATCH 01/12] refactor: Sort fields alphabetically and prioritize fields with DIRECT_TYPES in Template class Sort the fields alphabetically and prioritize fields with DIRECT_TYPES in the Template class. This improves the organization and readability of the code, ensuring that fields with specific types are listed first. --- src/backend/base/langflow/template/template/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/backend/base/langflow/template/template/base.py b/src/backend/base/langflow/template/template/base.py index 72eb53fc2..ef6082cf8 100644 --- a/src/backend/base/langflow/template/template/base.py +++ b/src/backend/base/langflow/template/template/base.py @@ -23,7 +23,9 @@ class Template(BaseModel): # first sort alphabetically # then sort fields so that fields that have .field_type in DIRECT_TYPES are first self.fields.sort(key=lambda x: x.name) - self.fields.sort(key=lambda x: x.field_type in DIRECT_TYPES, reverse=False) + self.fields.sort( + key=lambda x: x.field_type in DIRECT_TYPES if hasattr(x, "field_type") else False, reverse=False + ) @model_serializer(mode="wrap") def serialize_model(self, handler): From b3266bab9c37d66fa2967850cfeb50203a325f4d Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Thu, 13 Jun 2024 16:30:29 -0300 Subject: [PATCH 02/12] refactor: Fix issue with handling None values in ResultData class Fix the issue where None values were not being handled correctly in the ResultData class. This ensures that the code properly handles cases where the message is None, preventing any potential errors or unexpected behavior. --- src/backend/base/langflow/graph/schema.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/base/langflow/graph/schema.py b/src/backend/base/langflow/graph/schema.py index 0e99a5e84..825379800 100644 --- a/src/backend/base/langflow/graph/schema.py +++ b/src/backend/base/langflow/graph/schema.py @@ -35,8 +35,8 @@ class ResultData(BaseModel): message = values["artifacts"][key] # ! Temporary fix - if not isinstance(message, dict): - message = {"message": message} + if message is None: + continue if "stream_url" in message and "type" in message: stream_url = StreamURL(location=message["stream_url"]) From 88f03f0408add83c3812d062829206ce53c2c0be Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Thu, 13 Jun 2024 16:30:38 -0300 Subject: [PATCH 03/12] refactor: Update Vertex class to improve handling of template_dicts inputs Refactor the Vertex class to improve the handling of template_dicts inputs. Instead of using list comprehensions, the code now uses explicit loops to append the required and optional inputs. This change enhances the readability and maintainability of the code. --- .../base/langflow/graph/vertex/base.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 17d364a03..64f64aed9 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -234,16 +234,15 @@ class Vertex: self.has_session_id = "session_id" in template_dicts - self.required_inputs = [ - template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"] - ] - self.optional_inputs = [ - template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"] - ] - # Add the template_dicts[key]["input_types"] to the optional_inputs - self.optional_inputs.extend( - [input_type for value in template_dicts.values() for input_type in value.get("input_types", [])] - ) + self.required_inputs = [] + self.optional_inputs = [] + for value_dict in template_dicts.values(): + list_to_append = self.required_inputs if value_dict.get("required") else self.optional_inputs + + if "type" in value_dict: + list_to_append.append(value_dict["type"]) + elif "input_types" in value_dict: + list_to_append.extend(value_dict["input_types"]) template_dict = self.data["node"]["template"] self.vertex_type = ( From ffae471c7ba64b09204e844bf4af7b4ad4d6eb94 Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Thu, 13 Jun 2024 16:50:57 -0300 Subject: [PATCH 04/12] refactor: Add HandleInput to inputs module --- src/backend/base/langflow/inputs/__init__.py | 4 +++- src/backend/base/langflow/inputs/input_mixin.py | 2 +- src/backend/base/langflow/inputs/inputs.py | 14 +++++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/backend/base/langflow/inputs/__init__.py b/src/backend/base/langflow/inputs/__init__.py index dd227d5db..53927ca0f 100644 --- a/src/backend/base/langflow/inputs/__init__.py +++ b/src/backend/base/langflow/inputs/__init__.py @@ -5,11 +5,12 @@ from .inputs import ( FileInput, FloatInput, IntInput, + MultilineInput, NestedDictInput, PromptInput, SecretStrInput, StrInput, - MultilineInput, + HandleInput, ) __all__ = [ @@ -24,4 +25,5 @@ __all__ = [ "FileInput", "PromptInput", "MultilineInput", + "HandleInput", ] diff --git a/src/backend/base/langflow/inputs/input_mixin.py b/src/backend/base/langflow/inputs/input_mixin.py index 917d1ea65..07e4f6338 100644 --- a/src/backend/base/langflow/inputs/input_mixin.py +++ b/src/backend/base/langflow/inputs/input_mixin.py @@ -25,7 +25,7 @@ SerializableFieldTypes = Annotated[FieldTypes, PlainSerializer(lambda v: v.value class BaseInputMixin(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) - field_type: Optional[SerializableFieldTypes] = Field(default=FieldTypes.TEXT) + field_type: Optional[SerializableFieldTypes | str] = Field(default=FieldTypes.TEXT) required: bool = False """Specifies if the field is required. Defaults to False.""" diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index a59a76984..3342d6322 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -1,6 +1,6 @@ from typing import Callable, Optional, Union -from pydantic import Field +from pydantic import Field, model_validator from langflow.inputs.validators import StrictBoolean @@ -16,6 +16,17 @@ from .input_mixin import ( ) +class HandleInput(BaseInputMixin): + input_types: list[str] = Field(default_factory=list) + field_type: Optional[str] = "" + + @model_validator(mode="after") + def validate_model_type(self): + # FieldType should be a string + self.field_type = " | ".join(self.input_types) + return self + + class PromptInput(BaseInputMixin, ListableInputMixin): field_type: Optional[SerializableFieldTypes] = FieldTypes.PROMPT @@ -83,4 +94,5 @@ InputTypes = Union[ FileInput, PromptInput, MultilineInput, + HandleInput, ] From 96511fef4a8b59954877a744f2e506857f2fa802 Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Thu, 13 Jun 2024 16:51:27 -0300 Subject: [PATCH 05/12] refactor: Improve handling of template_dicts inputs in Vertex class Refactor the Vertex class to enhance the handling of template_dicts inputs. Instead of using list comprehensions, explicit loops are now used to append the required and optional inputs. This change improves the readability and maintainability of the code. --- .../custom/custom_component/component.py | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/backend/base/langflow/custom/custom_component/component.py b/src/backend/base/langflow/custom/custom_component/component.py index 5d0af85c3..f13d8a0a2 100644 --- a/src/backend/base/langflow/custom/custom_component/component.py +++ b/src/backend/base/langflow/custom/custom_component/component.py @@ -98,10 +98,15 @@ class Component(CustomComponent): raw = self.status if hasattr(raw, "data") and raw is not None: raw = raw.data + if raw is None: + raw = custom_repr elif hasattr(raw, "model_dump") and raw is not None: raw = raw.model_dump() - artifact_type = get_artifact_type(self.status, result) + if raw is None and isinstance(result, (dict, Data, str)): + raw = result.data if isinstance(result, Data) else result + + artifact_type = get_artifact_type(self.repr_value or raw, result) raw = post_process_raw(raw, artifact_type) artifact = {"repr": custom_repr, "raw": raw, "type": artifact_type} _artifacts[output.name] = artifact @@ -110,21 +115,15 @@ class Component(CustomComponent): return _results, _artifacts def custom_repr(self): - # ! Temporary REPR - # Since all are dict, yaml.dump them - if isinstance(self._results, dict): - _build_results = recursive_serialize_or_str(self._results) - try: - custom_repr = yaml.dump(_build_results) - except Exception as e: - logger.error(f"Error while dumping build_result: {e}") - custom_repr = str(self._results) - - if custom_repr is None and isinstance(self._results, (dict, Data, str)): - custom_repr = self._results - if not isinstance(custom_repr, str): - custom_repr = str(custom_repr) - return custom_repr + if self.repr_value == "": + self.repr_value = self.status + if isinstance(self.repr_value, dict): + return yaml.dump(self.repr_value) + if isinstance(self.repr_value, str): + return self.repr_value + if isinstance(self.repr_value, BaseModel) and not isinstance(self.repr_value, Data): + return str(self.repr_value) + return self.repr_value def build_inputs(self, user_id: Optional[Union[str, UUID]] = None): """ From c89f87baeab68c651cffe9a40431ea2aa511f393 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 13 Jun 2024 16:59:37 -0300 Subject: [PATCH 06/12] refactor: Update langflow components and constants --- .../base/langflow/components/data/URL.py | 35 +++++++++- .../components/experimental/TextOperator.py | 68 ++++++++++--------- .../components/helpers/CombineText.py | 47 +++++++------ .../helpers/CombineTextsUnsorted.py | 25 ------- .../langflow/components/helpers/DataToText.py | 36 ---------- .../{DocumentToData.py => DocumentsToData.py} | 5 +- .../langflow/components/helpers/FilterData.py | 28 +++++--- .../langflow/components/helpers/ParseData.py | 34 ++++++++++ .../langflow/components/helpers/__init__.py | 8 +-- src/backend/base/langflow/inputs/inputs.py | 1 + 10 files changed, 158 insertions(+), 129 deletions(-) delete mode 100644 src/backend/base/langflow/components/helpers/CombineTextsUnsorted.py delete mode 100644 src/backend/base/langflow/components/helpers/DataToText.py rename src/backend/base/langflow/components/helpers/{DocumentToData.py => DocumentsToData.py} (83%) create mode 100644 src/backend/base/langflow/components/helpers/ParseData.py diff --git a/src/backend/base/langflow/components/data/URL.py b/src/backend/base/langflow/components/data/URL.py index 6f7a5391a..d27d17fe6 100644 --- a/src/backend/base/langflow/components/data/URL.py +++ b/src/backend/base/langflow/components/data/URL.py @@ -5,6 +5,8 @@ from langflow.inputs import StrInput from langflow.schema import Data from langflow.template import Output +import re + class URLComponent(Component): display_name = "URL" @@ -25,8 +27,39 @@ class URLComponent(Component): Output(display_name="Data", name="data", method="fetch_content"), ] + def ensure_url(self, string: str) -> str: + """ + Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'. + Raises an error if the string is not a valid URL. + + Parameters: + string (str): The string to be checked and possibly modified. + + Returns: + str: The modified string that is ensured to be a URL. + + Raises: + ValueError: If the string is not a valid URL. + """ + if not string.startswith(("http://", "https://")): + string = "http://" + string + + # Basic URL validation regex + url_regex = re.compile( + r"^(http://|https://)?" # http:// or https:// + r"(([a-zA-Z0-9\.-]+)" # domain + r"(\.[a-zA-Z]{2,}))" # top-level domain + r"(:[0-9]{1,5})?" # optional port + r"(\/.*)?$" # optional path + ) + + if not re.match(url_regex, string): + raise ValueError(f"Invalid URL: {string}") + + return string + def fetch_content(self) -> Data: - urls = [url.strip() for url in self.urls if url.strip()] + urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()] loader = WebBaseLoader(web_paths=urls) docs = loader.load() data = [Data(content=doc.page_content, **doc.metadata) for doc in docs] diff --git a/src/backend/base/langflow/components/experimental/TextOperator.py b/src/backend/base/langflow/components/experimental/TextOperator.py index 73b472acf..62c7a8a1c 100644 --- a/src/backend/base/langflow/components/experimental/TextOperator.py +++ b/src/backend/base/langflow/components/experimental/TextOperator.py @@ -1,69 +1,71 @@ -from typing import Union - from langflow.custom import Component from langflow.field_typing import Text -from langflow.schema import Data -from langflow.template import Input, Output +from langflow.inputs import BoolInput, DropdownInput, StrInput +from langflow.template import Output class TextOperatorComponent(Component): display_name = "Text Operator" description = "Compares two text inputs based on a specified condition such as equality or inequality, with optional case sensitivity." + icon = "equal" inputs = [ - Input(name="input_text", type=str, display_name="Input Text", info="The primary text input for the operation."), - Input(name="match_text", type=str, display_name="Match Text", info="The text input to compare against."), - Input( - name="operator", - type=str, - display_name="Operator", - info="The operator to apply for comparing the texts.", - options=["equals", "not equals", "contains", "starts with", "ends with", "exists"], + StrInput( + name="input_text", + display_name="Input Text", + info="The primary text input for the operation.", ), - Input( + StrInput( + name="match_text", + display_name="Match Text", + info="The text input to compare against.", + ), + DropdownInput( + name="operator", + display_name="Operator", + options=["equals", "not equals", "contains", "starts with", "ends with"], + info="The operator to apply for comparing the texts.", + ), + BoolInput( name="case_sensitive", - type=bool, display_name="Case Sensitive", info="If true, the comparison will be case sensitive.", - default=False, + value=False, + advanced=True, ), - Input( + StrInput( name="true_output", - type=Union[str, Data], display_name="True Output", info="The output to return or display when the comparison is true.", - input_types=["Text", "Data"], + advanced=True, ), - Input( + StrInput( name="false_output", - type=Union[str, Data], display_name="False Output", info="The output to return or display when the comparison is false.", - input_types=["Text", "Data"], + advanced=True, ), ] + outputs = [ - Output(display_name="True Result", name="true_result", method="result_response"), - Output(display_name="False Result", name="false_result", method="result_response"), + Output(display_name="True Result", name="true_result", method="true_response"), + Output(display_name="False Result", name="false_result", method="false_response"), ] - def true_response(self) -> Union[Text, Data]: - self.stop("False Result") - return self.true_output if self.true_output else self.input_text + def true_response(self) -> Text: + self.stop("false_result") + return self.true_output or self.input_text - def false_response(self) -> Union[Text, Data]: - self.stop("True Result") - return self.false_output if self.false_output else self.input_text + def false_response(self) -> Text: + self.stop("true_result") + return self.false_output or self.input_text - def result_response(self) -> Union[Text, Data]: + def run(self) -> Text: input_text = self.input_text match_text = self.match_text operator = self.operator case_sensitive = self.case_sensitive - if not input_text or not match_text: - raise ValueError("Both 'input_text' and 'match_text' must be provided and non-empty.") - if not case_sensitive: input_text = input_text.lower() match_text = match_text.lower() diff --git a/src/backend/base/langflow/components/helpers/CombineText.py b/src/backend/base/langflow/components/helpers/CombineText.py index bedc4293d..641ab54ae 100644 --- a/src/backend/base/langflow/components/helpers/CombineText.py +++ b/src/backend/base/langflow/components/helpers/CombineText.py @@ -1,29 +1,38 @@ -from langflow.custom import CustomComponent +from langflow.custom import Component from langflow.field_typing import Text +from langflow.inputs import StrInput +from langflow.template import Output -class CombineTextComponent(CustomComponent): +class CombineTextComponent(Component): display_name = "Combine Text" description = "Concatenate two text sources into a single text chunk using a specified delimiter." icon = "merge" - def build_config(self): - return { - "text1": { - "display_name": "First Text", - "info": "The first text input to concatenate.", - }, - "text2": { - "display_name": "Second Text", - "info": "The second text input to concatenate.", - }, - "delimiter": { - "display_name": "Delimiter", - "info": "A string used to separate the two text inputs. Defaults to a whitespace.", - }, - } + inputs = [ + StrInput( + name="text1", + display_name="First Text", + info="The first text input to concatenate.", + ), + StrInput( + name="text2", + display_name="Second Text", + info="The second text input to concatenate.", + ), + StrInput( + name="delimiter", + display_name="Delimiter", + info="A string used to separate the two text inputs. Defaults to a whitespace.", + default=" ", + ), + ] - def build(self, text1: str, text2: str, delimiter: str = " ") -> Text: - combined = delimiter.join([text1, text2]) + outputs = [ + Output(display_name="Combined Text", name="combined_text", method="combine_texts"), + ] + + def combine_texts(self) -> Text: + combined = self.delimiter.join([self.text1, self.text2]) self.status = combined return combined diff --git a/src/backend/base/langflow/components/helpers/CombineTextsUnsorted.py b/src/backend/base/langflow/components/helpers/CombineTextsUnsorted.py deleted file mode 100644 index 67d315739..000000000 --- a/src/backend/base/langflow/components/helpers/CombineTextsUnsorted.py +++ /dev/null @@ -1,25 +0,0 @@ -from langflow.custom import CustomComponent -from langflow.field_typing import Text - - -class CombineTextsUnsortedComponent(CustomComponent): - display_name = "Combine Texts (Unsorted)" - description = "Concatenate text sources into a single text chunk using a specified delimiter." - icon = "merge" - - def build_config(self): - return { - "texts": { - "display_name": "Texts", - "info": "The first text input to concatenate.", - }, - "delimiter": { - "display_name": "Delimiter", - "info": "A string used to separate the two text inputs. Defaults to a whitespace.", - }, - } - - def build(self, texts: list[str], delimiter: str = " ") -> Text: - combined = delimiter.join(texts) - self.status = combined - return combined diff --git a/src/backend/base/langflow/components/helpers/DataToText.py b/src/backend/base/langflow/components/helpers/DataToText.py deleted file mode 100644 index 5bd688e9d..000000000 --- a/src/backend/base/langflow/components/helpers/DataToText.py +++ /dev/null @@ -1,36 +0,0 @@ -from langflow.custom import CustomComponent -from langflow.field_typing import Text -from langflow.helpers.data import data_to_text -from langflow.schema import Data - - -class DataToTextComponent(CustomComponent): - display_name = "Data To Text" - description = "Convert Data into plain text following a specified template." - - def build_config(self): - return { - "data": { - "display_name": "Data", - "info": "The data to convert to text.", - }, - "template": { - "display_name": "Template", - "info": "The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.", - "multiline": True, - }, - } - - def build( - self, - data: list[Data], - template: str = "Text: {text}\nData: {data}", - ) -> Text: - if not data: - return "" - if isinstance(data, Data): - data = [data] - - result_string = data_to_text(template, data) - self.status = result_string - return result_string diff --git a/src/backend/base/langflow/components/helpers/DocumentToData.py b/src/backend/base/langflow/components/helpers/DocumentsToData.py similarity index 83% rename from src/backend/base/langflow/components/helpers/DocumentToData.py rename to src/backend/base/langflow/components/helpers/DocumentsToData.py index 44ba079a0..13111db59 100644 --- a/src/backend/base/langflow/components/helpers/DocumentToData.py +++ b/src/backend/base/langflow/components/helpers/DocumentsToData.py @@ -6,9 +6,10 @@ from langflow.custom import CustomComponent from langflow.schema import Data -class DocumentToDataComponent(CustomComponent): - display_name = "Documents To Data" +class DocumentsToDataComponent(CustomComponent): + display_name = "Documents ⇢ Data" description = "Convert LangChain Documents into Data." + icon = "LangChain" field_config = { "documents": {"display_name": "Documents"}, diff --git a/src/backend/base/langflow/components/helpers/FilterData.py b/src/backend/base/langflow/components/helpers/FilterData.py index db402502d..3d7cdbcec 100644 --- a/src/backend/base/langflow/components/helpers/FilterData.py +++ b/src/backend/base/langflow/components/helpers/FilterData.py @@ -1,20 +1,28 @@ from typing import List from langflow.custom import Component -from langflow.inputs import StrInput +from langflow.inputs import StrInput, HandleInput from langflow.schema import Data -from langflow.template import Input, Output +from langflow.template import Output class FilterDataComponent(Component): - display_name = "Filter Message" - description = "Filters a Message object based on a list of strings." + display_name = "Filter Data" + description = "Filters a Data object based on a list of keys." icon = "filter" inputs = [ - Input(name="message", display_name="Message", info="Message object to filter.", input_types=["Message"]), + HandleInput( + name="data", + display_name="Data", + info="Data object to filter.", + input_types=["Message", "Data"], + ), StrInput( - name="filter_criteria", display_name="Filter Criteria", info="List of strings to filter by.", is_list=True + name="filter_criteria", + display_name="Filter Criteria", + info="List of keys to filter by.", + is_list=True, ), ] @@ -24,10 +32,12 @@ class FilterDataComponent(Component): def filter_data(self) -> Data: filter_criteria: List[str] = self.filter_criteria + data = self.data.data if isinstance(self.data, Data) else {} # Filter the data - filtered = {key: value for key, value in self.message.data.items() if key == filter_criteria} + filtered = {key: value for key, value in data.items() if key in filter_criteria} # Create a new Data object with the filtered data - self.status = filtered - return filtered + filtered_data = Data(data=filtered) + self.status = filtered_data + return filtered_data diff --git a/src/backend/base/langflow/components/helpers/ParseData.py b/src/backend/base/langflow/components/helpers/ParseData.py new file mode 100644 index 000000000..41cd669fe --- /dev/null +++ b/src/backend/base/langflow/components/helpers/ParseData.py @@ -0,0 +1,34 @@ +from langflow.custom import Component +from langflow.helpers.data import data_to_text +from langflow.field_typing import Text +from langflow.inputs import MultilineInput, HandleInput +from langflow.template import Output + + +class ParseDataComponent(Component): + display_name = "Parse Data" + description = "Convert Data into plain text following a specified template." + icon = "braces" + + inputs = [ + HandleInput( + name="data", display_name="Data", info="The data to convert to text.", input_types=["Message", "Data"] + ), + MultilineInput( + name="template", + display_name="Template", + info="The template to use for formatting the data. It can contain the keys {text}, {data} or any other key in the Data.", + ), + ] + + outputs = [ + Output(display_name="Text", name="text", method="parse_data_to_text"), + ] + + def parse_data_to_text(self) -> Text: + data = self.data if isinstance(self.data, list) else [self.data] + template = self.template or "Text: {text}" + + result_string = data_to_text(template, data) + self.status = result_string + return result_string diff --git a/src/backend/base/langflow/components/helpers/__init__.py b/src/backend/base/langflow/components/helpers/__init__.py index ee7ed977c..c10b83dd9 100644 --- a/src/backend/base/langflow/components/helpers/__init__.py +++ b/src/backend/base/langflow/components/helpers/__init__.py @@ -1,15 +1,15 @@ from .CreateData import CreateDataComponent from .CustomComponent import Component -from .DataToText import DataToTextComponent -from .DocumentToData import DocumentToDataComponent +from .ParseData import ParseDataComponent +from .DocumentToData import DocumentsToDataComponent from .IDGenerator import UUIDGeneratorComponent from .UpdateData import UpdateDataComponent __all__ = [ "Component", "UpdateDataComponent", - "DocumentToDataComponent", + "DocumentsToDataComponent", "UUIDGeneratorComponent", - "DataToTextComponent", + "ParseDataComponent", "CreateDataComponent", ] diff --git a/src/backend/base/langflow/inputs/inputs.py b/src/backend/base/langflow/inputs/inputs.py index 3342d6322..c8146b2c2 100644 --- a/src/backend/base/langflow/inputs/inputs.py +++ b/src/backend/base/langflow/inputs/inputs.py @@ -35,6 +35,7 @@ class PromptInput(BaseInputMixin, ListableInputMixin): class StrInput(BaseInputMixin, ListableInputMixin, DatabaseLoadMixin): # noqa: F821 field_type: Optional[SerializableFieldTypes] = FieldTypes.TEXT load_from_db: StrictBoolean = False + input_types: list[str] = ["Text"] """Defines if the field will allow the user to open a text editor. Default is False.""" From f821036f3ed0c8a55a80c6b39ed4b10182f15859 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Thu, 13 Jun 2024 16:58:03 -0300 Subject: [PATCH 07/12] Fixed hidden outputs dropdown maintaining state even if every output is shown --- src/frontend/src/CustomNodes/GenericNode/index.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 537d49e85..ed4471e8c 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -302,6 +302,12 @@ export default function GenericNode({ ); }; + useEffect(() => { + if (hiddenOutputs && hiddenOutputs.length == 0) { + setShowHiddenOutputs(false); + } + }, [hiddenOutputs]); + const memoizedNodeToolbarComponent = useMemo(() => { return ( From 3dbc648f007abc1aec115901582ca68ffc997933 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Thu, 13 Jun 2024 16:59:51 -0300 Subject: [PATCH 08/12] Changed Reload Components to Refresh All --- .../components/headerComponent/components/menuBar/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx index 3865b9975..426c7dd2f 100644 --- a/src/frontend/src/components/headerComponent/components/menuBar/index.tsx +++ b/src/frontend/src/components/headerComponent/components/menuBar/index.tsx @@ -123,7 +123,7 @@ export const MenuBar = ({}: {}): JSX.Element => { title: UPLOAD_ERROR_ALERT, list: [error], }); - } + }, ); }} > @@ -193,7 +193,7 @@ export const MenuBar = ({}: {}): JSX.Element => { name="RefreshCcw" className="header-menu-options" /> - Reload Components + Refresh All @@ -221,7 +221,7 @@ export const MenuBar = ({}: {}): JSX.Element => { name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"} className={cn( "h-4 w-4", - isBuilding || saveLoading ? "animate-spin" : "animate-wiggle" + isBuilding || saveLoading ? "animate-spin" : "animate-wiggle", )} /> {printByBuildStatus()} From 32170a79ae91494de4c525735e2ad49f5efef125 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Thu, 13 Jun 2024 17:07:41 -0300 Subject: [PATCH 09/12] refactor: Update TextOperatorComponent to handle default outputs Update the TextOperatorComponent to handle default outputs when the true_output or false_output is not passed. If not provided, the default output will be the input text. This change improves the flexibility and usability of the component. --- .../langflow/components/experimental/TextOperator.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/backend/base/langflow/components/experimental/TextOperator.py b/src/backend/base/langflow/components/experimental/TextOperator.py index 62c7a8a1c..68f6ea97e 100644 --- a/src/backend/base/langflow/components/experimental/TextOperator.py +++ b/src/backend/base/langflow/components/experimental/TextOperator.py @@ -25,6 +25,7 @@ class TextOperatorComponent(Component): display_name="Operator", options=["equals", "not equals", "contains", "starts with", "ends with"], info="The operator to apply for comparing the texts.", + value="equals", ), BoolInput( name="case_sensitive", @@ -36,13 +37,13 @@ class TextOperatorComponent(Component): StrInput( name="true_output", display_name="True Output", - info="The output to return or display when the comparison is true.", + info="The output to return or display when the comparison is true. If not passed, defaults to Input Text.", advanced=True, ), StrInput( name="false_output", display_name="False Output", - info="The output to return or display when the comparison is false.", + info="The output to return or display when the comparison is false. If not passed, defaults to Input Text.", advanced=True, ), ] @@ -54,11 +55,11 @@ class TextOperatorComponent(Component): def true_response(self) -> Text: self.stop("false_result") - return self.true_output or self.input_text + return self.true_output def false_response(self) -> Text: self.stop("true_result") - return self.false_output or self.input_text + return self.false_output def run(self) -> Text: input_text = self.input_text From 7d7e6412602b71e88f82d2e1b49f680b824cd91b Mon Sep 17 00:00:00 2001 From: ogabrielluiz Date: Thu, 13 Jun 2024 17:44:41 -0300 Subject: [PATCH 10/12] refactor: Improve handling of template_dicts inputs in Vertex class --- src/backend/base/langflow/graph/vertex/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 64f64aed9..bb0ddce23 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -241,7 +241,7 @@ class Vertex: if "type" in value_dict: list_to_append.append(value_dict["type"]) - elif "input_types" in value_dict: + if "input_types" in value_dict: list_to_append.extend(value_dict["input_types"]) template_dict = self.data["node"]["template"] From 505ca314670c7cdf27e04600cc0796c01f8b932b Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 14 Jun 2024 01:09:31 -0300 Subject: [PATCH 11/12] refactor: Update TextOperatorComponent to handle default outputs --- .../components/experimental/TextOperator.py | 49 +++++++++---------- src/frontend/src/utils/styleUtils.ts | 8 +-- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/src/backend/base/langflow/components/experimental/TextOperator.py b/src/backend/base/langflow/components/experimental/TextOperator.py index 68f6ea97e..84436e23b 100644 --- a/src/backend/base/langflow/components/experimental/TextOperator.py +++ b/src/backend/base/langflow/components/experimental/TextOperator.py @@ -25,7 +25,6 @@ class TextOperatorComponent(Component): display_name="Operator", options=["equals", "not equals", "contains", "starts with", "ends with"], info="The operator to apply for comparing the texts.", - value="equals", ), BoolInput( name="case_sensitive", @@ -37,13 +36,13 @@ class TextOperatorComponent(Component): StrInput( name="true_output", display_name="True Output", - info="The output to return or display when the comparison is true. If not passed, defaults to Input Text.", + info="The output to return or display when the comparison is true.", advanced=True, ), StrInput( name="false_output", display_name="False Output", - info="The output to return or display when the comparison is false. If not passed, defaults to Input Text.", + info="The output to return or display when the comparison is false.", advanced=True, ), ] @@ -53,41 +52,41 @@ class TextOperatorComponent(Component): Output(display_name="False Result", name="false_result", method="false_response"), ] - def true_response(self) -> Text: - self.stop("false_result") - return self.true_output - - def false_response(self) -> Text: - self.stop("true_result") - return self.false_output - - def run(self) -> Text: - input_text = self.input_text - match_text = self.match_text - operator = self.operator - case_sensitive = self.case_sensitive - + def evaluate_condition(self, input_text: str, match_text: str, operator: str, case_sensitive: bool) -> bool: if not case_sensitive: input_text = input_text.lower() match_text = match_text.lower() - result = False if operator == "equals": - result = input_text == match_text + return input_text == match_text elif operator == "not equals": - result = input_text != match_text + return input_text != match_text elif operator == "contains": - result = match_text in input_text + return match_text in input_text elif operator == "starts with": - result = input_text.startswith(match_text) + return input_text.startswith(match_text) elif operator == "ends with": - result = input_text.endswith(match_text) + return input_text.endswith(match_text) + return False + def true_response(self) -> Text: + result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive) if result: - response = self.true_response() + self.stop("false_result") + response = self.true_output if self.true_output else self.input_text self.status = response return response else: - response = self.false_response() + self.stop("true_result") + return "" + + def false_response(self) -> Text: + result = self.evaluate_condition(self.input_text, self.match_text, self.operator, self.case_sensitive) + if not result: + self.stop("true_result") + response = self.false_output if self.false_output else self.input_text self.status = response return response + else: + self.stop("false_result") + return "" diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 21c8092a8..fff15809c 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -244,7 +244,7 @@ export const nodeColors: { [char: string]: string } = { outputs: "#AA2411", data: "#198BF6", prompts: "#4367BF", - models: "#6344BE", + models: "#ab11ab", model_specs: "#6344BE", chains: "#FE7500", Document: "#7AAE42", @@ -271,10 +271,10 @@ export const nodeColors: { [char: string]: string } = { Text: "#4367BF", retrievers: "#e6b25a", unknown: "#9CA3AF", - custom_components: "#ab11ab", - Data: "#31a3cc", - Data: "#31a3cc", + // custom_components: "#ab11ab", + Data: "#9CA3AF", Message: "#4367BF", + BaseLanguageModel: "#ab11ab", }; export const nodeNames: { [char: string]: string } = { From 750624f097b9b25c732b3a2ba0c446cac17783a4 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Fri, 14 Jun 2024 01:12:07 -0300 Subject: [PATCH 12/12] refactor: Update max_tokens default value in AnthropicLLM classes --- .../base/langflow/components/model_specs/AnthropicLLMSpecs.py | 2 +- .../base/langflow/components/model_specs/ChatAnthropicSpecs.py | 2 +- src/backend/base/langflow/components/models/AnthropicModel.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/base/langflow/components/model_specs/AnthropicLLMSpecs.py b/src/backend/base/langflow/components/model_specs/AnthropicLLMSpecs.py index 786d558bb..1c97c1e28 100644 --- a/src/backend/base/langflow/components/model_specs/AnthropicLLMSpecs.py +++ b/src/backend/base/langflow/components/model_specs/AnthropicLLMSpecs.py @@ -54,7 +54,7 @@ class ChatAntropicSpecsComponent(CustomComponent): self, model: str, anthropic_api_key: Optional[str] = None, - max_tokens: Optional[int] = None, + max_tokens: Optional[int] = 1000, temperature: Optional[float] = None, api_endpoint: Optional[str] = None, ) -> BaseLanguageModel: diff --git a/src/backend/base/langflow/components/model_specs/ChatAnthropicSpecs.py b/src/backend/base/langflow/components/model_specs/ChatAnthropicSpecs.py index 7e4000d9b..ce01320eb 100644 --- a/src/backend/base/langflow/components/model_specs/ChatAnthropicSpecs.py +++ b/src/backend/base/langflow/components/model_specs/ChatAnthropicSpecs.py @@ -66,7 +66,7 @@ class AnthropicLLM(CustomComponent): self, model: str, anthropic_api_key: Optional[str] = None, - max_tokens: Optional[int] = None, + max_tokens: Optional[int] = 1000, temperature: Optional[float] = None, anthropic_api_url: Optional[str] = None, ) -> BaseLanguageModel: diff --git a/src/backend/base/langflow/components/models/AnthropicModel.py b/src/backend/base/langflow/components/models/AnthropicModel.py index 4a7e1a330..f4695418b 100644 --- a/src/backend/base/langflow/components/models/AnthropicModel.py +++ b/src/backend/base/langflow/components/models/AnthropicModel.py @@ -82,7 +82,7 @@ class AnthropicLLM(LCModelComponent): input_value: Text, system_message: Optional[str] = None, anthropic_api_key: Optional[str] = None, - max_tokens: Optional[int] = None, + max_tokens: Optional[int] = 1000, temperature: Optional[float] = None, anthropic_api_url: Optional[str] = None, stream: bool = False,