From 6da73895be74eddb3a73c9c02cafa3f0adf13e7c Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 11:17:39 -0300 Subject: [PATCH 1/8] Fix imports and add RangeSpec class --- src/backend/langflow/field_typing/__init__.py | 2 ++ .../langflow/field_typing/range_spec.py | 21 +++++++++++++++++++ src/backend/langflow/interface/types.py | 6 ++++++ src/backend/langflow/template/field/base.py | 9 ++++++++ 4 files changed, 38 insertions(+) create mode 100644 src/backend/langflow/field_typing/range_spec.py diff --git a/src/backend/langflow/field_typing/__init__.py b/src/backend/langflow/field_typing/__init__.py index 3ae912117..f25f1f840 100644 --- a/src/backend/langflow/field_typing/__init__.py +++ b/src/backend/langflow/field_typing/__init__.py @@ -22,6 +22,7 @@ from .constants import ( Tool, VectorStore, ) +from .range_spec import RangeSpec __all__ = [ "NestedDict", @@ -46,4 +47,5 @@ __all__ = [ "BasePromptTemplate", "ChatPromptTemplate", "Prompt", + "RangeSpec", ] diff --git a/src/backend/langflow/field_typing/range_spec.py b/src/backend/langflow/field_typing/range_spec.py new file mode 100644 index 000000000..036d21fa9 --- /dev/null +++ b/src/backend/langflow/field_typing/range_spec.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel, field_validator + + +class RangeSpec(BaseModel): + min: float = -1.0 + max: float = 1.0 + step: float = 0.1 + + @field_validator("max") + @classmethod + def max_must_be_greater_than_min(cls, v, values, **kwargs): + if "min" in values.data and v <= values.data["min"]: + raise ValueError("max must be greater than min") + return v + + @field_validator("step") + @classmethod + def step_must_be_positive(cls, v): + if v <= 0: + raise ValueError("step must be positive") + return v diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index c508816a0..175968994 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -10,6 +10,7 @@ from cachetools import LRUCache, cached from fastapi import HTTPException from loguru import logger +from langflow.field_typing.range_spec import RangeSpec from langflow.interface.agents.base import agent_creator from langflow.interface.chains.base import chain_creator from langflow.interface.custom.custom_component import CustomComponent @@ -256,6 +257,11 @@ def update_field_dict(field_dict): field_dict["value"] = field_dict["value"](field_dict.get("options", [])) field_dict["refresh"] = True + # Let's check if "range_spec" is a RangeSpec object + if "range_spec" in field_dict and isinstance(field_dict["range_spec"], RangeSpec): + field_dict["range_spec"] = field_dict["range_spec"].model_dump() + field_dict["refresh"] = True + def add_extra_fields(frontend_node, field_config, function_args): """Add extra fields to the frontend node""" diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index 751b72af0..1eb7ca67b 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -3,6 +3,8 @@ from typing import Any, Optional, Union from pydantic import BaseModel +from langflow.field_typing.range_spec import RangeSpec + class TemplateFieldCreator(BaseModel, ABC): field_type: str = "str" @@ -59,6 +61,9 @@ class TemplateFieldCreator(BaseModel, ABC): refresh: Optional[bool] = None """Specifies if the field should be refreshed. Defaults to False.""" + range_spec: Optional[RangeSpec] = None + """Range specification for the field. Defaults to None.""" + def to_dict(self): result = self.model_dump() # Remove key if it is None @@ -73,6 +78,10 @@ class TemplateFieldCreator(BaseModel, ABC): if self.field_type == "file": result["file_path"] = self.file_path + + # If type is float but range_spec is not set, set it to default + if self.field_type == "float" and not self.range_spec: + result["range_spec"] = RangeSpec().model_dump() return result From 154205b0cdf63a3d9f3bf6f1132398b95b58a379 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 11:18:25 -0300 Subject: [PATCH 2/8] Add rangeSpec to FloatComponent --- .../components/parameterComponent/index.tsx | 1 + .../src/components/codeTabsComponent/index.tsx | 5 +++++ .../src/components/floatComponent/index.tsx | 13 +++++++++---- .../src/components/intComponent/index.tsx | 4 ++-- src/frontend/src/modals/EditNodeModal/index.tsx | 4 ++++ src/frontend/src/types/components/index.ts | 16 ++++++++++++++++ 6 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 160f8c051..5a5de77d9 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -457,6 +457,7 @@ export default function ParameterComponent({ diff --git a/src/frontend/src/components/codeTabsComponent/index.tsx b/src/frontend/src/components/codeTabsComponent/index.tsx index 2ff5bd591..bf9f4ff74 100644 --- a/src/frontend/src/components/codeTabsComponent/index.tsx +++ b/src/frontend/src/components/codeTabsComponent/index.tsx @@ -472,6 +472,11 @@ export default function CodeTabsComponent({ templateField ].value } + rangeSpec={ + node.data.node.template[ + templateField + ].rangeSpec + } onChange={(target) => { setData((old) => { let newInputList = diff --git a/src/frontend/src/components/floatComponent/index.tsx b/src/frontend/src/components/floatComponent/index.tsx index 7e57361f0..9b3376499 100644 --- a/src/frontend/src/components/floatComponent/index.tsx +++ b/src/frontend/src/components/floatComponent/index.tsx @@ -7,11 +7,14 @@ export default function FloatComponent({ value, onChange, disabled, + rangeSpec, editNode = false, }: FloatComponentType): JSX.Element { - const step = 0.1; - const min = -2; - const max = 2; + const step = rangeSpec?.step ?? 0.1; + const min = rangeSpec?.min ?? -2; + const max = rangeSpec?.max ?? 2; + console.log("FloatComponent", value, disabled, rangeSpec, editNode); + console.log("FloatComponent", step, min, max); // Clear component state useEffect(() => { @@ -40,7 +43,9 @@ export default function FloatComponent({ disabled={disabled} className={editNode ? "input-edit-node" : ""} placeholder={ - editNode ? "Number -2 to 2" : "Type a number from minus two to two" + editNode + ? `Enter a value between ${min} and ${max}` + : `Enter a value between ${min} and ${max}` } onChange={(event) => { onChange(event.target.value); diff --git a/src/frontend/src/components/intComponent/index.tsx b/src/frontend/src/components/intComponent/index.tsx index 56fec347e..633d7bd8d 100644 --- a/src/frontend/src/components/intComponent/index.tsx +++ b/src/frontend/src/components/intComponent/index.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { FloatComponentType } from "../../types/components"; +import { IntComponentType } from "../../types/components"; import { handleKeyDown } from "../../utils/reactflowUtils"; import { Input } from "../ui/input"; @@ -9,7 +9,7 @@ export default function IntComponent({ disabled, editNode = false, id = "", -}: FloatComponentType): JSX.Element { +}: IntComponentType): JSX.Element { const min = 0; // Clear component state diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index e48816f5e..d7ceb53f2 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -360,6 +360,10 @@ const EditNodeModal = forwardRef( void; + editNode?: boolean; + id?: string; +}; + export type FloatComponentType = { value: string; disabled?: boolean; onChange: (value: string) => void; + rangeSpec: RangeSpecType; editNode?: boolean; id?: string; }; From 6d8d0cbe736cbced5c834933412e0751af4bf74f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 11:21:28 -0300 Subject: [PATCH 3/8] Add range_spec field to CustomComponent class --- docs/docs/components/custom.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/components/custom.mdx b/docs/docs/components/custom.mdx index 90282a73e..f41c5f845 100644 --- a/docs/docs/components/custom.mdx +++ b/docs/docs/components/custom.mdx @@ -73,7 +73,7 @@ The CustomComponent class serves as the foundation for creating custom component | _`required: bool`_ | Makes the field required. | | _`info: str`_ | Adds a tooltip to the field. | | _`file_types: List[str]`_ | This is a requirement if the _`field_type`_ is _file_. Defines which file types will be accepted. For example, _json_, _yaml_ or _yml_. | - + | _`range_spec: langflow.field_typing.RangeSpec`_ | This is a requirement if the _`field_type`_ is _`float`_. Defines the range of values accepted and the step size. If none is defined, the default is _`[-1, 1, 0.1]`_. | - The CustomComponent class also provides helpful methods for specific tasks (e.g., to load and use other flows from the Langflow platform): | Method Name | Description | From 6ea7454fe314d4d99216e14ac2c7fab8073a7011 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 11:41:01 -0300 Subject: [PATCH 4/8] Add range_spec to llms_template.py --- tests/test_llms_template.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_llms_template.py b/tests/test_llms_template.py index 494c1cc90..90e50dacd 100644 --- a/tests/test_llms_template.py +++ b/tests/test_llms_template.py @@ -84,6 +84,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, } assert template["max_tokens"] == { "required": False, @@ -112,6 +113,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, } assert template["frequency_penalty"] == { "required": False, @@ -126,6 +128,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, } assert template["presence_penalty"] == { "required": False, @@ -140,6 +143,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, } assert template["n"] == { "required": False, @@ -223,6 +227,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, } assert template["logit_bias"] == { "required": False, @@ -338,6 +343,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, } assert template["model_kwargs"] == { "required": False, @@ -379,6 +385,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, } assert template["max_retries"] == { "required": False, From 83392fe3b72a915a34774346150d6109bb2ba8af Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 16:06:15 -0300 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=94=80=20refactor(types.py):=20remove?= =?UTF-8?q?=20unnecessary=20"refresh"=20field=20assignment=20in=20update?= =?UTF-8?q?=5Ffield=5Fdict=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/interface/types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 175968994..8bb823502 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -260,7 +260,6 @@ def update_field_dict(field_dict): # Let's check if "range_spec" is a RangeSpec object if "range_spec" in field_dict and isinstance(field_dict["range_spec"], RangeSpec): field_dict["range_spec"] = field_dict["range_spec"].model_dump() - field_dict["refresh"] = True def add_extra_fields(frontend_node, field_config, function_args): From 0c2d7388dbc74e1d534791df4f69f7e6a9ec1f79 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 16:48:24 -0300 Subject: [PATCH 6/8] Fix serialization issue with range_spec in TemplateField --- src/backend/langflow/template/field/base.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index 754632e98..c8e1235cc 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -61,7 +61,7 @@ class TemplateField(BaseModel): refresh: Optional[bool] = None """Specifies if the field should be refreshed. Defaults to False.""" - range_spec: Optional[RangeSpec] = None + range_spec: Optional[RangeSpec] = Field(None, serialization_alias="rangeSpec") """Range specification for the field. Defaults to None.""" def to_dict(self): @@ -73,8 +73,10 @@ class TemplateField(BaseModel): return value return "" - @field_serializer("range_spec") - def serialize_range_spec(self, value): - if self.field_type == "float" and value is None: - return RangeSpec().model_dump() + @field_serializer("field_type") + def serialize_field_type(self, value, _info): + if value == "float": + # check if range_spec is set + if self.range_spec is None: + self.range_spec = RangeSpec() return value From f4628db481c6439bcd1590f304e787188585233e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 16:50:38 -0300 Subject: [PATCH 7/8] Fix range_spec to rangeSpec --- src/backend/langflow/interface/types.py | 4 ++-- .../GenericNode/components/parameterComponent/index.tsx | 2 +- src/frontend/src/modals/EditNodeModal/index.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index ac76a5fd0..8718579db 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -258,8 +258,8 @@ def update_field_dict(field_dict): field_dict["refresh"] = True # Let's check if "range_spec" is a RangeSpec object - if "range_spec" in field_dict and isinstance(field_dict["range_spec"], RangeSpec): - field_dict["range_spec"] = field_dict["range_spec"].model_dump() + if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec): + field_dict["rangeSpec"] = field_dict["rangeSpec"].model_dump() def add_extra_fields(frontend_node, field_config, function_args): diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 5a5de77d9..b08b92bb9 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -457,7 +457,7 @@ export default function ParameterComponent({ diff --git a/src/frontend/src/modals/EditNodeModal/index.tsx b/src/frontend/src/modals/EditNodeModal/index.tsx index d7ceb53f2..325fce195 100644 --- a/src/frontend/src/modals/EditNodeModal/index.tsx +++ b/src/frontend/src/modals/EditNodeModal/index.tsx @@ -362,7 +362,7 @@ const EditNodeModal = forwardRef( editNode={true} rangeSpec={ myData.node!.template[templateParam] - .range_spec + .rangeSpec } value={ myData.node.template[templateParam] From 1a348bb220a89d68f4fb9fc96cbe58064d05cd5b Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 16:51:00 -0300 Subject: [PATCH 8/8] Fix range_spec key in test_llms_template.py --- tests/test_llms_template.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_llms_template.py b/tests/test_llms_template.py index 6e0509202..30a15c932 100644 --- a/tests/test_llms_template.py +++ b/tests/test_llms_template.py @@ -88,7 +88,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", - "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, + "rangeSpec": {"max": 1.0, "min": -1.0, "step": 0.1}, "fileTypes": [], } assert template["max_tokens"] == { @@ -119,7 +119,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", - "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, + "rangeSpec": {"max": 1.0, "min": -1.0, "step": 0.1}, "fileTypes": [], } assert template["frequency_penalty"] == { @@ -135,7 +135,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", - "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, + "rangeSpec": {"max": 1.0, "min": -1.0, "step": 0.1}, "fileTypes": [], } assert template["presence_penalty"] == { @@ -151,7 +151,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", - "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, + "rangeSpec": {"max": 1.0, "min": -1.0, "step": 0.1}, "fileTypes": [], } assert template["n"] == { @@ -241,7 +241,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", - "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, + "rangeSpec": {"max": 1.0, "min": -1.0, "step": 0.1}, "fileTypes": [], } assert template["logit_bias"] == { @@ -364,7 +364,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", - "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, + "rangeSpec": {"max": 1.0, "min": -1.0, "step": 0.1}, "fileTypes": [], } assert template["model_kwargs"] == { @@ -409,7 +409,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", - "range_spec": {"max": 1.0, "min": -1.0, "step": 0.1}, + "rangeSpec": {"max": 1.0, "min": -1.0, "step": 0.1}, "fileTypes": [], } assert template["max_retries"] == {