From 0cca8efebd349703c5a0f003bb108c1b870d0b74 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sat, 9 Dec 2023 18:11:38 -0300 Subject: [PATCH 1/7] Update agent field types in agents.py --- .../langflow/template/frontend_node/agents.py | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/backend/langflow/template/frontend_node/agents.py b/src/backend/langflow/template/frontend_node/agents.py index d2e3589cf..852f8d09f 100644 --- a/src/backend/langflow/template/frontend_node/agents.py +++ b/src/backend/langflow/template/frontend_node/agents.py @@ -1,7 +1,6 @@ from typing import Optional from langchain.agents import types - from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode from langflow.template.template.base import Template @@ -29,17 +28,17 @@ class SQLAgentNode(FrontendNode): type_name="sql_agent", fields=[ TemplateField( - field_type="str", + field_type="str", # pyright: ignore required=True, placeholder="", - is_list=False, + is_list=False, # pyright: ignore show=True, multiline=False, value="", name="database_uri", ), TemplateField( - field_type="BaseLanguageModel", + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -60,14 +59,14 @@ class VectorStoreRouterAgentNode(FrontendNode): type_name="vectorstorerouter_agent", fields=[ TemplateField( - field_type="VectorStoreRouterToolkit", + field_type="VectorStoreRouterToolkit", # pyright: ignore required=True, show=True, name="vectorstoreroutertoolkit", display_name="Vector Store Router Toolkit", ), TemplateField( - field_type="BaseLanguageModel", + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -88,14 +87,14 @@ class VectorStoreAgentNode(FrontendNode): type_name="vectorstore_agent", fields=[ TemplateField( - field_type="VectorStoreInfo", + field_type="VectorStoreInfo", # pyright: ignore required=True, show=True, name="vectorstoreinfo", display_name="Vector Store Info", ), TemplateField( - field_type="BaseLanguageModel", + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -116,9 +115,9 @@ class SQLDatabaseNode(FrontendNode): type_name="sql_database", fields=[ TemplateField( - field_type="str", + field_type="str", # pyright: ignore required=True, - is_list=False, + is_list=False, # pyright: ignore show=True, multiline=False, value="", @@ -139,15 +138,15 @@ class CSVAgentNode(FrontendNode): type_name="csv_agent", fields=[ TemplateField( - field_type="file", + field_type="file", # pyright: ignore required=True, show=True, name="path", value="", - file_types=[".csv"], + file_types=[".csv"], # pyright: ignore ), TemplateField( - field_type="BaseLanguageModel", + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -169,9 +168,9 @@ class InitializeAgentNode(FrontendNode): type_name="initialize_agent", fields=[ TemplateField( - field_type="str", + field_type="str", # pyright: ignore required=True, - is_list=True, + is_list=True, # pyright: ignore show=True, multiline=False, options=list(NON_CHAT_AGENTS.keys()), @@ -180,22 +179,22 @@ class InitializeAgentNode(FrontendNode): advanced=False, ), TemplateField( - field_type="BaseChatMemory", + field_type="BaseChatMemory", # pyright: ignore required=False, show=True, name="memory", advanced=False, ), TemplateField( - field_type="Tool", + field_type="Tool", # pyright: ignore required=True, show=True, name="tools", - is_list=True, + is_list=True, # pyright: ignore advanced=False, ), TemplateField( - field_type="BaseLanguageModel", + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -222,13 +221,13 @@ class JsonAgentNode(FrontendNode): type_name="json_agent", fields=[ TemplateField( - field_type="BaseToolkit", + field_type="BaseToolkit", # pyright: ignore required=True, show=True, name="toolkit", ), TemplateField( - field_type="BaseLanguageModel", + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", From 3c955de5e31ebf53c06b2b2da5643197caa76187 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sat, 9 Dec 2023 18:11:51 -0300 Subject: [PATCH 2/7] Refactor field serialization and add model serialization in Template and FrontendNode classes --- src/backend/langflow/template/field/base.py | 49 +++++++++++-------- .../langflow/template/frontend_node/base.py | 42 +++++++++------- .../langflow/template/template/base.py | 26 ++++++---- 3 files changed, 69 insertions(+), 48 deletions(-) diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index 751b72af0..2c24e75bc 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -1,11 +1,11 @@ from abc import ABC from typing import Any, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Field, field_serializer, model_serializer class TemplateFieldCreator(BaseModel, ABC): - field_type: str = "str" + field_type: str = Field(default="str", alias="type") """The type of field this is. Default is a string.""" required: bool = False @@ -14,7 +14,7 @@ class TemplateFieldCreator(BaseModel, ABC): placeholder: str = "" """A placeholder string for the field. Default is an empty string.""" - is_list: bool = False + is_list: bool = Field(default=False, alias="list") """Defines if the field is a list. Default is False.""" show: bool = True @@ -26,7 +26,7 @@ class TemplateFieldCreator(BaseModel, ABC): value: Any = None """The value of the field. Default is None.""" - file_types: list[str] = [] + file_types: list[str] = Field(default=[], alias="fileTypes") """List of file types associated with the field. Default is an empty list. (duplicate)""" file_path: Union[str, None] = None @@ -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: list[str] = None """List of options for the field. Only used when is_list=True. Default is an empty list.""" name: str = "" @@ -47,7 +47,7 @@ class TemplateFieldCreator(BaseModel, ABC): advanced: bool = False """Specifies if the field will an advanced parameter (hidden). Defaults to False.""" - input_types: list[str] = [] + input_types: Optional[list[str]] = None """List of input types for the handle when the field has more than one type. Default is an empty list.""" dynamic: bool = False @@ -59,22 +59,31 @@ class TemplateFieldCreator(BaseModel, ABC): refresh: Optional[bool] = None """Specifies if the field should be refreshed. Defaults to False.""" - def to_dict(self): - result = self.model_dump() - # Remove key if it is None - for key in list(result.keys()): - if result[key] is None or result[key] == [] and key != "value": - del result[key] - result["type"] = result.pop("field_type") - result["list"] = result.pop("is_list") - - if result.get("file_types"): - result["fileTypes"] = result.pop("file_types") - - if self.field_type == "file": - result["file_path"] = self.file_path + @model_serializer(mode="wrap") + def serialize_model(self, handler): + # This will be the result of model_dump or dict() + # so we need to build a dict to return + result = handler(self) + result["value"] = self.value return result + + # for key in list(result.keys()): + # if result[key] is None or result[key] == [] and key != "value": + # del result[key] + # return result + + def to_dict(self): + return self.model_dump(by_alias=True, exclude_none=True) + + + @field_serializer("file_path") + def serialize_file_path(self, value): + if self.field_type == "file": + return value + return None + + class TemplateField(TemplateFieldCreator): pass diff --git a/src/backend/langflow/template/frontend_node/base.py b/src/backend/langflow/template/frontend_node/base.py index 04467a094..f05260f68 100644 --- a/src/backend/langflow/template/frontend_node/base.py +++ b/src/backend/langflow/template/frontend_node/base.py @@ -3,11 +3,12 @@ from collections import defaultdict from typing import ClassVar, Dict, List, Optional from langflow.template.field.base import TemplateField -from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE, FORCE_SHOW_FIELDS +from langflow.template.frontend_node.constants import (CLASSES_TO_REMOVE, + FORCE_SHOW_FIELDS) from langflow.template.frontend_node.formatter import field_formatters from langflow.template.template.base import Template from langflow.utils import constants -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_serializer, model_serializer class FieldFormatters(BaseModel): @@ -63,26 +64,31 @@ class FrontendNode(BaseModel): """Sets the documentation of the frontend node.""" self.documentation = documentation - def process_base_classes(self) -> None: + @field_serializer("base_classes") + def process_base_classes(self, base_classes: List[str]) -> List[str]: """Removes unwanted base classes from the list of base classes.""" - self.base_classes = [base_class for base_class in self.base_classes if base_class not in CLASSES_TO_REMOVE] + + return [base_class for base_class in base_classes if base_class not in CLASSES_TO_REMOVE] + + @field_serializer("display_name") + def process_display_name(self, display_name: str) -> str: + """Sets the display name of the frontend node.""" + + return display_name or self.name + + + @model_serializer(mode="wrap") + def serialize(self, handler): + result = handler(self) + result["template"] = self.template.to_dict(self.format_field) + name = result.pop("name") + + return {name: result} def to_dict(self) -> dict: """Returns a dict representation of the frontend node.""" - self.process_base_classes() - return { - self.name: { - "template": self.template.to_dict(self.format_field), - "description": self.description, - "base_classes": self.base_classes, - "display_name": self.display_name or self.name, - "custom_fields": self.custom_fields, - "output_types": self.output_types, - "documentation": self.documentation, - "beta": self.beta, - "error": self.error, - }, - } + + return self.model_dump(by_alias=True, exclude_none=True) def add_extra_fields(self) -> None: pass diff --git a/src/backend/langflow/template/template/base.py b/src/backend/langflow/template/template/base.py index c680fd468..6b1fc1b8e 100644 --- a/src/backend/langflow/template/template/base.py +++ b/src/backend/langflow/template/template/base.py @@ -1,9 +1,8 @@ -from typing import Callable, Optional, Union - -from pydantic import BaseModel +from typing import Callable, Union from langflow.template.field.base import TemplateField from langflow.utils.constants import DIRECT_TYPES +from pydantic import BaseModel, model_serializer class Template(BaseModel): @@ -12,12 +11,11 @@ class Template(BaseModel): def process_fields( self, - name: Optional[str] = None, format_field_func: Union[Callable, None] = None, ): if format_field_func: for field in self.fields: - format_field_func(field, name) + format_field_func(field, self.type_name) def sort_fields(self): # first sort alphabetically @@ -25,12 +23,20 @@ class Template(BaseModel): self.fields.sort(key=lambda x: x.name) self.fields.sort(key=lambda x: x.field_type in DIRECT_TYPES, reverse=False) - def to_dict(self, format_field_func=None): - self.process_fields(self.type_name, format_field_func) - self.sort_fields() - result = {field.name: field.to_dict() for field in self.fields} - result["_type"] = self.type_name # type: ignore + @model_serializer(mode="wrap") + def serialize_model(self, handler): + result = handler(self) + for field in self.fields: + result[field.name] = field.to_dict() + result["_type"] = result.pop("type_name") return result + def to_dict(self, format_field_func=None): + self.process_fields(format_field_func) + self.sort_fields() + # result = {field.name: field.to_dict() for field in self.fields} + # result["_type"] = self.type_name # type: ignore + return self.model_dump(by_alias=True, exclude_none=True, exclude={"fields"}) + def add_field(self, field: TemplateField) -> None: self.fields.append(field) From 53a0566f8c38eb181c1199ed63392b10b94e4fa4 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sat, 9 Dec 2023 23:22:49 -0300 Subject: [PATCH 3/7] Refactor code to use model_dump method for adding new custom field --- src/backend/langflow/interface/types.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index c508816a0..478bf0ca0 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -8,8 +8,6 @@ from uuid import UUID from cachetools import LRUCache, cached from fastapi import HTTPException -from loguru import logger - from langflow.interface.agents.base import agent_creator from langflow.interface.chains.base import chain_creator from langflow.interface.custom.custom_component import CustomComponent @@ -33,6 +31,7 @@ from langflow.template.field.base import TemplateField from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from langflow.utils.util import get_base_classes +from loguru import logger # Used to get the base_classes list @@ -130,7 +129,7 @@ def add_new_custom_field( display_name=display_name, **field_config, ) - template.get("template")[field_name] = new_field.to_dict() + template.get("template")[field_name] = new_field.model_dump(by_alias=True, exclude_none=True) template.get("custom_fields")[field_name] = None return template From 72a16ef60732acefc2ebb13a2b5ab5cd611f714f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sat, 9 Dec 2023 23:23:06 -0300 Subject: [PATCH 4/7] Refactor code by removing unused to_dict() methods --- src/backend/langflow/template/field/base.py | 42 ++++--------- .../langflow/template/frontend_node/agents.py | 61 ++++++------------- .../langflow/template/frontend_node/base.py | 8 +-- .../langflow/template/frontend_node/chains.py | 3 - .../frontend_node/custom_components.py | 3 - .../formatter/field_formatters.py | 4 +- .../template/frontend_node/prompts.py | 16 +---- .../langflow/template/frontend_node/tools.py | 13 +--- .../langflow/template/template/base.py | 5 +- 9 files changed, 41 insertions(+), 114 deletions(-) diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index 2c24e75bc..ef88ff0f5 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -1,11 +1,11 @@ -from abc import ABC -from typing import Any, Optional, Union +from typing import Any, Optional -from pydantic import BaseModel, Field, field_serializer, model_serializer +from pydantic import BaseModel, ConfigDict, Field, field_serializer -class TemplateFieldCreator(BaseModel, ABC): - field_type: str = Field(default="str", alias="type") +class TemplateField(BaseModel): + model_config = ConfigDict() + field_type: str = Field(default="str", serialization_alias="type") """The type of field this is. Default is a string.""" required: bool = False @@ -14,7 +14,7 @@ class TemplateFieldCreator(BaseModel, ABC): placeholder: str = "" """A placeholder string for the field. Default is an empty string.""" - is_list: bool = Field(default=False, alias="list") + is_list: bool = Field(default=False, serialization_alias="list") """Defines if the field is a list. Default is False.""" show: bool = True @@ -23,19 +23,19 @@ class TemplateFieldCreator(BaseModel, ABC): multiline: bool = False """Defines if the field will allow the user to open a text editor. Default is False.""" - value: Any = None + value: Any = "" """The value of the field. Default is None.""" - file_types: list[str] = Field(default=[], alias="fileTypes") + file_types: list[str] = Field(default=[], serialization_alias="fileTypes") """List of file types associated with the field. Default is an empty list. (duplicate)""" - file_path: Union[str, None] = None + file_path: Optional[str] = "" """The file path of the field if it is a file. Defaults to None.""" password: bool = False """Specifies if the field is a password. Defaults to False.""" - options: list[str] = None + options: Optional[list[str]] = None """List of options for the field. Only used when is_list=True. Default is an empty list.""" name: str = "" @@ -59,31 +59,11 @@ class TemplateFieldCreator(BaseModel, ABC): refresh: Optional[bool] = None """Specifies if the field should be refreshed. Defaults to False.""" - @model_serializer(mode="wrap") - def serialize_model(self, handler): - # This will be the result of model_dump or dict() - # so we need to build a dict to return - result = handler(self) - result["value"] = self.value - return result - - - - # for key in list(result.keys()): - # if result[key] is None or result[key] == [] and key != "value": - # del result[key] - # return result - def to_dict(self): return self.model_dump(by_alias=True, exclude_none=True) - @field_serializer("file_path") def serialize_file_path(self, value): if self.field_type == "file": return value - return None - - -class TemplateField(TemplateFieldCreator): - pass + return "" diff --git a/src/backend/langflow/template/frontend_node/agents.py b/src/backend/langflow/template/frontend_node/agents.py index 852f8d09f..b2a08d944 100644 --- a/src/backend/langflow/template/frontend_node/agents.py +++ b/src/backend/langflow/template/frontend_node/agents.py @@ -28,17 +28,17 @@ class SQLAgentNode(FrontendNode): type_name="sql_agent", fields=[ TemplateField( - field_type="str", # pyright: ignore + field_type="str", # pyright: ignore required=True, placeholder="", - is_list=False, # pyright: ignore + is_list=False, # pyright: ignore show=True, multiline=False, value="", name="database_uri", ), TemplateField( - field_type="BaseLanguageModel", # pyright: ignore + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -49,9 +49,6 @@ class SQLAgentNode(FrontendNode): description: str = """Construct an SQL agent from an LLM and tools.""" base_classes: list[str] = ["AgentExecutor"] - def to_dict(self): - return super().to_dict() - class VectorStoreRouterAgentNode(FrontendNode): name: str = "VectorStoreRouterAgent" @@ -59,14 +56,14 @@ class VectorStoreRouterAgentNode(FrontendNode): type_name="vectorstorerouter_agent", fields=[ TemplateField( - field_type="VectorStoreRouterToolkit", # pyright: ignore + field_type="VectorStoreRouterToolkit", # pyright: ignore required=True, show=True, name="vectorstoreroutertoolkit", display_name="Vector Store Router Toolkit", ), TemplateField( - field_type="BaseLanguageModel", # pyright: ignore + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -77,9 +74,6 @@ class VectorStoreRouterAgentNode(FrontendNode): description: str = """Construct an agent from a Vector Store Router.""" base_classes: list[str] = ["AgentExecutor"] - def to_dict(self): - return super().to_dict() - class VectorStoreAgentNode(FrontendNode): name: str = "VectorStoreAgent" @@ -87,14 +81,14 @@ class VectorStoreAgentNode(FrontendNode): type_name="vectorstore_agent", fields=[ TemplateField( - field_type="VectorStoreInfo", # pyright: ignore + field_type="VectorStoreInfo", # pyright: ignore required=True, show=True, name="vectorstoreinfo", display_name="Vector Store Info", ), TemplateField( - field_type="BaseLanguageModel", # pyright: ignore + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -105,9 +99,6 @@ class VectorStoreAgentNode(FrontendNode): description: str = """Construct an agent from a Vector Store.""" base_classes: list[str] = ["AgentExecutor"] - def to_dict(self): - return super().to_dict() - class SQLDatabaseNode(FrontendNode): name: str = "SQLDatabase" @@ -115,9 +106,9 @@ class SQLDatabaseNode(FrontendNode): type_name="sql_database", fields=[ TemplateField( - field_type="str", # pyright: ignore + field_type="str", # pyright: ignore required=True, - is_list=False, # pyright: ignore + is_list=False, # pyright: ignore show=True, multiline=False, value="", @@ -128,9 +119,6 @@ class SQLDatabaseNode(FrontendNode): description: str = """SQLAlchemy wrapper around a database.""" base_classes: list[str] = ["SQLDatabase"] - def to_dict(self): - return super().to_dict() - class CSVAgentNode(FrontendNode): name: str = "CSVAgent" @@ -138,15 +126,15 @@ class CSVAgentNode(FrontendNode): type_name="csv_agent", fields=[ TemplateField( - field_type="file", # pyright: ignore + field_type="file", # pyright: ignore required=True, show=True, name="path", value="", - file_types=[".csv"], # pyright: ignore + file_types=[".csv"], # pyright: ignore ), TemplateField( - field_type="BaseLanguageModel", # pyright: ignore + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -157,9 +145,6 @@ class CSVAgentNode(FrontendNode): description: str = """Construct a CSV agent from a CSV and tools.""" base_classes: list[str] = ["AgentExecutor"] - def to_dict(self): - return super().to_dict() - class InitializeAgentNode(FrontendNode): name: str = "AgentInitializer" @@ -168,9 +153,9 @@ class InitializeAgentNode(FrontendNode): type_name="initialize_agent", fields=[ TemplateField( - field_type="str", # pyright: ignore + field_type="str", # pyright: ignore required=True, - is_list=True, # pyright: ignore + is_list=True, # pyright: ignore show=True, multiline=False, options=list(NON_CHAT_AGENTS.keys()), @@ -179,22 +164,22 @@ class InitializeAgentNode(FrontendNode): advanced=False, ), TemplateField( - field_type="BaseChatMemory", # pyright: ignore + field_type="BaseChatMemory", # pyright: ignore required=False, show=True, name="memory", advanced=False, ), TemplateField( - field_type="Tool", # pyright: ignore + field_type="Tool", # pyright: ignore required=True, show=True, name="tools", - is_list=True, # pyright: ignore + is_list=True, # pyright: ignore advanced=False, ), TemplateField( - field_type="BaseLanguageModel", # pyright: ignore + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -206,9 +191,6 @@ class InitializeAgentNode(FrontendNode): description: str = """Construct a zero shot agent from an LLM and tools.""" base_classes: list[str] = ["AgentExecutor", "Callable"] - def to_dict(self): - return super().to_dict() - @staticmethod def format_field(field: TemplateField, name: Optional[str] = None) -> None: # do nothing and don't return anything @@ -221,13 +203,13 @@ class JsonAgentNode(FrontendNode): type_name="json_agent", fields=[ TemplateField( - field_type="BaseToolkit", # pyright: ignore + field_type="BaseToolkit", # pyright: ignore required=True, show=True, name="toolkit", ), TemplateField( - field_type="BaseLanguageModel", # pyright: ignore + field_type="BaseLanguageModel", # pyright: ignore required=True, show=True, name="llm", @@ -237,6 +219,3 @@ class JsonAgentNode(FrontendNode): ) description: str = """Construct a json agent from an LLM and tools.""" base_classes: list[str] = ["AgentExecutor"] - - def to_dict(self): - return super().to_dict() diff --git a/src/backend/langflow/template/frontend_node/base.py b/src/backend/langflow/template/frontend_node/base.py index f05260f68..bcf93ad30 100644 --- a/src/backend/langflow/template/frontend_node/base.py +++ b/src/backend/langflow/template/frontend_node/base.py @@ -3,8 +3,7 @@ from collections import defaultdict from typing import ClassVar, Dict, List, Optional from langflow.template.field.base import TemplateField -from langflow.template.frontend_node.constants import (CLASSES_TO_REMOVE, - FORCE_SHOW_FIELDS) +from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE, FORCE_SHOW_FIELDS from langflow.template.frontend_node.formatter import field_formatters from langflow.template.template.base import Template from langflow.utils import constants @@ -76,15 +75,16 @@ class FrontendNode(BaseModel): return display_name or self.name - @model_serializer(mode="wrap") def serialize(self, handler): result = handler(self) - result["template"] = self.template.to_dict(self.format_field) + if hasattr(self, "template") and hasattr(self.template, "to_dict"): + result["template"] = self.template.to_dict(self.format_field) name = result.pop("name") return {name: result} + # For backwards compatibility def to_dict(self) -> dict: """Returns a dict representation of the frontend node.""" diff --git a/src/backend/langflow/template/frontend_node/chains.py b/src/backend/langflow/template/frontend_node/chains.py index f6e1eacd1..4168bd9e1 100644 --- a/src/backend/langflow/template/frontend_node/chains.py +++ b/src/backend/langflow/template/frontend_node/chains.py @@ -247,9 +247,6 @@ class CombineDocsChainNode(FrontendNode): description: str = """Load question answering chain.""" base_classes: list[str] = ["BaseCombineDocumentsChain", "Callable"] - def to_dict(self): - return super().to_dict() - @staticmethod def format_field(field: TemplateField, name: Optional[str] = None) -> None: # do nothing and don't return anything diff --git a/src/backend/langflow/template/frontend_node/custom_components.py b/src/backend/langflow/template/frontend_node/custom_components.py index a0a5a9956..9c9f18d5d 100644 --- a/src/backend/langflow/template/frontend_node/custom_components.py +++ b/src/backend/langflow/template/frontend_node/custom_components.py @@ -66,6 +66,3 @@ class CustomComponentFrontendNode(FrontendNode): ) description: Optional[str] = None base_classes: list[str] = [] - - def to_dict(self): - return super().to_dict() diff --git a/src/backend/langflow/template/frontend_node/formatter/field_formatters.py b/src/backend/langflow/template/frontend_node/formatter/field_formatters.py index dc57ff04f..40f2b780c 100644 --- a/src/backend/langflow/template/frontend_node/formatter/field_formatters.py +++ b/src/backend/langflow/template/frontend_node/formatter/field_formatters.py @@ -129,7 +129,7 @@ class MultilineFieldFormatter(FieldFormatter): class DefaultValueFormatter(FieldFormatter): def format(self, field: TemplateField, name: Optional[str] = None) -> None: - value = field.to_dict() + value = field.model_dump(by_alias=True, exclude_none=True) if "default" in value: field.value = value["default"] @@ -144,7 +144,7 @@ class HeadersDefaultValueFormatter(FieldFormatter): class DictCodeFileFormatter(FieldFormatter): def format(self, field: TemplateField, name: Optional[str] = None) -> None: key = field.name - value = field.to_dict() + value = field.model_dump(by_alias=True, exclude_none=True) _type = value["type"] if "dict" in _type.lower() and key == "dict_": field.field_type = "file" diff --git a/src/backend/langflow/template/frontend_node/prompts.py b/src/backend/langflow/template/frontend_node/prompts.py index dccd66301..ffeef921c 100644 --- a/src/backend/langflow/template/frontend_node/prompts.py +++ b/src/backend/langflow/template/frontend_node/prompts.py @@ -1,14 +1,9 @@ from typing import Optional from langchain.agents.mrkl import prompt - -from langflow.template.frontend_node.constants import ( - DEFAULT_PROMPT, - HUMAN_PROMPT, - SYSTEM_PROMPT, -) from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode +from langflow.template.frontend_node.constants import DEFAULT_PROMPT, HUMAN_PROMPT, SYSTEM_PROMPT from langflow.template.template.base import Template @@ -50,9 +45,6 @@ class PromptTemplateNode(FrontendNode): description: str base_classes: list[str] = ["BasePromptTemplate"] - def to_dict(self): - return super().to_dict() - @staticmethod def format_field(field: TemplateField, name: Optional[str] = None) -> None: FrontendNode.format_field(field, name) @@ -66,9 +58,6 @@ class BasePromptFrontendNode(FrontendNode): description: str base_classes: list[str] - def to_dict(self): - return super().to_dict() - class ZeroShotPromptNode(BasePromptFrontendNode): name: str = "ZeroShotPrompt" @@ -110,9 +99,6 @@ class ZeroShotPromptNode(BasePromptFrontendNode): description: str = "Prompt template for Zero Shot Agent." base_classes: list[str] = ["BasePromptTemplate"] - def to_dict(self): - return super().to_dict() - @staticmethod def format_field(field: TemplateField, name: Optional[str] = None) -> None: PromptFrontendNode.format_field(field, name) diff --git a/src/backend/langflow/template/frontend_node/tools.py b/src/backend/langflow/template/frontend_node/tools.py index 315b216a4..5bed90c05 100644 --- a/src/backend/langflow/template/frontend_node/tools.py +++ b/src/backend/langflow/template/frontend_node/tools.py @@ -1,9 +1,7 @@ from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode from langflow.template.template.base import Template -from langflow.utils.constants import ( - DEFAULT_PYTHON_FUNCTION, -) +from langflow.utils.constants import DEFAULT_PYTHON_FUNCTION class ToolNode(FrontendNode): @@ -57,9 +55,6 @@ class ToolNode(FrontendNode): description: str = "Converts a chain, agent or function into a tool." base_classes: list[str] = ["Tool", "BaseTool"] - def to_dict(self): - return super().to_dict() - class PythonFunctionToolNode(FrontendNode): name: str = "PythonFunctionTool" @@ -113,9 +108,6 @@ class PythonFunctionToolNode(FrontendNode): description: str = "Python function to be executed." base_classes: list[str] = ["BaseTool", "Tool"] - def to_dict(self): - return super().to_dict() - class PythonFunctionNode(FrontendNode): name: str = "PythonFunction" @@ -136,6 +128,3 @@ class PythonFunctionNode(FrontendNode): ) description: str = "Python function to be executed." base_classes: list[str] = ["Callable"] - - def to_dict(self): - return super().to_dict() diff --git a/src/backend/langflow/template/template/base.py b/src/backend/langflow/template/template/base.py index 6b1fc1b8e..bb5d35375 100644 --- a/src/backend/langflow/template/template/base.py +++ b/src/backend/langflow/template/template/base.py @@ -27,15 +27,14 @@ class Template(BaseModel): def serialize_model(self, handler): result = handler(self) for field in self.fields: - result[field.name] = field.to_dict() + result[field.name] = field.model_dump(by_alias=True, exclude_none=True) result["_type"] = result.pop("type_name") return result + # For backwards compatibility def to_dict(self, format_field_func=None): self.process_fields(format_field_func) self.sort_fields() - # result = {field.name: field.to_dict() for field in self.fields} - # result["_type"] = self.type_name # type: ignore return self.model_dump(by_alias=True, exclude_none=True, exclude={"fields"}) def add_field(self, field: TemplateField) -> None: From 628855c50d553df4b07196cc7a1dc7afe44a864d Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sat, 9 Dec 2023 23:23:14 -0300 Subject: [PATCH 5/7] Update template files with new fileTypes field --- tests/test_agents_template.py | 23 ++++++++++++++++++++++- tests/test_chains_template.py | 24 ++++++++++++++++++++++++ tests/test_llms_template.py | 29 +++++++++++++++++++++++++++++ tests/test_prompts_template.py | 6 ++++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/tests/test_agents_template.py b/tests/test_agents_template.py index 6b0ccce8d..a7919faac 100644 --- a/tests/test_agents_template.py +++ b/tests/test_agents_template.py @@ -28,6 +28,8 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "list": True, "advanced": False, "info": "", + "fileTypes": [], + "value": None, } # Additional assertions for other template variables @@ -43,6 +45,8 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], + "value": None, } assert template["llm"] == { "required": True, @@ -56,6 +60,8 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], + "value": None, } assert template["output_parser"] == { "required": False, @@ -69,6 +75,8 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], + "value": None, } assert template["input_variables"] == { "required": False, @@ -82,6 +90,8 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "list": True, "advanced": False, "info": "", + "fileTypes": [], + "value": None, } assert template["prefix"] == { "required": False, @@ -96,6 +106,7 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["suffix"] == { "required": False, @@ -110,6 +121,7 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } @@ -135,6 +147,9 @@ def test_json_agent(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "file_path": "", + "fileTypes": [], + "value": "", } assert template["llm"] == { "required": True, @@ -149,6 +164,9 @@ def test_json_agent(client: TestClient, logged_in_headers): "advanced": False, "display_name": "LLM", "info": "", + "file_path": "", + "fileTypes": [], + "value": "", } @@ -174,7 +192,7 @@ def test_csv_agent(client: TestClient, logged_in_headers): "name": "path", "type": "file", "list": False, - "file_path": None, + "file_path": "", "advanced": False, "info": "", } @@ -191,4 +209,7 @@ def test_csv_agent(client: TestClient, logged_in_headers): "advanced": False, "display_name": "LLM", "info": "", + "file_path": "", + "fileTypes": [], + "value": "", } diff --git a/tests/test_chains_template.py b/tests/test_chains_template.py index 6627fb26c..2e705ac00 100644 --- a/tests/test_chains_template.py +++ b/tests/test_chains_template.py @@ -35,6 +35,7 @@ def test_llm_checker_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["_type"] == "LLMCheckerChain" @@ -69,6 +70,7 @@ def test_llm_math_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["verbose"] == { "required": False, @@ -83,6 +85,7 @@ def test_llm_math_chain(client: TestClient, logged_in_headers): "list": False, "advanced": True, "info": "", + "fileTypes": [], } assert template["llm"] == { "required": True, @@ -96,6 +99,7 @@ def test_llm_math_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["input_key"] == { "required": True, @@ -110,6 +114,7 @@ def test_llm_math_chain(client: TestClient, logged_in_headers): "list": False, "advanced": True, "info": "", + "fileTypes": [], } assert template["output_key"] == { "required": True, @@ -124,6 +129,7 @@ def test_llm_math_chain(client: TestClient, logged_in_headers): "list": False, "advanced": True, "info": "", + "fileTypes": [], } assert template["_type"] == "LLMMathChain" @@ -163,6 +169,9 @@ def test_series_character_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], + "file_path": "", + "value": "", } assert template["character"] == { "required": True, @@ -176,6 +185,9 @@ def test_series_character_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], + "file_path": "", + "value": "", } assert template["series"] == { "required": True, @@ -189,6 +201,9 @@ def test_series_character_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], + "file_path": "", + "value": "", } assert template["_type"] == "SeriesCharacterChain" @@ -232,6 +247,9 @@ def test_mid_journey_prompt_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "file_path": "", + "fileTypes": [], + "value": "", } # Test the description object assert chain["description"] == "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts." @@ -270,6 +288,9 @@ def test_time_travel_guide_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "file_path": "", + "fileTypes": [], + "value": "", } assert template["memory"] == { "required": False, @@ -283,6 +304,9 @@ def test_time_travel_guide_chain(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "file_path": "", + "fileTypes": [], + "value": "", } assert chain["description"] == "Time travel guide chain." diff --git a/tests/test_llms_template.py b/tests/test_llms_template.py index 494c1cc90..352056de7 100644 --- a/tests/test_llms_template.py +++ b/tests/test_llms_template.py @@ -22,6 +22,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["verbose"] == { "required": False, @@ -35,6 +36,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["client"] == { "required": False, @@ -48,6 +50,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["model_name"] == { "required": False, @@ -69,6 +72,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": True, "advanced": False, "info": "", + "fileTypes": [], } # Add more assertions for other properties here assert template["temperature"] == { @@ -84,6 +88,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["max_tokens"] == { "required": False, @@ -98,6 +103,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["top_p"] == { "required": False, @@ -112,6 +118,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["frequency_penalty"] == { "required": False, @@ -126,6 +133,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["presence_penalty"] == { "required": False, @@ -140,6 +148,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["n"] == { "required": False, @@ -154,6 +163,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["best_of"] == { "required": False, @@ -168,6 +178,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["model_kwargs"] == { "required": False, @@ -181,6 +192,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": True, "info": "", + "fileTypes": [], } assert template["openai_api_key"] == { "required": False, @@ -196,6 +208,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["batch_size"] == { "required": False, @@ -210,6 +223,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["request_timeout"] == { "required": False, @@ -223,6 +237,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["logit_bias"] == { "required": False, @@ -236,6 +251,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["max_retries"] == { "required": False, @@ -250,6 +266,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["streaming"] == { "required": False, @@ -264,6 +281,7 @@ def test_openai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } @@ -289,6 +307,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["client"] == { "required": False, @@ -302,6 +321,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["model_name"] == { "required": False, @@ -324,6 +344,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": True, "advanced": False, "info": "", + "fileTypes": [], } assert template["temperature"] == { "required": False, @@ -338,6 +359,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["model_kwargs"] == { "required": False, @@ -351,6 +373,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": True, "info": "", + "fileTypes": [], } assert template["openai_api_key"] == { "required": False, @@ -366,6 +389,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["request_timeout"] == { "required": False, @@ -379,6 +403,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["max_retries"] == { "required": False, @@ -393,6 +418,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["streaming"] == { "required": False, @@ -407,6 +433,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["n"] == { "required": False, @@ -421,6 +448,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["max_tokens"] == { @@ -435,6 +463,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["_type"] == "ChatOpenAI" assert ( diff --git a/tests/test_prompts_template.py b/tests/test_prompts_template.py index b9e55ce77..e1f679738 100644 --- a/tests/test_prompts_template.py +++ b/tests/test_prompts_template.py @@ -31,6 +31,7 @@ def test_prompt_template(client: TestClient, logged_in_headers): "list": True, "advanced": False, "info": "", + "fileTypes": [], } assert template["output_parser"] == { @@ -45,6 +46,7 @@ def test_prompt_template(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["partial_variables"] == { @@ -59,6 +61,7 @@ def test_prompt_template(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["template"] == { @@ -73,6 +76,7 @@ def test_prompt_template(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["template_format"] == { @@ -88,6 +92,7 @@ def test_prompt_template(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } assert template["validate_template"] == { @@ -103,4 +108,5 @@ def test_prompt_template(client: TestClient, logged_in_headers): "list": False, "advanced": False, "info": "", + "fileTypes": [], } From 61673d7bf9656d6f11833200fbdcb3efc09d9135 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 00:08:21 -0300 Subject: [PATCH 6/7] Add field serializer for display name in CustomComponentFrontendNode --- .../langflow/template/frontend_node/custom_components.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/langflow/template/frontend_node/custom_components.py b/src/backend/langflow/template/frontend_node/custom_components.py index 9c9f18d5d..22fd70814 100644 --- a/src/backend/langflow/template/frontend_node/custom_components.py +++ b/src/backend/langflow/template/frontend_node/custom_components.py @@ -3,6 +3,7 @@ from typing import Optional from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode from langflow.template.template.base import Template +from pydantic import field_serializer DEFAULT_CUSTOM_COMPONENT_CODE = """from langflow import CustomComponent from typing import Optional, List, Dict, Union @@ -66,3 +67,9 @@ class CustomComponentFrontendNode(FrontendNode): ) description: Optional[str] = None base_classes: list[str] = [] + + @field_serializer("display_name") + def process_display_name(self, display_name: str) -> str: + """Sets the display name of the frontend node.""" + + return display_name From f275471285ab1a2d4222b7b39334279432a8a280 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Sun, 10 Dec 2023 00:08:28 -0300 Subject: [PATCH 7/7] Remove redundant code and update default values for template fields --- tests/test_agents_template.py | 5 ----- tests/test_frontend_nodes.py | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/tests/test_agents_template.py b/tests/test_agents_template.py index a7919faac..01891ec05 100644 --- a/tests/test_agents_template.py +++ b/tests/test_agents_template.py @@ -29,7 +29,6 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "advanced": False, "info": "", "fileTypes": [], - "value": None, } # Additional assertions for other template variables @@ -46,7 +45,6 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "advanced": False, "info": "", "fileTypes": [], - "value": None, } assert template["llm"] == { "required": True, @@ -61,7 +59,6 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "advanced": False, "info": "", "fileTypes": [], - "value": None, } assert template["output_parser"] == { "required": False, @@ -76,7 +73,6 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "advanced": False, "info": "", "fileTypes": [], - "value": None, } assert template["input_variables"] == { "required": False, @@ -91,7 +87,6 @@ def test_zero_shot_agent(client: TestClient, logged_in_headers): "advanced": False, "info": "", "fileTypes": [], - "value": None, } assert template["prefix"] == { "required": False, diff --git a/tests/test_frontend_nodes.py b/tests/test_frontend_nodes.py index 815cc13c2..e92ad1fe4 100644 --- a/tests/test_frontend_nodes.py +++ b/tests/test_frontend_nodes.py @@ -31,9 +31,9 @@ def test_template_field_defaults(sample_template_field: TemplateField): assert sample_template_field.is_list is False assert sample_template_field.show is True assert sample_template_field.multiline is False - assert sample_template_field.value is None + assert sample_template_field.value == "" assert sample_template_field.file_types == [] - assert sample_template_field.file_path is None + assert sample_template_field.file_path == "" assert sample_template_field.password is False assert sample_template_field.name == "test_field"