diff --git a/build_and_push b/build_and_push index 30d8501d5..dc98bc3e1 100755 --- a/build_and_push +++ b/build_and_push @@ -1,5 +1,5 @@ #! /bin/bash poetry remove langchain -docker build -t ibiscp/expert:v0.0.6 . && docker push ibiscp/expert:v0.0.6 +docker build -t ibiscp/expert:v0.0.11 . && docker push ibiscp/expert:v0.0.11 poetry add --editable ../langchain diff --git a/src/app.py b/src/app.py index 65ea4f7f2..d91a1c4ef 100644 --- a/src/app.py +++ b/src/app.py @@ -2,11 +2,27 @@ from fastapi import FastAPI from endpoints import router as endpoints_router from list import router as list_router from signature import router as signatures_router +from fastapi.middleware.cors import CORSMiddleware def create_app(): """Create the FastAPI app and include the router.""" app = FastAPI() + + origins = [ + "http://localhost", + "http://localhost:8080", + "http://localhost:3000", + ] + + app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + app.include_router(endpoints_router) app.include_router(list_router) app.include_router(signatures_router) diff --git a/src/endpoints.py b/src/endpoints.py index 05d2af226..0e79cde13 100644 --- a/src/endpoints.py +++ b/src/endpoints.py @@ -19,7 +19,7 @@ AGENT_TYPE = "conversational-react-description" @router.get("/") def get_all(): - tools = list.list_tools() + # tools = list.list_tools() return { "chains": {chain: signature.chain(chain) for chain in list.list_chains()}, "agents": {agent: signature.agent(agent) for agent in list.list_agents()}, @@ -41,13 +41,19 @@ def get_all(): # }, # "vectorstores": {"template": {}}, # "docstores": {"template": {}}, - "tools": { - tool: {"template": signature.tool(tool), **values} - for tool, values in tools.items() - }, + # "tools": { + # tool: {"template": signature.tool(tool), **values} + # for tool, values in tools.items() + # }, + "tools": {tool: signature.tool(tool) for tool in list.list_tools()}, } +@router.post("/load") +def get_load(data: dict[str, str]) -> str: + return "Hello Otávio!" + + # @router.get("/chain") # def get_chain(): # llm = OpenAI(temperature=0) diff --git a/src/list.py b/src/list.py index 8a3e6f807..4ccbdf358 100644 --- a/src/list.py +++ b/src/list.py @@ -9,12 +9,7 @@ from langchain.chains.conversation import memory as memories from langchain import document_loaders from langchain import vectorstores from langchain import docstore -from langchain.agents.load_tools import ( - _BASE_TOOLS, - _LLM_TOOLS, - _EXTRA_LLM_TOOLS, - _EXTRA_OPTIONAL_TOOLS, -) +from langchain.agents.load_tools import get_all_tool_names import util @@ -106,11 +101,4 @@ def list_memories(): def list_tools(): """List all load tools""" - merged_dict = { - **_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()}, - } - - return {k: util.get_tool_params(v) for k, v in merged_dict.items()} + return get_all_tool_names() diff --git a/src/signature.py b/src/signature.py index e6fe1e23c..63959c6e5 100644 --- a/src/signature.py +++ b/src/signature.py @@ -24,8 +24,6 @@ router = APIRouter( tags=["signatures"], ) -KEYS_TO_REMOVE = ["name", "default_factory"] - def build_template_from_function(name: str, dict: dict): classes = [item.__annotations__["return"].__name__ for item in dict.values()] @@ -34,41 +32,38 @@ def build_template_from_function(name: str, dict: dict): if name not in classes: raise Exception(f"{name} not found.") - for k, v in dict.items(): + for _type, v in dict.items(): if v.__annotations__["return"].__name__ == name: - _type = k _class = v.__annotations__["return"] docs = util.get_class_doc(_class) - variables = {} + 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_ not in KEYS_TO_REMOVE: + if name_ == "default_factory": + try: + variables[name]["default"] = util.get_default_factory( + module=_class.__base__.__module__, function=value_ + ) + except: + 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 "" + + variables[name]["placeholder"] = ( + docs["Attributes"][name] if name in docs["Attributes"] else "" + ) + return { "template": util.format_dict(variables), - "_type": _type, "description": docs["Description"], + "base_classes": util.get_base_classes(_class), } - # return { - # "template": util.format_dict( - # { - # name: { - # name: value - # for (name, value) in value.__repr_args__() - # if name not in KEYS_TO_REMOVE - # } - # for name, value in _class.__fields__.items() - # } - # ), - # "_type": _type, - # "description": _class.__doc__, - # } - def build_template_from_class(name: str, dict: dict): classes = [item.__name__ for item in dict.values()] @@ -77,42 +72,38 @@ def build_template_from_class(name: str, dict: dict): if name not in classes: raise Exception(f"{name} not found.") - for k, v in dict.items(): + for _type, v in dict.items(): if v.__name__ == name: - _type = k _class = v docs = util.get_class_doc(_class) - variables = {} + 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_ not in KEYS_TO_REMOVE: + if name_ == "default_factory": + try: + variables[name]["default"] = util.get_default_factory( + module=_class.__base__.__module__, function=value_ + ) + except: + 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 "" + + variables[name]["placeholder"] = ( + docs["Attributes"][name] if name in docs["Attributes"] else "" + ) return { "template": util.format_dict(variables), - "_type": _type, "description": docs["Description"], + "base_classes": util.get_base_classes(_class), } - # return { - # "template": util.format_dict( - # { - # name: { - # name: value - # for (name, value) in value.__repr_args__() - # if name not in KEYS_TO_REMOVE - # } - # for name, value in _class.__fields__.items() - # } - # ), - # "_type": _type, - # "description": _class.__doc__, - # } - @router.get("/chain") def chain(name: str): @@ -193,6 +184,16 @@ def tool(name: str): params = extra_keys return { - param: (type_dict[param] if param == "llm" else type_dict["str"]) - for param in params + "template": { + param: (type_dict[param] if param == "llm" else type_dict["str"]) + for param in params + }, + **util.get_tool_params(util.get_tools_dict(name)), + "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/src/util.py b/src/util.py index f1c1bd1fa..baac2dad3 100644 --- a/src/util.py +++ b/src/util.py @@ -1,5 +1,47 @@ import ast import inspect +import re +import importlib +from langchain.agents.load_tools import ( + _BASE_TOOLS, + _LLM_TOOLS, + _EXTRA_LLM_TOOLS, + _EXTRA_OPTIONAL_TOOLS, +) +from typing import Optional + + +def get_base_classes(cls): + bases = cls.__bases__ + if not bases: + return [] + else: + result = [] + for base in bases: + if any(type in base.__module__ for type in ["pydantic", "abc"]): + continue + result.append(base.__name__) + result.extend(get_base_classes(base)) + return result + + +def get_default_factory(module: str, function: str): + pattern = r"" + + if match := re.search(pattern, function): + module = importlib.import_module(module) + return getattr(module, match[1])() + return None + + +def get_tools_dict(name: Optional[str] = None): + 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()}, + } + return tools[name] if name else tools def get_tool_params(func): @@ -69,61 +111,15 @@ def get_class_doc(class_name): current_section = line[:-1] continue - if current_section == "Description": - data[current_section] += line - elif current_section == "Example": + if current_section in ["Description", "Example"]: data[current_section] += line else: - try: - param, desc = line.split(":") - except: - param, desc = "", "" + param, desc = line.split(":") data[current_section][param.strip()] = desc.strip() return data -# def format_dict(d): -# # Remove from keys -# keys_to_remove = ["callback_manager"] -# for key in keys_to_remove: -# if key in d: -# d.pop(key) - -# for key, value in d.items(): -# _type = value["type"] - -# # Add optional parameter -# if "Optional" in _type: -# _type = _type.replace("Optional[", "")[:-1] - -# # Add list parameter -# if "List" in _type: -# _type = _type.replace("List[", "")[:-1] -# value["list"] = True -# else: -# value["list"] = False - -# if "Mapping" in _type: -# _type = _type.replace("Mapping", "dict") - -# value["type"] = _type - -# # Show if required -# if value["required"] or key in ["allowed_tools", "verbose", "Memory"]: -# value["show"] = True -# else: -# value["show"] = False - -# # If default, change to value -# if value['type'] == 'str': -# value["value"] = value["default"] if 'default' in value else '' -# if 'default' in value: -# value.pop("default") - -# return {key: value for key, value in d.items() if value["show"]} - - def format_dict(d): """ Formats a dictionary by removing certain keys and modifying the values of other keys. @@ -134,12 +130,11 @@ def format_dict(d): Returns: A new dictionary with the desired modifications applied. """ - # Remove keys to exclude - keys_to_exclude = ["callback_manager"] - d = {key: value for key, value in d.items() if key not in keys_to_exclude} # Process remaining keys for key, value in d.items(): + if key == "_type": + continue _type = value["type"] # Remove 'Optional' wrapper @@ -157,18 +152,22 @@ def format_dict(d): if "Mapping" in _type: _type = _type.replace("Mapping", "dict") - value["type"] = _type + value["type"] = "Tool" if key == "allowed_tools" else _type # Show if required value["show"] = bool( - value["required"] or key in ["allowed_tools", "verbose", "Memory"] + (value["required"] and key not in ["input_variables"]) + or key in ["allowed_tools", "verbose", "Memory", "memory", "prefix"] + or "api_key" in key ) # Replace default value with actual value - if _type == "str": + if _type in ["str", "bool"]: value["value"] = value.get("default", "") if "default" in value: value.pop("default") # Filter out keys that should not be shown - return {key: value for key, value in d.items() if value["show"]} + return ( + d # {key: value for key, value in d.items() if key == "_type" or value["show"]} + )