letta-nightly 0.5.1.dev20241024104118__py3-none-any.whl → 0.5.1.dev20241026104101__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/agent.py +1 -6
- letta/cli/cli.py +6 -4
- letta/client/client.py +75 -92
- letta/config.py +0 -6
- letta/constants.py +0 -9
- letta/functions/functions.py +24 -0
- letta/functions/helpers.py +4 -3
- letta/functions/schema_generator.py +10 -2
- letta/metadata.py +2 -99
- letta/o1_agent.py +2 -2
- letta/orm/__all__.py +15 -0
- letta/orm/mixins.py +16 -1
- letta/orm/organization.py +2 -0
- letta/orm/sqlalchemy_base.py +17 -18
- letta/orm/tool.py +54 -0
- letta/orm/user.py +7 -1
- letta/schemas/block.py +6 -9
- letta/schemas/memory.py +27 -29
- letta/schemas/tool.py +62 -61
- letta/schemas/user.py +2 -2
- letta/server/rest_api/admin/users.py +1 -1
- letta/server/rest_api/routers/v1/tools.py +19 -23
- letta/server/server.py +42 -327
- letta/services/organization_manager.py +19 -12
- letta/services/tool_manager.py +193 -0
- letta/services/user_manager.py +16 -11
- {letta_nightly-0.5.1.dev20241024104118.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/METADATA +1 -1
- {letta_nightly-0.5.1.dev20241024104118.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/RECORD +31 -29
- {letta_nightly-0.5.1.dev20241024104118.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.1.dev20241024104118.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.1.dev20241024104118.dist-info → letta_nightly-0.5.1.dev20241026104101.dist-info}/entry_points.txt +0 -0
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 =
|
|
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
|
|
63
|
-
cls,
|
|
64
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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["
|
|
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 =
|
|
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["
|
|
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 =
|
|
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["
|
|
211
|
+
def load_default_composio_tools(cls) -> List["ToolCreate"]:
|
|
200
212
|
from composio_langchain import Action
|
|
201
213
|
|
|
202
|
-
calculator =
|
|
203
|
-
serp_news =
|
|
204
|
-
serp_google_search =
|
|
205
|
-
serp_google_maps =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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)
|