From 18b3fa6c3404bd6e5b79db471f8dfa5b82142e5f Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Thu, 30 Mar 2023 18:13:35 -0300 Subject: [PATCH] refac: listing and signature for tools moved --- src/backend/langflow/api/list_endpoints.py | 58 ----- src/backend/langflow/api/signature.py | 63 ------ src/backend/langflow/config.yaml | 5 - src/backend/langflow/graph/base.py | 7 +- src/backend/langflow/graph/graph.py | 2 +- .../langflow/interface/importing/utils.py | 2 +- src/backend/langflow/interface/listing.py | 129 ++--------- src/backend/langflow/interface/signature.py | 204 ------------------ src/backend/langflow/interface/tools/base.py | 21 +- .../langflow/interface/tools/constants.py | 11 + src/backend/langflow/interface/tools/util.py | 122 +++++++++++ src/backend/langflow/main.py | 4 - src/backend/langflow/utils/util.py | 125 +---------- tests/test_endpoints.py | 2 +- 14 files changed, 169 insertions(+), 586 deletions(-) delete mode 100644 src/backend/langflow/api/list_endpoints.py delete mode 100644 src/backend/langflow/api/signature.py delete mode 100644 src/backend/langflow/interface/signature.py create mode 100644 src/backend/langflow/interface/tools/constants.py create mode 100644 src/backend/langflow/interface/tools/util.py diff --git a/src/backend/langflow/api/list_endpoints.py b/src/backend/langflow/api/list_endpoints.py deleted file mode 100644 index 15946a2db..000000000 --- a/src/backend/langflow/api/list_endpoints.py +++ /dev/null @@ -1,58 +0,0 @@ -from fastapi import APIRouter - -from langflow.interface.listing import list_type - -# build router -router = APIRouter( - prefix="/list", - tags=["list"], -) - - -@router.get("/") -def read_items(): - """List all components""" - return [ - "chains", - "agents", - "prompts", - "llms", - "tools", - ] - - -@router.get("/chains") -def list_chains(): - """List all chain types""" - return list_type("chains") - - -@router.get("/agents") -def list_agents(): - """List all agent types""" - # return list(agents.loading.AGENT_TO_CLASS.keys()) - return list_type("agents") - - -@router.get("/prompts") -def list_prompts(): - """List all prompt types""" - return list_type("prompts") - - -@router.get("/llms") -def list_llms(): - """List all llm types""" - return list_type("llms") - - -@router.get("/memories") -def list_memories(): - """List all memory types""" - return list_type("memories") - - -@router.get("/tools") -def list_tools(): - """List all load tools""" - return list_type("tools") diff --git a/src/backend/langflow/api/signature.py b/src/backend/langflow/api/signature.py deleted file mode 100644 index 96b654dbe..000000000 --- a/src/backend/langflow/api/signature.py +++ /dev/null @@ -1,63 +0,0 @@ -from fastapi import APIRouter, HTTPException - -from langflow.interface.signature import get_signature - -# build router -router = APIRouter( - prefix="/signatures", - tags=["signatures"], -) - - -@router.get("/chain") -def get_chain(name: str): - """Get the signature of a chain.""" - try: - return get_signature(name, "chains") - except ValueError as exc: - raise HTTPException(status_code=404, detail="Chain not found") from exc - - -@router.get("/agent") -def get_agent(name: str): - """Get the signature of an agent.""" - try: - return get_signature(name, "agents") - except ValueError as exc: - raise HTTPException(status_code=404, detail="Agent not found") from exc - - -@router.get("/prompt") -def get_prompt(name: str): - """Get the signature of a prompt.""" - try: - return get_signature(name, "prompts") - except ValueError as exc: - raise HTTPException(status_code=404, detail="Prompt not found") from exc - - -@router.get("/llm") -def get_llm(name: str): - """Get the signature of an llm.""" - try: - return get_signature(name, "llms") - except ValueError as exc: - raise HTTPException(status_code=404, detail="LLM not found") from exc - - -@router.get("/memory") -def get_memory(name: str): - """Get the signature of a memory.""" - try: - return get_signature(name, "memories") - except ValueError as exc: - raise HTTPException(status_code=404, detail="Memory not found") from exc - - -@router.get("/tool") -def get_tool(name: str): - """Get the signature of a tool.""" - try: - return get_signature(name, "tools") - except ValueError as exc: - raise HTTPException(status_code=404, detail="Tool not found") from exc diff --git a/src/backend/langflow/config.yaml b/src/backend/langflow/config.yaml index 4e3c3e6b7..8a7d6c00b 100644 --- a/src/backend/langflow/config.yaml +++ b/src/backend/langflow/config.yaml @@ -24,9 +24,4 @@ tools: - PythonFunction - JsonSpec -memories: - # - ConversationBufferMemory - - - dev: false diff --git a/src/backend/langflow/graph/base.py b/src/backend/langflow/graph/base.py index 1b1fb7459..ca0bdb823 100644 --- a/src/backend/langflow/graph/base.py +++ b/src/backend/langflow/graph/base.py @@ -6,8 +6,9 @@ from copy import deepcopy import types from typing import Any, Dict, List -from langflow.interface.listing import ALL_TYPES_DICT, TOOLS_DICT +from langflow.interface.listing import ALL_TYPES_DICT from langflow.interface import loading +from langflow.interface.tools.base import tool_creator class Node: @@ -139,7 +140,7 @@ class Node: # and return the instance for base_type, value in ALL_TYPES_DICT.items(): if base_type == "tools": - value = TOOLS_DICT + value = tool_creator.type_to_loader_dict if self.node_type in value: self._built_object = loading.instantiate_class( @@ -208,5 +209,3 @@ class Edge: f"Edge(source={self.source.id}, target={self.target.id}, valid={self.valid}" f", matched_type={self.matched_type})" ) - - diff --git a/src/backend/langflow/graph/graph.py b/src/backend/langflow/graph/graph.py index de8469641..e7110e977 100644 --- a/src/backend/langflow/graph/graph.py +++ b/src/backend/langflow/graph/graph.py @@ -1,6 +1,6 @@ from typing import Dict, List, Union from langflow.utils import payload -from langflow.interface.listing import ALL_TOOLS_NAMES +from langflow.interface.tools.constants import ALL_TOOLS_NAMES from langflow.graph.base import Node, Edge from langflow.graph.nodes import ( diff --git a/src/backend/langflow/interface/importing/utils.py b/src/backend/langflow/interface/importing/utils.py index 5524e2d32..33ea6f7d9 100644 --- a/src/backend/langflow/interface/importing/utils.py +++ b/src/backend/langflow/interface/importing/utils.py @@ -8,7 +8,7 @@ from langchain.agents import Agent from langchain.chains.base import Chain from langchain.llms.base import BaseLLM from langchain.tools import BaseTool -from langflow.utils.util import get_tool_by_name +from langflow.interface.tools.util import get_tool_by_name def import_module(module_path: str) -> Any: diff --git a/src/backend/langflow/interface/listing.py b/src/backend/langflow/interface/listing.py index c67b3ab83..c46c272a5 100644 --- a/src/backend/langflow/interface/listing.py +++ b/src/backend/langflow/interface/listing.py @@ -1,126 +1,23 @@ -from langchain import agents, chains, prompts -from langchain.agents import agent_toolkits -from langchain import requests -from langflow.custom import customs -from langflow.interface.custom_lists import ( - llm_type_to_cls_dict, - memory_type_to_cls_dict, -) -from langflow.settings import settings -from langflow.utils import util -from langchain.agents.load_tools import get_all_tool_names -from langchain.agents import Tool -from langflow.interface.custom_types import JsonAgent, PythonFunction -from langchain.tools.json.tool import JsonSpec - -OTHER_TOOLS = {"JsonSpec": JsonSpec} -CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction} -TOOLS_DICT = util.get_tools_dict() -ALL_TOOLS_NAMES = set( - get_all_tool_names() + list(CUSTOM_TOOLS.keys()) + list(OTHER_TOOLS.keys()) -) +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.tools.base import tool_creator def get_type_dict(): return { - "chains": list_chain_types, - "agents": list_agents, - "prompts": list_prompts, - "llms": list_llms, - "tools": list_tools, - "memories": list_memories, - "toolkits": list_toolkis, - "wrappers": list_wrappers, + "agents": agent_creator.to_list(), + "prompts": prompt_creator.to_list(), + "llms": llm_creator.to_list(), + "tools": tool_creator.to_list(), + "chains": chain_creator.to_list(), + "memory": memory_creator.to_list(), } -def list_type(object_type: str): - """List all components""" - return get_type_dict().get(object_type, lambda: None)() - - -def list_wrappers(): - """List all wrapper types""" - return [requests.RequestsWrapper.__name__] - - -def list_agents(): - """List all agent types""" - return [ - agent.__name__ - for agent in agents.loading.AGENT_TO_CLASS.values() - if agent.__name__ in settings.agents or settings.dev - ] + [JsonAgent.__name__] - - -def list_toolkis(): - """List all toolkit types""" - return agent_toolkits.__all__ - - -def list_prompts(): - """List all prompt types""" - custom_prompts = customs.get_custom_nodes("prompts") - library_prompts = [ - prompt.__annotations__["return"].__name__ - for prompt in prompts.loading.type_to_loader_dict.values() - if prompt.__annotations__["return"].__name__ in settings.prompts or settings.dev - ] - return library_prompts + list(custom_prompts.keys()) - - -def list_tools(): - """List all load tools""" - - tools = [] - - for tool in ALL_TOOLS_NAMES: - tool_params = util.get_tool_params(util.get_tool_by_name(tool)) - - if "name" not in tool_params: - tool_params["name"] = tool - - if tool_params and ( - tool_params.get("name") in settings.tools - or (tool_params.get("name") and settings.dev) - ): - tools.append(tool_params["name"]) - - # Add Tool - custom_tools = customs.get_custom_nodes("tools") - return tools + list(custom_tools.keys()) - - -def list_llms(): - """List all llm types""" - return [ - llm.__name__ - for llm in llm_type_to_cls_dict.values() - if llm.__name__ in settings.llms or settings.dev - ] - - -def list_chain_types(): - """List all chain types""" - return [ - chain.__annotations__["return"].__name__ - for chain in chains.loading.type_to_loader_dict.values() - if chain.__annotations__["return"].__name__ in settings.chains or settings.dev - ] - - -def list_memories(): - """List all memory types""" - return [ - memory.__name__ - for memory in memory_type_to_cls_dict.values() - if memory.__name__ in settings.memories or settings.dev - ] - - -LANGCHAIN_TYPES_DICT = { - k: list_function() for k, list_function in get_type_dict().items() -} +LANGCHAIN_TYPES_DICT = get_type_dict() # Now we'll build a dict with Langchain types and ours diff --git a/src/backend/langflow/interface/signature.py b/src/backend/langflow/interface/signature.py deleted file mode 100644 index f9de437a7..000000000 --- a/src/backend/langflow/interface/signature.py +++ /dev/null @@ -1,204 +0,0 @@ -from typing import Any, Dict # noqa: F401 - -from langchain import agents, chains, prompts -from langchain.agents.load_tools import ( - _BASE_TOOLS, - _EXTRA_LLM_TOOLS, - _EXTRA_OPTIONAL_TOOLS, - _LLM_TOOLS, -) - -from langflow.custom import customs -from langflow.interface.custom_lists import ( - llm_type_to_cls_dict, - memory_type_to_cls_dict, - toolkit_type_to_cls_dict, - wrapper_type_to_cls_dict, -) - -from langflow.interface.listing import CUSTOM_TOOLS, ALL_TOOLS_NAMES -from langflow.template.template import Field, Template -from langflow.utils import util - - -def get_signature(name: str, object_type: str): - """Get the signature of an object.""" - return { - "toolkits": get_toolkit_signature, - "chains": get_chain_signature, - "agents": get_agent_signature, - "prompts": get_prompt_signature, - "llms": get_llm_signature, - # "memories": get_memory_signature, - "tools": get_tool_signature, - "wrappers": get_wrapper_signature, - }.get(object_type, lambda name: f"Invalid type: {name}")(name) - - -def get_toolkit_signature(name: str): - """Get the signature of a toolkit.""" - try: - if name.islower(): - ... - # return util.build_template_from_function( - # name, toolkit_type_to_loader_dict, add_function=True - # ) - else: - return util.build_template_from_class( - name, toolkit_type_to_cls_dict, add_function=True - ) - except ValueError as exc: - raise ValueError("Toolkit not found") from exc - - -def get_wrapper_signature(name: str): - """Get the signature of a wrapper.""" - try: - return util.build_template_from_class( - name, - wrapper_type_to_cls_dict, - ) - except ValueError as exc: - raise ValueError("Wrapper not found") from exc - - -def get_chain_signature(name: str): - """Get the chain type by signature.""" - try: - return util.build_template_from_function( - name, chains.loading.type_to_loader_dict, add_function=True - ) - - except ValueError as exc: - raise ValueError("Chain not found") from exc - - -def get_agent_signature(name: str): - """Get the signature of an agent.""" - try: - if name in customs.get_custom_nodes("agents").keys(): - return customs.get_custom_nodes("agents")[name] - return util.build_template_from_class( - name, agents.loading.AGENT_TO_CLASS, add_function=True - ) - except ValueError as exc: - raise ValueError("Agent not found") from exc - - -def get_prompt_signature(name: str): - """Get the signature of a prompt.""" - try: - if name in customs.get_custom_nodes("prompts").keys(): - return customs.get_custom_nodes("prompts")[name] - return util.build_template_from_function( - name, prompts.loading.type_to_loader_dict - ) - except ValueError as exc: - raise ValueError("Prompt not found") from exc - - -def get_llm_signature(name: str): - """Get the signature of an llm.""" - try: - return util.build_template_from_class(name, llm_type_to_cls_dict) - except ValueError as exc: - raise ValueError("LLM not found") from exc - - -def get_memory_signature(name: str): - """Get the signature of a memory.""" - try: - return util.build_template_from_class(name, memory_type_to_cls_dict) - except ValueError as exc: - raise ValueError("Memory not found") from exc - - -def get_tool_signature(name: str): - """Get the signature of a tool.""" - - NODE_INPUTS = ["llm", "func"] - base_classes = ["Tool"] - all_tools = {} - for tool in ALL_TOOLS_NAMES: - if tool_params := util.get_tool_params(util.get_tool_by_name(tool)): - tool_name = tool_params.get("name") or str(tool) - all_tools[tool_name] = {"type": tool, "params": tool_params} - - # Raise error if name is not in tools - if name not in all_tools.keys(): - raise ValueError("Tool not found") - - type_dict = { - "str": Field( - field_type="str", - required=True, - is_list=False, - show=True, - placeholder="", - value="", - ), - "llm": Field(field_type="BaseLLM", required=True, is_list=False, show=True), - "func": Field( - field_type="function", - required=True, - is_list=False, - show=True, - multiline=True, - ), - "code": Field( - field_type="str", - required=True, - is_list=False, - show=True, - value="", - multiline=True, - ), - } - - tool_type: str = all_tools[name]["type"] # type: ignore - - if tool_type in _BASE_TOOLS: - params = [] - elif tool_type in _LLM_TOOLS: - params = ["llm"] - elif tool_type in _EXTRA_LLM_TOOLS: - _, extra_keys = _EXTRA_LLM_TOOLS[tool_type] - params = ["llm"] + extra_keys - elif tool_type in _EXTRA_OPTIONAL_TOOLS: - _, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type] - params = extra_keys - elif tool_type == "Tool": - params = ["name", "description", "func"] - elif tool_type in CUSTOM_TOOLS: - # Get custom tool params - params = all_tools[name]["params"] # type: ignore - base_classes = ["function"] - if node := customs.get_custom_nodes("tools").get(tool_type): - return node - - else: - params = [] - - # Copy the field and add the name - fields = [] - for param in params: - if param in NODE_INPUTS: - field = type_dict[param].copy() - else: - field = type_dict["str"].copy() - field.name = param - if param == "aiosession": - field.show = False - field.required = False - fields.append(field) - - template = Template(fields=fields, type_name=tool_type) - - tool_params = util.get_tool_params(util.get_tool_by_name(tool_type)) - if tool_params is None: - tool_params = {} - return { - "template": util.format_dict(template.to_dict()), - **tool_params, - "base_classes": base_classes, - } diff --git a/src/backend/langflow/interface/tools/base.py b/src/backend/langflow/interface/tools/base.py index 9b9a072c1..496fef878 100644 --- a/src/backend/langflow/interface/tools/base.py +++ b/src/backend/langflow/interface/tools/base.py @@ -1,5 +1,6 @@ from langflow.custom import customs -from langflow.interface.listing import ALL_TOOLS_NAMES, CUSTOM_TOOLS +from langflow.interface.tools.constants import ALL_TOOLS_NAMES, CUSTOM_TOOLS +import langflow.interface.tools.util from langflow.template.template import Field, Template from langflow.utils import util from langflow.settings import settings @@ -11,14 +12,18 @@ from langchain.agents.load_tools import ( _EXTRA_OPTIONAL_TOOLS, _LLM_TOOLS, ) +from langflow.interface.tools.util import get_tools_dict class ToolCreator(LangChainTypeCreator): type_name: str = "tools" + tools_dict: Dict | None = None @property def type_to_loader_dict(self) -> Dict: - return util.get_tools_dict() + if self.tools_dict is None: + self.tools_dict = get_tools_dict() + return self.tools_dict def get_signature(self, name: str) -> Dict | None: """Get the signature of a tool.""" @@ -27,7 +32,9 @@ class ToolCreator(LangChainTypeCreator): base_classes = ["Tool"] all_tools = {} for tool in self.type_to_loader_dict.keys(): - if tool_params := util.get_tool_params(util.get_tool_by_name(tool)): + if tool_params := langflow.interface.tools.util.get_tool_params( + langflow.interface.tools.util.get_tool_by_name(tool) + ): tool_name = tool_params.get("name") or str(tool) all_tools[tool_name] = {"type": tool, "params": tool_params} @@ -101,7 +108,9 @@ class ToolCreator(LangChainTypeCreator): template = Template(fields=fields, type_name=tool_type) - tool_params = util.get_tool_params(util.get_tool_by_name(tool_type)) + tool_params = langflow.interface.tools.util.get_tool_params( + langflow.interface.tools.util.get_tool_by_name(tool_type) + ) if tool_params is None: tool_params = {} return { @@ -116,7 +125,9 @@ class ToolCreator(LangChainTypeCreator): tools = [] for tool in ALL_TOOLS_NAMES: - tool_params = util.get_tool_params(util.get_tool_by_name(tool)) + tool_params = langflow.interface.tools.util.get_tool_params( + langflow.interface.tools.util.get_tool_by_name(tool) + ) if tool_params and ( tool_params.get("name") in settings.tools or (tool_params.get("name") and settings.dev) diff --git a/src/backend/langflow/interface/tools/constants.py b/src/backend/langflow/interface/tools/constants.py new file mode 100644 index 000000000..b1e412e7d --- /dev/null +++ b/src/backend/langflow/interface/tools/constants.py @@ -0,0 +1,11 @@ +from langchain.agents.load_tools import get_all_tool_names +from langchain.agents import Tool +from langflow.interface.custom_types import PythonFunction +from langchain.tools.json.tool import JsonSpec + + +OTHER_TOOLS = {"JsonSpec": JsonSpec} +CUSTOM_TOOLS = {"Tool": Tool, "PythonFunction": PythonFunction} +ALL_TOOLS_NAMES = set( + get_all_tool_names() + list(CUSTOM_TOOLS.keys()) + list(OTHER_TOOLS.keys()) +) diff --git a/src/backend/langflow/interface/tools/util.py b/src/backend/langflow/interface/tools/util.py new file mode 100644 index 000000000..2ec273e67 --- /dev/null +++ b/src/backend/langflow/interface/tools/util.py @@ -0,0 +1,122 @@ +import ast +import inspect +from typing import Dict, Union +from langchain.agents.load_tools import ( + _BASE_TOOLS, + _EXTRA_LLM_TOOLS, + _EXTRA_OPTIONAL_TOOLS, + _LLM_TOOLS, +) +from langchain.agents.tools import Tool +from langflow.interface.tools.constants import CUSTOM_TOOLS, OTHER_TOOLS + + +def get_tools_dict(): + """Get the tools dictionary.""" + + return { + **_BASE_TOOLS, + **_LLM_TOOLS, + **{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()}, + **{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()}, + **CUSTOM_TOOLS, + **OTHER_TOOLS, + } + + +def get_tool_by_name(name: str): + """Get a tool from the tools dictionary.""" + tools = get_tools_dict() + if name not in tools: + raise ValueError(f"{name} not found.") + return tools[name] + + +def get_func_tool_params(func, **kwargs) -> Union[Dict, None]: + tree = ast.parse(inspect.getsource(func)) + + # Iterate over the statements in the abstract syntax tree + for node in ast.walk(tree): + # Find the first return statement + if isinstance(node, ast.Return): + tool = node.value + if isinstance(tool, ast.Call): + if isinstance(tool.func, ast.Name) and tool.func.id == "Tool": + if tool.keywords: + tool_params = {} + for keyword in tool.keywords: + if keyword.arg == "name": + tool_params["name"] = ast.literal_eval(keyword.value) + elif keyword.arg == "description": + tool_params["description"] = ast.literal_eval( + keyword.value + ) + + return tool_params + return { + "name": ast.literal_eval(tool.args[0]), + "description": ast.literal_eval(tool.args[2]), + } + # + else: + # get the class object from the return statement + try: + class_obj = eval( + compile(ast.Expression(tool), "", "eval") + ) + except Exception: + return None + + return { + "name": getattr(class_obj, "name"), + "description": getattr(class_obj, "description"), + } + # Return None if no return statement was found + return None + + +def get_class_tool_params(cls, **kwargs) -> Union[Dict, None]: + tree = ast.parse(inspect.getsource(cls)) + + tool_params = {} + + # Iterate over the statements in the abstract syntax tree + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef): + # Find the class definition and look for methods + for stmt in node.body: + if isinstance(stmt, ast.FunctionDef) and stmt.name == "__init__": + # There is no assignment statements in the __init__ method + # So we need to get the params from the function definition + for arg in stmt.args.args: + if arg.arg == "name": + # It should be the name of the class + tool_params[arg.arg] = cls.__name__ + elif arg.arg == "self": + continue + # If there is not default value, set it to an empty string + else: + try: + annotation = ast.literal_eval(arg.annotation) # type: ignore + tool_params[arg.arg] = annotation + except ValueError: + tool_params[arg.arg] = "" + # Get the attribute name and the annotation + elif cls != Tool and isinstance(stmt, ast.AnnAssign): + # Get the attribute name and the annotation + tool_params[stmt.target.id] = "" # type: ignore + + return tool_params + + +def get_tool_params(tool, **kwargs) -> Dict: + # Parse the function code into an abstract syntax tree + # Define if it is a function or a class + if inspect.isfunction(tool): + return get_func_tool_params(tool, **kwargs) or {} + elif inspect.isclass(tool): + # Get the parameters necessary to + # instantiate the class + return get_class_tool_params(tool, **kwargs) or {} + else: + raise ValueError("Tool must be a function or class.") diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index a2a02465e..21d17690a 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -2,8 +2,6 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from langflow.api.endpoints import router as endpoints_router -from langflow.api.list_endpoints import router as list_router -from langflow.api.signature import router as signatures_router def create_app(): @@ -23,8 +21,6 @@ def create_app(): ) app.include_router(endpoints_router) - app.include_router(list_router) - app.include_router(signatures_router) return app diff --git a/src/backend/langflow/utils/util.py b/src/backend/langflow/utils/util.py index 10048c831..23bff03ad 100644 --- a/src/backend/langflow/utils/util.py +++ b/src/backend/langflow/utils/util.py @@ -1,17 +1,7 @@ -import ast import importlib import inspect import re -from typing import Dict, Optional, Union -from langchain.agents.load_tools import ( - _BASE_TOOLS, - _EXTRA_LLM_TOOLS, - _EXTRA_OPTIONAL_TOOLS, - _LLM_TOOLS, -) - - -from langchain.agents.tools import Tool +from typing import Dict, Optional from langflow.utils import constants @@ -190,119 +180,6 @@ def get_default_factory(module: str, function: str): return None -def get_tools_dict(): - """Get the tools dictionary.""" - from langflow.interface.listing import CUSTOM_TOOLS, OTHER_TOOLS - - tools = { - **_BASE_TOOLS, - **_LLM_TOOLS, - **{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()}, - **{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()}, - **CUSTOM_TOOLS, - **OTHER_TOOLS, - } - return tools - - -def get_tool_by_name(name: str): - """Get a tool from the tools dictionary.""" - tools = get_tools_dict() - if name not in tools: - raise ValueError(f"{name} not found.") - return tools[name] - - -def get_tool_params(tool, **kwargs) -> Dict: - # Parse the function code into an abstract syntax tree - # Define if it is a function or a class - if inspect.isfunction(tool): - return get_func_tool_params(tool, **kwargs) or {} - elif inspect.isclass(tool): - # Get the parameters necessary to - # instantiate the class - return get_class_tool_params(tool, **kwargs) or {} - else: - raise ValueError("Tool must be a function or class.") - - -def get_func_tool_params(func, **kwargs) -> Union[Dict, None]: - tree = ast.parse(inspect.getsource(func)) - - # Iterate over the statements in the abstract syntax tree - for node in ast.walk(tree): - # Find the first return statement - if isinstance(node, ast.Return): - tool = node.value - if isinstance(tool, ast.Call): - if isinstance(tool.func, ast.Name) and tool.func.id == "Tool": - if tool.keywords: - tool_params = {} - for keyword in tool.keywords: - if keyword.arg == "name": - tool_params["name"] = ast.literal_eval(keyword.value) - elif keyword.arg == "description": - tool_params["description"] = ast.literal_eval( - keyword.value - ) - - return tool_params - return { - "name": ast.literal_eval(tool.args[0]), - "description": ast.literal_eval(tool.args[2]), - } - # - else: - # get the class object from the return statement - try: - class_obj = eval( - compile(ast.Expression(tool), "", "eval") - ) - except Exception: - return None - - return { - "name": getattr(class_obj, "name"), - "description": getattr(class_obj, "description"), - } - # Return None if no return statement was found - return None - - -def get_class_tool_params(cls, **kwargs) -> Union[Dict, None]: - tree = ast.parse(inspect.getsource(cls)) - - tool_params = {} - - # Iterate over the statements in the abstract syntax tree - for node in ast.walk(tree): - if isinstance(node, ast.ClassDef): - # Find the class definition and look for methods - for stmt in node.body: - if isinstance(stmt, ast.FunctionDef) and stmt.name == "__init__": - # There is no assignment statements in the __init__ method - # So we need to get the params from the function definition - for arg in stmt.args.args: - if arg.arg == "name": - # It should be the name of the class - tool_params[arg.arg] = cls.__name__ - elif arg.arg == "self": - continue - # If there is not default value, set it to an empty string - else: - try: - annotation = ast.literal_eval(arg.annotation) # type: ignore - tool_params[arg.arg] = annotation - except ValueError: - tool_params[arg.arg] = "" - # Get the attribute name and the annotation - elif cls != Tool and isinstance(stmt, ast.AnnAssign): - # Get the attribute name and the annotation - tool_params[stmt.target.id] = "" # type: ignore - - return tool_params - - def get_class_doc(class_name): """ Extracts information from the docstring of a given class. diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 36daa926e..e6eb8b85c 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -1,4 +1,4 @@ -from langflow.interface.listing import CUSTOM_TOOLS +from langflow.interface.tools.constants import CUSTOM_TOOLS from fastapi.testclient import TestClient