letta-nightly 0.5.1.dev20241025104130__py3-none-any.whl → 0.5.1.dev20241027104016__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

letta/schemas/tool.py CHANGED
@@ -10,19 +10,13 @@ from letta.functions.helpers import (
10
10
  from letta.functions.schema_generator import generate_schema_from_args_schema
11
11
  from letta.schemas.letta_base import LettaBase
12
12
  from letta.schemas.openai.chat_completions import ToolCall
13
+ from letta.services.organization_manager import OrganizationManager
14
+ from letta.services.user_manager import UserManager
13
15
 
14
16
 
15
17
  class BaseTool(LettaBase):
16
18
  __id_prefix__ = "tool"
17
19
 
18
- # optional fields
19
- description: Optional[str] = Field(None, description="The description of the tool.")
20
- source_type: Optional[str] = Field(None, description="The type of the source code.")
21
- module: Optional[str] = Field(None, description="The module of the function.")
22
-
23
- # optional: user_id (user-specific tools)
24
- user_id: Optional[str] = Field(None, description="The unique identifier of the user associated with the function.")
25
-
26
20
 
27
21
  class Tool(BaseTool):
28
22
  """
@@ -37,8 +31,12 @@ class Tool(BaseTool):
37
31
 
38
32
  """
39
33
 
40
- id: str = BaseTool.generate_id_field()
41
-
34
+ id: str = Field(..., description="The id of the tool.")
35
+ description: Optional[str] = Field(None, description="The description of the tool.")
36
+ source_type: Optional[str] = Field(None, description="The type of the source code.")
37
+ module: Optional[str] = Field(None, description="The module of the function.")
38
+ user_id: str = Field(..., description="The unique identifier of the user associated with the tool.")
39
+ organization_id: str = Field(..., description="The unique identifier of the organization associated with the tool.")
42
40
  name: str = Field(..., description="The name of the function.")
43
41
  tags: List[str] = Field(..., description="Metadata tags.")
44
42
 
@@ -58,14 +56,31 @@ class Tool(BaseTool):
58
56
  )
59
57
  )
60
58
 
59
+
60
+ class ToolCreate(LettaBase):
61
+ user_id: str = Field(UserManager.DEFAULT_USER_ID, description="The user that this tool belongs to. Defaults to the default user ID.")
62
+ organization_id: str = Field(
63
+ OrganizationManager.DEFAULT_ORG_ID,
64
+ description="The organization that this tool belongs to. Defaults to the default organization ID.",
65
+ )
66
+ name: Optional[str] = Field(None, description="The name of the function (auto-generated from source_code if not provided).")
67
+ description: Optional[str] = Field(None, description="The description of the tool.")
68
+ tags: List[str] = Field([], description="Metadata tags.")
69
+ module: Optional[str] = Field(None, description="The source code of the function.")
70
+ source_code: str = Field(..., description="The source code of the function.")
71
+ source_type: str = Field(..., description="The source type of the function.")
72
+ json_schema: Optional[Dict] = Field(
73
+ None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
74
+ )
75
+ terminal: Optional[bool] = Field(None, description="Whether the tool is a terminal tool (allow requesting heartbeats).")
76
+
61
77
  @classmethod
62
- def get_composio_tool(
63
- cls,
64
- action: "ActionType",
65
- ) -> "Tool":
78
+ def from_composio(
79
+ cls, action: "ActionType", user_id: str = UserManager.DEFAULT_USER_ID, organization_id: str = OrganizationManager.DEFAULT_ORG_ID
80
+ ) -> "ToolCreate":
66
81
  """
67
82
  Class method to create an instance of Letta-compatible Composio Tool.
68
- Check https://docs.composio.dev/introduction/intro/overview to look at options for get_composio_tool
83
+ Check https://docs.composio.dev/introduction/intro/overview to look at options for from_composio
69
84
 
70
85
  This function will error if we find more than one tool, or 0 tools.
71
86
 
@@ -90,14 +105,9 @@ class Tool(BaseTool):
90
105
  wrapper_func_name, wrapper_function_str = generate_composio_tool_wrapper(action)
91
106
  json_schema = generate_schema_from_args_schema(composio_tool.args_schema, name=wrapper_func_name, description=description)
92
107
 
93
- # append heartbeat (necessary for triggering another reasoning step after this tool call)
94
- json_schema["parameters"]["properties"]["request_heartbeat"] = {
95
- "type": "boolean",
96
- "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.",
97
- }
98
- json_schema["parameters"]["required"].append("request_heartbeat")
99
-
100
108
  return cls(
109
+ user_id=user_id,
110
+ organization_id=organization_id,
101
111
  name=wrapper_func_name,
102
112
  description=description,
103
113
  source_type=source_type,
@@ -107,7 +117,13 @@ class Tool(BaseTool):
107
117
  )
108
118
 
109
119
  @classmethod
110
- def from_langchain(cls, langchain_tool: "LangChainBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> "Tool":
120
+ def from_langchain(
121
+ cls,
122
+ langchain_tool: "LangChainBaseTool",
123
+ additional_imports_module_attr_map: dict[str, str] = None,
124
+ user_id: str = UserManager.DEFAULT_USER_ID,
125
+ organization_id: str = OrganizationManager.DEFAULT_ORG_ID,
126
+ ) -> "ToolCreate":
111
127
  """
112
128
  Class method to create an instance of Tool from a Langchain tool (must be from langchain_community.tools).
113
129
 
@@ -125,14 +141,9 @@ class Tool(BaseTool):
125
141
  wrapper_func_name, wrapper_function_str = generate_langchain_tool_wrapper(langchain_tool, additional_imports_module_attr_map)
126
142
  json_schema = generate_schema_from_args_schema(langchain_tool.args_schema, name=wrapper_func_name, description=description)
127
143
 
128
- # append heartbeat (necessary for triggering another reasoning step after this tool call)
129
- json_schema["parameters"]["properties"]["request_heartbeat"] = {
130
- "type": "boolean",
131
- "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.",
132
- }
133
- json_schema["parameters"]["required"].append("request_heartbeat")
134
-
135
144
  return cls(
145
+ user_id=user_id,
146
+ organization_id=organization_id,
136
147
  name=wrapper_func_name,
137
148
  description=description,
138
149
  source_type=source_type,
@@ -142,7 +153,13 @@ class Tool(BaseTool):
142
153
  )
143
154
 
144
155
  @classmethod
145
- def from_crewai(cls, crewai_tool: "CrewAIBaseTool", additional_imports_module_attr_map: dict[str, str] = None) -> "Tool":
156
+ def from_crewai(
157
+ cls,
158
+ crewai_tool: "CrewAIBaseTool",
159
+ additional_imports_module_attr_map: dict[str, str] = None,
160
+ user_id: str = UserManager.DEFAULT_USER_ID,
161
+ organization_id: str = OrganizationManager.DEFAULT_ORG_ID,
162
+ ) -> "ToolCreate":
146
163
  """
147
164
  Class method to create an instance of Tool from a crewAI BaseTool object.
148
165
 
@@ -158,14 +175,9 @@ class Tool(BaseTool):
158
175
  wrapper_func_name, wrapper_function_str = generate_crewai_tool_wrapper(crewai_tool, additional_imports_module_attr_map)
159
176
  json_schema = generate_schema_from_args_schema(crewai_tool.args_schema, name=wrapper_func_name, description=description)
160
177
 
161
- # append heartbeat (necessary for triggering another reasoning step after this tool call)
162
- json_schema["parameters"]["properties"]["request_heartbeat"] = {
163
- "type": "boolean",
164
- "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.",
165
- }
166
- json_schema["parameters"]["required"].append("request_heartbeat")
167
-
168
178
  return cls(
179
+ user_id=user_id,
180
+ organization_id=organization_id,
169
181
  name=wrapper_func_name,
170
182
  description=description,
171
183
  source_type=source_type,
@@ -175,54 +187,43 @@ class Tool(BaseTool):
175
187
  )
176
188
 
177
189
  @classmethod
178
- def load_default_langchain_tools(cls) -> List["Tool"]:
190
+ def load_default_langchain_tools(cls) -> List["ToolCreate"]:
179
191
  # For now, we only support wikipedia tool
180
192
  from langchain_community.tools import WikipediaQueryRun
181
193
  from langchain_community.utilities import WikipediaAPIWrapper
182
194
 
183
- wikipedia_tool = Tool.from_langchain(
195
+ wikipedia_tool = ToolCreate.from_langchain(
184
196
  WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()), {"langchain_community.utilities": "WikipediaAPIWrapper"}
185
197
  )
186
198
 
187
199
  return [wikipedia_tool]
188
200
 
189
201
  @classmethod
190
- def load_default_crewai_tools(cls) -> List["Tool"]:
202
+ def load_default_crewai_tools(cls) -> List["ToolCreate"]:
191
203
  # For now, we only support scrape website tool
192
204
  from crewai_tools import ScrapeWebsiteTool
193
205
 
194
- web_scrape_tool = Tool.from_crewai(ScrapeWebsiteTool())
206
+ web_scrape_tool = ToolCreate.from_crewai(ScrapeWebsiteTool())
195
207
 
196
208
  return [web_scrape_tool]
197
209
 
198
210
  @classmethod
199
- def load_default_composio_tools(cls) -> List["Tool"]:
211
+ def load_default_composio_tools(cls) -> List["ToolCreate"]:
200
212
  from composio_langchain import Action
201
213
 
202
- calculator = Tool.get_composio_tool(action=Action.MATHEMATICAL_CALCULATOR)
203
- serp_news = Tool.get_composio_tool(action=Action.SERPAPI_NEWS_SEARCH)
204
- serp_google_search = Tool.get_composio_tool(action=Action.SERPAPI_SEARCH)
205
- serp_google_maps = Tool.get_composio_tool(action=Action.SERPAPI_GOOGLE_MAPS_SEARCH)
214
+ calculator = ToolCreate.from_composio(action=Action.MATHEMATICAL_CALCULATOR)
215
+ serp_news = ToolCreate.from_composio(action=Action.SERPAPI_NEWS_SEARCH)
216
+ serp_google_search = ToolCreate.from_composio(action=Action.SERPAPI_SEARCH)
217
+ serp_google_maps = ToolCreate.from_composio(action=Action.SERPAPI_GOOGLE_MAPS_SEARCH)
206
218
 
207
219
  return [calculator, serp_news, serp_google_search, serp_google_maps]
208
220
 
209
221
 
210
- class ToolCreate(BaseTool):
211
- id: Optional[str] = Field(None, description="The unique identifier of the tool. If this is not provided, it will be autogenerated.")
212
- name: Optional[str] = Field(None, description="The name of the function (auto-generated from source_code if not provided).")
213
- description: Optional[str] = Field(None, description="The description of the tool.")
214
- tags: List[str] = Field([], description="Metadata tags.")
215
- source_code: str = Field(..., description="The source code of the function.")
216
- json_schema: Optional[Dict] = Field(
217
- None, description="The JSON schema of the function (auto-generated from source_code if not provided)"
218
- )
219
- terminal: Optional[bool] = Field(None, description="Whether the tool is a terminal tool (allow requesting heartbeats).")
220
-
221
-
222
- class ToolUpdate(ToolCreate):
223
- id: str = Field(..., description="The unique identifier of the tool.")
222
+ class ToolUpdate(LettaBase):
224
223
  description: Optional[str] = Field(None, description="The description of the tool.")
225
224
  name: Optional[str] = Field(None, description="The name of the function.")
226
225
  tags: Optional[List[str]] = Field(None, description="Metadata tags.")
226
+ module: Optional[str] = Field(None, description="The source code of the function.")
227
227
  source_code: Optional[str] = Field(None, description="The source code of the function.")
228
228
  json_schema: Optional[Dict] = Field(None, description="The JSON schema of the function.")
229
+ source_type: Optional[str] = Field(None, description="The type of the source code.")
letta/schemas/user.py CHANGED
@@ -3,8 +3,8 @@ from typing import Optional
3
3
 
4
4
  from pydantic import Field
5
5
 
6
- from letta.constants import DEFAULT_ORG_ID
7
6
  from letta.schemas.letta_base import LettaBase
7
+ from letta.services.organization_manager import OrganizationManager
8
8
 
9
9
 
10
10
  class UserBase(LettaBase):
@@ -22,7 +22,7 @@ class User(UserBase):
22
22
  """
23
23
 
24
24
  id: str = Field(..., description="The id of the user.")
25
- organization_id: Optional[str] = Field(DEFAULT_ORG_ID, description="The organization id of the user")
25
+ organization_id: Optional[str] = Field(OrganizationManager.DEFAULT_ORG_ID, description="The organization id of the user")
26
26
  name: str = Field(..., description="The name of the user.")
27
27
  created_at: datetime = Field(default_factory=datetime.utcnow, description="The creation date of the user.")
28
28
  updated_at: datetime = Field(default_factory=datetime.utcnow, description="The update date of the user.")
@@ -31,7 +31,7 @@ def setup_admin_router(server: SyncServer, interface: QueuingInterface):
31
31
  Create a new user in the database
32
32
  """
33
33
  try:
34
- user = server.create_user(request)
34
+ user = server.user_manager.create_user(request)
35
35
  except HTTPException:
36
36
  raise
37
37
  except Exception as e:
@@ -2,6 +2,7 @@ from typing import List, Optional
2
2
 
3
3
  from fastapi import APIRouter, Body, Depends, Header, HTTPException
4
4
 
5
+ from letta.orm.errors import NoResultFound
5
6
  from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
6
7
  from letta.server.rest_api.utils import get_letta_server
7
8
  from letta.server.server import SyncServer
@@ -13,13 +14,12 @@ router = APIRouter(prefix="/tools", tags=["tools"])
13
14
  def delete_tool(
14
15
  tool_id: str,
15
16
  server: SyncServer = Depends(get_letta_server),
16
- user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
17
17
  ):
18
18
  """
19
19
  Delete a tool by name
20
20
  """
21
21
  # actor = server.get_user_or_default(user_id=user_id)
22
- server.delete_tool(tool_id=tool_id)
22
+ server.tool_manager.delete_tool(tool_id=tool_id)
23
23
 
24
24
 
25
25
  @router.get("/{tool_id}", response_model=Tool, operation_id="get_tool")
@@ -30,9 +30,7 @@ def get_tool(
30
30
  """
31
31
  Get a tool by ID
32
32
  """
33
- # actor = server.get_current_user()
34
-
35
- tool = server.get_tool(tool_id=tool_id)
33
+ tool = server.tool_manager.get_tool_by_id(tool_id=tool_id)
36
34
  if tool is None:
37
35
  # return 404 error
38
36
  raise HTTPException(status_code=404, detail=f"Tool with id {tool_id} not found.")
@@ -50,26 +48,26 @@ def get_tool_id(
50
48
  """
51
49
  actor = server.get_user_or_default(user_id=user_id)
52
50
 
53
- tool_id = server.get_tool_id(tool_name, user_id=actor.id)
54
- if tool_id is None:
55
- # return 404 error
56
- raise HTTPException(status_code=404, detail=f"Tool with name {tool_name} not found.")
57
- return tool_id
51
+ try:
52
+ tool = server.tool_manager.get_tool_by_name_and_org_id(tool_name=tool_name, organization_id=actor.organization_id)
53
+ return tool.id
54
+ except NoResultFound:
55
+ raise HTTPException(status_code=404, detail=f"Tool with name {tool_name} and organization id {actor.organization_id} not found.")
58
56
 
59
57
 
60
58
  @router.get("/", response_model=List[Tool], operation_id="list_tools")
61
- def list_all_tools(
59
+ def list_tools(
62
60
  cursor: Optional[str] = None,
63
61
  limit: Optional[int] = 50,
64
62
  server: SyncServer = Depends(get_letta_server),
65
63
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
66
64
  ):
67
65
  """
68
- Get a list of all tools available to agents created by a user
66
+ Get a list of all tools available to agents belonging to the org of the user
69
67
  """
70
68
  try:
71
69
  actor = server.get_user_or_default(user_id=user_id)
72
- return server.list_tools(cursor=cursor, limit=limit, user_id=actor.id)
70
+ return server.tool_manager.list_tools_for_org(organization_id=actor.organization_id, cursor=cursor, limit=limit)
73
71
  except Exception as e:
74
72
  # Log or print the full exception here for debugging
75
73
  print(f"Error occurred: {e}")
@@ -78,21 +76,21 @@ def list_all_tools(
78
76
 
79
77
  @router.post("/", response_model=Tool, operation_id="create_tool")
80
78
  def create_tool(
81
- tool: ToolCreate = Body(...),
82
- update: bool = False,
79
+ request: ToolCreate = Body(...),
83
80
  server: SyncServer = Depends(get_letta_server),
84
81
  user_id: Optional[str] = Header(None, alias="user_id"), # Extract user_id from header, default to None if not present
85
82
  ):
86
83
  """
87
84
  Create a new tool
88
85
  """
86
+ # Derive user and org id from actor
89
87
  actor = server.get_user_or_default(user_id=user_id)
88
+ request.organization_id = actor.organization_id
89
+ request.user_id = actor.id
90
90
 
91
- return server.create_tool(
92
- request=tool,
93
- # update=update,
94
- update=True,
95
- user_id=actor.id,
91
+ # Send request to create the tool
92
+ return server.tool_manager.create_or_update_tool(
93
+ tool_create=request,
96
94
  )
97
95
 
98
96
 
@@ -106,6 +104,4 @@ def update_tool(
106
104
  """
107
105
  Update an existing tool
108
106
  """
109
- assert tool_id == request.id, "Tool ID in path must match tool ID in request body"
110
- # actor = server.get_user_or_default(user_id=user_id)
111
- return server.update_tool(request, user_id)
107
+ return server.tool_manager.update_tool_by_id(tool_id, request)