Skip to content

Commit

Permalink
chore: refactor of template, frontend_node and field
Browse files Browse the repository at this point in the history
This will improve the readability of the code and maintainability.
  • Loading branch information
ogabrielluiz committed May 27, 2023
1 parent 35dce22 commit 7da8c0e
Show file tree
Hide file tree
Showing 26 changed files with 1,066 additions and 968 deletions.
29 changes: 16 additions & 13 deletions src/backend/langflow/custom/customs.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
from langflow.template import nodes
from langflow.template import frontend_node

# These should always be instantiated
CUSTOM_NODES = {
"prompts": {"ZeroShotPrompt": nodes.ZeroShotPromptNode()},
"tools": {"PythonFunction": nodes.PythonFunctionNode(), "Tool": nodes.ToolNode()},
"prompts": {"ZeroShotPrompt": frontend_node.prompts.ZeroShotPromptNode()},
"tools": {
"PythonFunction": frontend_node.tools.PythonFunctionNode(),
"Tool": frontend_node.tools.ToolNode(),
},
"agents": {
"JsonAgent": nodes.JsonAgentNode(),
"CSVAgent": nodes.CSVAgentNode(),
"initialize_agent": nodes.InitializeAgentNode(),
"VectorStoreAgent": nodes.VectorStoreAgentNode(),
"VectorStoreRouterAgent": nodes.VectorStoreRouterAgentNode(),
"SQLAgent": nodes.SQLAgentNode(),
"JsonAgent": frontend_node.agents.JsonAgentNode(),
"CSVAgent": frontend_node.agents.CSVAgentNode(),
"initialize_agent": frontend_node.agents.InitializeAgentNode(),
"VectorStoreAgent": frontend_node.agents.VectorStoreAgentNode(),
"VectorStoreRouterAgent": frontend_node.agents.VectorStoreRouterAgentNode(),
"SQLAgent": frontend_node.agents.SQLAgentNode(),
},
"utilities": {
"SQLDatabase": nodes.SQLDatabaseNode(),
"SQLDatabase": frontend_node.agents.SQLDatabaseNode(),
},
"chains": {
"SeriesCharacterChain": nodes.SeriesCharacterChainNode(),
"TimeTravelGuideChain": nodes.TimeTravelGuideChainNode(),
"MidJourneyPromptChain": nodes.MidJourneyPromptChainNode(),
"SeriesCharacterChain": frontend_node.chains.SeriesCharacterChainNode(),
"TimeTravelGuideChain": frontend_node.chains.TimeTravelGuideChainNode(),
"MidJourneyPromptChain": frontend_node.chains.MidJourneyPromptChainNode(),
},
}

Expand Down
4 changes: 3 additions & 1 deletion src/backend/langflow/interface/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

from pydantic import BaseModel

from langflow.template.base import FrontendNode, Template, TemplateField
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.logger import logger

# Assuming necessary imports for Field, Template, and FrontendNode classes
Expand Down
2 changes: 1 addition & 1 deletion src/backend/langflow/interface/chains/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import chain_type_to_cls_dict
from langflow.settings import settings
from langflow.template.nodes import ChainFrontendNode
from langflow.template.frontend_node.chains import ChainFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class

Expand Down
4 changes: 2 additions & 2 deletions src/backend/langflow/interface/embeddings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import embedding_type_to_cls_dict
from langflow.settings import settings
from langflow.template.base import FrontendNode
from langflow.template.nodes import EmbeddingFrontendNode
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class

Expand Down
2 changes: 1 addition & 1 deletion src/backend/langflow/interface/llms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import llm_type_to_cls_dict
from langflow.settings import settings
from langflow.template.nodes import LLMFrontendNode
from langflow.template.frontend_node.llms import LLMFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class

Expand Down
4 changes: 2 additions & 2 deletions src/backend/langflow/interface/memories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import memory_type_to_cls_dict
from langflow.settings import settings
from langflow.template.base import FrontendNode
from langflow.template.nodes import MemoryFrontendNode
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.frontend_node.memories import MemoryFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class

Expand Down
2 changes: 1 addition & 1 deletion src/backend/langflow/interface/prompts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.settings import settings
from langflow.template.nodes import PromptFrontendNode
from langflow.template.frontend_node.prompts import PromptFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_class

Expand Down
3 changes: 2 additions & 1 deletion src/backend/langflow/interface/tools/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
)
from langflow.interface.tools.util import get_tool_params
from langflow.settings import settings
from langflow.template.base import Template, TemplateField
from langflow.template.field.base import TemplateField
from langflow.template.template.base import Template
from langflow.utils import util
from langflow.utils.util import build_template_from_class

Expand Down
2 changes: 1 addition & 1 deletion src/backend/langflow/interface/vector_store/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from langflow.interface.base import LangChainTypeCreator
from langflow.interface.importing.utils import import_class
from langflow.settings import settings
from langflow.template.nodes import VectorStoreFrontendNode
from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode
from langflow.utils.logger import logger
from langflow.utils.util import build_template_from_method

Expand Down
254 changes: 0 additions & 254 deletions src/backend/langflow/template/base.py
Original file line number Diff line number Diff line change
@@ -1,255 +1 @@
import re
from abc import ABC
from typing import Any, Callable, List, Optional, Union

from pydantic import BaseModel

from langflow.template.constants import FORCE_SHOW_FIELDS
from langflow.utils import constants


class TemplateFieldCreator(BaseModel, ABC):
field_type: str = "str"
required: bool = False
placeholder: str = ""
is_list: bool = False
show: bool = True
multiline: bool = False
value: Any = None
suffixes: list[str] = []
fileTypes: list[str] = []
file_types: list[str] = []
content: Union[str, None] = None
password: bool = False
options: list[str] = []
name: str = ""
display_name: Optional[str] = None
advanced: bool = False

def to_dict(self):
result = self.dict()
# Remove key if it is None
for key in list(result.keys()):
if result[key] is None or result[key] == []:
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["content"] = self.content
return result


class TemplateField(TemplateFieldCreator):
pass


class Template(BaseModel):
type_name: str
fields: list[TemplateField]

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)

def to_dict(self, format_field_func=None):
self.process_fields(self.type_name, format_field_func)
result = {field.name: field.to_dict() for field in self.fields}
result["_type"] = self.type_name # type: ignore
return result


class FrontendNode(BaseModel):
template: Template
description: str
base_classes: List[str]
name: str = ""

def to_dict(self) -> dict:
return {
self.name: {
"template": self.template.to_dict(self.format_field),
"description": self.description,
"base_classes": self.base_classes,
}
}

@staticmethod
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
"""Formats a given field based on its attributes and value."""
SPECIAL_FIELD_HANDLERS = {
"allowed_tools": lambda field: "Tool",
"max_value_length": lambda field: "int",
}

key = field.name
value = field.to_dict()
_type = value["type"]

_type = FrontendNode.remove_optional(_type)
_type, is_list = FrontendNode.check_for_list_type(_type)
field.is_list = is_list or field.is_list
_type = FrontendNode.replace_mapping_with_dict(_type)
_type = FrontendNode.handle_union_type(_type)

field.field_type = FrontendNode.handle_special_field(
field, key, _type, SPECIAL_FIELD_HANDLERS
)
field.field_type = FrontendNode.handle_dict_type(field, _type)
field.show = FrontendNode.should_show_field(key, field.required)
field.password = FrontendNode.should_be_password(key, field.show)
field.multiline = FrontendNode.should_be_multiline(key)

FrontendNode.replace_default_value(field, value)
FrontendNode.handle_specific_field_values(field, key, name)
FrontendNode.handle_kwargs_field(field)
FrontendNode.handle_api_key_field(field, key)

@staticmethod
def remove_optional(_type: str) -> str:
"""Removes 'Optional' wrapper from the type if present."""
return re.sub(r"Optional\[(.*)\]", r"\1", _type)

@staticmethod
def check_for_list_type(_type: str) -> tuple:
"""Checks for list type and returns the modified type and a boolean indicating if it's a list."""
is_list = "List" in _type or "Sequence" in _type
if is_list:
_type = re.sub(r"(List|Sequence)\[(.*)\]", r"\2", _type)
return _type, is_list

@staticmethod
def replace_mapping_with_dict(_type: str) -> str:
"""Replaces 'Mapping' with 'dict'."""
return _type.replace("Mapping", "dict")

@staticmethod
def handle_union_type(_type: str) -> str:
"""Simplifies the 'Union' type to the first type in the Union."""
if "Union" in _type:
_type = _type.replace("Union[", "")[:-1]
_type = _type.split(",")[0]
_type = _type.replace("]", "").replace("[", "")
return _type

@staticmethod
def handle_special_field(
field, key: str, _type: str, SPECIAL_FIELD_HANDLERS
) -> str:
"""Handles special field by using the respective handler if present."""
handler = SPECIAL_FIELD_HANDLERS.get(key)
return handler(field) if handler else _type

@staticmethod
def handle_dict_type(field: TemplateField, _type: str) -> str:
"""Handles 'dict' type by replacing it with 'code' or 'file' based on the field name."""
if "dict" in _type.lower():
if field.name == "dict_":
field.field_type = "file"
field.suffixes = [".json", ".yaml", ".yml"]
field.file_types = ["json", "yaml", "yml"]
else:
field.field_type = "code"
return _type

@staticmethod
def replace_default_value(field: TemplateField, value: dict) -> None:
"""Replaces default value with actual value if 'default' is present in value."""
if "default" in value:
field.value = value["default"]

@staticmethod
def handle_specific_field_values(
field: TemplateField, key: str, name: Optional[str] = None
) -> None:
"""Handles specific field values for certain fields."""
if key == "headers":
field.value = """{'Authorization':
'Bearer <token>'}"""
if name == "OpenAI" and key == "model_name":
field.options = constants.OPENAI_MODELS
field.is_list = True
elif name == "ChatOpenAI" and key == "model_name":
field.options = constants.CHAT_OPENAI_MODELS
field.is_list = True
if "api_key" in key and "OpenAI" in str(name):
field.display_name = "OpenAI API Key"
field.required = False
if field.value is None:
field.value = ""

@staticmethod
def handle_kwargs_field(field: TemplateField) -> None:
"""Handles kwargs field by setting certain attributes."""
if "kwargs" in field.name.lower():
field.advanced = True
field.required = False
field.show = False

@staticmethod
def handle_api_key_field(field: TemplateField, key: str) -> None:
"""Handles api key field by setting certain attributes."""
if "api" in key.lower() and "key" in key.lower():
field.required = False
field.advanced = False

@staticmethod
def should_show_field(key: str, required: bool) -> bool:
"""Determines whether the field should be shown."""
return (
(required and key not in ["input_variables"])
or key in FORCE_SHOW_FIELDS
or "api" in key
or ("key" in key and "input" not in key and "output" not in key)
)

@staticmethod
def should_be_password(key: str, show: bool) -> bool:
"""Determines whether the field should be a password field."""
return (
any(text in key.lower() for text in {"password", "token", "api", "key"})
and show
)

@staticmethod
def should_be_multiline(key: str) -> bool:
"""Determines whether the field should be multiline."""
return key in {
"suffix",
"prefix",
"template",
"examples",
"code",
"headers",
"description",
}

@staticmethod
def replace_dict_with_code_or_file(
field: TemplateField, _type: str, key: str
) -> str:
"""Replaces 'dict' type with 'code' or 'file'."""
if "dict" in _type.lower():
if key == "dict_":
field.field_type = "file"
field.suffixes = [".json", ".yaml", ".yml"]
field.file_types = ["json", "yaml", "yml"]
else:
field.field_type = "code"
return field.field_type

@staticmethod
def set_field_default_value(field: TemplateField, value: dict, key: str) -> None:
"""Sets the field value with the default value if present."""
if "default" in value:
field.value = value["default"]
if key == "headers":
field.value = """{'Authorization': 'Bearer <token>'}"""
Empty file.
Loading

0 comments on commit 7da8c0e

Please sign in to comment.