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.

Files changed (35) hide show
  1. letta/__init__.py +8 -3
  2. letta/agent_store/db.py +4 -2
  3. letta/cli/cli_config.py +2 -2
  4. letta/client/client.py +13 -0
  5. letta/constants.py +7 -4
  6. letta/embeddings.py +34 -16
  7. letta/llm_api/azure_openai.py +44 -4
  8. letta/llm_api/openai.py +7 -1
  9. letta/metadata.py +1 -145
  10. letta/orm/__all__.py +0 -0
  11. letta/orm/__init__.py +0 -0
  12. letta/orm/base.py +75 -0
  13. letta/orm/enums.py +8 -0
  14. letta/orm/errors.py +6 -0
  15. letta/orm/mixins.py +67 -0
  16. letta/orm/organization.py +28 -0
  17. letta/orm/sqlalchemy_base.py +204 -0
  18. letta/orm/user.py +25 -0
  19. letta/schemas/organization.py +3 -3
  20. letta/schemas/user.py +13 -6
  21. letta/server/rest_api/interface.py +47 -9
  22. letta/server/rest_api/routers/v1/organizations.py +5 -6
  23. letta/server/rest_api/routers/v1/users.py +6 -7
  24. letta/server/server.py +51 -85
  25. letta/services/__init__.py +0 -0
  26. letta/services/organization_manager.py +76 -0
  27. letta/services/user_manager.py +99 -0
  28. letta/settings.py +5 -0
  29. {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/METADATA +2 -1
  30. {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/RECORD +33 -23
  31. letta/base.py +0 -3
  32. letta/client/admin.py +0 -171
  33. {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/LICENSE +0 -0
  34. {letta_nightly-0.5.0.dev20241022104124.dist-info → letta_nightly-0.5.1.dev20241023193051.dist-info}/WHEEL +0 -0
  35. {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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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 = User(name=request.name, org_id=request.org_id)
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id)
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
- json_schema = generate_schema(func, func_name)
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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
- """Get the user"""
1246
- user = self.ms.get_user(user_id=user_id)
1247
- if user is None:
1248
- raise ValueError(f"User with user_id {user_id} does not exist")
1249
- else:
1250
- return user
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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.ms.get_user(user_id=user_id) is None:
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
- user = self.ms.get_user_from_api_key(api_key=api_key)
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
- tools = Tool.load_default_langchain_tools() + Tool.load_default_crewai_tools() + Tool.load_default_composio_tools()
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
- from letta.constants import (
2129
- DEFAULT_ORG_ID,
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
- # check if default org exists
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.get_user(user_id=user_id)
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.0.dev20241022104124
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"