From 8324c40074a9cc0a83e973623eac8b9d33638f04 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 15:47:40 -0300 Subject: [PATCH 1/6] Refactor field configuration in add_new_custom_field function --- src/backend/langflow/interface/types.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index c508816a0..e137e4c25 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -128,7 +128,7 @@ def add_new_custom_field( advanced=field_advanced, placeholder=placeholder, display_name=display_name, - **field_config, + **sanitize_field_config(field_config), ) template.get("template")[field_name] = new_field.to_dict() template.get("custom_fields")[field_name] = None @@ -136,6 +136,13 @@ def add_new_custom_field( return template +def sanitize_field_config(field_config: Dict): + # If any of the already existing keys are in field_config, remove them + for key in ["name", "field_type", "value", "required", "placeholder", "display_name", "advanced", "show"]: + field_config.pop(key, None) + return field_config + + # TODO: Move to correct place def add_code_field(template, raw_code, field_config): # Field with the Python code to allow update @@ -223,7 +230,10 @@ def build_field_config( try: build_config: Dict = custom_class(user_id=user_id).build_config() - for field_name, field_dict in build_config.items(): + for field_name, field in build_config.items(): + # Allow user to build TemplateField as well + # as a dict with the same keys as TemplateField + field_dict = get_field_dict(field) if update_field is not None and field_name != update_field: continue try: @@ -245,6 +255,13 @@ def build_field_config( ) from exc +def get_field_dict(field): + """Get the field dictionary from a TemplateField or a dict""" + if isinstance(field, TemplateField): + return field.model_dump(by_alias=True, exclude_none=True) + return field + + def update_field_dict(field_dict): """Update the field dictionary by calling options() or value() if they are callable""" if "options" in field_dict and callable(field_dict["options"]): From a74b86e93bfab7f693ffbc7752893697fc457ec0 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 15:48:05 -0300 Subject: [PATCH 2/6] Update options field in TemplateFieldCreator class --- src/backend/langflow/template/field/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index 751b72af0..5d2502f88 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -1,5 +1,5 @@ from abc import ABC -from typing import Any, Optional, Union +from typing import Any, Callable, Optional, Union from pydantic import BaseModel @@ -35,7 +35,7 @@ class TemplateFieldCreator(BaseModel, ABC): password: bool = False """Specifies if the field is a password. Defaults to False.""" - options: list[str] = [] + options: Union[list[str], Callable] = [] """List of options for the field. Only used when is_list=True. Default is an empty list.""" name: str = "" From d670d0fd93349e8a9704886dc88e368011d030c9 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 15:48:51 -0300 Subject: [PATCH 3/6] Add test for custom component with TemplateField --- tests/conftest.py | 8 ++++++++ tests/data/component_with_templatefield.py | 17 +++++++++++++++++ tests/test_custom_component.py | 14 ++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 tests/data/component_with_templatefield.py diff --git a/tests/conftest.py b/tests/conftest.py index ce279162f..fdc896266 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -308,3 +308,11 @@ def test_component_code(): # load the content as a string with open(path, "r") as f: return f.read() + + +@pytest.fixture +def test_component_with_templatefield_code(): + path = Path(__file__).parent.absolute() / "data" / "component_with_templatefield.py" + # load the content as a string + with open(path, "r") as f: + return f.read() diff --git a/tests/data/component_with_templatefield.py b/tests/data/component_with_templatefield.py new file mode 100644 index 000000000..725bec9b2 --- /dev/null +++ b/tests/data/component_with_templatefield.py @@ -0,0 +1,17 @@ +import random + +from langflow import CustomComponent +from langflow.template.field.base import TemplateField + + +class TestComponent(CustomComponent): + def refresh_values(self): + # This is a function that will be called every time the component is updated + # and should return a list of random strings + return [f"Random {random.randint(1, 100)}" for _ in range(5)] + + def build_config(self): + return {"param": TemplateField(display_name="Param", options=self.refresh_values)} + + def build(self, param: int): + return param diff --git a/tests/test_custom_component.py b/tests/test_custom_component.py index 3be85ddd2..007374763 100644 --- a/tests/test_custom_component.py +++ b/tests/test_custom_component.py @@ -549,3 +549,17 @@ def test_build_langchain_template_custom_component_valid_code(test_component_cod frontend_node = build_custom_component_template(component, update_field="param") new_param_options = frontend_node["template"]["param"]["options"] assert param_options != new_param_options + + +def test_build_langchain_template_custom_component_templatefield(test_component_with_templatefield_code): + component = create_and_validate_component(test_component_with_templatefield_code) + frontend_node = build_custom_component_template(component) + assert isinstance(frontend_node, dict) + template = frontend_node["template"] + assert isinstance(template, dict) + assert "param" in template + param_options = template["param"]["options"] + # Now run it again with an update field + frontend_node = build_custom_component_template(component, update_field="param") + new_param_options = frontend_node["template"]["param"]["options"] + assert param_options != new_param_options From 9235cdfdb6709e52e00178bad28b45e5252aae96 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 15:53:08 -0300 Subject: [PATCH 4/6] Add TemplateField to field_typing --- src/backend/langflow/field_typing/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/backend/langflow/field_typing/__init__.py b/src/backend/langflow/field_typing/__init__.py index 3ae912117..30a8d8abe 100644 --- a/src/backend/langflow/field_typing/__init__.py +++ b/src/backend/langflow/field_typing/__init__.py @@ -1,3 +1,5 @@ +from langflow.template.field.base import TemplateField + from .constants import ( AgentExecutor, BaseChatMemory, @@ -46,4 +48,5 @@ __all__ = [ "BasePromptTemplate", "ChatPromptTemplate", "Prompt", + "TemplateField", ] From d82b98c58b2ebe1f366d77422a5da65bd41f691e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 15:54:33 -0300 Subject: [PATCH 5/6] Update import statement for TemplateField --- tests/data/component_with_templatefield.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/data/component_with_templatefield.py b/tests/data/component_with_templatefield.py index 725bec9b2..6b2ce011b 100644 --- a/tests/data/component_with_templatefield.py +++ b/tests/data/component_with_templatefield.py @@ -1,7 +1,7 @@ import random from langflow import CustomComponent -from langflow.template.field.base import TemplateField +from langflow.field_typing import TemplateField class TestComponent(CustomComponent): From 32ac91e43fb302acbb4aa42abf1e4cabfc2b4aab Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 16:00:28 -0300 Subject: [PATCH 6/6] Update build_config field value type --- 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..0982848e0 100644 --- a/docs/docs/components/custom.mdx +++ b/docs/docs/components/custom.mdx @@ -56,7 +56,7 @@ The CustomComponent class serves as the foundation for creating custom component - **build_config**: Used to define the configuration fields of the component (if applicable). It should always return a dictionary with specific keys representing the field names and corresponding configurations. This method is called when the code is processed (i.e., when you click _Check and Save_ in the code editor). It must follow the format described below: - Top-level keys are field names. - - Their values are also of type _`dict`_. They specify the behavior of the generated fields. + - Their values are can be of type _`langflow.field_typing.TemplateField`_ or _`dict`_. They specify the behavior of the generated fields. Below are the available keys used to configure component fields: