feat: Add orm for Tools and clean up Tool logic (#1935)

This commit is contained in:
Matthew Zhou
2024-10-25 14:25:40 -07:00
committed by GitHub
parent 150240c7a7
commit d74406af41
37 changed files with 833 additions and 789 deletions

View File

@@ -10,19 +10,13 @@ from letta.functions.helpers import (
from letta.functions.schema_generator import generate_schema_from_args_schema
from letta.schemas.letta_base import LettaBase
from letta.schemas.openai.chat_completions import ToolCall
from letta.services.organization_manager import OrganizationManager
from letta.services.user_manager import UserManager
class BaseTool(LettaBase):
__id_prefix__ = "tool"
# optional fields
description: Optional[str] = Field(None, description="The description of the tool.")
source_type: Optional[str] = Field(None, description="The type of the source code.")
module: Optional[str] = Field(None, description="The module of the function.")
# optional: user_id (user-specific tools)
user_id: Optional[str] = Field(None, description="The unique identifier of the user associated with the function.")
class Tool(BaseTool):
"""
@@ -37,8 +31,12 @@ class Tool(BaseTool):
"""
id: str = BaseTool.generate_id_field()
id: str = Field(..., description="The id of the tool.")
description: Optional[str] = Field(None, description="The description of the tool.")
source_type: Optional[str] = Field(None, description="The type of the source code.")
module: Optional[str] = Field(None, description="The module of the function.")
user_id: str = Field(..., description="The unique identifier of the user associated with the tool.")
organization_id: str = Field(..., description="The unique identifier of the organization associated with the tool.")
name: str = Field(..., description="The name of the function.")
tags: List[str] = Field(..., description="Metadata tags.")
@@ -58,14 +56,31 @@ class Tool(BaseTool):
)
)
class ToolCreate(LettaBase):
user_id: str = Field(UserManager.DEFAULT_USER_ID, description="The user that this tool belongs to. Defaults to the default user ID.")
organization_id: str = Field(
OrganizationManager.DEFAULT_ORG_ID,
description="The organization that this tool belongs to. Defaults to the default organization ID.",
)
name: Optional[str] = Field(None, description="The name of the function (auto-generated from source_code if not provided).")
description: Optional[str] = Field(None, description="The description of the tool.")
tags: List[str] = Field([], description="Metadata tags.")
module: Optional[str] = Field(None, description="The source code of the function.")
source_code: str = Field(..., description="The source code of the function.")
source_type: str = Field(..., description="The source type of the function.")
json_schema: Optional[Dict] = Field(
None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
)
terminal: Optional[bool] = Field(None, description="Whether the tool is a terminal tool (allow requesting heartbeats).")
@classmethod
def get_composio_tool(
cls,
action: "ActionType",
) -> "Tool":
def from_composio(
cls, action: "ActionType", user_id: str = UserManager.DEFAULT_USER_ID, organization_id: str = OrganizationManager.DEFAULT_ORG_ID
) -> "ToolCreate":
"""
Class method to create an instance of Letta-compatible Composio Tool.
Check https://docs.composio.dev/introduction/intro/overview to look at options for get_composio_tool
Check https://docs.composio.dev/introduction/intro/overview to look at options for from_composio
This function will error if we find more than one tool, or 0 tools.
@@ -90,14 +105,9 @@ class Tool(BaseTool):
wrapper_func_name, wrapper_function_str = generate_composio_tool_wrapper(action)
json_schema = generate_schema_from_args_schema(composio_tool.args_schema, name=wrapper_func_name, description=description)
# append heartbeat (necessary for triggering another reasoning step after this tool call)
json_schema["parameters"]["properties"]["request_heartbeat"] = {
"type": "boolean",
"description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function.",
}
json_schema["parameters"]["required"].append("request_heartbeat")
return cls(
user_id=user_id,
organization_id=organization_id,
name=wrapper_func_name,
description=description,
source_type=source_type,
@@ -107,7 +117,13 @@ class Tool(BaseTool):
)
@classmethod
def from_langchain(cls, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> "Tool":
def from_langchain(
cls,
langchain_tool: "LangChainBaseTool",
additional_imports_module_attr_map: dict[str, str] = None,
user_id: str = UserManager.DEFAULT_USER_ID,
organization_id: str = OrganizationManager.DEFAULT_ORG_ID,
) -> "ToolCreate":
"""
Class method to create an instance of Tool from a Langchain tool (must be from langchain_community.tools).
@@ -125,14 +141,9 @@ class Tool(BaseTool):
wrapper_func_name, wrapper_function_str = generate_langchain_tool_wrapper(langchain_tool, additional_imports_module_attr_map)
json_schema = generate_schema_from_args_schema(langchain_tool.args_schema, name=wrapper_func_name, description=description)
# append heartbeat (necessary for triggering another reasoning step after this tool call)
json_schema["parameters"]["properties"]["request_heartbeat"] = {
"type": "boolean",
"description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function.",
}
json_schema["parameters"]["required"].append("request_heartbeat")
return cls(
user_id=user_id,
organization_id=organization_id,
name=wrapper_func_name,
description=description,
source_type=source_type,
@@ -142,7 +153,13 @@ class Tool(BaseTool):
)
@classmethod
def from_crewai(cls, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> "Tool":
def from_crewai(
cls,
crewai_tool: "CrewAIBaseTool",
additional_imports_module_attr_map: dict[str, str] = None,
user_id: str = UserManager.DEFAULT_USER_ID,
organization_id: str = OrganizationManager.DEFAULT_ORG_ID,
) -> "ToolCreate":
"""
Class method to create an instance of Tool from a crewAI BaseTool object.
@@ -158,14 +175,9 @@ class Tool(BaseTool):
wrapper_func_name, wrapper_function_str = generate_crewai_tool_wrapper(crewai_tool, additional_imports_module_attr_map)
json_schema = generate_schema_from_args_schema(crewai_tool.args_schema, name=wrapper_func_name, description=description)
# append heartbeat (necessary for triggering another reasoning step after this tool call)
json_schema["parameters"]["properties"]["request_heartbeat"] = {
"type": "boolean",
"description": "Request an immediate heartbeat after function execution. Set to `True` if you want to send a follow-up message or run a follow-up function.",
}
json_schema["parameters"]["required"].append("request_heartbeat")
return cls(
user_id=user_id,
organization_id=organization_id,
name=wrapper_func_name,
description=description,
source_type=source_type,
@@ -175,54 +187,43 @@ class Tool(BaseTool):
)
@classmethod
def load_default_langchain_tools(cls) -> List["Tool"]:
def load_default_langchain_tools(cls) -> List["ToolCreate"]:
# For now, we only support wikipedia tool
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
wikipedia_tool = Tool.from_langchain(
wikipedia_tool = ToolCreate.from_langchain(
WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()), {"langchain_community.utilities": "WikipediaAPIWrapper"}
)
return [wikipedia_tool]
@classmethod
def load_default_crewai_tools(cls) -> List["Tool"]:
def load_default_crewai_tools(cls) -> List["ToolCreate"]:
# For now, we only support scrape website tool
from crewai_tools import ScrapeWebsiteTool
web_scrape_tool = Tool.from_crewai(ScrapeWebsiteTool())
web_scrape_tool = ToolCreate.from_crewai(ScrapeWebsiteTool())
return [web_scrape_tool]
@classmethod
def load_default_composio_tools(cls) -> List["Tool"]:
def load_default_composio_tools(cls) -> List["ToolCreate"]:
from composio_langchain import Action
calculator = Tool.get_composio_tool(action=Action.MATHEMATICAL_CALCULATOR)
serp_news = Tool.get_composio_tool(action=Action.SERPAPI_NEWS_SEARCH)
serp_google_search = Tool.get_composio_tool(action=Action.SERPAPI_SEARCH)
serp_google_maps = Tool.get_composio_tool(action=Action.SERPAPI_GOOGLE_MAPS_SEARCH)
calculator = ToolCreate.from_composio(action=Action.MATHEMATICAL_CALCULATOR)
serp_news = ToolCreate.from_composio(action=Action.SERPAPI_NEWS_SEARCH)
serp_google_search = ToolCreate.from_composio(action=Action.SERPAPI_SEARCH)
serp_google_maps = ToolCreate.from_composio(action=Action.SERPAPI_GOOGLE_MAPS_SEARCH)
return [calculator, serp_news, serp_google_search, serp_google_maps]
class ToolCreate(BaseTool):
id: Optional[str] = Field(None, description="The unique identifier of the tool. If this is not provided, it will be autogenerated.")
name: Optional[str] = Field(None, description="The name of the function (auto-generated from source_code if not provided).")
description: Optional[str] = Field(None, description="The description of the tool.")
tags: List[str] = Field([], description="Metadata tags.")
source_code: str = Field(..., description="The source code of the function.")
json_schema: Optional[Dict] = Field(
None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
)
terminal: Optional[bool] = Field(None, description="Whether the tool is a terminal tool (allow requesting heartbeats).")
class ToolUpdate(ToolCreate):
id: str = Field(..., description="The unique identifier of the tool.")
class ToolUpdate(LettaBase):
description: Optional[str] = Field(None, description="The description of the tool.")
name: Optional[str] = Field(None, description="The name of the function.")
tags: Optional[List[str]] = Field(None, description="Metadata tags.")
module: Optional[str] = Field(None, description="The source code of the function.")
source_code: Optional[str] = Field(None, description="The source code of the function.")
json_schema: Optional[Dict] = Field(None, description="The JSON schema of the function.")
source_type: Optional[str] = Field(None, description="The type of the source code.")