feat: Add orm for Tools and clean up Tool logic (#1935)
This commit is contained in:
@@ -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.")
|
||||
|
||||
Reference in New Issue
Block a user