diff --git a/langflow/backend/list_endpoints.py b/langflow/backend/list_endpoints.py index bf5741666..5448b3613 100644 --- a/langflow/backend/list_endpoints.py +++ b/langflow/backend/list_endpoints.py @@ -98,7 +98,15 @@ def list_memories(): def list_tools(): """List all load tools""" - return [ - util.get_tool_params(util.get_tools_dict(tool))["name"] - for tool in get_all_tool_names() - ] + tools = [] + + for tool in get_all_tool_names(): + if tool_params := util.get_tool_params(util.get_tools_dict(tool)): + tools.append(tool_params["name"]) + + return tools + + # return [ + # util.get_tool_params(util.get_tools_dict(tool))["name"] + # for tool in get_all_tool_names() + # ] diff --git a/langflow/backend/poetry.lock b/langflow/backend/poetry.lock index 2be4e0a9e..a6a343680 100644 --- a/langflow/backend/poetry.lock +++ b/langflow/backend/poetry.lock @@ -524,12 +524,12 @@ test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] [[package]] name = "langchain" -version = "0.0.86" +version = "0.0.100" description = "Building applications with LLMs through composability" category = "main" optional = false python-versions = ">=3.8.1,<4.0" -develop = false +develop = true [package.dependencies] aiohttp = "^3.8.3" @@ -542,14 +542,12 @@ SQLAlchemy = "^1" tenacity = "^8.1.0" [package.extras] -all = ["anthropic (>=0.2.2,<0.3.0)", "beautifulsoup4 (>=4,<5)", "cohere (>=3,<4)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "openai (>=0,<1)", "pinecone-client (>=2,<3)", "pypdf (>=3.4.0,<4.0.0)", "qdrant-client (>=0.11.7,<0.12.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0,<1)", "torch (>=1,<2)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] +all = ["anthropic (>=0.2.2,<0.3.0)", "beautifulsoup4 (>=4,<5)", "cohere (>=3,<4)", "elasticsearch (>=8,<9)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-search-results (>=2,<3)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "manifest-ml (>=0.0.1,<0.0.2)", "networkx (>=2.6.3,<3.0.0)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "opensearch-py (>=2.0.0,<3.0.0)", "pinecone-client (>=2,<3)", "pypdf (>=3.4.0,<4.0.0)", "qdrant-client (>=1.0.4,<2.0.0)", "redis (>=4,<5)", "sentence-transformers (>=2,<3)", "spacy (>=3,<4)", "tensorflow-text (>=2.11.0,<3.0.0)", "tiktoken (>=0,<1)", "torch (>=1,<2)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] llms = ["anthropic (>=0.2.2,<0.3.0)", "cohere (>=3,<4)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "torch (>=1,<2)", "transformers (>=4,<5)"] [package.source] -type = "git" -url = "https://github.com/ibiscp/langchain.git" -reference = "ibis" -resolved_reference = "059e90090639e3c8c23928f1799e715b5635f681" +type = "directory" +url = "../../../langchain" [[package]] name = "marshmallow" @@ -1101,7 +1099,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.10" -content-hash = "fb4b97be211d190c4feff42124fce6062b8717373d32b4894ae53f760742faaa" +content-hash = "37ef2fda1358fdb0b52d9584f0fc903a9581310cc271c5bbfb79b6085176bf2e" [metadata.files] aiohttp = [ diff --git a/langflow/backend/pyproject.toml b/langflow/backend/pyproject.toml index fca09f975..d83a0488c 100644 --- a/langflow/backend/pyproject.toml +++ b/langflow/backend/pyproject.toml @@ -16,7 +16,7 @@ uvicorn = "^0.20.0" beautifulsoup4 = "^4.11.2" google-search-results = "^2.4.1" google-api-python-client = "^2.79.0" -langchain = {git = "https://github.com/ibiscp/langchain.git", rev = "ibis"} +langchain = {path = "../../../langchain", develop = true} [tool.poetry.group.dev.dependencies] black = "^23.1.0" diff --git a/langflow/backend/signature.py b/langflow/backend/signature.py index 4ba2e009d..bfa2c94f6 100644 --- a/langflow/backend/signature.py +++ b/langflow/backend/signature.py @@ -18,93 +18,13 @@ router = APIRouter( ) -def build_template_from_function(name: str, type_to_loader_dict: dict): - classes = [ - item.__annotations__["return"].__name__ for item in type_to_loader_dict.values() - ] - - # Raise error if name is not in chains - if name not in classes: - raise ValueError(f"{name} not found") - - for _type, v in type_to_loader_dict.items(): - if v.__annotations__["return"].__name__ == name: - _class = v.__annotations__["return"] - - docs = util.get_class_doc(_class) - - variables = {"_type": _type} - for name, value in _class.__fields__.items(): - if name in ["callback_manager", "requests_wrapper"]: - continue - variables[name] = {} - for name_, value_ in value.__repr_args__(): - if name_ == "default_factory": - try: - variables[name]["default"] = util.get_default_factory( - module=_class.__base__.__module__, function=value_ - ) - except Exception: - variables[name]["default"] = None - elif name_ not in ["name"]: - variables[name][name_] = value_ - - variables[name]["placeholder"] = ( - docs["Attributes"][name] if name in docs["Attributes"] else "" - ) - - return { - "template": util.format_dict(variables), - "description": docs["Description"], - "base_classes": util.get_base_classes(_class), - } - - -def build_template_from_class(name: str, type_to_cls_dict: dict): - classes = [item.__name__ for item in type_to_cls_dict.values()] - - # Raise error if name is not in chains - if name not in classes: - raise ValueError(f"{name} not found.") - - for _type, v in type_to_cls_dict.items(): - if v.__name__ == name: - _class = v - - docs = util.get_class_doc(_class) - - variables = {"_type": _type} - for name, value in _class.__fields__.items(): - if name in ["callback_manager"]: - continue - variables[name] = {} - for name_, value_ in value.__repr_args__(): - if name_ == "default_factory": - try: - variables[name]["default"] = util.get_default_factory( - module=_class.__base__.__module__, function=value_ - ) - except Exception: - variables[name]["default"] = None - elif name_ not in ["name"]: - variables[name][name_] = value_ - - variables[name]["placeholder"] = ( - docs["Attributes"][name] if name in docs["Attributes"] else "" - ) - - return { - "template": util.format_dict(variables), - "description": docs["Description"], - "base_classes": util.get_base_classes(_class), - } - - @router.get("/chain") def get_chain(name: str): """Get the signature of a chain.""" try: - return build_template_from_function(name, chains.loading.type_to_loader_dict) + return util.build_template_from_function( + name, chains.loading.type_to_loader_dict + ) except ValueError as exc: raise HTTPException(status_code=404, detail="Chain not found") from exc @@ -113,7 +33,7 @@ def get_chain(name: str): def get_agent(name: str): """Get the signature of an agent.""" try: - return build_template_from_class(name, agents.loading.AGENT_TO_CLASS) + return util.build_template_from_class(name, agents.loading.AGENT_TO_CLASS) except ValueError as exc: raise HTTPException(status_code=404, detail="Agent not found") from exc @@ -122,7 +42,9 @@ def get_agent(name: str): def get_prompt(name: str): """Get the signature of a prompt.""" try: - return build_template_from_function(name, prompts.loading.type_to_loader_dict) + return util.build_template_from_function( + name, prompts.loading.type_to_loader_dict + ) except ValueError as exc: raise HTTPException(status_code=404, detail="Prompt not found") from exc @@ -131,7 +53,7 @@ def get_prompt(name: str): def get_llm(name: str): """Get the signature of an llm.""" try: - return build_template_from_class(name, llms.type_to_cls_dict) + return util.build_template_from_class(name, llms.type_to_cls_dict) except ValueError as exc: raise HTTPException(status_code=404, detail="LLM not found") from exc @@ -152,7 +74,7 @@ def get_llm(name: str): def get_memory(name: str): """Get the signature of a memory.""" try: - return build_template_from_class(name, memories.type_to_cls_dict) + return util.build_template_from_class(name, memories.type_to_cls_dict) except ValueError as exc: raise HTTPException(status_code=404, detail="Memory not found") from exc @@ -173,10 +95,10 @@ def get_memory(name: str): def get_tool(name: str): """Get the signature of a tool.""" - all_tools = { - util.get_tool_params(util.get_tools_dict(tool))["name"]: tool - for tool in get_all_tool_names() - } + all_tools = {} + for tool in get_all_tool_names(): + if tool_params := util.get_tool_params(util.get_tools_dict(tool)): + all_tools[tool_params["name"]] = tool # Raise error if name is not in tools if name not in all_tools.keys(): @@ -185,7 +107,7 @@ def get_tool(name: str): type_dict = { "str": { "type": "str", - "required": True, + "required": False, "list": False, "show": True, "placeholder": "", @@ -218,9 +140,3 @@ def get_tool(name: str): **util.get_tool_params(util.get_tools_dict(tool_type)), "base_classes": ["Tool"], } - - -# {"template": signature.tool(tool), **values} -# for tool, values in tools.items() -# } -# return {k: util.get_tool_params(v) for k, v in merged_dict.items()} diff --git a/langflow/backend/util.py b/langflow/backend/util.py index 615ba32ce..1596adb4a 100644 --- a/langflow/backend/util.py +++ b/langflow/backend/util.py @@ -2,6 +2,8 @@ import ast import inspect import re import importlib + +from langchain.agents.load_tools import * from langchain.agents.load_tools import ( _BASE_TOOLS, _LLM_TOOLS, @@ -11,6 +13,88 @@ from langchain.agents.load_tools import ( from typing import Optional +def build_template_from_function(name: str, type_to_loader_dict: dict): + classes = [ + item.__annotations__["return"].__name__ for item in type_to_loader_dict.values() + ] + + # Raise error if name is not in chains + if name not in classes: + raise ValueError(f"{name} not found") + + for _type, v in type_to_loader_dict.items(): + if v.__annotations__["return"].__name__ == name: + _class = v.__annotations__["return"] + + docs = get_class_doc(_class) + + variables = {"_type": _type} + for name, value in _class.__fields__.items(): + if name in ["callback_manager", "requests_wrapper"]: + continue + variables[name] = {} + for name_, value_ in value.__repr_args__(): + if name_ == "default_factory": + try: + variables[name]["default"] = get_default_factory( + module=_class.__base__.__module__, function=value_ + ) + except Exception: + variables[name]["default"] = None + elif name_ not in ["name"]: + variables[name][name_] = value_ + + variables[name]["placeholder"] = ( + docs["Attributes"][name] if name in docs["Attributes"] else "" + ) + + return { + "template": format_dict(variables), + "description": docs["Description"], + "base_classes": get_base_classes(_class), + } + + +def build_template_from_class(name: str, type_to_cls_dict: dict): + classes = [item.__name__ for item in type_to_cls_dict.values()] + + # Raise error if name is not in chains + if name not in classes: + raise ValueError(f"{name} not found.") + + for _type, v in type_to_cls_dict.items(): + if v.__name__ == name: + _class = v + + docs = get_class_doc(_class) + + variables = {"_type": _type} + for name, value in _class.__fields__.items(): + if name in ["callback_manager"]: + continue + variables[name] = {} + for name_, value_ in value.__repr_args__(): + if name_ == "default_factory": + try: + variables[name]["default"] = get_default_factory( + module=_class.__base__.__module__, function=value_ + ) + except Exception: + variables[name]["default"] = None + elif name_ not in ["name"]: + variables[name][name_] = value_ + + variables[name]["placeholder"] = ( + docs["Attributes"][name] if name in docs["Attributes"] else "" + ) + + return { + "template": format_dict(variables), + "description": docs["Description"], + "base_classes": get_base_classes(_class), + } + + def get_base_classes(cls): bases = cls.__bases__ if not bases: @@ -45,7 +129,7 @@ def get_tools_dict(name: Optional[str] = None): return tools[name] if name else tools -def get_tool_params(func): +def get_tool_params(func, **kwargs): # Parse the function code into an abstract syntax tree tree = ast.parse(inspect.getsource(func)) @@ -54,19 +138,35 @@ def get_tool_params(func): # Find the first return statement if isinstance(node, ast.Return): tool = node.value - if isinstance(tool, ast.Call) 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]), - } + if isinstance(tool, ast.Call): + if 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 @@ -162,7 +262,15 @@ def format_dict(d): value["show"] = bool( (value["required"] and key not in ["input_variables"]) or key - in ["allowed_tools", "verbose", "Memory", "memory", "prefix", "examples"] + in [ + "allowed_tools", + "verbose", + "Memory", + "memory", + "prefix", + "examples", + "temperature", + ] or "api_key" in key )