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/server/server.py
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
# inspecting tools
|
|
2
|
-
import importlib
|
|
3
|
-
import inspect
|
|
4
2
|
import os
|
|
5
3
|
import traceback
|
|
6
4
|
import warnings
|
|
@@ -16,7 +14,6 @@ import letta.system as system
|
|
|
16
14
|
from letta.agent import Agent, save_agent
|
|
17
15
|
from letta.agent_store.db import attach_base
|
|
18
16
|
from letta.agent_store.storage import StorageConnector, TableType
|
|
19
|
-
from letta.client.utils import derive_function_name_regex
|
|
20
17
|
from letta.credentials import LettaCredentials
|
|
21
18
|
from letta.data_sources.connectors import DataConnector, load_data
|
|
22
19
|
|
|
@@ -30,11 +27,7 @@ from letta.data_sources.connectors import DataConnector, load_data
|
|
|
30
27
|
# Token,
|
|
31
28
|
# User,
|
|
32
29
|
# )
|
|
33
|
-
from letta.functions.functions import
|
|
34
|
-
generate_schema,
|
|
35
|
-
load_function_set,
|
|
36
|
-
parse_source_code,
|
|
37
|
-
)
|
|
30
|
+
from letta.functions.functions import generate_schema, parse_source_code
|
|
38
31
|
from letta.functions.schema_generator import generate_schema
|
|
39
32
|
|
|
40
33
|
# TODO use custom interface
|
|
@@ -82,10 +75,11 @@ from letta.schemas.memory import (
|
|
|
82
75
|
from letta.schemas.message import Message, MessageCreate, MessageRole, UpdateMessage
|
|
83
76
|
from letta.schemas.passage import Passage
|
|
84
77
|
from letta.schemas.source import Source, SourceCreate, SourceUpdate
|
|
85
|
-
from letta.schemas.tool import Tool, ToolCreate
|
|
78
|
+
from letta.schemas.tool import Tool, ToolCreate
|
|
86
79
|
from letta.schemas.usage import LettaUsageStatistics
|
|
87
|
-
from letta.schemas.user import User
|
|
80
|
+
from letta.schemas.user import User
|
|
88
81
|
from letta.services.organization_manager import OrganizationManager
|
|
82
|
+
from letta.services.tool_manager import ToolManager
|
|
89
83
|
from letta.services.user_manager import UserManager
|
|
90
84
|
from letta.utils import create_random_username, json_dumps, json_loads
|
|
91
85
|
|
|
@@ -214,6 +208,7 @@ class SyncServer(Server):
|
|
|
214
208
|
chaining: bool = True,
|
|
215
209
|
max_chaining_steps: Optional[bool] = None,
|
|
216
210
|
default_interface_factory: Callable[[], AgentInterface] = lambda: CLIInterface(),
|
|
211
|
+
init_with_default_org_and_user: bool = True,
|
|
217
212
|
# default_interface: AgentInterface = CLIInterface(),
|
|
218
213
|
# default_persistence_manager_cls: PersistenceManager = LocalStateManager,
|
|
219
214
|
# auth_mode: str = "none", # "none, "jwt", "external"
|
|
@@ -249,13 +244,19 @@ class SyncServer(Server):
|
|
|
249
244
|
# Managers that interface with data models
|
|
250
245
|
self.organization_manager = OrganizationManager()
|
|
251
246
|
self.user_manager = UserManager()
|
|
247
|
+
self.tool_manager = ToolManager()
|
|
252
248
|
|
|
253
|
-
#
|
|
254
|
-
|
|
255
|
-
|
|
249
|
+
# Make default user and org
|
|
250
|
+
if init_with_default_org_and_user:
|
|
251
|
+
self.default_org = self.organization_manager.create_default_organization()
|
|
252
|
+
self.default_user = self.user_manager.create_default_user()
|
|
253
|
+
self.add_default_blocks(self.default_user.id)
|
|
254
|
+
self.tool_manager.add_default_tools(module_name="base", user_id=self.default_user.id, org_id=self.default_org.id)
|
|
256
255
|
|
|
257
|
-
|
|
258
|
-
|
|
256
|
+
# If there is a default org/user
|
|
257
|
+
# This logic may have to change in the future
|
|
258
|
+
if settings.load_default_external_tools:
|
|
259
|
+
self.add_default_external_tools(user_id=self.default_user.id, org_id=self.default_org.id)
|
|
259
260
|
|
|
260
261
|
# collect providers (always has Letta as a default)
|
|
261
262
|
self._enabled_providers: List[Provider] = [LettaProvider()]
|
|
@@ -364,7 +365,7 @@ class SyncServer(Server):
|
|
|
364
365
|
logger.debug(f"Creating an agent object")
|
|
365
366
|
tool_objs = []
|
|
366
367
|
for name in agent_state.tools:
|
|
367
|
-
tool_obj = self.
|
|
368
|
+
tool_obj = self.tool_manager.get_tool_by_name_and_user_id(tool_name=name, user_id=user_id)
|
|
368
369
|
if not tool_obj:
|
|
369
370
|
logger.exception(f"Tool {name} does not exist for user {user_id}")
|
|
370
371
|
raise ValueError(f"Tool {name} does not exist for user {user_id}")
|
|
@@ -755,22 +756,6 @@ class SyncServer(Server):
|
|
|
755
756
|
command = command[1:] # strip the prefix
|
|
756
757
|
return self._command(user_id=user_id, agent_id=agent_id, command=command)
|
|
757
758
|
|
|
758
|
-
def create_user(self, request: UserCreate) -> User:
|
|
759
|
-
"""Create a new user using a config"""
|
|
760
|
-
if not request.name:
|
|
761
|
-
# auto-generate a name
|
|
762
|
-
request.name = create_random_username()
|
|
763
|
-
user = self.user_manager.create_user(request)
|
|
764
|
-
logger.debug(f"Created new user from config: {user}")
|
|
765
|
-
|
|
766
|
-
# add default for the user
|
|
767
|
-
# TODO: move to org
|
|
768
|
-
assert user.id is not None, f"User id is None: {user}"
|
|
769
|
-
self.add_default_blocks(user.id)
|
|
770
|
-
self.add_default_tools(module_name="base", user_id=user.id)
|
|
771
|
-
|
|
772
|
-
return user
|
|
773
|
-
|
|
774
759
|
def create_agent(
|
|
775
760
|
self,
|
|
776
761
|
request: CreateAgent,
|
|
@@ -816,8 +801,7 @@ class SyncServer(Server):
|
|
|
816
801
|
tool_objs = []
|
|
817
802
|
if request.tools:
|
|
818
803
|
for tool_name in request.tools:
|
|
819
|
-
tool_obj = self.
|
|
820
|
-
assert tool_obj, f"Tool {tool_name} does not exist"
|
|
804
|
+
tool_obj = self.tool_manager.get_tool_by_name_and_user_id(tool_name=tool_name, user_id=user_id)
|
|
821
805
|
tool_objs.append(tool_obj)
|
|
822
806
|
|
|
823
807
|
assert request.memory is not None
|
|
@@ -832,16 +816,15 @@ class SyncServer(Server):
|
|
|
832
816
|
json_schema = generate_schema(func, terminal=False, name=func_name)
|
|
833
817
|
source_type = "python"
|
|
834
818
|
tags = ["memory", "memgpt-base"]
|
|
835
|
-
tool = self.
|
|
836
|
-
|
|
819
|
+
tool = self.tool_manager.create_or_update_tool(
|
|
820
|
+
ToolCreate(
|
|
837
821
|
source_code=source_code,
|
|
838
822
|
source_type=source_type,
|
|
839
823
|
tags=tags,
|
|
840
824
|
json_schema=json_schema,
|
|
841
825
|
user_id=user_id,
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
user_id=user_id,
|
|
826
|
+
organization_id=user.organization_id,
|
|
827
|
+
)
|
|
845
828
|
)
|
|
846
829
|
tool_objs.append(tool)
|
|
847
830
|
if not request.tools:
|
|
@@ -939,7 +922,7 @@ class SyncServer(Server):
|
|
|
939
922
|
# (1) get tools + make sure they exist
|
|
940
923
|
tool_objs = []
|
|
941
924
|
for tool_name in request.tools:
|
|
942
|
-
tool_obj = self.
|
|
925
|
+
tool_obj = self.tool_manager.get_tool_by_name_and_user_id(tool_name=tool_name, user_id=user_id)
|
|
943
926
|
assert tool_obj, f"Tool {tool_name} does not exist"
|
|
944
927
|
tool_objs.append(tool_obj)
|
|
945
928
|
|
|
@@ -995,12 +978,12 @@ class SyncServer(Server):
|
|
|
995
978
|
|
|
996
979
|
# Get all the tool objects from the request
|
|
997
980
|
tool_objs = []
|
|
998
|
-
tool_obj = self.
|
|
981
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool_id)
|
|
999
982
|
assert tool_obj, f"Tool with id={tool_id} does not exist"
|
|
1000
983
|
tool_objs.append(tool_obj)
|
|
1001
984
|
|
|
1002
985
|
for tool in letta_agent.tools:
|
|
1003
|
-
tool_obj = self.
|
|
986
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id)
|
|
1004
987
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
1005
988
|
|
|
1006
989
|
# If it's not the already added tool
|
|
@@ -1035,7 +1018,7 @@ class SyncServer(Server):
|
|
|
1035
1018
|
# Get all the tool_objs
|
|
1036
1019
|
tool_objs = []
|
|
1037
1020
|
for tool in letta_agent.tools:
|
|
1038
|
-
tool_obj = self.
|
|
1021
|
+
tool_obj = self.tool_manager.get_tool_by_id(tool_id=tool.id)
|
|
1039
1022
|
assert tool_obj, f"Tool with id={tool.id} does not exist"
|
|
1040
1023
|
|
|
1041
1024
|
# If it's not the tool we want to remove
|
|
@@ -1076,86 +1059,6 @@ class SyncServer(Server):
|
|
|
1076
1059
|
agents_states = self.ms.list_agents(user_id=user_id)
|
|
1077
1060
|
return agents_states
|
|
1078
1061
|
|
|
1079
|
-
# TODO make return type pydantic
|
|
1080
|
-
def list_agents_legacy(
|
|
1081
|
-
self,
|
|
1082
|
-
user_id: str,
|
|
1083
|
-
) -> dict:
|
|
1084
|
-
"""List all available agents to a user"""
|
|
1085
|
-
|
|
1086
|
-
if user_id is None:
|
|
1087
|
-
agents_states = self.ms.list_all_agents()
|
|
1088
|
-
else:
|
|
1089
|
-
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1090
|
-
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1091
|
-
|
|
1092
|
-
agents_states = self.ms.list_agents(user_id=user_id)
|
|
1093
|
-
|
|
1094
|
-
agents_states_dicts = [self._agent_state_to_config(state) for state in agents_states]
|
|
1095
|
-
|
|
1096
|
-
# TODO add a get_message_obj_from_message_id(...) function
|
|
1097
|
-
# this would allow grabbing Message.created_by without having to load the agent object
|
|
1098
|
-
# all_available_tools = self.ms.list_tools(user_id=user_id) # TODO: add back when user-specific
|
|
1099
|
-
self.ms.list_tools()
|
|
1100
|
-
|
|
1101
|
-
for agent_state, return_dict in zip(agents_states, agents_states_dicts):
|
|
1102
|
-
|
|
1103
|
-
# Get the agent object (loaded in memory)
|
|
1104
|
-
letta_agent = self._get_or_load_agent(user_id=agent_state.user_id, agent_id=agent_state.id)
|
|
1105
|
-
|
|
1106
|
-
# TODO remove this eventually when return type get pydanticfied
|
|
1107
|
-
# this is to add persona_name and human_name so that the columns in UI can populate
|
|
1108
|
-
# TODO hack for frontend, remove
|
|
1109
|
-
# (top level .persona is persona_name, and nested memory.persona is the state)
|
|
1110
|
-
# TODO: eventually modify this to be contained in the metadata
|
|
1111
|
-
return_dict["persona"] = agent_state._metadata.get("persona", None)
|
|
1112
|
-
return_dict["human"] = agent_state._metadata.get("human", None)
|
|
1113
|
-
|
|
1114
|
-
# Add information about tools
|
|
1115
|
-
# TODO letta_agent should really have a field of List[ToolModel]
|
|
1116
|
-
# then we could just pull that field and return it here
|
|
1117
|
-
# return_dict["tools"] = [tool for tool in all_available_tools if tool.json_schema in letta_agent.functions]
|
|
1118
|
-
|
|
1119
|
-
# get tool info from agent state
|
|
1120
|
-
tools = []
|
|
1121
|
-
for tool_name in agent_state.tools:
|
|
1122
|
-
tool = self.ms.get_tool(tool_name=tool_name, user_id=user_id)
|
|
1123
|
-
tools.append(tool)
|
|
1124
|
-
return_dict["tools"] = tools
|
|
1125
|
-
|
|
1126
|
-
# Add information about memory (raw core, size of recall, size of archival)
|
|
1127
|
-
core_memory = letta_agent.memory
|
|
1128
|
-
recall_memory = letta_agent.persistence_manager.recall_memory
|
|
1129
|
-
archival_memory = letta_agent.persistence_manager.archival_memory
|
|
1130
|
-
memory_obj = {
|
|
1131
|
-
"core_memory": core_memory.to_flat_dict(),
|
|
1132
|
-
"recall_memory": len(recall_memory) if recall_memory is not None else None,
|
|
1133
|
-
"archival_memory": len(archival_memory) if archival_memory is not None else None,
|
|
1134
|
-
}
|
|
1135
|
-
return_dict["memory"] = memory_obj
|
|
1136
|
-
|
|
1137
|
-
# Add information about last run
|
|
1138
|
-
# NOTE: 'last_run' is just the timestamp on the latest message in the buffer
|
|
1139
|
-
# Retrieve the Message object via the recall storage or by directly access _messages
|
|
1140
|
-
last_msg_obj = letta_agent._messages[-1]
|
|
1141
|
-
return_dict["last_run"] = last_msg_obj.created_at
|
|
1142
|
-
|
|
1143
|
-
# Add information about attached sources
|
|
1144
|
-
sources_ids = self.ms.list_attached_sources(agent_id=agent_state.id)
|
|
1145
|
-
sources = [self.ms.get_source(source_id=s_id) for s_id in sources_ids]
|
|
1146
|
-
return_dict["sources"] = [vars(s) for s in sources]
|
|
1147
|
-
|
|
1148
|
-
# Sort agents by "last_run" in descending order, most recent first
|
|
1149
|
-
agents_states_dicts.sort(key=lambda x: x["last_run"], reverse=True)
|
|
1150
|
-
|
|
1151
|
-
logger.debug(f"Retrieved {len(agents_states)} agents for user {user_id}")
|
|
1152
|
-
return {
|
|
1153
|
-
"num_agents": len(agents_states),
|
|
1154
|
-
"agents": agents_states_dicts,
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
# blocks
|
|
1158
|
-
|
|
1159
1062
|
def get_blocks(
|
|
1160
1063
|
self,
|
|
1161
1064
|
user_id: Optional[str] = None,
|
|
@@ -1195,7 +1098,7 @@ class SyncServer(Server):
|
|
|
1195
1098
|
block.value = request.value if request.value is not None else block.value
|
|
1196
1099
|
block.name = request.name if request.name is not None else block.name
|
|
1197
1100
|
self.ms.update_block(block=block)
|
|
1198
|
-
return
|
|
1101
|
+
return self.ms.get_block(block_id=request.id)
|
|
1199
1102
|
|
|
1200
1103
|
def delete_block(self, block_id: str):
|
|
1201
1104
|
block = self.get_block(block_id)
|
|
@@ -1222,9 +1125,9 @@ class SyncServer(Server):
|
|
|
1222
1125
|
raise ValueError("Source does not exist")
|
|
1223
1126
|
return existing_source.id
|
|
1224
1127
|
|
|
1225
|
-
def get_agent(self, user_id: str, agent_id: str, agent_name: Optional[str] = None):
|
|
1128
|
+
def get_agent(self, user_id: str, agent_id: Optional[str] = None, agent_name: Optional[str] = None):
|
|
1226
1129
|
"""Get the agent state"""
|
|
1227
|
-
return self.ms.get_agent(agent_id=agent_id, user_id=user_id)
|
|
1130
|
+
return self.ms.get_agent(agent_id=agent_id, agent_name=agent_name, user_id=user_id)
|
|
1228
1131
|
|
|
1229
1132
|
# def get_user(self, user_id: str) -> User:
|
|
1230
1133
|
# """Get the user"""
|
|
@@ -1510,7 +1413,7 @@ class SyncServer(Server):
|
|
|
1510
1413
|
if value is None:
|
|
1511
1414
|
continue
|
|
1512
1415
|
if letta_agent.memory.get_block(key) != value:
|
|
1513
|
-
letta_agent.memory.update_block_value(
|
|
1416
|
+
letta_agent.memory.update_block_value(label=key, value=value) # update agent memory
|
|
1514
1417
|
modified = True
|
|
1515
1418
|
|
|
1516
1419
|
# If we modified the memory contents, we need to rebuild the memory block inside the system message
|
|
@@ -1830,195 +1733,17 @@ class SyncServer(Server):
|
|
|
1830
1733
|
|
|
1831
1734
|
return sources_with_metadata
|
|
1832
1735
|
|
|
1833
|
-
def
|
|
1834
|
-
"""Get tool by ID."""
|
|
1835
|
-
return self.ms.get_tool(tool_id=tool_id)
|
|
1836
|
-
|
|
1837
|
-
def tool_with_name_and_user_id_exists(self, tool: Tool, user_id: Optional[str] = None) -> bool:
|
|
1838
|
-
"""Check if tool exists"""
|
|
1839
|
-
tool = self.ms.get_tool_with_name_and_user_id(tool_name=tool.name, user_id=user_id)
|
|
1840
|
-
|
|
1841
|
-
if tool is None:
|
|
1842
|
-
return False
|
|
1843
|
-
else:
|
|
1844
|
-
return True
|
|
1845
|
-
|
|
1846
|
-
def get_tool_id(self, name: str, user_id: str) -> Optional[str]:
|
|
1847
|
-
"""Get tool ID from name and user_id."""
|
|
1848
|
-
tool = self.ms.get_tool(tool_name=name, user_id=user_id)
|
|
1849
|
-
if not tool or tool.id is None:
|
|
1850
|
-
return None
|
|
1851
|
-
return tool.id
|
|
1852
|
-
|
|
1853
|
-
def update_tool(self, request: ToolUpdate, user_id: Optional[str] = None) -> Tool:
|
|
1854
|
-
"""Update an existing tool"""
|
|
1855
|
-
if request.name:
|
|
1856
|
-
existing_tool = self.ms.get_tool_with_name_and_user_id(tool_name=request.name, user_id=user_id)
|
|
1857
|
-
if existing_tool is None:
|
|
1858
|
-
raise ValueError(f"Tool with name={request.name}, user_id={user_id} does not exist")
|
|
1859
|
-
else:
|
|
1860
|
-
existing_tool = self.ms.get_tool(tool_id=request.id)
|
|
1861
|
-
if existing_tool is None:
|
|
1862
|
-
raise ValueError(f"Tool with id={request.id} does not exist")
|
|
1863
|
-
|
|
1864
|
-
# Preserve the original tool id
|
|
1865
|
-
# As we can override the tool id as well
|
|
1866
|
-
# This is probably bad design if this is exposed to users...
|
|
1867
|
-
original_id = existing_tool.id
|
|
1868
|
-
|
|
1869
|
-
# override updated fields
|
|
1870
|
-
if request.id:
|
|
1871
|
-
existing_tool.id = request.id
|
|
1872
|
-
if request.description:
|
|
1873
|
-
existing_tool.description = request.description
|
|
1874
|
-
if request.source_code:
|
|
1875
|
-
existing_tool.source_code = request.source_code
|
|
1876
|
-
if request.source_type:
|
|
1877
|
-
existing_tool.source_type = request.source_type
|
|
1878
|
-
if request.tags:
|
|
1879
|
-
existing_tool.tags = request.tags
|
|
1880
|
-
if request.json_schema:
|
|
1881
|
-
existing_tool.json_schema = request.json_schema
|
|
1882
|
-
|
|
1883
|
-
# If name is explicitly provided here, overide the tool name
|
|
1884
|
-
if request.name:
|
|
1885
|
-
existing_tool.name = request.name
|
|
1886
|
-
# Otherwise, if there's no name, and there's source code, we try to derive the name
|
|
1887
|
-
elif request.source_code:
|
|
1888
|
-
existing_tool.name = derive_function_name_regex(request.source_code)
|
|
1889
|
-
|
|
1890
|
-
self.ms.update_tool(original_id, existing_tool)
|
|
1891
|
-
return self.ms.get_tool(tool_id=request.id)
|
|
1892
|
-
|
|
1893
|
-
def create_tool(self, request: ToolCreate, user_id: Optional[str] = None, update: bool = True) -> Tool: # TODO: add other fields
|
|
1894
|
-
"""Create a new tool"""
|
|
1895
|
-
|
|
1896
|
-
# NOTE: deprecated code that existed when we were trying to pretend that `self` was the memory object
|
|
1897
|
-
# if request.tags and "memory" in request.tags:
|
|
1898
|
-
# # special modifications to memory functions
|
|
1899
|
-
# # self.memory -> self.memory.memory, since Agent.memory.memory needs to be modified (not BaseMemory.memory)
|
|
1900
|
-
# request.source_code = request.source_code.replace("self.memory", "self.memory.memory")
|
|
1901
|
-
|
|
1902
|
-
if not request.json_schema:
|
|
1903
|
-
# auto-generate openai schema
|
|
1904
|
-
try:
|
|
1905
|
-
env = {}
|
|
1906
|
-
env.update(globals())
|
|
1907
|
-
exec(request.source_code, env)
|
|
1908
|
-
|
|
1909
|
-
# get available functions
|
|
1910
|
-
functions = [f for f in env if callable(env[f])]
|
|
1911
|
-
|
|
1912
|
-
except Exception as e:
|
|
1913
|
-
logger.error(f"Failed to execute source code: {e}")
|
|
1914
|
-
|
|
1915
|
-
# TODO: not sure if this always works
|
|
1916
|
-
func = env[functions[-1]]
|
|
1917
|
-
json_schema = generate_schema(func, terminal=request.terminal)
|
|
1918
|
-
else:
|
|
1919
|
-
# provided by client
|
|
1920
|
-
json_schema = request.json_schema
|
|
1921
|
-
|
|
1922
|
-
if not request.name:
|
|
1923
|
-
# use name from JSON schema
|
|
1924
|
-
request.name = json_schema["name"]
|
|
1925
|
-
assert request.name, f"Tool name must be provided in json_schema {json_schema}. This should never happen."
|
|
1926
|
-
|
|
1927
|
-
# check if already exists:
|
|
1928
|
-
existing_tool = self.ms.get_tool(tool_id=request.id, tool_name=request.name, user_id=user_id)
|
|
1929
|
-
if existing_tool:
|
|
1930
|
-
if update:
|
|
1931
|
-
# id is an optional field, so we will fill it with the existing tool id
|
|
1932
|
-
if not request.id:
|
|
1933
|
-
request.id = existing_tool.id
|
|
1934
|
-
updated_tool = self.update_tool(ToolUpdate(**vars(request)), user_id)
|
|
1935
|
-
assert updated_tool is not None, f"Failed to update tool {request.name}"
|
|
1936
|
-
return updated_tool
|
|
1937
|
-
else:
|
|
1938
|
-
raise ValueError(f"Tool {request.name} already exists and update=False")
|
|
1939
|
-
|
|
1940
|
-
# check for description
|
|
1941
|
-
description = None
|
|
1942
|
-
if request.description:
|
|
1943
|
-
description = request.description
|
|
1944
|
-
|
|
1945
|
-
tool = Tool(
|
|
1946
|
-
name=request.name,
|
|
1947
|
-
source_code=request.source_code,
|
|
1948
|
-
source_type=request.source_type,
|
|
1949
|
-
tags=request.tags,
|
|
1950
|
-
json_schema=json_schema,
|
|
1951
|
-
user_id=user_id,
|
|
1952
|
-
description=description,
|
|
1953
|
-
)
|
|
1954
|
-
|
|
1955
|
-
if request.id:
|
|
1956
|
-
tool.id = request.id
|
|
1957
|
-
|
|
1958
|
-
self.ms.create_tool(tool)
|
|
1959
|
-
created_tool = self.ms.get_tool(tool_id=tool.id, user_id=user_id)
|
|
1960
|
-
return created_tool
|
|
1961
|
-
|
|
1962
|
-
def delete_tool(self, tool_id: str):
|
|
1963
|
-
"""Delete a tool"""
|
|
1964
|
-
self.ms.delete_tool(tool_id)
|
|
1965
|
-
|
|
1966
|
-
def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50, user_id: Optional[str] = None) -> List[Tool]:
|
|
1967
|
-
"""List tools available to user_id"""
|
|
1968
|
-
tools = self.ms.list_tools(cursor=cursor, limit=limit, user_id=user_id)
|
|
1969
|
-
return tools
|
|
1970
|
-
|
|
1971
|
-
def add_default_tools(self, module_name="base", user_id: Optional[str] = None):
|
|
1972
|
-
"""Add default tools in {module_name}.py"""
|
|
1973
|
-
full_module_name = f"letta.functions.function_sets.{module_name}"
|
|
1974
|
-
try:
|
|
1975
|
-
module = importlib.import_module(full_module_name)
|
|
1976
|
-
except Exception as e:
|
|
1977
|
-
# Handle other general exceptions
|
|
1978
|
-
raise e
|
|
1979
|
-
|
|
1980
|
-
functions_to_schema = []
|
|
1981
|
-
try:
|
|
1982
|
-
# Load the function set
|
|
1983
|
-
functions_to_schema = load_function_set(module)
|
|
1984
|
-
except ValueError as e:
|
|
1985
|
-
err = f"Error loading function set '{module_name}': {e}"
|
|
1986
|
-
warnings.warn(err)
|
|
1987
|
-
|
|
1988
|
-
# create tool in db
|
|
1989
|
-
for name, schema in functions_to_schema.items():
|
|
1990
|
-
# print([str(inspect.getsource(line)) for line in schema["imports"]])
|
|
1991
|
-
source_code = inspect.getsource(schema["python_function"])
|
|
1992
|
-
tags = [module_name]
|
|
1993
|
-
if module_name == "base":
|
|
1994
|
-
tags.append("letta-base")
|
|
1995
|
-
|
|
1996
|
-
# create to tool
|
|
1997
|
-
self.create_tool(
|
|
1998
|
-
ToolCreate(
|
|
1999
|
-
name=name,
|
|
2000
|
-
tags=tags,
|
|
2001
|
-
source_type="python",
|
|
2002
|
-
module=schema["module"],
|
|
2003
|
-
source_code=source_code,
|
|
2004
|
-
json_schema=schema["json_schema"],
|
|
2005
|
-
user_id=user_id,
|
|
2006
|
-
),
|
|
2007
|
-
update=True,
|
|
2008
|
-
)
|
|
2009
|
-
|
|
2010
|
-
def add_default_external_tools(self, user_id: Optional[str] = None) -> bool:
|
|
1736
|
+
def add_default_external_tools(self, user_id: str, org_id: str) -> bool:
|
|
2011
1737
|
"""Add default langchain tools. Return true if successful, false otherwise."""
|
|
2012
1738
|
success = True
|
|
1739
|
+
tool_creates = ToolCreate.load_default_langchain_tools() + ToolCreate.load_default_crewai_tools()
|
|
2013
1740
|
if tool_settings.composio_api_key:
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
tools = Tool.load_default_langchain_tools() + Tool.load_default_crewai_tools()
|
|
2017
|
-
for tool in tools:
|
|
1741
|
+
tool_creates += ToolCreate.load_default_composio_tools()
|
|
1742
|
+
for tool_create in tool_creates:
|
|
2018
1743
|
try:
|
|
2019
|
-
self.
|
|
1744
|
+
self.tool_manager.create_or_update_tool(tool_create)
|
|
2020
1745
|
except Exception as e:
|
|
2021
|
-
warnings.warn(f"An error occurred while creating tool {
|
|
1746
|
+
warnings.warn(f"An error occurred while creating tool {tool_create}: {e}")
|
|
2022
1747
|
warnings.warn(traceback.format_exc())
|
|
2023
1748
|
success = False
|
|
2024
1749
|
|
|
@@ -2108,25 +1833,15 @@ class SyncServer(Server):
|
|
|
2108
1833
|
letta_agent = self._get_or_load_agent(agent_id=agent_id)
|
|
2109
1834
|
return letta_agent.retry_message()
|
|
2110
1835
|
|
|
2111
|
-
# TODO: Move a lot of this default logic to the ORM
|
|
2112
|
-
def get_default_user(self) -> User:
|
|
2113
|
-
self.organization_manager.create_default_organization()
|
|
2114
|
-
user = self.user_manager.create_default_user()
|
|
2115
|
-
|
|
2116
|
-
self.add_default_blocks(user.id)
|
|
2117
|
-
self.add_default_tools(module_name="base", user_id=user.id)
|
|
2118
|
-
|
|
2119
|
-
return user
|
|
2120
|
-
|
|
2121
1836
|
def get_user_or_default(self, user_id: Optional[str]) -> User:
|
|
2122
1837
|
"""Get the user object for user_id if it exists, otherwise return the default user object"""
|
|
2123
1838
|
if user_id is None:
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
1839
|
+
user_id = self.user_manager.DEFAULT_USER_ID
|
|
1840
|
+
|
|
1841
|
+
try:
|
|
1842
|
+
return self.user_manager.get_user_by_id(user_id=user_id)
|
|
1843
|
+
except ValueError:
|
|
1844
|
+
raise HTTPException(status_code=404, detail=f"User with id {user_id} not found")
|
|
2130
1845
|
|
|
2131
1846
|
def list_llm_models(self) -> List[LLMConfig]:
|
|
2132
1847
|
"""List available models"""
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
from typing import List, Optional
|
|
2
2
|
|
|
3
|
-
from letta.constants import DEFAULT_ORG_ID, DEFAULT_ORG_NAME
|
|
4
3
|
from letta.orm.errors import NoResultFound
|
|
5
|
-
from letta.orm.organization import Organization
|
|
4
|
+
from letta.orm.organization import Organization as OrganizationModel
|
|
6
5
|
from letta.schemas.organization import Organization as PydanticOrganization
|
|
7
6
|
from letta.utils import create_random_username, enforce_types
|
|
8
7
|
|
|
@@ -10,6 +9,9 @@ from letta.utils import create_random_username, enforce_types
|
|
|
10
9
|
class OrganizationManager:
|
|
11
10
|
"""Manager class to handle business logic related to Organizations."""
|
|
12
11
|
|
|
12
|
+
DEFAULT_ORG_ID = "organization-00000000-0000-4000-8000-000000000000"
|
|
13
|
+
DEFAULT_ORG_NAME = "default_org"
|
|
14
|
+
|
|
13
15
|
def __init__(self):
|
|
14
16
|
# This is probably horrible but we reuse this technique from metadata.py
|
|
15
17
|
# TODO: Please refactor this out
|
|
@@ -19,12 +21,17 @@ class OrganizationManager:
|
|
|
19
21
|
|
|
20
22
|
self.session_maker = db_context
|
|
21
23
|
|
|
24
|
+
@enforce_types
|
|
25
|
+
def get_default_organization(self) -> PydanticOrganization:
|
|
26
|
+
"""Fetch the default organization."""
|
|
27
|
+
return self.get_organization_by_id(self.DEFAULT_ORG_ID)
|
|
28
|
+
|
|
22
29
|
@enforce_types
|
|
23
30
|
def get_organization_by_id(self, org_id: str) -> PydanticOrganization:
|
|
24
31
|
"""Fetch an organization by ID."""
|
|
25
32
|
with self.session_maker() as session:
|
|
26
33
|
try:
|
|
27
|
-
organization =
|
|
34
|
+
organization = OrganizationModel.read(db_session=session, identifier=org_id)
|
|
28
35
|
return organization.to_pydantic()
|
|
29
36
|
except NoResultFound:
|
|
30
37
|
raise ValueError(f"Organization with id {org_id} not found.")
|
|
@@ -33,7 +40,7 @@ class OrganizationManager:
|
|
|
33
40
|
def create_organization(self, name: Optional[str] = None) -> PydanticOrganization:
|
|
34
41
|
"""Create a new organization. If a name is provided, it is used, otherwise, a random one is generated."""
|
|
35
42
|
with self.session_maker() as session:
|
|
36
|
-
org =
|
|
43
|
+
org = OrganizationModel(name=name if name else create_random_username())
|
|
37
44
|
org.create(session)
|
|
38
45
|
return org.to_pydantic()
|
|
39
46
|
|
|
@@ -43,10 +50,10 @@ class OrganizationManager:
|
|
|
43
50
|
with self.session_maker() as session:
|
|
44
51
|
# Try to get it first
|
|
45
52
|
try:
|
|
46
|
-
org =
|
|
53
|
+
org = OrganizationModel.read(db_session=session, identifier=self.DEFAULT_ORG_ID)
|
|
47
54
|
# If it doesn't exist, make it
|
|
48
55
|
except NoResultFound:
|
|
49
|
-
org =
|
|
56
|
+
org = OrganizationModel(name=self.DEFAULT_ORG_NAME, id=self.DEFAULT_ORG_ID)
|
|
50
57
|
org.create(session)
|
|
51
58
|
|
|
52
59
|
return org.to_pydantic()
|
|
@@ -55,22 +62,22 @@ class OrganizationManager:
|
|
|
55
62
|
def update_organization_name_using_id(self, org_id: str, name: Optional[str] = None) -> PydanticOrganization:
|
|
56
63
|
"""Update an organization."""
|
|
57
64
|
with self.session_maker() as session:
|
|
58
|
-
|
|
65
|
+
org = OrganizationModel.read(db_session=session, identifier=org_id)
|
|
59
66
|
if name:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
67
|
+
org.name = name
|
|
68
|
+
org.update(session)
|
|
69
|
+
return org.to_pydantic()
|
|
63
70
|
|
|
64
71
|
@enforce_types
|
|
65
72
|
def delete_organization_by_id(self, org_id: str):
|
|
66
73
|
"""Delete an organization by marking it as deleted."""
|
|
67
74
|
with self.session_maker() as session:
|
|
68
|
-
organization =
|
|
75
|
+
organization = OrganizationModel.read(db_session=session, identifier=org_id)
|
|
69
76
|
organization.delete(session)
|
|
70
77
|
|
|
71
78
|
@enforce_types
|
|
72
79
|
def list_organizations(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticOrganization]:
|
|
73
80
|
"""List organizations with pagination based on cursor (org_id) and limit."""
|
|
74
81
|
with self.session_maker() as session:
|
|
75
|
-
results =
|
|
82
|
+
results = OrganizationModel.list(db_session=session, cursor=cursor, limit=limit)
|
|
76
83
|
return [org.to_pydantic() for org in results]
|