letta-nightly 0.5.0.dev20241022104124__py3-none-any.whl → 0.5.1.dev20241023193051__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/__init__.py +8 -3
- letta/agent_store/db.py +4 -2
- letta/cli/cli_config.py +2 -2
- letta/client/client.py +13 -0
- letta/constants.py +7 -4
- letta/embeddings.py +34 -16
- letta/llm_api/azure_openai.py +44 -4
- letta/llm_api/openai.py +7 -1
- letta/metadata.py +1 -145
- letta/orm/__all__.py +0 -0
- letta/orm/__init__.py +0 -0
- letta/orm/base.py +75 -0
- letta/orm/enums.py +8 -0
- letta/orm/errors.py +6 -0
- letta/orm/mixins.py +67 -0
- letta/orm/organization.py +28 -0
- letta/orm/sqlalchemy_base.py +204 -0
- letta/orm/user.py +25 -0
- letta/schemas/organization.py +3 -3
- letta/schemas/user.py +13 -6
- letta/server/rest_api/interface.py +47 -9
- letta/server/rest_api/routers/v1/organizations.py +5 -6
- letta/server/rest_api/routers/v1/users.py +6 -7
- letta/server/server.py +51 -85
- letta/services/__init__.py +0 -0
- letta/services/organization_manager.py +76 -0
- letta/services/user_manager.py +99 -0
- letta/settings.py +5 -0
- {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/METADATA +2 -1
- {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/RECORD +33 -23
- letta/base.py +0 -3
- letta/client/admin.py +0 -171
- {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/LICENSE +0 -0
- {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/WHEEL +0 -0
- {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/entry_points.txt +0 -0
letta/server/server.py
CHANGED
|
@@ -80,12 +80,13 @@ from letta.schemas.memory import (
|
|
|
80
80
|
RecallMemorySummary,
|
|
81
81
|
)
|
|
82
82
|
from letta.schemas.message import Message, MessageCreate, MessageRole, UpdateMessage
|
|
83
|
-
from letta.schemas.organization import Organization, OrganizationCreate
|
|
84
83
|
from letta.schemas.passage import Passage
|
|
85
84
|
from letta.schemas.source import Source, SourceCreate, SourceUpdate
|
|
86
85
|
from letta.schemas.tool import Tool, ToolCreate, ToolUpdate
|
|
87
86
|
from letta.schemas.usage import LettaUsageStatistics
|
|
88
87
|
from letta.schemas.user import User, UserCreate
|
|
88
|
+
from letta.services.organization_manager import OrganizationManager
|
|
89
|
+
from letta.services.user_manager import UserManager
|
|
89
90
|
from letta.utils import create_random_username, json_dumps, json_loads
|
|
90
91
|
|
|
91
92
|
# from letta.llm_api_tools import openai_get_model_list, azure_openai_get_model_list, smart_urljoin
|
|
@@ -167,7 +168,7 @@ from sqlalchemy.orm import sessionmaker
|
|
|
167
168
|
from letta.config import LettaConfig
|
|
168
169
|
|
|
169
170
|
# NOTE: hack to see if single session management works
|
|
170
|
-
from letta.settings import model_settings, settings
|
|
171
|
+
from letta.settings import model_settings, settings, tool_settings
|
|
171
172
|
|
|
172
173
|
config = LettaConfig.load()
|
|
173
174
|
|
|
@@ -245,6 +246,10 @@ class SyncServer(Server):
|
|
|
245
246
|
self.config = config
|
|
246
247
|
self.ms = MetadataStore(self.config)
|
|
247
248
|
|
|
249
|
+
# Managers that interface with data models
|
|
250
|
+
self.organization_manager = OrganizationManager()
|
|
251
|
+
self.user_manager = UserManager()
|
|
252
|
+
|
|
248
253
|
# TODO: this should be removed
|
|
249
254
|
# add global default tools (for admin)
|
|
250
255
|
self.add_default_tools(module_name="base")
|
|
@@ -572,7 +577,7 @@ class SyncServer(Server):
|
|
|
572
577
|
timestamp: Optional[datetime] = None,
|
|
573
578
|
) -> LettaUsageStatistics:
|
|
574
579
|
"""Process an incoming user message and feed it through the Letta agent"""
|
|
575
|
-
if self.
|
|
580
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
576
581
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
577
582
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
578
583
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -621,7 +626,7 @@ class SyncServer(Server):
|
|
|
621
626
|
timestamp: Optional[datetime] = None,
|
|
622
627
|
) -> LettaUsageStatistics:
|
|
623
628
|
"""Process an incoming system message and feed it through the Letta agent"""
|
|
624
|
-
if self.
|
|
629
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
625
630
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
626
631
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
627
632
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -690,7 +695,7 @@ class SyncServer(Server):
|
|
|
690
695
|
|
|
691
696
|
Otherwise, we can pass them in directly.
|
|
692
697
|
"""
|
|
693
|
-
if self.
|
|
698
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
694
699
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
695
700
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
696
701
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -739,7 +744,7 @@ class SyncServer(Server):
|
|
|
739
744
|
# @LockingServer.agent_lock_decorator
|
|
740
745
|
def run_command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
|
|
741
746
|
"""Run a command on the agent"""
|
|
742
|
-
if self.
|
|
747
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
743
748
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
744
749
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
745
750
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -750,19 +755,12 @@ class SyncServer(Server):
|
|
|
750
755
|
command = command[1:] # strip the prefix
|
|
751
756
|
return self._command(user_id=user_id, agent_id=agent_id, command=command)
|
|
752
757
|
|
|
753
|
-
def list_users_paginated(self, cursor: str, limit: int) -> List[User]:
|
|
754
|
-
"""List all users"""
|
|
755
|
-
# TODO: make this paginated
|
|
756
|
-
next_cursor, users = self.ms.get_all_users(cursor, limit)
|
|
757
|
-
return next_cursor, users
|
|
758
|
-
|
|
759
758
|
def create_user(self, request: UserCreate) -> User:
|
|
760
759
|
"""Create a new user using a config"""
|
|
761
760
|
if not request.name:
|
|
762
761
|
# auto-generate a name
|
|
763
762
|
request.name = create_random_username()
|
|
764
|
-
user =
|
|
765
|
-
self.ms.create_user(user)
|
|
763
|
+
user = self.user_manager.create_user(request)
|
|
766
764
|
logger.debug(f"Created new user from config: {user}")
|
|
767
765
|
|
|
768
766
|
# add default for the user
|
|
@@ -773,20 +771,6 @@ class SyncServer(Server):
|
|
|
773
771
|
|
|
774
772
|
return user
|
|
775
773
|
|
|
776
|
-
def create_organization(self, request: OrganizationCreate) -> Organization:
|
|
777
|
-
"""Create a new org using a config"""
|
|
778
|
-
if not request.name:
|
|
779
|
-
# auto-generate a name
|
|
780
|
-
request.name = create_random_username()
|
|
781
|
-
org = Organization(name=request.name)
|
|
782
|
-
self.ms.create_organization(org)
|
|
783
|
-
logger.info(f"Created new org from config: {org}")
|
|
784
|
-
|
|
785
|
-
# add default for the org
|
|
786
|
-
# TODO: add default data
|
|
787
|
-
|
|
788
|
-
return org
|
|
789
|
-
|
|
790
774
|
def create_agent(
|
|
791
775
|
self,
|
|
792
776
|
request: CreateAgent,
|
|
@@ -795,7 +779,7 @@ class SyncServer(Server):
|
|
|
795
779
|
interface: Union[AgentInterface, None] = None,
|
|
796
780
|
) -> AgentState:
|
|
797
781
|
"""Create a new agent using a config"""
|
|
798
|
-
if self.
|
|
782
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
799
783
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
800
784
|
|
|
801
785
|
if interface is None:
|
|
@@ -819,7 +803,7 @@ class SyncServer(Server):
|
|
|
819
803
|
raise ValueError(f"Invalid agent type: {request.agent_type}")
|
|
820
804
|
|
|
821
805
|
logger.debug(f"Attempting to find user: {user_id}")
|
|
822
|
-
user = self.
|
|
806
|
+
user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
823
807
|
if not user:
|
|
824
808
|
raise ValueError(f"cannot find user with associated client id: {user_id}")
|
|
825
809
|
|
|
@@ -844,7 +828,8 @@ class SyncServer(Server):
|
|
|
844
828
|
# tool already added
|
|
845
829
|
continue
|
|
846
830
|
source_code = parse_source_code(func)
|
|
847
|
-
|
|
831
|
+
# memory functions are not terminal
|
|
832
|
+
json_schema = generate_schema(func, terminal=False, name=func_name)
|
|
848
833
|
source_type = "python"
|
|
849
834
|
tags = ["memory", "memgpt-base"]
|
|
850
835
|
tool = self.create_tool(
|
|
@@ -922,7 +907,7 @@ class SyncServer(Server):
|
|
|
922
907
|
user_id: str,
|
|
923
908
|
):
|
|
924
909
|
"""Update the agents core memory block, return the new state"""
|
|
925
|
-
if self.
|
|
910
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
926
911
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
927
912
|
if self.ms.get_agent(agent_id=request.id) is None:
|
|
928
913
|
raise ValueError(f"Agent agent_id={request.id} does not exist")
|
|
@@ -984,7 +969,7 @@ class SyncServer(Server):
|
|
|
984
969
|
|
|
985
970
|
def get_tools_from_agent(self, agent_id: str, user_id: Optional[str]) -> List[Tool]:
|
|
986
971
|
"""Get tools from an existing agent"""
|
|
987
|
-
if self.
|
|
972
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
988
973
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
989
974
|
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
990
975
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1000,7 +985,7 @@ class SyncServer(Server):
|
|
|
1000
985
|
user_id: str,
|
|
1001
986
|
):
|
|
1002
987
|
"""Add tools from an existing agent"""
|
|
1003
|
-
if self.
|
|
988
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1004
989
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1005
990
|
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
1006
991
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1039,7 +1024,7 @@ class SyncServer(Server):
|
|
|
1039
1024
|
user_id: str,
|
|
1040
1025
|
):
|
|
1041
1026
|
"""Remove tools from an existing agent"""
|
|
1042
|
-
if self.
|
|
1027
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1043
1028
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1044
1029
|
if self.ms.get_agent(agent_id=agent_id) is None:
|
|
1045
1030
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1085,7 +1070,7 @@ class SyncServer(Server):
|
|
|
1085
1070
|
user_id: str,
|
|
1086
1071
|
) -> List[AgentState]:
|
|
1087
1072
|
"""List all available agents to a user"""
|
|
1088
|
-
if self.
|
|
1073
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1089
1074
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1090
1075
|
|
|
1091
1076
|
agents_states = self.ms.list_agents(user_id=user_id)
|
|
@@ -1101,7 +1086,7 @@ class SyncServer(Server):
|
|
|
1101
1086
|
if user_id is None:
|
|
1102
1087
|
agents_states = self.ms.list_all_agents()
|
|
1103
1088
|
else:
|
|
1104
|
-
if self.
|
|
1089
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1105
1090
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1106
1091
|
|
|
1107
1092
|
agents_states = self.ms.list_agents(user_id=user_id)
|
|
@@ -1241,13 +1226,13 @@ class SyncServer(Server):
|
|
|
1241
1226
|
"""Get the agent state"""
|
|
1242
1227
|
return self.ms.get_agent(agent_id=agent_id, user_id=user_id)
|
|
1243
1228
|
|
|
1244
|
-
def get_user(self, user_id: str) -> User:
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1229
|
+
# def get_user(self, user_id: str) -> User:
|
|
1230
|
+
# """Get the user"""
|
|
1231
|
+
# user = self.user_manager.get_user_by_id(user_id=user_id)
|
|
1232
|
+
# if user is None:
|
|
1233
|
+
# raise ValueError(f"User with user_id {user_id} does not exist")
|
|
1234
|
+
# else:
|
|
1235
|
+
# return user
|
|
1251
1236
|
|
|
1252
1237
|
def get_agent_memory(self, agent_id: str) -> Memory:
|
|
1253
1238
|
"""Return the memory of an agent (core memory)"""
|
|
@@ -1338,7 +1323,7 @@ class SyncServer(Server):
|
|
|
1338
1323
|
|
|
1339
1324
|
def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
|
|
1340
1325
|
"""Paginated query of all messages in agent archival memory"""
|
|
1341
|
-
if self.
|
|
1326
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1342
1327
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1343
1328
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1344
1329
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1363,7 +1348,7 @@ class SyncServer(Server):
|
|
|
1363
1348
|
order_by: Optional[str] = "created_at",
|
|
1364
1349
|
reverse: Optional[bool] = False,
|
|
1365
1350
|
) -> List[Passage]:
|
|
1366
|
-
if self.
|
|
1351
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1367
1352
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1368
1353
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1369
1354
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1378,7 +1363,7 @@ class SyncServer(Server):
|
|
|
1378
1363
|
return records
|
|
1379
1364
|
|
|
1380
1365
|
def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[Passage]:
|
|
1381
|
-
if self.
|
|
1366
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1382
1367
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1383
1368
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1384
1369
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1393,7 +1378,7 @@ class SyncServer(Server):
|
|
|
1393
1378
|
return [letta_agent.persistence_manager.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
|
|
1394
1379
|
|
|
1395
1380
|
def delete_archival_memory(self, user_id: str, agent_id: str, memory_id: str):
|
|
1396
|
-
if self.
|
|
1381
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1397
1382
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1398
1383
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1399
1384
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1424,7 +1409,7 @@ class SyncServer(Server):
|
|
|
1424
1409
|
assistant_message_function_name: str = constants.DEFAULT_MESSAGE_TOOL,
|
|
1425
1410
|
assistant_message_function_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
|
|
1426
1411
|
) -> Union[List[Message], List[LettaMessage]]:
|
|
1427
|
-
if self.
|
|
1412
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1428
1413
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1429
1414
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1430
1415
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1466,7 +1451,7 @@ class SyncServer(Server):
|
|
|
1466
1451
|
|
|
1467
1452
|
def get_agent_state(self, user_id: str, agent_id: Optional[str], agent_name: Optional[str] = None) -> Optional[AgentState]:
|
|
1468
1453
|
"""Return the config of an agent"""
|
|
1469
|
-
if self.
|
|
1454
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1470
1455
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1471
1456
|
if agent_id:
|
|
1472
1457
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
@@ -1507,7 +1492,7 @@ class SyncServer(Server):
|
|
|
1507
1492
|
|
|
1508
1493
|
def update_agent_core_memory(self, user_id: str, agent_id: str, new_memory_contents: dict) -> Memory:
|
|
1509
1494
|
"""Update the agents core memory block, return the new state"""
|
|
1510
|
-
if self.
|
|
1495
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1511
1496
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1512
1497
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1513
1498
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1538,7 +1523,7 @@ class SyncServer(Server):
|
|
|
1538
1523
|
|
|
1539
1524
|
def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> AgentState:
|
|
1540
1525
|
"""Update the name of the agent in the database"""
|
|
1541
|
-
if self.
|
|
1526
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1542
1527
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1543
1528
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1544
1529
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1560,13 +1545,9 @@ class SyncServer(Server):
|
|
|
1560
1545
|
assert isinstance(letta_agent.agent_state.id, str)
|
|
1561
1546
|
return letta_agent.agent_state
|
|
1562
1547
|
|
|
1563
|
-
def delete_user(self, user_id: str):
|
|
1564
|
-
# TODO: delete user
|
|
1565
|
-
pass
|
|
1566
|
-
|
|
1567
1548
|
def delete_agent(self, user_id: str, agent_id: str):
|
|
1568
1549
|
"""Delete an agent in the database"""
|
|
1569
|
-
if self.
|
|
1550
|
+
if self.user_manager.get_user_by_id(user_id=user_id) is None:
|
|
1570
1551
|
raise ValueError(f"User user_id={user_id} does not exist")
|
|
1571
1552
|
if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
|
|
1572
1553
|
raise ValueError(f"Agent agent_id={agent_id} does not exist")
|
|
@@ -1595,7 +1576,8 @@ class SyncServer(Server):
|
|
|
1595
1576
|
|
|
1596
1577
|
def api_key_to_user(self, api_key: str) -> str:
|
|
1597
1578
|
"""Decode an API key to a user"""
|
|
1598
|
-
|
|
1579
|
+
token = self.ms.get_api_key(api_key=api_key)
|
|
1580
|
+
user = self.user_manager.get_user_by_id(token.user_id)
|
|
1599
1581
|
if user is None:
|
|
1600
1582
|
raise HTTPException(status_code=403, detail="Invalid credentials")
|
|
1601
1583
|
else:
|
|
@@ -2028,7 +2010,10 @@ class SyncServer(Server):
|
|
|
2028
2010
|
def add_default_external_tools(self, user_id: Optional[str] = None) -> bool:
|
|
2029
2011
|
"""Add default langchain tools. Return true if successful, false otherwise."""
|
|
2030
2012
|
success = True
|
|
2031
|
-
|
|
2013
|
+
if tool_settings.composio_api_key:
|
|
2014
|
+
tools = Tool.load_default_langchain_tools() + Tool.load_default_crewai_tools() + Tool.load_default_composio_tools()
|
|
2015
|
+
else:
|
|
2016
|
+
tools = Tool.load_default_langchain_tools() + Tool.load_default_crewai_tools()
|
|
2032
2017
|
for tool in tools:
|
|
2033
2018
|
try:
|
|
2034
2019
|
self.ms.create_tool(tool)
|
|
@@ -2123,34 +2108,15 @@ class SyncServer(Server):
|
|
|
2123
2108
|
letta_agent = self._get_or_load_agent(agent_id=agent_id)
|
|
2124
2109
|
return letta_agent.retry_message()
|
|
2125
2110
|
|
|
2111
|
+
# TODO: Move a lot of this default logic to the ORM
|
|
2126
2112
|
def get_default_user(self) -> User:
|
|
2113
|
+
self.organization_manager.create_default_organization()
|
|
2114
|
+
user = self.user_manager.create_default_user()
|
|
2127
2115
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
DEFAULT_ORG_NAME,
|
|
2131
|
-
DEFAULT_USER_ID,
|
|
2132
|
-
DEFAULT_USER_NAME,
|
|
2133
|
-
)
|
|
2134
|
-
|
|
2135
|
-
# check if default org exists
|
|
2136
|
-
default_org = self.ms.get_organization(DEFAULT_ORG_ID)
|
|
2137
|
-
if not default_org:
|
|
2138
|
-
org = Organization(name=DEFAULT_ORG_NAME, id=DEFAULT_ORG_ID)
|
|
2139
|
-
self.ms.create_organization(org)
|
|
2140
|
-
|
|
2141
|
-
# check if default user exists
|
|
2142
|
-
try:
|
|
2143
|
-
self.get_user(DEFAULT_USER_ID)
|
|
2144
|
-
except ValueError:
|
|
2145
|
-
user = User(name=DEFAULT_USER_NAME, org_id=DEFAULT_ORG_ID, id=DEFAULT_USER_ID)
|
|
2146
|
-
self.ms.create_user(user)
|
|
2147
|
-
|
|
2148
|
-
# add default data (TODO: move to org)
|
|
2149
|
-
self.add_default_blocks(user.id)
|
|
2150
|
-
self.add_default_tools(module_name="base", user_id=user.id)
|
|
2116
|
+
self.add_default_blocks(user.id)
|
|
2117
|
+
self.add_default_tools(module_name="base", user_id=user.id)
|
|
2151
2118
|
|
|
2152
|
-
|
|
2153
|
-
return self.get_user(DEFAULT_USER_ID)
|
|
2119
|
+
return user
|
|
2154
2120
|
|
|
2155
2121
|
def get_user_or_default(self, user_id: Optional[str]) -> User:
|
|
2156
2122
|
"""Get the user object for user_id if it exists, otherwise return the default user object"""
|
|
@@ -2158,7 +2124,7 @@ class SyncServer(Server):
|
|
|
2158
2124
|
return self.get_default_user()
|
|
2159
2125
|
else:
|
|
2160
2126
|
try:
|
|
2161
|
-
return self.
|
|
2127
|
+
return self.user_manager.get_user_by_id(user_id=user_id)
|
|
2162
2128
|
except ValueError:
|
|
2163
2129
|
raise HTTPException(status_code=404, detail=f"User with id {user_id} not found")
|
|
2164
2130
|
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from letta.constants import DEFAULT_ORG_ID, DEFAULT_ORG_NAME
|
|
4
|
+
from letta.orm.errors import NoResultFound
|
|
5
|
+
from letta.orm.organization import Organization
|
|
6
|
+
from letta.schemas.organization import Organization as PydanticOrganization
|
|
7
|
+
from letta.utils import create_random_username, enforce_types
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class OrganizationManager:
|
|
11
|
+
"""Manager class to handle business logic related to Organizations."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
# This is probably horrible but we reuse this technique from metadata.py
|
|
15
|
+
# TODO: Please refactor this out
|
|
16
|
+
# I am currently working on a ORM refactor and would like to make a more minimal set of changes
|
|
17
|
+
# - Matt
|
|
18
|
+
from letta.server.server import db_context
|
|
19
|
+
|
|
20
|
+
self.session_maker = db_context
|
|
21
|
+
|
|
22
|
+
@enforce_types
|
|
23
|
+
def get_organization_by_id(self, org_id: str) -> PydanticOrganization:
|
|
24
|
+
"""Fetch an organization by ID."""
|
|
25
|
+
with self.session_maker() as session:
|
|
26
|
+
try:
|
|
27
|
+
organization = Organization.read(db_session=session, identifier=org_id)
|
|
28
|
+
return organization.to_pydantic()
|
|
29
|
+
except NoResultFound:
|
|
30
|
+
raise ValueError(f"Organization with id {org_id} not found.")
|
|
31
|
+
|
|
32
|
+
@enforce_types
|
|
33
|
+
def create_organization(self, name: Optional[str] = None) -> PydanticOrganization:
|
|
34
|
+
"""Create a new organization. If a name is provided, it is used, otherwise, a random one is generated."""
|
|
35
|
+
with self.session_maker() as session:
|
|
36
|
+
org = Organization(name=name if name else create_random_username())
|
|
37
|
+
org.create(session)
|
|
38
|
+
return org.to_pydantic()
|
|
39
|
+
|
|
40
|
+
@enforce_types
|
|
41
|
+
def create_default_organization(self) -> PydanticOrganization:
|
|
42
|
+
"""Create the default organization."""
|
|
43
|
+
with self.session_maker() as session:
|
|
44
|
+
# Try to get it first
|
|
45
|
+
try:
|
|
46
|
+
org = Organization.read(db_session=session, identifier=DEFAULT_ORG_ID)
|
|
47
|
+
# If it doesn't exist, make it
|
|
48
|
+
except NoResultFound:
|
|
49
|
+
org = Organization(name=DEFAULT_ORG_NAME, id=DEFAULT_ORG_ID)
|
|
50
|
+
org.create(session)
|
|
51
|
+
|
|
52
|
+
return org.to_pydantic()
|
|
53
|
+
|
|
54
|
+
@enforce_types
|
|
55
|
+
def update_organization_name_using_id(self, org_id: str, name: Optional[str] = None) -> PydanticOrganization:
|
|
56
|
+
"""Update an organization."""
|
|
57
|
+
with self.session_maker() as session:
|
|
58
|
+
organization = Organization.read(db_session=session, identifier=org_id)
|
|
59
|
+
if name:
|
|
60
|
+
organization.name = name
|
|
61
|
+
organization.update(session)
|
|
62
|
+
return organization.to_pydantic()
|
|
63
|
+
|
|
64
|
+
@enforce_types
|
|
65
|
+
def delete_organization_by_id(self, org_id: str):
|
|
66
|
+
"""Delete an organization by marking it as deleted."""
|
|
67
|
+
with self.session_maker() as session:
|
|
68
|
+
organization = Organization.read(db_session=session, identifier=org_id)
|
|
69
|
+
organization.delete(session)
|
|
70
|
+
|
|
71
|
+
@enforce_types
|
|
72
|
+
def list_organizations(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticOrganization]:
|
|
73
|
+
"""List organizations with pagination based on cursor (org_id) and limit."""
|
|
74
|
+
with self.session_maker() as session:
|
|
75
|
+
results = Organization.list(db_session=session, cursor=cursor, limit=limit)
|
|
76
|
+
return [org.to_pydantic() for org in results]
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from typing import List, Optional, Tuple
|
|
2
|
+
|
|
3
|
+
from letta.constants import DEFAULT_ORG_ID, DEFAULT_USER_ID, DEFAULT_USER_NAME
|
|
4
|
+
|
|
5
|
+
# TODO: Remove this once we translate all of these to the ORM
|
|
6
|
+
from letta.metadata import AgentModel, AgentSourceMappingModel, SourceModel
|
|
7
|
+
from letta.orm.errors import NoResultFound
|
|
8
|
+
from letta.orm.organization import Organization as OrganizationModel
|
|
9
|
+
from letta.orm.user import User as UserModel
|
|
10
|
+
from letta.schemas.user import User as PydanticUser
|
|
11
|
+
from letta.schemas.user import UserCreate, UserUpdate
|
|
12
|
+
from letta.utils import enforce_types
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class UserManager:
|
|
16
|
+
"""Manager class to handle business logic related to Users."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
# Fetching the db_context similarly as in OrganizationManager
|
|
20
|
+
from letta.server.server import db_context
|
|
21
|
+
|
|
22
|
+
self.session_maker = db_context
|
|
23
|
+
|
|
24
|
+
@enforce_types
|
|
25
|
+
def create_default_user(self, org_id: str = DEFAULT_ORG_ID) -> PydanticUser:
|
|
26
|
+
"""Create the default user."""
|
|
27
|
+
with self.session_maker() as session:
|
|
28
|
+
# Make sure the org id exists
|
|
29
|
+
try:
|
|
30
|
+
OrganizationModel.read(db_session=session, identifier=org_id)
|
|
31
|
+
except NoResultFound:
|
|
32
|
+
raise ValueError(f"No organization with {org_id} exists in the organization table.")
|
|
33
|
+
|
|
34
|
+
# Try to retrieve the user
|
|
35
|
+
try:
|
|
36
|
+
user = UserModel.read(db_session=session, identifier=DEFAULT_USER_ID)
|
|
37
|
+
except NoResultFound:
|
|
38
|
+
# If it doesn't exist, make it
|
|
39
|
+
user = UserModel(id=DEFAULT_USER_ID, name=DEFAULT_USER_NAME, organization_id=org_id)
|
|
40
|
+
user.create(session)
|
|
41
|
+
|
|
42
|
+
return user.to_pydantic()
|
|
43
|
+
|
|
44
|
+
@enforce_types
|
|
45
|
+
def create_user(self, user_create: UserCreate) -> PydanticUser:
|
|
46
|
+
"""Create a new user if it doesn't already exist."""
|
|
47
|
+
with self.session_maker() as session:
|
|
48
|
+
new_user = UserModel(**user_create.model_dump())
|
|
49
|
+
new_user.create(session)
|
|
50
|
+
return new_user.to_pydantic()
|
|
51
|
+
|
|
52
|
+
@enforce_types
|
|
53
|
+
def update_user(self, user_update: UserUpdate) -> PydanticUser:
|
|
54
|
+
"""Update user details."""
|
|
55
|
+
with self.session_maker() as session:
|
|
56
|
+
# Retrieve the existing user by ID
|
|
57
|
+
existing_user = UserModel.read(db_session=session, identifier=user_update.id)
|
|
58
|
+
|
|
59
|
+
# Update only the fields that are provided in UserUpdate
|
|
60
|
+
update_data = user_update.model_dump(exclude_unset=True, exclude_none=True)
|
|
61
|
+
for key, value in update_data.items():
|
|
62
|
+
setattr(existing_user, key, value)
|
|
63
|
+
|
|
64
|
+
# Commit the updated user
|
|
65
|
+
existing_user.update(session)
|
|
66
|
+
return existing_user.to_pydantic()
|
|
67
|
+
|
|
68
|
+
@enforce_types
|
|
69
|
+
def delete_user_by_id(self, user_id: str):
|
|
70
|
+
"""Delete a user and their associated records (agents, sources, mappings)."""
|
|
71
|
+
with self.session_maker() as session:
|
|
72
|
+
# Delete from user table
|
|
73
|
+
user = UserModel.read(db_session=session, identifier=user_id)
|
|
74
|
+
user.delete(session)
|
|
75
|
+
|
|
76
|
+
# TODO: Remove this once we have ORM models for the Agent, Source, and AgentSourceMapping
|
|
77
|
+
# Cascade delete for related models: Agent, Source, AgentSourceMapping
|
|
78
|
+
session.query(AgentModel).filter(AgentModel.user_id == user_id).delete()
|
|
79
|
+
session.query(SourceModel).filter(SourceModel.user_id == user_id).delete()
|
|
80
|
+
session.query(AgentSourceMappingModel).filter(AgentSourceMappingModel.user_id == user_id).delete()
|
|
81
|
+
|
|
82
|
+
session.commit()
|
|
83
|
+
|
|
84
|
+
@enforce_types
|
|
85
|
+
def get_user_by_id(self, user_id: str) -> PydanticUser:
|
|
86
|
+
"""Fetch a user by ID."""
|
|
87
|
+
with self.session_maker() as session:
|
|
88
|
+
try:
|
|
89
|
+
user = UserModel.read(db_session=session, identifier=user_id)
|
|
90
|
+
return user.to_pydantic()
|
|
91
|
+
except NoResultFound:
|
|
92
|
+
raise ValueError(f"User with id {user_id} not found.")
|
|
93
|
+
|
|
94
|
+
@enforce_types
|
|
95
|
+
def list_users(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> Tuple[Optional[str], List[PydanticUser]]:
|
|
96
|
+
"""List users with pagination using cursor (id) and limit."""
|
|
97
|
+
with self.session_maker() as session:
|
|
98
|
+
results = UserModel.list(db_session=session, cursor=cursor, limit=limit)
|
|
99
|
+
return [user.to_pydantic() for user in results]
|
letta/settings.py
CHANGED
|
@@ -7,6 +7,10 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
7
7
|
from letta.local_llm.constants import DEFAULT_WRAPPER_NAME
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
class ToolSettings(BaseSettings):
|
|
11
|
+
composio_api_key: Optional[str] = None
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
class ModelSettings(BaseSettings):
|
|
11
15
|
|
|
12
16
|
# env_prefix='my_prefix_'
|
|
@@ -99,3 +103,4 @@ class TestSettings(Settings):
|
|
|
99
103
|
settings = Settings(_env_parse_none_str="None")
|
|
100
104
|
test_settings = TestSettings()
|
|
101
105
|
model_settings = ModelSettings()
|
|
106
|
+
tool_settings = ToolSettings()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: letta-nightly
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1.dev20241023193051
|
|
4
4
|
Summary: Create LLM agents with long-term memory and custom tools
|
|
5
5
|
License: Apache License
|
|
6
6
|
Author: Letta Team
|
|
@@ -55,6 +55,7 @@ Requires-Dist: prettytable (>=3.9.0,<4.0.0)
|
|
|
55
55
|
Requires-Dist: pyautogen (==0.2.22) ; extra == "autogen"
|
|
56
56
|
Requires-Dist: pydantic (>=2.7.4,<3.0.0)
|
|
57
57
|
Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
|
|
58
|
+
Requires-Dist: pyhumps (>=3.8.0,<4.0.0)
|
|
58
59
|
Requires-Dist: pymilvus (>=2.4.3,<3.0.0) ; extra == "milvus"
|
|
59
60
|
Requires-Dist: pyright (>=1.1.347,<2.0.0) ; extra == "dev"
|
|
60
61
|
Requires-Dist: pytest-asyncio (>=0.23.2,<0.24.0) ; extra == "dev"
|