letta-nightly 0.5.0.dev20241023104105__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 CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.5.0"
1
+ __version__ = "0.5.1"
2
2
 
3
3
  # import clients
4
4
  from letta.client.client import LocalClient, RESTClient, create_client
letta/constants.py CHANGED
@@ -4,13 +4,13 @@ from logging import CRITICAL, DEBUG, ERROR, INFO, NOTSET, WARN, WARNING
4
4
  LETTA_DIR = os.path.join(os.path.expanduser("~"), ".letta")
5
5
 
6
6
  # Defaults
7
- DEFAULT_USER_ID = "user-00000000"
7
+ DEFAULT_USER_ID = "user-00000000-0000-4000-8000-000000000000"
8
8
  # This UUID follows the UUID4 rules:
9
9
  # The 13th character (4) indicates it's version 4.
10
10
  # The first character of the third segment (8) ensures the variant is correctly set.
11
11
  DEFAULT_ORG_ID = "organization-00000000-0000-4000-8000-000000000000"
12
- DEFAULT_USER_NAME = "default"
13
- DEFAULT_ORG_NAME = "default"
12
+ DEFAULT_USER_NAME = "default_user"
13
+ DEFAULT_ORG_NAME = "default_org"
14
14
 
15
15
 
16
16
  # String in the error message for when the context window is too large
letta/metadata.py CHANGED
@@ -15,7 +15,6 @@ from sqlalchemy import (
15
15
  String,
16
16
  TypeDecorator,
17
17
  asc,
18
- desc,
19
18
  or_,
20
19
  )
21
20
  from sqlalchemy.sql import func
@@ -31,8 +30,6 @@ from letta.schemas.file import FileMetadata
31
30
  from letta.schemas.job import Job
32
31
  from letta.schemas.llm_config import LLMConfig
33
32
  from letta.schemas.memory import Memory
34
-
35
- # from letta.schemas.message import Message, Passage, Record, RecordType, ToolCall
36
33
  from letta.schemas.openai.chat_completions import ToolCall, ToolCallFunction
37
34
  from letta.schemas.source import Source
38
35
  from letta.schemas.tool import Tool
@@ -154,25 +151,6 @@ class ToolCallColumn(TypeDecorator):
154
151
  return value
155
152
 
156
153
 
157
- class UserModel(Base):
158
- __tablename__ = "users"
159
- __table_args__ = {"extend_existing": True}
160
-
161
- id = Column(String, primary_key=True)
162
- org_id = Column(String)
163
- name = Column(String, nullable=False)
164
- created_at = Column(DateTime(timezone=True))
165
-
166
- # TODO: what is this?
167
- policies_accepted = Column(Boolean, nullable=False, default=False)
168
-
169
- def __repr__(self) -> str:
170
- return f"<User(id='{self.id}' name='{self.name}')>"
171
-
172
- def to_record(self) -> User:
173
- return User(id=self.id, name=self.name, created_at=self.created_at, org_id=self.org_id)
174
-
175
-
176
154
  # TODO: eventually store providers?
177
155
  # class Provider(Base):
178
156
  # __tablename__ = "providers"
@@ -497,15 +475,6 @@ class MetadataStore:
497
475
  tokens = [r.to_record() for r in results]
498
476
  return tokens
499
477
 
500
- @enforce_types
501
- def get_user_from_api_key(self, api_key: str) -> Optional[User]:
502
- """Get the user associated with a given API key"""
503
- token = self.get_api_key(api_key=api_key)
504
- if token is None:
505
- raise ValueError(f"Provided token does not exist")
506
- else:
507
- return self.get_user(user_id=token.user_id)
508
-
509
478
  @enforce_types
510
479
  def create_agent(self, agent: AgentState):
511
480
  # insert into agent table
@@ -527,14 +496,6 @@ class MetadataStore:
527
496
  session.add(SourceModel(**vars(source)))
528
497
  session.commit()
529
498
 
530
- @enforce_types
531
- def create_user(self, user: User):
532
- with self.session_maker() as session:
533
- if session.query(UserModel).filter(UserModel.id == user.id).count() > 0:
534
- raise ValueError(f"User with id {user.id} already exists")
535
- session.add(UserModel(**vars(user)))
536
- session.commit()
537
-
538
499
  @enforce_types
539
500
  def create_block(self, block: Block):
540
501
  with self.session_maker() as session:
@@ -573,12 +534,6 @@ class MetadataStore:
573
534
  session.query(AgentModel).filter(AgentModel.id == agent.id).update(fields)
574
535
  session.commit()
575
536
 
576
- @enforce_types
577
- def update_user(self, user: User):
578
- with self.session_maker() as session:
579
- session.query(UserModel).filter(UserModel.id == user.id).update(vars(user))
580
- session.commit()
581
-
582
537
  @enforce_types
583
538
  def update_source(self, source: Source):
584
539
  with self.session_maker() as session:
@@ -657,23 +612,6 @@ class MetadataStore:
657
612
 
658
613
  session.commit()
659
614
 
660
- @enforce_types
661
- def delete_user(self, user_id: str):
662
- with self.session_maker() as session:
663
- # delete from users table
664
- session.query(UserModel).filter(UserModel.id == user_id).delete()
665
-
666
- # delete associated agents
667
- session.query(AgentModel).filter(AgentModel.user_id == user_id).delete()
668
-
669
- # delete associated sources
670
- session.query(SourceModel).filter(SourceModel.user_id == user_id).delete()
671
-
672
- # delete associated mappings
673
- session.query(AgentSourceMappingModel).filter(AgentSourceMappingModel.user_id == user_id).delete()
674
-
675
- session.commit()
676
-
677
615
  @enforce_types
678
616
  def list_tools(self, cursor: Optional[str] = None, limit: Optional[int] = 50, user_id: Optional[str] = None) -> List[ToolModel]:
679
617
  with self.session_maker() as session:
@@ -719,30 +657,6 @@ class MetadataStore:
719
657
  assert len(results) == 1, f"Expected 1 result, got {len(results)}" # should only be one result
720
658
  return results[0].to_record()
721
659
 
722
- @enforce_types
723
- def get_user(self, user_id: str) -> Optional[User]:
724
- with self.session_maker() as session:
725
- results = session.query(UserModel).filter(UserModel.id == user_id).all()
726
- if len(results) == 0:
727
- return None
728
- assert len(results) == 1, f"Expected 1 result, got {len(results)}"
729
- return results[0].to_record()
730
-
731
- @enforce_types
732
- def get_all_users(self, cursor: Optional[str] = None, limit: Optional[int] = 50):
733
- with self.session_maker() as session:
734
- query = session.query(UserModel).order_by(desc(UserModel.id))
735
- if cursor:
736
- query = query.filter(UserModel.id < cursor)
737
- results = query.limit(limit).all()
738
- if not results:
739
- return None, []
740
- user_records = [r.to_record() for r in results]
741
- next_cursor = user_records[-1].id
742
- assert isinstance(next_cursor, str)
743
-
744
- return next_cursor, user_records
745
-
746
660
  @enforce_types
747
661
  def get_source(
748
662
  self, source_id: Optional[str] = None, user_id: Optional[str] = None, source_name: Optional[str] = None
letta/orm/errors.py CHANGED
@@ -1,2 +1,6 @@
1
1
  class NoResultFound(Exception):
2
2
  """A record or records cannot be found given the provided search params"""
3
+
4
+
5
+ class MalformedIdError(Exception):
6
+ """An id not in the right format, most likely violating uuid4 format."""
letta/orm/mixins.py CHANGED
@@ -1,24 +1,35 @@
1
- from typing import Optional, Type
1
+ from typing import Optional
2
2
  from uuid import UUID
3
3
 
4
+ from sqlalchemy import ForeignKey, String
5
+ from sqlalchemy.orm import Mapped, mapped_column
6
+
4
7
  from letta.orm.base import Base
8
+ from letta.orm.errors import MalformedIdError
5
9
 
6
10
 
7
- class MalformedIdError(Exception):
8
- pass
11
+ def is_valid_uuid4(uuid_string: str) -> bool:
12
+ """Check if a string is a valid UUID4."""
13
+ try:
14
+ uuid_obj = UUID(uuid_string)
15
+ return uuid_obj.version == 4
16
+ except ValueError:
17
+ return False
9
18
 
10
19
 
11
20
  def _relation_getter(instance: "Base", prop: str) -> Optional[str]:
21
+ """Get relation and return id with prefix as a string."""
12
22
  prefix = prop.replace("_", "")
13
23
  formatted_prop = f"_{prop}_id"
14
24
  try:
15
- uuid_ = getattr(instance, formatted_prop)
16
- return f"{prefix}-{uuid_}"
25
+ id_ = getattr(instance, formatted_prop) # Get the string id directly
26
+ return f"{prefix}-{id_}"
17
27
  except AttributeError:
18
28
  return None
19
29
 
20
30
 
21
- def _relation_setter(instance: Type["Base"], prop: str, value: str) -> None:
31
+ def _relation_setter(instance: "Base", prop: str, value: str) -> None:
32
+ """Set relation using the id with prefix, ensuring the id is a valid UUIDv4."""
22
33
  formatted_prop = f"_{prop}_id"
23
34
  prefix = prop.replace("_", "")
24
35
  if not value:
@@ -28,13 +39,29 @@ def _relation_setter(instance: Type["Base"], prop: str, value: str) -> None:
28
39
  found_prefix, id_ = value.split("-", 1)
29
40
  except ValueError as e:
30
41
  raise MalformedIdError(f"{value} is not a valid ID.") from e
31
- assert (
32
- # TODO: should be able to get this from the Mapped typing, not sure how though
33
- # prefix = getattr(?, "prefix")
34
- found_prefix
35
- == prefix
36
- ), f"{found_prefix} is not a valid id prefix, expecting {prefix}"
37
- try:
38
- setattr(instance, formatted_prop, UUID(id_))
39
- except ValueError as e:
40
- raise MalformedIdError("Hash segment of {value} is not a valid UUID") from e
42
+
43
+ # Ensure prefix matches
44
+ assert found_prefix == prefix, f"{found_prefix} is not a valid id prefix, expecting {prefix}"
45
+
46
+ # Validate that the id is a valid UUID4 string
47
+ if not is_valid_uuid4(id_):
48
+ raise MalformedIdError(f"Hash segment of {value} is not a valid UUID4")
49
+
50
+ setattr(instance, formatted_prop, id_) # Store id as a string
51
+
52
+
53
+ class OrganizationMixin(Base):
54
+ """Mixin for models that belong to an organization."""
55
+
56
+ __abstract__ = True
57
+
58
+ # Changed _organization_id to store string (still a valid UUID4 string)
59
+ _organization_id: Mapped[str] = mapped_column(String, ForeignKey("organization._id"))
60
+
61
+ @property
62
+ def organization_id(self) -> str:
63
+ return _relation_getter(self, "organization")
64
+
65
+ @organization_id.setter
66
+ def organization_id(self, value: str) -> None:
67
+ _relation_setter(self, "organization", value)
letta/orm/organization.py CHANGED
@@ -1,35 +1,28 @@
1
- from typing import TYPE_CHECKING
1
+ from typing import TYPE_CHECKING, List
2
2
 
3
- from sqlalchemy.exc import NoResultFound
4
- from sqlalchemy.orm import Mapped, mapped_column
3
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
5
4
 
6
5
  from letta.orm.sqlalchemy_base import SqlalchemyBase
7
6
  from letta.schemas.organization import Organization as PydanticOrganization
8
7
 
9
8
  if TYPE_CHECKING:
10
- from sqlalchemy.orm import Session
9
+
10
+ from letta.orm.user import User
11
11
 
12
12
 
13
13
  class Organization(SqlalchemyBase):
14
14
  """The highest level of the object tree. All Entities belong to one and only one Organization."""
15
15
 
16
- __tablename__ = "organizations"
16
+ __tablename__ = "organization"
17
17
  __pydantic_model__ = PydanticOrganization
18
18
 
19
19
  name: Mapped[str] = mapped_column(doc="The display name of the organization.")
20
20
 
21
+ users: Mapped[List["User"]] = relationship("User", back_populates="organization", cascade="all, delete-orphan")
22
+
21
23
  # TODO: Map these relationships later when we actually make these models
22
24
  # below is just a suggestion
23
- # users: Mapped[List["User"]] = relationship("User", back_populates="organization", cascade="all, delete-orphan")
24
25
  # agents: Mapped[List["Agent"]] = relationship("Agent", back_populates="organization", cascade="all, delete-orphan")
25
26
  # sources: Mapped[List["Source"]] = relationship("Source", back_populates="organization", cascade="all, delete-orphan")
26
27
  # tools: Mapped[List["Tool"]] = relationship("Tool", back_populates="organization", cascade="all, delete-orphan")
27
28
  # documents: Mapped[List["Document"]] = relationship("Document", back_populates="organization", cascade="all, delete-orphan")
28
-
29
- @classmethod
30
- def default(cls, db_session: "Session") -> "Organization":
31
- """Get the default org, or create it if it doesn't exist."""
32
- try:
33
- return db_session.query(cls).filter(cls.name == "Default Organization").one()
34
- except NoResultFound:
35
- return cls(name="Default Organization").create(db_session)
@@ -8,6 +8,7 @@ from sqlalchemy.orm import Mapped, mapped_column
8
8
  from letta.log import get_logger
9
9
  from letta.orm.base import Base, CommonSqlalchemyMetaMixins
10
10
  from letta.orm.errors import NoResultFound
11
+ from letta.orm.mixins import is_valid_uuid4
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from pydantic import BaseModel
@@ -42,7 +43,7 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
42
43
  return
43
44
  prefix, id_ = value.split("-", 1)
44
45
  assert prefix == self.__prefix__(), f"{prefix} is not a valid id prefix for {self.__class__.__name__}"
45
- assert SqlalchemyBase.is_valid_uuid4(id_), f"{id_} is not a valid uuid4"
46
+ assert is_valid_uuid4(id_), f"{id_} is not a valid uuid4"
46
47
  self._id = id_
47
48
 
48
49
  @classmethod
@@ -78,22 +79,11 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
78
79
  """
79
80
  try:
80
81
  uuid_string = identifier.split("-", 1)[1] if indifferent else identifier.replace(f"{cls.__prefix__()}-", "")
81
- assert SqlalchemyBase.is_valid_uuid4(uuid_string)
82
+ assert is_valid_uuid4(uuid_string)
82
83
  return uuid_string
83
84
  except ValueError as e:
84
85
  raise ValueError(f"{identifier} is not a valid identifier for class {cls.__name__}") from e
85
86
 
86
- @classmethod
87
- def is_valid_uuid4(cls, uuid_string: str) -> bool:
88
- try:
89
- # Try to create a UUID object from the string
90
- uuid_obj = UUID(uuid_string)
91
- # Check if the UUID is version 4
92
- return uuid_obj.version == 4
93
- except ValueError:
94
- # Raised if the string is not a valid UUID
95
- return False
96
-
97
87
  @classmethod
98
88
  def read(
99
89
  cls,
letta/orm/user.py ADDED
@@ -0,0 +1,25 @@
1
+ from sqlalchemy.orm import Mapped, mapped_column, relationship
2
+
3
+ from letta.orm.mixins import OrganizationMixin
4
+ from letta.orm.organization import Organization
5
+ from letta.orm.sqlalchemy_base import SqlalchemyBase
6
+ from letta.schemas.user import User as PydanticUser
7
+
8
+
9
+ class User(SqlalchemyBase, OrganizationMixin):
10
+ """User ORM class"""
11
+
12
+ __tablename__ = "user"
13
+ __pydantic_model__ = PydanticUser
14
+
15
+ name: Mapped[str] = mapped_column(nullable=False, doc="The display name of the user.")
16
+
17
+ # relationships
18
+ organization: Mapped["Organization"] = relationship("Organization", back_populates="users")
19
+
20
+ # TODO: Add this back later potentially
21
+ # agents: Mapped[List["Agent"]] = relationship(
22
+ # "Agent", secondary="users_agents", back_populates="users", doc="the agents associated with this user."
23
+ # )
24
+ # tokens: Mapped[List["Token"]] = relationship("Token", back_populates="user", doc="the tokens associated with this user.")
25
+ # jobs: Mapped[List["Job"]] = relationship("Job", back_populates="user", doc="the jobs associated with this user.")
letta/schemas/user.py CHANGED
@@ -3,6 +3,7 @@ from typing import Optional
3
3
 
4
4
  from pydantic import Field
5
5
 
6
+ from letta.constants import DEFAULT_ORG_ID
6
7
  from letta.schemas.letta_base import LettaBase
7
8
 
8
9
 
@@ -20,14 +21,20 @@ class User(UserBase):
20
21
  created_at (datetime): The creation date of the user.
21
22
  """
22
23
 
23
- id: str = UserBase.generate_id_field()
24
- org_id: Optional[str] = Field(
25
- ..., description="The organization id of the user"
26
- ) # TODO: dont make optional, and pass in default org ID
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")
27
26
  name: str = Field(..., description="The name of the user.")
28
27
  created_at: datetime = Field(default_factory=datetime.utcnow, description="The creation date of the user.")
28
+ updated_at: datetime = Field(default_factory=datetime.utcnow, description="The update date of the user.")
29
+ is_deleted: bool = Field(False, description="Whether this user is deleted or not.")
29
30
 
30
31
 
31
32
  class UserCreate(UserBase):
32
- name: Optional[str] = Field(None, description="The name of the user.")
33
- org_id: Optional[str] = Field(None, description="The organization id of the user.")
33
+ name: str = Field(..., description="The name of the user.")
34
+ organization_id: str = Field(..., description="The organization id of the user.")
35
+
36
+
37
+ class UserUpdate(UserBase):
38
+ id: str = Field(..., description="The id of the user to update.")
39
+ name: Optional[str] = Field(None, description="The new name of the user.")
40
+ organization_id: Optional[str] = Field(None, description="The new organization id of the user.")
@@ -42,7 +42,7 @@ def create_org(
42
42
  return org
43
43
 
44
44
 
45
- @router.delete("/", tags=["admin"], response_model=Organization, operation_id="delete_organization")
45
+ @router.delete("/", tags=["admin"], response_model=Organization, operation_id="delete_organization_by_id")
46
46
  def delete_org(
47
47
  org_id: str = Query(..., description="The org_id key to be deleted."),
48
48
  server: "SyncServer" = Depends(get_letta_server),
@@ -52,7 +52,7 @@ def delete_org(
52
52
  org = server.organization_manager.get_organization_by_id(org_id=org_id)
53
53
  if org is None:
54
54
  raise HTTPException(status_code=404, detail=f"Organization does not exist")
55
- server.organization_manager.delete_organization(org_id=org_id)
55
+ server.organization_manager.delete_organization_by_id(org_id=org_id)
56
56
  except HTTPException:
57
57
  raise
58
58
  except Exception as e:
@@ -26,7 +26,7 @@ router = APIRouter(prefix="/users", tags=["users", "admin"])
26
26
 
27
27
 
28
28
  @router.get("/", tags=["admin"], response_model=List[User], operation_id="list_users")
29
- def get_all_users(
29
+ def list_users(
30
30
  cursor: Optional[str] = Query(None),
31
31
  limit: Optional[int] = Query(50),
32
32
  server: "SyncServer" = Depends(get_letta_server),
@@ -35,8 +35,7 @@ def get_all_users(
35
35
  Get a list of all users in the database
36
36
  """
37
37
  try:
38
- next_cursor, users = server.ms.get_all_users(cursor=cursor, limit=limit)
39
- # processed_users = [{"user_id": user.id} for user in users]
38
+ next_cursor, users = server.user_manager.list_users(cursor=cursor, limit=limit)
40
39
  except HTTPException:
41
40
  raise
42
41
  except Exception as e:
@@ -53,7 +52,7 @@ def create_user(
53
52
  Create a new user in the database
54
53
  """
55
54
 
56
- user = server.create_user(request)
55
+ user = server.user_manager.create_user(request)
57
56
  return user
58
57
 
59
58
 
@@ -64,10 +63,10 @@ def delete_user(
64
63
  ):
65
64
  # TODO make a soft deletion, instead of a hard deletion
66
65
  try:
67
- user = server.ms.get_user(user_id=user_id)
66
+ user = server.user_manager.get_user_by_id(user_id=user_id)
68
67
  if user is None:
69
68
  raise HTTPException(status_code=404, detail=f"User does not exist")
70
- server.ms.delete_user(user_id=user_id)
69
+ server.user_manager.delete_user_by_id(user_id=user_id)
71
70
  except HTTPException:
72
71
  raise
73
72
  except Exception as e:
@@ -95,7 +94,7 @@ def get_api_keys(
95
94
  """
96
95
  Get a list of all API keys for a user
97
96
  """
98
- if server.ms.get_user(user_id=user_id) is None:
97
+ if server.user_manager.get_user_by_id(user_id=user_id) is None:
99
98
  raise HTTPException(status_code=404, detail=f"User does not exist")
100
99
  api_keys = server.ms.get_all_api_keys_for_user(user_id=user_id)
101
100
  return api_keys
letta/server/server.py CHANGED
@@ -44,7 +44,6 @@ from letta.log import get_logger
44
44
  from letta.memory import get_memory_functions
45
45
  from letta.metadata import Base, MetadataStore
46
46
  from letta.o1_agent import O1Agent
47
- from letta.orm.errors import NoResultFound
48
47
  from letta.prompts import gpt_system
49
48
  from letta.providers import (
50
49
  AnthropicProvider,
@@ -87,6 +86,7 @@ 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
89
88
  from letta.services.organization_manager import OrganizationManager
89
+ from letta.services.user_manager import UserManager
90
90
  from letta.utils import create_random_username, json_dumps, json_loads
91
91
 
92
92
  # from letta.llm_api_tools import openai_get_model_list, azure_openai_get_model_list, smart_urljoin
@@ -168,7 +168,7 @@ from sqlalchemy.orm import sessionmaker
168
168
  from letta.config import LettaConfig
169
169
 
170
170
  # NOTE: hack to see if single session management works
171
- from letta.settings import model_settings, settings
171
+ from letta.settings import model_settings, settings, tool_settings
172
172
 
173
173
  config = LettaConfig.load()
174
174
 
@@ -248,6 +248,7 @@ class SyncServer(Server):
248
248
 
249
249
  # Managers that interface with data models
250
250
  self.organization_manager = OrganizationManager()
251
+ self.user_manager = UserManager()
251
252
 
252
253
  # TODO: this should be removed
253
254
  # add global default tools (for admin)
@@ -576,7 +577,7 @@ class SyncServer(Server):
576
577
  timestamp: Optional[datetime] = None,
577
578
  ) -> LettaUsageStatistics:
578
579
  """Process an incoming user message and feed it through the Letta agent"""
579
- 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:
580
581
  raise ValueError(f"User user_id={user_id} does not exist")
581
582
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
582
583
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -625,7 +626,7 @@ class SyncServer(Server):
625
626
  timestamp: Optional[datetime] = None,
626
627
  ) -> LettaUsageStatistics:
627
628
  """Process an incoming system message and feed it through the Letta agent"""
628
- 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:
629
630
  raise ValueError(f"User user_id={user_id} does not exist")
630
631
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
631
632
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -694,7 +695,7 @@ class SyncServer(Server):
694
695
 
695
696
  Otherwise, we can pass them in directly.
696
697
  """
697
- 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:
698
699
  raise ValueError(f"User user_id={user_id} does not exist")
699
700
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
700
701
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -743,7 +744,7 @@ class SyncServer(Server):
743
744
  # @LockingServer.agent_lock_decorator
744
745
  def run_command(self, user_id: str, agent_id: str, command: str) -> LettaUsageStatistics:
745
746
  """Run a command on the agent"""
746
- 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:
747
748
  raise ValueError(f"User user_id={user_id} does not exist")
748
749
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
749
750
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -754,19 +755,12 @@ class SyncServer(Server):
754
755
  command = command[1:] # strip the prefix
755
756
  return self._command(user_id=user_id, agent_id=agent_id, command=command)
756
757
 
757
- def list_users_paginated(self, cursor: str, limit: int) -> List[User]:
758
- """List all users"""
759
- # TODO: make this paginated
760
- next_cursor, users = self.ms.get_all_users(cursor, limit)
761
- return next_cursor, users
762
-
763
758
  def create_user(self, request: UserCreate) -> User:
764
759
  """Create a new user using a config"""
765
760
  if not request.name:
766
761
  # auto-generate a name
767
762
  request.name = create_random_username()
768
- user = User(name=request.name, org_id=request.org_id)
769
- self.ms.create_user(user)
763
+ user = self.user_manager.create_user(request)
770
764
  logger.debug(f"Created new user from config: {user}")
771
765
 
772
766
  # add default for the user
@@ -785,7 +779,7 @@ class SyncServer(Server):
785
779
  interface: Union[AgentInterface, None] = None,
786
780
  ) -> AgentState:
787
781
  """Create a new agent using a config"""
788
- 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:
789
783
  raise ValueError(f"User user_id={user_id} does not exist")
790
784
 
791
785
  if interface is None:
@@ -809,7 +803,7 @@ class SyncServer(Server):
809
803
  raise ValueError(f"Invalid agent type: {request.agent_type}")
810
804
 
811
805
  logger.debug(f"Attempting to find user: {user_id}")
812
- user = self.ms.get_user(user_id=user_id)
806
+ user = self.user_manager.get_user_by_id(user_id=user_id)
813
807
  if not user:
814
808
  raise ValueError(f"cannot find user with associated client id: {user_id}")
815
809
 
@@ -834,7 +828,8 @@ class SyncServer(Server):
834
828
  # tool already added
835
829
  continue
836
830
  source_code = parse_source_code(func)
837
- json_schema = generate_schema(func, func_name)
831
+ # memory functions are not terminal
832
+ json_schema = generate_schema(func, terminal=False, name=func_name)
838
833
  source_type = "python"
839
834
  tags = ["memory", "memgpt-base"]
840
835
  tool = self.create_tool(
@@ -912,7 +907,7 @@ class SyncServer(Server):
912
907
  user_id: str,
913
908
  ):
914
909
  """Update the agents core memory block, return the new state"""
915
- 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:
916
911
  raise ValueError(f"User user_id={user_id} does not exist")
917
912
  if self.ms.get_agent(agent_id=request.id) is None:
918
913
  raise ValueError(f"Agent agent_id={request.id} does not exist")
@@ -974,7 +969,7 @@ class SyncServer(Server):
974
969
 
975
970
  def get_tools_from_agent(self, agent_id: str, user_id: Optional[str]) -> List[Tool]:
976
971
  """Get tools from an existing agent"""
977
- 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:
978
973
  raise ValueError(f"User user_id={user_id} does not exist")
979
974
  if self.ms.get_agent(agent_id=agent_id) is None:
980
975
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -990,7 +985,7 @@ class SyncServer(Server):
990
985
  user_id: str,
991
986
  ):
992
987
  """Add tools from an existing agent"""
993
- 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:
994
989
  raise ValueError(f"User user_id={user_id} does not exist")
995
990
  if self.ms.get_agent(agent_id=agent_id) is None:
996
991
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1029,7 +1024,7 @@ class SyncServer(Server):
1029
1024
  user_id: str,
1030
1025
  ):
1031
1026
  """Remove tools from an existing agent"""
1032
- 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:
1033
1028
  raise ValueError(f"User user_id={user_id} does not exist")
1034
1029
  if self.ms.get_agent(agent_id=agent_id) is None:
1035
1030
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1075,7 +1070,7 @@ class SyncServer(Server):
1075
1070
  user_id: str,
1076
1071
  ) -> List[AgentState]:
1077
1072
  """List all available agents to a user"""
1078
- 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:
1079
1074
  raise ValueError(f"User user_id={user_id} does not exist")
1080
1075
 
1081
1076
  agents_states = self.ms.list_agents(user_id=user_id)
@@ -1091,7 +1086,7 @@ class SyncServer(Server):
1091
1086
  if user_id is None:
1092
1087
  agents_states = self.ms.list_all_agents()
1093
1088
  else:
1094
- 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:
1095
1090
  raise ValueError(f"User user_id={user_id} does not exist")
1096
1091
 
1097
1092
  agents_states = self.ms.list_agents(user_id=user_id)
@@ -1231,13 +1226,13 @@ class SyncServer(Server):
1231
1226
  """Get the agent state"""
1232
1227
  return self.ms.get_agent(agent_id=agent_id, user_id=user_id)
1233
1228
 
1234
- def get_user(self, user_id: str) -> User:
1235
- """Get the user"""
1236
- user = self.ms.get_user(user_id=user_id)
1237
- if user is None:
1238
- raise ValueError(f"User with user_id {user_id} does not exist")
1239
- else:
1240
- 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
1241
1236
 
1242
1237
  def get_agent_memory(self, agent_id: str) -> Memory:
1243
1238
  """Return the memory of an agent (core memory)"""
@@ -1328,7 +1323,7 @@ class SyncServer(Server):
1328
1323
 
1329
1324
  def get_agent_archival(self, user_id: str, agent_id: str, start: int, count: int) -> List[Passage]:
1330
1325
  """Paginated query of all messages in agent archival memory"""
1331
- 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:
1332
1327
  raise ValueError(f"User user_id={user_id} does not exist")
1333
1328
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1334
1329
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1353,7 +1348,7 @@ class SyncServer(Server):
1353
1348
  order_by: Optional[str] = "created_at",
1354
1349
  reverse: Optional[bool] = False,
1355
1350
  ) -> List[Passage]:
1356
- 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:
1357
1352
  raise ValueError(f"User user_id={user_id} does not exist")
1358
1353
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1359
1354
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1368,7 +1363,7 @@ class SyncServer(Server):
1368
1363
  return records
1369
1364
 
1370
1365
  def insert_archival_memory(self, user_id: str, agent_id: str, memory_contents: str) -> List[Passage]:
1371
- 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:
1372
1367
  raise ValueError(f"User user_id={user_id} does not exist")
1373
1368
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1374
1369
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1383,7 +1378,7 @@ class SyncServer(Server):
1383
1378
  return [letta_agent.persistence_manager.archival_memory.storage.get(id=passage_id) for passage_id in passage_ids]
1384
1379
 
1385
1380
  def delete_archival_memory(self, user_id: str, agent_id: str, memory_id: str):
1386
- 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:
1387
1382
  raise ValueError(f"User user_id={user_id} does not exist")
1388
1383
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1389
1384
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1414,7 +1409,7 @@ class SyncServer(Server):
1414
1409
  assistant_message_function_name: str = constants.DEFAULT_MESSAGE_TOOL,
1415
1410
  assistant_message_function_kwarg: str = constants.DEFAULT_MESSAGE_TOOL_KWARG,
1416
1411
  ) -> Union[List[Message], List[LettaMessage]]:
1417
- 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:
1418
1413
  raise ValueError(f"User user_id={user_id} does not exist")
1419
1414
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1420
1415
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1456,7 +1451,7 @@ class SyncServer(Server):
1456
1451
 
1457
1452
  def get_agent_state(self, user_id: str, agent_id: Optional[str], agent_name: Optional[str] = None) -> Optional[AgentState]:
1458
1453
  """Return the config of an agent"""
1459
- 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:
1460
1455
  raise ValueError(f"User user_id={user_id} does not exist")
1461
1456
  if agent_id:
1462
1457
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
@@ -1497,7 +1492,7 @@ class SyncServer(Server):
1497
1492
 
1498
1493
  def update_agent_core_memory(self, user_id: str, agent_id: str, new_memory_contents: dict) -> Memory:
1499
1494
  """Update the agents core memory block, return the new state"""
1500
- 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:
1501
1496
  raise ValueError(f"User user_id={user_id} does not exist")
1502
1497
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1503
1498
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1528,7 +1523,7 @@ class SyncServer(Server):
1528
1523
 
1529
1524
  def rename_agent(self, user_id: str, agent_id: str, new_agent_name: str) -> AgentState:
1530
1525
  """Update the name of the agent in the database"""
1531
- 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:
1532
1527
  raise ValueError(f"User user_id={user_id} does not exist")
1533
1528
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1534
1529
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1550,13 +1545,9 @@ class SyncServer(Server):
1550
1545
  assert isinstance(letta_agent.agent_state.id, str)
1551
1546
  return letta_agent.agent_state
1552
1547
 
1553
- def delete_user(self, user_id: str):
1554
- # TODO: delete user
1555
- pass
1556
-
1557
1548
  def delete_agent(self, user_id: str, agent_id: str):
1558
1549
  """Delete an agent in the database"""
1559
- 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:
1560
1551
  raise ValueError(f"User user_id={user_id} does not exist")
1561
1552
  if self.ms.get_agent(agent_id=agent_id, user_id=user_id) is None:
1562
1553
  raise ValueError(f"Agent agent_id={agent_id} does not exist")
@@ -1585,7 +1576,8 @@ class SyncServer(Server):
1585
1576
 
1586
1577
  def api_key_to_user(self, api_key: str) -> str:
1587
1578
  """Decode an API key to a user"""
1588
- 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)
1589
1581
  if user is None:
1590
1582
  raise HTTPException(status_code=403, detail="Invalid credentials")
1591
1583
  else:
@@ -2018,7 +2010,10 @@ class SyncServer(Server):
2018
2010
  def add_default_external_tools(self, user_id: Optional[str] = None) -> bool:
2019
2011
  """Add default langchain tools. Return true if successful, false otherwise."""
2020
2012
  success = True
2021
- 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()
2022
2017
  for tool in tools:
2023
2018
  try:
2024
2019
  self.ms.create_tool(tool)
@@ -2113,29 +2108,15 @@ class SyncServer(Server):
2113
2108
  letta_agent = self._get_or_load_agent(agent_id=agent_id)
2114
2109
  return letta_agent.retry_message()
2115
2110
 
2111
+ # TODO: Move a lot of this default logic to the ORM
2116
2112
  def get_default_user(self) -> User:
2113
+ self.organization_manager.create_default_organization()
2114
+ user = self.user_manager.create_default_user()
2117
2115
 
2118
- from letta.constants import DEFAULT_ORG_ID, DEFAULT_USER_ID, DEFAULT_USER_NAME
2119
-
2120
- # check if default org exists
2121
- try:
2122
- self.organization_manager.get_organization_by_id(DEFAULT_ORG_ID)
2123
- except NoResultFound:
2124
- self.organization_manager.create_default_organization()
2125
-
2126
- # check if default user exists
2127
- try:
2128
- self.get_user(DEFAULT_USER_ID)
2129
- except ValueError:
2130
- user = User(name=DEFAULT_USER_NAME, org_id=DEFAULT_ORG_ID, id=DEFAULT_USER_ID)
2131
- self.ms.create_user(user)
2132
-
2133
- # add default data (TODO: move to org)
2134
- self.add_default_blocks(user.id)
2135
- 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)
2136
2118
 
2137
- # check if default org exists
2138
- return self.get_user(DEFAULT_USER_ID)
2119
+ return user
2139
2120
 
2140
2121
  def get_user_or_default(self, user_id: Optional[str]) -> User:
2141
2122
  """Get the user object for user_id if it exists, otherwise return the default user object"""
@@ -2143,7 +2124,7 @@ class SyncServer(Server):
2143
2124
  return self.get_default_user()
2144
2125
  else:
2145
2126
  try:
2146
- return self.get_user(user_id=user_id)
2127
+ return self.user_manager.get_user_by_id(user_id=user_id)
2147
2128
  except ValueError:
2148
2129
  raise HTTPException(status_code=404, detail=f"User with id {user_id} not found")
2149
2130
 
@@ -1,11 +1,10 @@
1
1
  from typing import List, Optional
2
2
 
3
- from sqlalchemy.exc import NoResultFound
4
-
5
3
  from letta.constants import DEFAULT_ORG_ID, DEFAULT_ORG_NAME
4
+ from letta.orm.errors import NoResultFound
6
5
  from letta.orm.organization import Organization
7
6
  from letta.schemas.organization import Organization as PydanticOrganization
8
- from letta.utils import create_random_username
7
+ from letta.utils import create_random_username, enforce_types
9
8
 
10
9
 
11
10
  class OrganizationManager:
@@ -20,6 +19,7 @@ class OrganizationManager:
20
19
 
21
20
  self.session_maker = db_context
22
21
 
22
+ @enforce_types
23
23
  def get_organization_by_id(self, org_id: str) -> PydanticOrganization:
24
24
  """Fetch an organization by ID."""
25
25
  with self.session_maker() as session:
@@ -29,6 +29,7 @@ class OrganizationManager:
29
29
  except NoResultFound:
30
30
  raise ValueError(f"Organization with id {org_id} not found.")
31
31
 
32
+ @enforce_types
32
33
  def create_organization(self, name: Optional[str] = None) -> PydanticOrganization:
33
34
  """Create a new organization. If a name is provided, it is used, otherwise, a random one is generated."""
34
35
  with self.session_maker() as session:
@@ -36,14 +37,21 @@ class OrganizationManager:
36
37
  org.create(session)
37
38
  return org.to_pydantic()
38
39
 
40
+ @enforce_types
39
41
  def create_default_organization(self) -> PydanticOrganization:
40
42
  """Create the default organization."""
41
43
  with self.session_maker() as session:
42
- org = Organization(name=DEFAULT_ORG_NAME)
43
- org.id = DEFAULT_ORG_ID
44
- org.create(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
+
45
52
  return org.to_pydantic()
46
53
 
54
+ @enforce_types
47
55
  def update_organization_name_using_id(self, org_id: str, name: Optional[str] = None) -> PydanticOrganization:
48
56
  """Update an organization."""
49
57
  with self.session_maker() as session:
@@ -53,12 +61,14 @@ class OrganizationManager:
53
61
  organization.update(session)
54
62
  return organization.to_pydantic()
55
63
 
56
- def delete_organization(self, org_id: str):
64
+ @enforce_types
65
+ def delete_organization_by_id(self, org_id: str):
57
66
  """Delete an organization by marking it as deleted."""
58
67
  with self.session_maker() as session:
59
68
  organization = Organization.read(db_session=session, identifier=org_id)
60
69
  organization.delete(session)
61
70
 
71
+ @enforce_types
62
72
  def list_organizations(self, cursor: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticOrganization]:
63
73
  """List organizations with pagination based on cursor (org_id) and limit."""
64
74
  with self.session_maker() as session:
@@ -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.dev20241023104105
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
@@ -1,4 +1,4 @@
1
- letta/__init__.py,sha256=kRTa2BzRf4EWmx6MkOXUyaisXDj7-6NkmY22gd7h_xs,1014
1
+ letta/__init__.py,sha256=zlZXKr0iQIsC7FTkN9fjTvAgQGPXyr0nnkMJ7_OE1D0,1014
2
2
  letta/__main__.py,sha256=6Hs2PV7EYc5Tid4g4OtcLXhqVHiNYTGzSBdoOnW2HXA,29
3
3
  letta/agent.py,sha256=PRMDj0vZu5TGj-k2apeWcWYzJnBR36prdMXTKWnRYc8,72997
4
4
  letta/agent_store/chroma.py,sha256=upR5zGnGs6I6btulEYbiZdGG87BgKjxUJOQZ4Y-RQ_M,12492
@@ -17,7 +17,7 @@ letta/client/client.py,sha256=CLOdsr3rVfWHQBPG-NN4sHKKn0Cn0VqIcjtHGxYaix4,93462
17
17
  letta/client/streaming.py,sha256=bfWlUu7z7EoPfKxBqIarYxGKyrL7Pj79BlliToqcCgI,4592
18
18
  letta/client/utils.py,sha256=OJlAKWrldc4I6M1WpcTWNtPJ4wfxlzlZqWLfCozkFtI,2872
19
19
  letta/config.py,sha256=j2I90fOh9d9__kOYObwTDLbvVwYR50rIql5nzrvREKg,19161
20
- letta/constants.py,sha256=Y7MNpkAZfQ1Mo-AhgFiA_dFQLDCiZT6ebCG_n0R_0FQ,6784
20
+ letta/constants.py,sha256=CLC--_fz5Gibaot0rJObjt53o-KO1LfxiAOJuwplHSI,6821
21
21
  letta/credentials.py,sha256=D9mlcPsdDWlIIXQQD8wSPE9M_QvsRrb0p3LB5i9OF5Q,5806
22
22
  letta/data_sources/connectors.py,sha256=qO81ASB6V-vDPthfHYtZiyqcQDQPTT0NuD8hVwC6xI0,9907
23
23
  letta/data_sources/connectors_helper.py,sha256=2TQjCt74fCgT5sw1AP8PalDEk06jPBbhrPG4HVr-WLs,3371
@@ -83,7 +83,7 @@ letta/local_llm/webui/settings.py,sha256=gmLHfiOl1u4JmlAZU2d2O8YKF9lafdakyjwR_ft
83
83
  letta/log.py,sha256=QHquDnL7oUAvdKlAwUlCK9zXKDMUjrU9WA0bxnMsP0Y,2101
84
84
  letta/main.py,sha256=yHgM1lltQZvbE8k0QDQMmVyJiWEj07ZTOYIBHDxE_DQ,18709
85
85
  letta/memory.py,sha256=6q1x3-PY-PeXzAt6hvP-UF1ajvroPZ7XW-5nLy-JhMo,17657
86
- letta/metadata.py,sha256=dcQk1dx5ugO73I6bcOMpNapbFtphSiaG_80yL3rIU-U,34811
86
+ letta/metadata.py,sha256=a4XvHD6m52Pu1_Mr0m0yN0mFgo99OLKDtglfRBz6YRs,31548
87
87
  letta/o1_agent.py,sha256=0jospImZUKhuQZ0cop0INj8xI6cxhxNffGA8iloHyfU,3114
88
88
  letta/openai_backcompat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  letta/openai_backcompat/openai_object.py,sha256=Y1ZS1sATP60qxJiOsjOP3NbwSzuzvkNAvb3DeuhM5Uk,13490
@@ -91,10 +91,11 @@ letta/orm/__all__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
91
91
  letta/orm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
92
  letta/orm/base.py,sha256=9k7mwKDApJNpbrk6oXosQ7amlpeYhDzArdYTZ6BhfpQ,2405
93
93
  letta/orm/enums.py,sha256=KfHcFt_fR6GUmSlmfsa-TetvmuRxGESNve8MStRYW64,145
94
- letta/orm/errors.py,sha256=dYyFwsBuEOS11Wjdz3AAoR4OhejSXNBdQhU71pxymd0,111
95
- letta/orm/mixins.py,sha256=nOG8Bt4cP75dPPOnI35efFrBZ2OSa6bVRuEFrUuJfBw,1224
96
- letta/orm/organization.py,sha256=JykIMHJ_g3JVgBaRliBVlqe9iMoahHqSlfwWZv_LDxA,1695
97
- letta/orm/sqlalchemy_base.py,sha256=YJJBqZ3S6Gp7fr_nBTXBtOt_5rKk-zBBuBUMTAgyRWk,9337
94
+ letta/orm/errors.py,sha256=somsGtotFlb3SDM6tKdZ5TDGwEEP3ppx47ICAvNMnkg,225
95
+ letta/orm/mixins.py,sha256=NtJUSsOv6SM5GY9gXgHdq0smdXEeFv6tWXW_chpW0rw,2157
96
+ letta/orm/organization.py,sha256=afjWs0FU2lOPOHsw_BhDvdmx4f19e1_Jfc0b1w2Bz_8,1316
97
+ letta/orm/sqlalchemy_base.py,sha256=_UBfwpAeuHsPChu6EVV6jHQE_AMPDzE8Loj1a71M2ng,8973
98
+ letta/orm/user.py,sha256=wRo776d-bsKE6ChJkbjO15jHUQ22RiJauhJtXSkOzoc,1079
98
99
  letta/persistence_manager.py,sha256=LlLgEDpSafCPAiyKmuq0NvVAnfBkZo6TWbGIKYQjQBs,5200
99
100
  letta/personas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
101
  letta/personas/examples/anna_pa.txt,sha256=zgiNdSNhy1HQy58cF_6RFPzcg2i37F9v38YuL1CW40A,1849
@@ -145,7 +146,7 @@ letta/schemas/passage.py,sha256=eYQMxD_XjHAi72jmqcGBU4wM4VZtSU0XK8uhQxxN3Ug,3563
145
146
  letta/schemas/source.py,sha256=hB4Ai6Nj8dFdbxv5_Qaf4uN_cmdGmnzgc-4QnHXcV3o,2562
146
147
  letta/schemas/tool.py,sha256=m8jWIsPUhekoQcjX7U_Y5vwhhQqSKn748RcXNXRiLGg,10329
147
148
  letta/schemas/usage.py,sha256=lvn1ooHwLEdv6gwQpw5PBUbcwn_gwdT6HA-fCiix6sY,817
148
- letta/schemas/user.py,sha256=D7DiPzieXZIHOLInJdYZlHjKOy2bl7KxGCesNk0yf5E,1003
149
+ letta/schemas/user.py,sha256=s1D9QoyBwxsbcZALXg0cJIfKz-3k47UGJeaheiGxusU,1478
149
150
  letta/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
150
151
  letta/server/constants.py,sha256=yAdGbLkzlOU_dLTx0lKDmAnj0ZgRXCEaIcPJWO69eaE,92
151
152
  letta/server/rest_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -172,13 +173,13 @@ letta/server/rest_api/routers/v1/blocks.py,sha256=0WekE_yBD2U3jYgPxI0DCFjACWavCA
172
173
  letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
173
174
  letta/server/rest_api/routers/v1/jobs.py,sha256=a-j0v-5A0un0pVCOHpfeWnzpOWkVDQO6ti42k_qAlZY,2272
174
175
  letta/server/rest_api/routers/v1/llms.py,sha256=TcyvSx6MEM3je5F4DysL7ligmssL_pFlJaaO4uL95VY,877
175
- letta/server/rest_api/routers/v1/organizations.py,sha256=yIni1i1Yurcbu0SwURm01IEEmlTgiWQ7ci6BefNZT_I,2025
176
+ letta/server/rest_api/routers/v1/organizations.py,sha256=3XlHPUc6ZdMOVZNdarwDM8ZxYisoHunSZQ0ozzX5orU,2037
176
177
  letta/server/rest_api/routers/v1/sources.py,sha256=eY_pk9jRL2Y9yIZdsTjH6EuKsfH1neaTU15MKNL0dvw,8749
177
178
  letta/server/rest_api/routers/v1/tools.py,sha256=vxE4b5juoiBiNWmplktuv6GEgenCkKBRov-t6usUJ9A,3665
178
- letta/server/rest_api/routers/v1/users.py,sha256=Y2rDvHOG1B5FLSOjutY3R22vt48IngbZ-9h8CohG5rc,3378
179
+ letta/server/rest_api/routers/v1/users.py,sha256=bxQ-YdevjDqmgNDfbSPAG_4KEVvPNBHD_-Lp1MdeMec,3374
179
180
  letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
180
181
  letta/server/rest_api/utils.py,sha256=Fc2ZGKzLaBa2sEtSTVjJ8D5M0xIwsWC0CVAOIJaD3rY,2176
181
- letta/server/server.py,sha256=TI9VItwqQ0M-zBHQJwdSKlx5aDMDGczK6vOzagy2zeA,91063
182
+ letta/server/server.py,sha256=EBDj5iH2tGr_GsMX6I_yRO6IBYpNpZUZxFY2nGqPDrc,90912
182
183
  letta/server/startup.sh,sha256=jeGV7B_PS0hS-tT6o6GpACrUbV9WV1NI2L9aLoUDDtc,311
183
184
  letta/server/static_files/assets/index-3ab03d5b.css,sha256=OrA9W4iKJ5h2Wlr7GwdAT4wow0CM8hVit1yOxEL49Qw,54295
184
185
  letta/server/static_files/assets/index-d6b3669a.js,sha256=i1nHReU0RPnj-a5W0nNPV4Y9bQ0FOW0ztjMz8a2AE-Y,1821560
@@ -192,14 +193,15 @@ letta/server/ws_api/interface.py,sha256=TWl9vkcMCnLsUtgsuENZ-ku2oMDA-OUTzLh_yNRo
192
193
  letta/server/ws_api/protocol.py,sha256=M_-gM5iuDBwa1cuN2IGNCG5GxMJwU2d3XW93XALv9s8,1821
193
194
  letta/server/ws_api/server.py,sha256=C2Kv48PCwl46DQFb0ZP30s86KJLQ6dZk2AhWQEZn9pY,6004
194
195
  letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
195
- letta/services/organization_manager.py,sha256=834mNQDVaf6TKx-icAMeOgiYaq1NXtuM7je3SjULlYI,3036
196
- letta/settings.py,sha256=gNdH-Ty6f-Nfz2j9ZMZFRQHac2KzgsxLZNt5l_TiAyo,3301
196
+ letta/services/organization_manager.py,sha256=skEByzSiLgFesBRbm87VKR-TSUK3LhJitTwN1Zl6-Hw,3379
197
+ letta/services/user_manager.py,sha256=q2GiGGv-bMTob2CT17onk38oHkWqU_DQap0sxQMhmkg,4376
198
+ letta/settings.py,sha256=yiYNmnYKj_BdTm0cBEIvQKYGU-lCmFntqsyVfRUy3_k,3411
197
199
  letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,15736
198
200
  letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
199
201
  letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
200
202
  letta/utils.py,sha256=SXLEYhyp3gHyIjrxNIKNZZ5ittKo3KOj6zxgC_Trex0,31012
201
- letta_nightly-0.5.0.dev20241023104105.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
202
- letta_nightly-0.5.0.dev20241023104105.dist-info/METADATA,sha256=n2RNxyAEkgg6neEBHsFKxoUpeu9o4qlPSIdJ3ynWx0M,10660
203
- letta_nightly-0.5.0.dev20241023104105.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
204
- letta_nightly-0.5.0.dev20241023104105.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
205
- letta_nightly-0.5.0.dev20241023104105.dist-info/RECORD,,
203
+ letta_nightly-0.5.1.dev20241023193051.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
204
+ letta_nightly-0.5.1.dev20241023193051.dist-info/METADATA,sha256=2A5lizaE-2kLa_idJ7BxYSzz9PJYEZdkgtvwrFVpF2Q,10660
205
+ letta_nightly-0.5.1.dev20241023193051.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
206
+ letta_nightly-0.5.1.dev20241023193051.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
207
+ letta_nightly-0.5.1.dev20241023193051.dist-info/RECORD,,