diff --git a/src/backend/langflow/graph/utils.py b/src/backend/langflow/graph/utils.py new file mode 100644 index 000000000..89e3a0f48 --- /dev/null +++ b/src/backend/langflow/graph/utils.py @@ -0,0 +1,26 @@ +import base64 +import json +from typing import Dict + +import yaml + + +def load_dict(file_name, file_content, accepted_types) -> Dict: + """Load a file from a string.""" + # Check if the file is accepted + if not any(file_name.endswith(suffix) for suffix in accepted_types): + raise ValueError(f"File {file_name} is not accepted") + # Get the suffix + suffix = file_name.split(".")[-1] + # file_content == 'data:application/x-yaml;base64,b3BlbmFwaTogIjMuMC4wIg...' + data = file_content.split(",")[1] + decoded_bytes = base64.b64decode(data) + + # Convert the bytes object to a string + decoded_string = decoded_bytes.decode("utf-8") + if suffix == "json": + # Return the json content + return json.loads(decoded_string) + elif suffix in ["yaml", "yml"]: + # Return the yaml content + return yaml.safe_load(decoded_string) diff --git a/src/backend/langflow/interface/agents/base.py b/src/backend/langflow/interface/agents/base.py index e474ba1a6..35df3bee6 100644 --- a/src/backend/langflow/interface/agents/base.py +++ b/src/backend/langflow/interface/agents/base.py @@ -1,10 +1,12 @@ -from langchain.agents import loading -from langflow.custom.customs import get_custom_nodes -from langflow.interface.base import LangChainTypeCreator -from langflow.utils.util import build_template_from_class -from langflow.settings import settings from typing import Dict, List -from langflow.interface.agents.custom import JsonAgent + +from langchain.agents import loading + +from langflow.custom.customs import get_custom_nodes +from langflow.interface.agents.custom import CUSTOM_AGENTS +from langflow.interface.base import LangChainTypeCreator +from langflow.settings import settings +from langflow.utils.util import build_template_from_class class AgentCreator(LangChainTypeCreator): @@ -15,7 +17,8 @@ class AgentCreator(LangChainTypeCreator): if self.type_dict is None: self.type_dict = loading.AGENT_TO_CLASS # Add JsonAgent to the list of agents - self.type_dict["JsonAgent"] = JsonAgent + for name, agent in CUSTOM_AGENTS.items(): + self.type_dict[name] = agent return self.type_dict def get_signature(self, name: str) -> Dict | None: diff --git a/src/backend/langflow/interface/listing.py b/src/backend/langflow/interface/listing.py index c2462d15e..b11b3cef9 100644 --- a/src/backend/langflow/interface/listing.py +++ b/src/backend/langflow/interface/listing.py @@ -5,6 +5,7 @@ from langflow.interface.memories.base import memory_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.wrappers.base import wrapper_creator def get_type_dict(): @@ -16,6 +17,7 @@ def get_type_dict(): "chains": chain_creator.to_list(), "memory": memory_creator.to_list(), "toolkits": toolkits_creator.to_list(), + "wrappers": wrapper_creator.to_list(), } diff --git a/src/backend/langflow/interface/loading.py b/src/backend/langflow/interface/loading.py index d9ed2552a..adc01c933 100644 --- a/src/backend/langflow/interface/loading.py +++ b/src/backend/langflow/interface/loading.py @@ -1,19 +1,16 @@ import json from typing import Any, Dict, Optional +from langchain.agents import ZeroShotAgent +from langchain.agents import agent as agent_module from langchain.agents.agent import AgentExecutor +from langchain.agents.agent_toolkits.base import BaseToolkit from langchain.agents.load_tools import ( _BASE_TOOLS, _EXTRA_LLM_TOOLS, _EXTRA_OPTIONAL_TOOLS, _LLM_TOOLS, ) -from langchain.agents import agent as agent_module - - -from langflow.interface.importing.utils import import_by_type - -from langchain.agents import ZeroShotAgent from langchain.agents.loading import load_agent_from_config from langchain.agents.tools import Tool from langchain.callbacks.base import BaseCallbackManager @@ -21,20 +18,29 @@ from langchain.chains.loading import load_chain_from_config from langchain.llms.base import BaseLLM from langchain.llms.loading import load_llm_from_config +from langflow.interface.agents.custom import CUSTOM_AGENTS +from langflow.interface.importing.utils import import_by_type +from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.types import get_type_list from langflow.utils import payload, util, validate def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: """Instantiate class from module type and key, and params""" + if node_type in CUSTOM_AGENTS: + if custom_agent := CUSTOM_AGENTS.get(node_type): + return custom_agent.initialize(**params) + class_object = import_by_type(_type=base_type, name=node_type) + if base_type == "agents": # We need to initialize it differently - allowed_tools = params["allowed_tools"] - llm_chain = params["llm_chain"] - return load_agent_executor(class_object, allowed_tools, llm_chain) - elif base_type == "tools" or node_type != "ZeroShotPrompt": - return class_object(**params) + return load_agent_executor(class_object, params) + elif node_type == "ZeroShotPrompt": + if "tools" not in params: + params["tools"] = [] + return ZeroShotAgent.create_prompt(**params) + elif node_type == "PythonFunction": # If the node_type is "PythonFunction" # we need to get the function from the params @@ -45,10 +51,14 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: if isinstance(function_string, str): return validate.eval_function(function_string) raise ValueError("Function should be a string") + elif base_type == "toolkits": + loaded_toolkit = class_object(**params) + # Check if node_type has a loader + if toolkits_creator.has_create_function(node_type): + return load_toolkits_executor(node_type, loaded_toolkit, params) + return loaded_toolkit else: - if "tools" not in params: - params["tools"] = [] - return ZeroShotAgent.create_prompt(**params) + return class_object(**params) def load_flow_from_json(path: str): @@ -122,10 +132,10 @@ def load_agent_executor_from_config( ) -def load_agent_executor( - agent_class: type[agent_module.Agent], allowed_tools, llm_chain, **kwargs -): +def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs): """Load agent executor from agent class, tools and chain""" + allowed_tools = params["allowed_tools"] + llm_chain = params["llm_chain"] tool_names = [tool.name for tool in allowed_tools] agent = agent_class(allowed_tools=tool_names, llm_chain=llm_chain) return AgentExecutor.from_agent_and_tools( @@ -135,6 +145,14 @@ def load_agent_executor( ) +def load_toolkits_executor(node_type: str, toolkit: BaseToolkit, params: dict): + create_function = toolkits_creator.get_create_function(node_type) + llm = params.get("llm", None) + if llm: + return create_function(llm=llm, toolkit=toolkit) + return + + def load_tools_from_config(tool_list: list[dict]) -> list: """Load tools based on a config list. diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 69d259a59..2db27bfc3 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -1,8 +1,8 @@ from langflow.interface.agents.base import agent_creator +from langflow.interface.chains.base import chain_creator from langflow.interface.llms.base import llm_creator from langflow.interface.memories.base import memory_creator from langflow.interface.prompts.base import prompt_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.wrappers.base import wrapper_creator diff --git a/src/backend/langflow/template/fields.py b/src/backend/langflow/template/fields.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/conftest.py b/tests/conftest.py index 5fe1de280..3c9837957 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ from pathlib import Path + import pytest from fastapi.testclient import TestClient