feat: implement file type

This commit is contained in:
Gabriel Almeida 2023-03-30 19:39:44 -03:00
commit 2db74fc30e
18 changed files with 149 additions and 61 deletions

View file

@ -10,7 +10,9 @@ class AgentCreator(LangChainTypeCreator):
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
return loading.AGENT_TO_CLASS if self.type_dict is None:
self.type_dict = loading.AGENT_TO_CLASS
return self.type_dict
def get_signature(self, name: str) -> Dict | None: def get_signature(self, name: str) -> Dict | None:
try: try:

View file

@ -1,44 +1,10 @@
from typing import Callable, Optional
from langchain import LLMChain from langchain import LLMChain
from langchain.agents import AgentExecutor, ZeroShotAgent from langchain.agents import AgentExecutor, ZeroShotAgent
from langflow.utils import validate
from pydantic import BaseModel, validator
from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX from langchain.agents.agent_toolkits.json.prompt import JSON_PREFIX, JSON_SUFFIX
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit from langchain.agents.agent_toolkits.json.toolkit import JsonToolkit
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.schema import BaseLanguageModel from langchain.schema import BaseLanguageModel
from pydantic import BaseModel
class Function(BaseModel):
code: str
function: Optional[Callable] = None
imports: Optional[str] = None
# Eval code and store the function
def __init__(self, **data):
super().__init__(**data)
# Validate the function
@validator("code")
def validate_func(cls, v):
try:
validate.eval_function(v)
except Exception as e:
raise e
return v
def get_function(self):
"""Get the function"""
function_name = validate.extract_function_name(self.code)
return validate.create_function(self.code, function_name)
class PythonFunction(Function):
"""Python function"""
code: str
class JsonAgent(BaseModel): class JsonAgent(BaseModel):

View file

@ -9,11 +9,14 @@ from langflow.template.template import Template, Field, FrontendNode
class LangChainTypeCreator(BaseModel, ABC): class LangChainTypeCreator(BaseModel, ABC):
type_name: str type_name: str
type_dict: Optional[Dict] = None
@property @property
@abstractmethod @abstractmethod
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
pass if self.type_dict is None:
raise NotImplementedError
return self.type_dict
@abstractmethod @abstractmethod
def get_signature(self, name: str) -> Optional[Dict[Any, Any]]: def get_signature(self, name: str) -> Optional[Dict[Any, Any]]:
@ -27,7 +30,10 @@ class LangChainTypeCreator(BaseModel, ABC):
result: Dict = {self.type_name: {}} result: Dict = {self.type_name: {}}
for name in self.to_list(): for name in self.to_list():
result[self.type_name][name] = self.frontend_node(name).to_dict() # frontend_node.to_dict() returns a dict with the following structure:
# {name: {template: {fields}, description: str}}
# so we should update the result dict
result[self.type_name].update(self.frontend_node(name).to_dict())
return result return result
@ -45,6 +51,9 @@ class LangChainTypeCreator(BaseModel, ABC):
show=value.get("show", True), show=value.get("show", True),
multiline=value.get("multiline", False), multiline=value.get("multiline", False),
value=value.get("value", None), value=value.get("value", None),
suffixes=value.get("suffixes", []),
file_types=value.get("fileTypes", []),
content=value.get("content", None),
) )
for key, value in signature["template"].items() for key, value in signature["template"].items()
if key != "_type" if key != "_type"
@ -52,7 +61,7 @@ class LangChainTypeCreator(BaseModel, ABC):
template = Template(type_name=name, fields=fields) template = Template(type_name=name, fields=fields)
return FrontendNode( return FrontendNode(
template=template, template=template,
description=signature["description"], description=signature.get("description", ""),
base_classes=signature["base_classes"], base_classes=signature["base_classes"],
name=name, name=name,
) )

View file

@ -12,7 +12,9 @@ class ChainCreator(LangChainTypeCreator):
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
return chains_loading.type_to_loader_dict if self.type_dict is None:
self.type_dict = chains_loading.type_to_loader_dict
return self.type_dict
def get_signature(self, name: str) -> Dict | None: def get_signature(self, name: str) -> Dict | None:
try: try:

View file

@ -0,0 +1,37 @@
from langflow.utils import validate
from pydantic import BaseModel, validator
from typing import Callable, Optional
class Function(BaseModel):
code: str
function: Optional[Callable] = None
imports: Optional[str] = None
# Eval code and store the function
def __init__(self, **data):
super().__init__(**data)
# Validate the function
@validator("code")
def validate_func(cls, v):
try:
validate.eval_function(v)
except Exception as e:
raise e
return v
def get_function(self):
"""Get the function"""
function_name = validate.extract_function_name(self.code)
return validate.create_function(self.code, function_name)
class PythonFunction(Function):
"""Python function"""
code: str

View file

@ -3,6 +3,7 @@ from langflow.interface.chains.base import chain_creator
from langflow.interface.llms.base import llm_creator from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator from langflow.interface.prompts.base import prompt_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator from langflow.interface.tools.base import tool_creator
@ -14,6 +15,7 @@ def get_type_dict():
"tools": tool_creator.to_list(), "tools": tool_creator.to_list(),
"chains": chain_creator.to_list(), "chains": chain_creator.to_list(),
"memory": memory_creator.to_list(), "memory": memory_creator.to_list(),
"toolkits": toolkits_creator.to_list(),
} }

View file

@ -10,7 +10,9 @@ class LLMCreator(LangChainTypeCreator):
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
return llm_type_to_cls_dict if self.type_dict is None:
self.type_dict = llm_type_to_cls_dict
return self.type_dict
def get_signature(self, name: str) -> Dict | None: def get_signature(self, name: str) -> Dict | None:
"""Get the signature of an llm.""" """Get the signature of an llm."""

View file

@ -10,7 +10,9 @@ class MemoryCreator(LangChainTypeCreator):
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
return memory_type_to_cls_dict if self.type_dict is None:
self.type_dict = memory_type_to_cls_dict
return self.type_dict
def get_signature(self, name: str) -> Dict | None: def get_signature(self, name: str) -> Dict | None:
"""Get the signature of a memory.""" """Get the signature of a memory."""

View file

@ -11,7 +11,9 @@ class PromptCreator(LangChainTypeCreator):
@property @property
def type_to_loader_dict(self) -> Dict: def type_to_loader_dict(self) -> Dict:
return loading.type_to_loader_dict if self.type_dict is None:
self.type_dict = loading.type_to_loader_dict
return self.type_dict
def get_signature(self, name: str) -> Dict | None: def get_signature(self, name: str) -> Dict | None:
try: try:

View file

@ -0,0 +1,34 @@
from langflow.interface.base import LangChainTypeCreator
from langflow.utils.util import build_template_from_class
from typing import Dict, List
from langchain.agents import agent_toolkits
from langflow.interface.importing.utils import import_class
class ToolkitCreator(LangChainTypeCreator):
type_name: str = "toolkits"
@property
def type_to_loader_dict(self) -> Dict:
if self.type_dict is None:
self.type_dict = {
toolkit_name: import_class(
f"langchain.agents.agent_toolkits.{toolkit_name}"
)
# if toolkit_name is not lower case it is a class
for toolkit_name in agent_toolkits.__all__
if not toolkit_name.islower()
}
return self.type_dict
def get_signature(self, name: str) -> Dict | None:
try:
return build_template_from_class(name, self.type_to_loader_dict)
except ValueError as exc:
raise ValueError("Prompt not found") from exc
def to_list(self) -> List[str]:
return list(self.type_to_loader_dict.keys())
toolkits_creator = ToolkitCreator()

View file

@ -1,6 +1,9 @@
from langflow.custom import customs from langflow.custom import customs
from langflow.interface.tools.constants import ALL_TOOLS_NAMES, CUSTOM_TOOLS from langflow.interface.tools.constants import (
import langflow.interface.tools.util ALL_TOOLS_NAMES,
CUSTOM_TOOLS,
OTHER_TOOLS,
)
from langflow.template.template import Field, Template from langflow.template.template import Field, Template
from langflow.utils import util from langflow.utils import util
from langflow.settings import settings from langflow.settings import settings
@ -12,7 +15,11 @@ from langchain.agents.load_tools import (
_EXTRA_OPTIONAL_TOOLS, _EXTRA_OPTIONAL_TOOLS,
_LLM_TOOLS, _LLM_TOOLS,
) )
from langflow.interface.tools.util import get_tools_dict from langflow.interface.tools.util import (
get_tool_by_name,
get_tools_dict,
get_tool_params,
)
class ToolCreator(LangChainTypeCreator): class ToolCreator(LangChainTypeCreator):
@ -32,9 +39,7 @@ class ToolCreator(LangChainTypeCreator):
base_classes = ["Tool"] base_classes = ["Tool"]
all_tools = {} all_tools = {}
for tool in self.type_to_loader_dict.keys(): for tool in self.type_to_loader_dict.keys():
if tool_params := langflow.interface.tools.util.get_tool_params( if tool_params := get_tool_params(get_tool_by_name(tool)):
langflow.interface.tools.util.get_tool_by_name(tool)
):
tool_name = tool_params.get("name") or str(tool) tool_name = tool_params.get("name") or str(tool)
all_tools[tool_name] = {"type": tool, "params": tool_params} all_tools[tool_name] = {"type": tool, "params": tool_params}
@ -67,6 +72,13 @@ class ToolCreator(LangChainTypeCreator):
value="", value="",
multiline=True, multiline=True,
), ),
"dict_": Field(
field_type="file",
required=True,
is_list=False,
show=True,
value="",
),
} }
tool_type: str = all_tools[name]["type"] # type: ignore tool_type: str = all_tools[name]["type"] # type: ignore
@ -89,6 +101,8 @@ class ToolCreator(LangChainTypeCreator):
base_classes = ["function"] base_classes = ["function"]
if node := customs.get_custom_nodes("tools").get(tool_type): if node := customs.get_custom_nodes("tools").get(tool_type):
return node return node
elif tool_type in OTHER_TOOLS:
params = all_tools[name]["params"] # type: ignore
else: else:
params = [] params = []
@ -108,9 +122,7 @@ class ToolCreator(LangChainTypeCreator):
template = Template(fields=fields, type_name=tool_type) template = Template(fields=fields, type_name=tool_type)
tool_params = langflow.interface.tools.util.get_tool_params( tool_params = get_tool_params(get_tool_by_name(tool_type))
langflow.interface.tools.util.get_tool_by_name(tool_type)
)
if tool_params is None: if tool_params is None:
tool_params = {} tool_params = {}
return { return {
@ -125,9 +137,11 @@ class ToolCreator(LangChainTypeCreator):
tools = [] tools = []
for tool in ALL_TOOLS_NAMES: for tool in ALL_TOOLS_NAMES:
tool_params = langflow.interface.tools.util.get_tool_params( tool_params = get_tool_params(get_tool_by_name(tool))
langflow.interface.tools.util.get_tool_by_name(tool)
) if tool_params and not tool_params.get("name"):
tool_params["name"] = tool
if tool_params and ( if tool_params and (
tool_params.get("name") in settings.tools tool_params.get("name") in settings.tools
or (tool_params.get("name") and settings.dev) or (tool_params.get("name") and settings.dev)

View file

@ -1,6 +1,6 @@
from langchain.agents.load_tools import get_all_tool_names from langchain.agents.load_tools import get_all_tool_names
from langchain.agents import Tool from langchain.agents import Tool
from langflow.interface.custom_types import PythonFunction from langflow.interface.custom.types import PythonFunction
from langchain.tools.json.tool import JsonSpec from langchain.tools.json.tool import JsonSpec

View file

@ -3,6 +3,7 @@ from langflow.interface.llms.base import llm_creator
from langflow.interface.memories.base import memory_creator from langflow.interface.memories.base import memory_creator
from langflow.interface.prompts.base import prompt_creator from langflow.interface.prompts.base import prompt_creator
from langflow.interface.chains.base import chain_creator from langflow.interface.chains.base import chain_creator
from langflow.interface.toolkits.base import toolkits_creator
from langflow.interface.tools.base import tool_creator from langflow.interface.tools.base import tool_creator
@ -30,6 +31,7 @@ def build_langchain_types_dict():
llm_creator, llm_creator,
memory_creator, memory_creator,
tool_creator, tool_creator,
toolkits_creator,
] ]
all_types = {} all_types = {}

View file

@ -1,4 +1,4 @@
from typing import Any from typing import Any, Union
from pydantic import BaseModel from pydantic import BaseModel
@ -10,6 +10,9 @@ class Field(BaseModel):
show: bool = True show: bool = True
multiline: bool = False multiline: bool = False
value: Any = None value: Any = None
suffixes: list[str] = []
file_types: list[str] = []
content: Union[str, None] = None
# _name will be used to store the name of the field # _name will be used to store the name of the field
# in the template # in the template
name: str = "" name: str = ""
@ -18,10 +21,16 @@ class Field(BaseModel):
result = self.dict() result = self.dict()
# Remove key if it is None # Remove key if it is None
for key in list(result.keys()): for key in list(result.keys()):
if result[key] is None: if result[key] is None or result[key] == []:
del result[key] del result[key]
result["type"] = result.pop("field_type") result["type"] = result.pop("field_type")
result["list"] = result.pop("is_list") 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 return result

View file

@ -272,6 +272,8 @@ def format_dict(d, name: Optional[str] = None):
# Change type from str to Tool # Change type from str to Tool
value["type"] = "Tool" if key in ["allowed_tools"] else _type value["type"] = "Tool" if key in ["allowed_tools"] else _type
value["type"] = "int" if key in ["max_value_length"] else value["type"]
# Show or not field # Show or not field
value["show"] = bool( value["show"] = bool(
(value["required"] and key not in ["input_variables"]) (value["required"] and key not in ["input_variables"])
@ -307,7 +309,10 @@ def format_dict(d, name: Optional[str] = None):
if "dict" in value["type"].lower(): if "dict" in value["type"].lower():
value["type"] = "code" value["type"] = "code"
value["file"] = key in ["dict_"] if key == "dict_":
value["type"] = "file"
value["suffixes"] = [".json", ".yaml", ".yml"]
value["fileTypes"] = ["json", "yaml", "yml"]
# Replace default value with actual value # Replace default value with actual value
if "default" in value: if "default" in value:

View file

@ -1,5 +1,5 @@
# Test this: # Test this:
from langflow.interface.custom_types import PythonFunction from langflow.interface.custom.types import PythonFunction
from langflow.utils import constants from langflow.utils import constants
import pytest import pytest