letta-nightly 0.7.13.dev20250512104305__py3-none-any.whl → 0.7.14.dev20250513020711__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.
Files changed (43) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +14 -17
  3. letta/agents/base_agent.py +112 -1
  4. letta/agents/letta_agent.py +35 -55
  5. letta/agents/letta_agent_batch.py +22 -45
  6. letta/agents/voice_agent.py +10 -42
  7. letta/functions/schema_generator.py +7 -3
  8. letta/llm_api/anthropic.py +4 -2
  9. letta/llm_api/openai.py +4 -2
  10. letta/orm/agents_tags.py +5 -2
  11. letta/orm/blocks_agents.py +3 -1
  12. letta/orm/sqlalchemy_base.py +91 -1
  13. letta/schemas/message.py +1 -1
  14. letta/serialize_schemas/marshmallow_agent.py +4 -4
  15. letta/server/db.py +180 -88
  16. letta/server/rest_api/app.py +6 -3
  17. letta/server/rest_api/chat_completions_interface.py +1 -0
  18. letta/server/rest_api/interface.py +54 -16
  19. letta/server/rest_api/routers/v1/sources.py +1 -0
  20. letta/server/server.py +1 -2
  21. letta/services/agent_manager.py +40 -31
  22. letta/services/block_manager.py +61 -34
  23. letta/services/group_manager.py +11 -15
  24. letta/services/identity_manager.py +9 -13
  25. letta/services/job_manager.py +12 -17
  26. letta/services/llm_batch_manager.py +17 -21
  27. letta/services/message_manager.py +53 -31
  28. letta/services/organization_manager.py +7 -14
  29. letta/services/passage_manager.py +6 -10
  30. letta/services/provider_manager.py +5 -9
  31. letta/services/sandbox_config_manager.py +13 -17
  32. letta/services/source_manager.py +13 -17
  33. letta/services/step_manager.py +5 -9
  34. letta/services/tool_manager.py +9 -14
  35. letta/services/user_manager.py +7 -12
  36. letta/settings.py +2 -0
  37. letta/streaming_interface.py +2 -0
  38. letta/utils.py +1 -1
  39. {letta_nightly-0.7.13.dev20250512104305.dist-info → letta_nightly-0.7.14.dev20250513020711.dist-info}/METADATA +2 -1
  40. {letta_nightly-0.7.13.dev20250512104305.dist-info → letta_nightly-0.7.14.dev20250513020711.dist-info}/RECORD +43 -43
  41. {letta_nightly-0.7.13.dev20250512104305.dist-info → letta_nightly-0.7.14.dev20250513020711.dist-info}/LICENSE +0 -0
  42. {letta_nightly-0.7.13.dev20250512104305.dist-info → letta_nightly-0.7.14.dev20250513020711.dist-info}/WHEEL +0 -0
  43. {letta_nightly-0.7.13.dev20250512104305.dist-info → letta_nightly-0.7.14.dev20250513020711.dist-info}/entry_points.txt +0 -0
@@ -12,6 +12,7 @@ from letta.schemas.letta_message import LettaMessageUpdateUnion
12
12
  from letta.schemas.message import Message as PydanticMessage
13
13
  from letta.schemas.message import MessageUpdate
14
14
  from letta.schemas.user import User as PydanticUser
15
+ from letta.server.db import db_registry
15
16
  from letta.utils import enforce_types
16
17
 
17
18
  logger = get_logger(__name__)
@@ -20,15 +21,10 @@ logger = get_logger(__name__)
20
21
  class MessageManager:
21
22
  """Manager class to handle business logic related to Messages."""
22
23
 
23
- def __init__(self):
24
- from letta.server.db import db_context
25
-
26
- self.session_maker = db_context
27
-
28
24
  @enforce_types
29
25
  def get_message_by_id(self, message_id: str, actor: PydanticUser) -> Optional[PydanticMessage]:
30
26
  """Fetch a message by ID."""
31
- with self.session_maker() as session:
27
+ with db_registry.session() as session:
32
28
  try:
33
29
  message = MessageModel.read(db_session=session, identifier=message_id, actor=actor)
34
30
  return message.to_pydantic()
@@ -38,7 +34,7 @@ class MessageManager:
38
34
  @enforce_types
39
35
  def get_messages_by_ids(self, message_ids: List[str], actor: PydanticUser) -> List[PydanticMessage]:
40
36
  """Fetch messages by ID and return them in the requested order."""
41
- with self.session_maker() as session:
37
+ with db_registry.session() as session:
42
38
  results = MessageModel.list(db_session=session, id=message_ids, organization_id=actor.organization_id, limit=len(message_ids))
43
39
 
44
40
  if len(results) != len(message_ids):
@@ -53,7 +49,7 @@ class MessageManager:
53
49
  @enforce_types
54
50
  def create_message(self, pydantic_msg: PydanticMessage, actor: PydanticUser) -> PydanticMessage:
55
51
  """Create a new message."""
56
- with self.session_maker() as session:
52
+ with db_registry.session() as session:
57
53
  # Set the organization id of the Pydantic message
58
54
  pydantic_msg.organization_id = actor.organization_id
59
55
  msg_data = pydantic_msg.model_dump(to_orm=True)
@@ -86,7 +82,7 @@ class MessageManager:
86
82
  orm_messages.append(MessageModel(**msg_data))
87
83
 
88
84
  # Use the batch_create method for efficient creation
89
- with self.session_maker() as session:
85
+ with db_registry.session() as session:
90
86
  created_messages = MessageModel.batch_create(orm_messages, session, actor=actor)
91
87
 
92
88
  # Convert back to Pydantic models
@@ -173,7 +169,7 @@ class MessageManager:
173
169
  """
174
170
  Updates an existing record in the database with values from the provided record object.
175
171
  """
176
- with self.session_maker() as session:
172
+ with db_registry.session() as session:
177
173
  # Fetch existing message from database
178
174
  message = MessageModel.read(
179
175
  db_session=session,
@@ -181,31 +177,57 @@ class MessageManager:
181
177
  actor=actor,
182
178
  )
183
179
 
184
- # Some safety checks specific to messages
185
- if message_update.tool_calls and message.role != MessageRole.assistant:
186
- raise ValueError(
187
- f"Tool calls {message_update.tool_calls} can only be added to assistant messages. Message {message_id} has role {message.role}."
188
- )
189
- if message_update.tool_call_id and message.role != MessageRole.tool:
190
- raise ValueError(
191
- f"Tool call IDs {message_update.tool_call_id} can only be added to tool messages. Message {message_id} has role {message.role}."
192
- )
193
-
194
- # get update dictionary
195
- update_data = message_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
196
- # Remove redundant update fields
197
- update_data = {key: value for key, value in update_data.items() if getattr(message, key) != value}
198
-
199
- for key, value in update_data.items():
200
- setattr(message, key, value)
180
+ message = self._update_message_by_id_impl(message_id, message_update, actor, message)
201
181
  message.update(db_session=session, actor=actor)
182
+ return message.to_pydantic()
183
+
184
+ @enforce_types
185
+ async def update_message_by_id_async(self, message_id: str, message_update: MessageUpdate, actor: PydanticUser) -> PydanticMessage:
186
+ """
187
+ Updates an existing record in the database with values from the provided record object.
188
+ Async version of the function above.
189
+ """
190
+ async with db_registry.async_session() as session:
191
+ # Fetch existing message from database
192
+ message = await MessageModel.read_async(
193
+ db_session=session,
194
+ identifier=message_id,
195
+ actor=actor,
196
+ )
202
197
 
198
+ message = self._update_message_by_id_impl(message_id, message_update, actor, message)
199
+ await message.update_async(db_session=session, actor=actor)
203
200
  return message.to_pydantic()
204
201
 
202
+ def _update_message_by_id_impl(
203
+ self, message_id: str, message_update: MessageUpdate, actor: PydanticUser, message: MessageModel
204
+ ) -> MessageModel:
205
+ """
206
+ Modifies the existing message object to update the database in the sync/async functions.
207
+ """
208
+ # Some safety checks specific to messages
209
+ if message_update.tool_calls and message.role != MessageRole.assistant:
210
+ raise ValueError(
211
+ f"Tool calls {message_update.tool_calls} can only be added to assistant messages. Message {message_id} has role {message.role}."
212
+ )
213
+ if message_update.tool_call_id and message.role != MessageRole.tool:
214
+ raise ValueError(
215
+ f"Tool call IDs {message_update.tool_call_id} can only be added to tool messages. Message {message_id} has role {message.role}."
216
+ )
217
+
218
+ # get update dictionary
219
+ update_data = message_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
220
+ # Remove redundant update fields
221
+ update_data = {key: value for key, value in update_data.items() if getattr(message, key) != value}
222
+
223
+ for key, value in update_data.items():
224
+ setattr(message, key, value)
225
+ return message
226
+
205
227
  @enforce_types
206
228
  def delete_message_by_id(self, message_id: str, actor: PydanticUser) -> bool:
207
229
  """Delete a message."""
208
- with self.session_maker() as session:
230
+ with db_registry.session() as session:
209
231
  try:
210
232
  msg = MessageModel.read(
211
233
  db_session=session,
@@ -229,7 +251,7 @@ class MessageManager:
229
251
  actor: The user requesting the count
230
252
  role: The role of the message
231
253
  """
232
- with self.session_maker() as session:
254
+ with db_registry.session() as session:
233
255
  return MessageModel.size(db_session=session, actor=actor, role=role, agent_id=agent_id)
234
256
 
235
257
  @enforce_types
@@ -293,7 +315,7 @@ class MessageManager:
293
315
  NoResultFound: If the provided after/before message IDs do not exist.
294
316
  """
295
317
 
296
- with self.session_maker() as session:
318
+ with db_registry.session() as session:
297
319
  # Permission check: raise if the agent doesn't exist or actor is not allowed.
298
320
  AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
299
321
 
@@ -363,7 +385,7 @@ class MessageManager:
363
385
  Efficiently deletes all messages associated with a given agent_id,
364
386
  while enforcing permission checks and avoiding any ORM‑level loads.
365
387
  """
366
- with self.session_maker() as session:
388
+ with db_registry.session() as session:
367
389
  # 1) verify the agent exists and the actor has access
368
390
  AgentModel.read(db_session=session, identifier=agent_id, actor=actor)
369
391
 
@@ -4,6 +4,7 @@ from letta.orm.errors import NoResultFound
4
4
  from letta.orm.organization import Organization as OrganizationModel
5
5
  from letta.schemas.organization import Organization as PydanticOrganization
6
6
  from letta.schemas.organization import OrganizationUpdate
7
+ from letta.server.db import db_registry
7
8
  from letta.utils import enforce_types
8
9
 
9
10
 
@@ -13,14 +14,6 @@ class OrganizationManager:
13
14
  DEFAULT_ORG_ID = "org-00000000-0000-4000-8000-000000000000"
14
15
  DEFAULT_ORG_NAME = "default_org"
15
16
 
16
- def __init__(self):
17
- # TODO: Please refactor this out
18
- # I am currently working on a ORM refactor and would like to make a more minimal set of changes
19
- # - Matt
20
- from letta.server.db import db_context
21
-
22
- self.session_maker = db_context
23
-
24
17
  @enforce_types
25
18
  def get_default_organization(self) -> PydanticOrganization:
26
19
  """Fetch the default organization."""
@@ -29,7 +22,7 @@ class OrganizationManager:
29
22
  @enforce_types
30
23
  def get_organization_by_id(self, org_id: str) -> Optional[PydanticOrganization]:
31
24
  """Fetch an organization by ID."""
32
- with self.session_maker() as session:
25
+ with db_registry.session() as session:
33
26
  organization = OrganizationModel.read(db_session=session, identifier=org_id)
34
27
  return organization.to_pydantic()
35
28
 
@@ -44,7 +37,7 @@ class OrganizationManager:
44
37
 
45
38
  @enforce_types
46
39
  def _create_organization(self, pydantic_org: PydanticOrganization) -> PydanticOrganization:
47
- with self.session_maker() as session:
40
+ with db_registry.session() as session:
48
41
  org = OrganizationModel(**pydantic_org.model_dump(to_orm=True))
49
42
  org.create(session)
50
43
  return org.to_pydantic()
@@ -57,7 +50,7 @@ class OrganizationManager:
57
50
  @enforce_types
58
51
  def update_organization_name_using_id(self, org_id: str, name: Optional[str] = None) -> PydanticOrganization:
59
52
  """Update an organization."""
60
- with self.session_maker() as session:
53
+ with db_registry.session() as session:
61
54
  org = OrganizationModel.read(db_session=session, identifier=org_id)
62
55
  if name:
63
56
  org.name = name
@@ -67,7 +60,7 @@ class OrganizationManager:
67
60
  @enforce_types
68
61
  def update_organization(self, org_id: str, org_update: OrganizationUpdate) -> PydanticOrganization:
69
62
  """Update an organization."""
70
- with self.session_maker() as session:
63
+ with db_registry.session() as session:
71
64
  org = OrganizationModel.read(db_session=session, identifier=org_id)
72
65
  if org_update.name:
73
66
  org.name = org_update.name
@@ -79,14 +72,14 @@ class OrganizationManager:
79
72
  @enforce_types
80
73
  def delete_organization_by_id(self, org_id: str):
81
74
  """Delete an organization by marking it as deleted."""
82
- with self.session_maker() as session:
75
+ with db_registry.session() as session:
83
76
  organization = OrganizationModel.read(db_session=session, identifier=org_id)
84
77
  organization.hard_delete(session)
85
78
 
86
79
  @enforce_types
87
80
  def list_organizations(self, after: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticOrganization]:
88
81
  """List all organizations with optional pagination."""
89
- with self.session_maker() as session:
82
+ with db_registry.session() as session:
90
83
  organizations = OrganizationModel.list(
91
84
  db_session=session,
92
85
  after=after,
@@ -10,21 +10,17 @@ from letta.orm.passage import AgentPassage, SourcePassage
10
10
  from letta.schemas.agent import AgentState
11
11
  from letta.schemas.passage import Passage as PydanticPassage
12
12
  from letta.schemas.user import User as PydanticUser
13
+ from letta.server.db import db_registry
13
14
  from letta.utils import enforce_types
14
15
 
15
16
 
16
17
  class PassageManager:
17
18
  """Manager class to handle business logic related to Passages."""
18
19
 
19
- def __init__(self):
20
- from letta.server.db import db_context
21
-
22
- self.session_maker = db_context
23
-
24
20
  @enforce_types
25
21
  def get_passage_by_id(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
26
22
  """Fetch a passage by ID."""
27
- with self.session_maker() as session:
23
+ with db_registry.session() as session:
28
24
  # Try source passages first
29
25
  try:
30
26
  passage = SourcePassage.read(db_session=session, identifier=passage_id, actor=actor)
@@ -69,7 +65,7 @@ class PassageManager:
69
65
  else:
70
66
  raise ValueError("Passage must have either agent_id or source_id")
71
67
 
72
- with self.session_maker() as session:
68
+ with db_registry.session() as session:
73
69
  passage.create(session, actor=actor)
74
70
  return passage.to_pydantic()
75
71
 
@@ -145,7 +141,7 @@ class PassageManager:
145
141
  if not passage_id:
146
142
  raise ValueError("Passage ID must be provided.")
147
143
 
148
- with self.session_maker() as session:
144
+ with db_registry.session() as session:
149
145
  # Try source passages first
150
146
  try:
151
147
  curr_passage = SourcePassage.read(
@@ -179,7 +175,7 @@ class PassageManager:
179
175
  if not passage_id:
180
176
  raise ValueError("Passage ID must be provided.")
181
177
 
182
- with self.session_maker() as session:
178
+ with db_registry.session() as session:
183
179
  # Try source passages first
184
180
  try:
185
181
  passage = SourcePassage.read(db_session=session, identifier=passage_id, actor=actor)
@@ -217,7 +213,7 @@ class PassageManager:
217
213
  actor: The user requesting the count
218
214
  agent_id: The agent ID of the messages
219
215
  """
220
- with self.session_maker() as session:
216
+ with db_registry.session() as session:
221
217
  return AgentPassage.size(db_session=session, actor=actor, agent_id=agent_id)
222
218
 
223
219
  def estimate_embeddings_size(
@@ -5,20 +5,16 @@ from letta.schemas.enums import ProviderCategory, ProviderType
5
5
  from letta.schemas.providers import Provider as PydanticProvider
6
6
  from letta.schemas.providers import ProviderCheck, ProviderCreate, ProviderUpdate
7
7
  from letta.schemas.user import User as PydanticUser
8
+ from letta.server.db import db_registry
8
9
  from letta.utils import enforce_types
9
10
 
10
11
 
11
12
  class ProviderManager:
12
13
 
13
- def __init__(self):
14
- from letta.server.db import db_context
15
-
16
- self.session_maker = db_context
17
-
18
14
  @enforce_types
19
15
  def create_provider(self, request: ProviderCreate, actor: PydanticUser) -> PydanticProvider:
20
16
  """Create a new provider if it doesn't already exist."""
21
- with self.session_maker() as session:
17
+ with db_registry.session() as session:
22
18
  provider_create_args = {**request.model_dump(), "provider_category": ProviderCategory.byok}
23
19
  provider = PydanticProvider(**provider_create_args)
24
20
 
@@ -38,7 +34,7 @@ class ProviderManager:
38
34
  @enforce_types
39
35
  def update_provider(self, provider_id: str, provider_update: ProviderUpdate, actor: PydanticUser) -> PydanticProvider:
40
36
  """Update provider details."""
41
- with self.session_maker() as session:
37
+ with db_registry.session() as session:
42
38
  # Retrieve the existing provider by ID
43
39
  existing_provider = ProviderModel.read(db_session=session, identifier=provider_id, actor=actor)
44
40
 
@@ -54,7 +50,7 @@ class ProviderManager:
54
50
  @enforce_types
55
51
  def delete_provider_by_id(self, provider_id: str, actor: PydanticUser):
56
52
  """Delete a provider."""
57
- with self.session_maker() as session:
53
+ with db_registry.session() as session:
58
54
  # Clear api key field
59
55
  existing_provider = ProviderModel.read(db_session=session, identifier=provider_id, actor=actor)
60
56
  existing_provider.api_key = None
@@ -80,7 +76,7 @@ class ProviderManager:
80
76
  filter_kwargs["name"] = name
81
77
  if provider_type:
82
78
  filter_kwargs["provider_type"] = provider_type
83
- with self.session_maker() as session:
79
+ with db_registry.session() as session:
84
80
  providers = ProviderModel.list(
85
81
  db_session=session,
86
82
  after=after,
@@ -11,6 +11,7 @@ from letta.schemas.sandbox_config import LocalSandboxConfig
11
11
  from letta.schemas.sandbox_config import SandboxConfig as PydanticSandboxConfig
12
12
  from letta.schemas.sandbox_config import SandboxConfigCreate, SandboxConfigUpdate, SandboxType
13
13
  from letta.schemas.user import User as PydanticUser
14
+ from letta.server.db import db_registry
14
15
  from letta.utils import enforce_types, printd
15
16
 
16
17
  logger = get_logger(__name__)
@@ -19,11 +20,6 @@ logger = get_logger(__name__)
19
20
  class SandboxConfigManager:
20
21
  """Manager class to handle business logic related to SandboxConfig and SandboxEnvironmentVariable."""
21
22
 
22
- def __init__(self):
23
- from letta.server.db import db_context
24
-
25
- self.session_maker = db_context
26
-
27
23
  @enforce_types
28
24
  def get_or_create_default_sandbox_config(self, sandbox_type: SandboxType, actor: PydanticUser) -> PydanticSandboxConfig:
29
25
  sandbox_config = self.get_sandbox_config_by_type(sandbox_type, actor=actor)
@@ -69,7 +65,7 @@ class SandboxConfigManager:
69
65
  return db_sandbox
70
66
  else:
71
67
  # If the sandbox configuration doesn't exist, create a new one
72
- with self.session_maker() as session:
68
+ with db_registry.session() as session:
73
69
  db_sandbox = SandboxConfigModel(**sandbox_config.model_dump(exclude_none=True))
74
70
  db_sandbox.create(session, actor=actor)
75
71
  return db_sandbox.to_pydantic()
@@ -79,7 +75,7 @@ class SandboxConfigManager:
79
75
  self, sandbox_config_id: str, sandbox_update: SandboxConfigUpdate, actor: PydanticUser
80
76
  ) -> PydanticSandboxConfig:
81
77
  """Update an existing sandbox configuration."""
82
- with self.session_maker() as session:
78
+ with db_registry.session() as session:
83
79
  sandbox = SandboxConfigModel.read(db_session=session, identifier=sandbox_config_id, actor=actor)
84
80
  # We need to check that the sandbox_update provided is the same type as the original sandbox
85
81
  if sandbox.type != sandbox_update.config.type:
@@ -104,7 +100,7 @@ class SandboxConfigManager:
104
100
  @enforce_types
105
101
  def delete_sandbox_config(self, sandbox_config_id: str, actor: PydanticUser) -> PydanticSandboxConfig:
106
102
  """Delete a sandbox configuration by its ID."""
107
- with self.session_maker() as session:
103
+ with db_registry.session() as session:
108
104
  sandbox = SandboxConfigModel.read(db_session=session, identifier=sandbox_config_id, actor=actor)
109
105
  sandbox.hard_delete(db_session=session, actor=actor)
110
106
  return sandbox.to_pydantic()
@@ -122,14 +118,14 @@ class SandboxConfigManager:
122
118
  if sandbox_type:
123
119
  kwargs.update({"type": sandbox_type})
124
120
 
125
- with self.session_maker() as session:
121
+ with db_registry.session() as session:
126
122
  sandboxes = SandboxConfigModel.list(db_session=session, after=after, limit=limit, **kwargs)
127
123
  return [sandbox.to_pydantic() for sandbox in sandboxes]
128
124
 
129
125
  @enforce_types
130
126
  def get_sandbox_config_by_id(self, sandbox_config_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticSandboxConfig]:
131
127
  """Retrieve a sandbox configuration by its ID."""
132
- with self.session_maker() as session:
128
+ with db_registry.session() as session:
133
129
  try:
134
130
  sandbox = SandboxConfigModel.read(db_session=session, identifier=sandbox_config_id, actor=actor)
135
131
  return sandbox.to_pydantic()
@@ -139,7 +135,7 @@ class SandboxConfigManager:
139
135
  @enforce_types
140
136
  def get_sandbox_config_by_type(self, type: SandboxType, actor: Optional[PydanticUser] = None) -> Optional[PydanticSandboxConfig]:
141
137
  """Retrieve a sandbox config by its type."""
142
- with self.session_maker() as session:
138
+ with db_registry.session() as session:
143
139
  try:
144
140
  sandboxes = SandboxConfigModel.list(
145
141
  db_session=session,
@@ -175,7 +171,7 @@ class SandboxConfigManager:
175
171
 
176
172
  return db_env_var
177
173
  else:
178
- with self.session_maker() as session:
174
+ with db_registry.session() as session:
179
175
  env_var = SandboxEnvVarModel(**env_var.model_dump(to_orm=True, exclude_none=True))
180
176
  env_var.create(session, actor=actor)
181
177
  return env_var.to_pydantic()
@@ -185,7 +181,7 @@ class SandboxConfigManager:
185
181
  self, env_var_id: str, env_var_update: SandboxEnvironmentVariableUpdate, actor: PydanticUser
186
182
  ) -> PydanticEnvVar:
187
183
  """Update an existing sandbox environment variable."""
188
- with self.session_maker() as session:
184
+ with db_registry.session() as session:
189
185
  env_var = SandboxEnvVarModel.read(db_session=session, identifier=env_var_id, actor=actor)
190
186
  update_data = env_var_update.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
191
187
  update_data = {key: value for key, value in update_data.items() if getattr(env_var, key) != value}
@@ -204,7 +200,7 @@ class SandboxConfigManager:
204
200
  @enforce_types
205
201
  def delete_sandbox_env_var(self, env_var_id: str, actor: PydanticUser) -> PydanticEnvVar:
206
202
  """Delete a sandbox environment variable by its ID."""
207
- with self.session_maker() as session:
203
+ with db_registry.session() as session:
208
204
  env_var = SandboxEnvVarModel.read(db_session=session, identifier=env_var_id, actor=actor)
209
205
  env_var.hard_delete(db_session=session, actor=actor)
210
206
  return env_var.to_pydantic()
@@ -218,7 +214,7 @@ class SandboxConfigManager:
218
214
  limit: Optional[int] = 50,
219
215
  ) -> List[PydanticEnvVar]:
220
216
  """List all sandbox environment variables with optional pagination."""
221
- with self.session_maker() as session:
217
+ with db_registry.session() as session:
222
218
  env_vars = SandboxEnvVarModel.list(
223
219
  db_session=session,
224
220
  after=after,
@@ -233,7 +229,7 @@ class SandboxConfigManager:
233
229
  self, key: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
234
230
  ) -> List[PydanticEnvVar]:
235
231
  """List all sandbox environment variables with optional pagination."""
236
- with self.session_maker() as session:
232
+ with db_registry.session() as session:
237
233
  env_vars = SandboxEnvVarModel.list(
238
234
  db_session=session,
239
235
  after=after,
@@ -258,7 +254,7 @@ class SandboxConfigManager:
258
254
  self, key: str, sandbox_config_id: str, actor: Optional[PydanticUser] = None
259
255
  ) -> Optional[PydanticEnvVar]:
260
256
  """Retrieve a sandbox environment variable by its key and sandbox_config_id."""
261
- with self.session_maker() as session:
257
+ with db_registry.session() as session:
262
258
  try:
263
259
  env_var = SandboxEnvVarModel.list(
264
260
  db_session=session,
@@ -8,17 +8,13 @@ from letta.schemas.file import FileMetadata as PydanticFileMetadata
8
8
  from letta.schemas.source import Source as PydanticSource
9
9
  from letta.schemas.source import SourceUpdate
10
10
  from letta.schemas.user import User as PydanticUser
11
+ from letta.server.db import db_registry
11
12
  from letta.utils import enforce_types, printd
12
13
 
13
14
 
14
15
  class SourceManager:
15
16
  """Manager class to handle business logic related to Sources."""
16
17
 
17
- def __init__(self):
18
- from letta.server.db import db_context
19
-
20
- self.session_maker = db_context
21
-
22
18
  @enforce_types
23
19
  def create_source(self, source: PydanticSource, actor: PydanticUser) -> PydanticSource:
24
20
  """Create a new source based on the PydanticSource schema."""
@@ -27,7 +23,7 @@ class SourceManager:
27
23
  if db_source:
28
24
  return db_source
29
25
  else:
30
- with self.session_maker() as session:
26
+ with db_registry.session() as session:
31
27
  # Provide default embedding config if not given
32
28
  source.organization_id = actor.organization_id
33
29
  source = SourceModel(**source.model_dump(to_orm=True, exclude_none=True))
@@ -37,7 +33,7 @@ class SourceManager:
37
33
  @enforce_types
38
34
  def update_source(self, source_id: str, source_update: SourceUpdate, actor: PydanticUser) -> PydanticSource:
39
35
  """Update a source by its ID with the given SourceUpdate object."""
40
- with self.session_maker() as session:
36
+ with db_registry.session() as session:
41
37
  source = SourceModel.read(db_session=session, identifier=source_id, actor=actor)
42
38
 
43
39
  # get update dictionary
@@ -59,7 +55,7 @@ class SourceManager:
59
55
  @enforce_types
60
56
  def delete_source(self, source_id: str, actor: PydanticUser) -> PydanticSource:
61
57
  """Delete a source by its ID."""
62
- with self.session_maker() as session:
58
+ with db_registry.session() as session:
63
59
  source = SourceModel.read(db_session=session, identifier=source_id)
64
60
  source.hard_delete(db_session=session, actor=actor)
65
61
  return source.to_pydantic()
@@ -67,7 +63,7 @@ class SourceManager:
67
63
  @enforce_types
68
64
  def list_sources(self, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50, **kwargs) -> List[PydanticSource]:
69
65
  """List all sources with optional pagination."""
70
- with self.session_maker() as session:
66
+ with db_registry.session() as session:
71
67
  sources = SourceModel.list(
72
68
  db_session=session,
73
69
  after=after,
@@ -85,7 +81,7 @@ class SourceManager:
85
81
  """
86
82
  Get the total count of sources for the given user.
87
83
  """
88
- with self.session_maker() as session:
84
+ with db_registry.session() as session:
89
85
  return SourceModel.size(db_session=session, actor=actor)
90
86
 
91
87
  @enforce_types
@@ -100,7 +96,7 @@ class SourceManager:
100
96
  Returns:
101
97
  List[PydanticAgentState]: List of agents that have this source attached
102
98
  """
103
- with self.session_maker() as session:
99
+ with db_registry.session() as session:
104
100
  # Verify source exists and user has permission to access it
105
101
  source = SourceModel.read(db_session=session, identifier=source_id, actor=actor)
106
102
 
@@ -112,7 +108,7 @@ class SourceManager:
112
108
  @enforce_types
113
109
  def get_source_by_id(self, source_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticSource]:
114
110
  """Retrieve a source by its ID."""
115
- with self.session_maker() as session:
111
+ with db_registry.session() as session:
116
112
  try:
117
113
  source = SourceModel.read(db_session=session, identifier=source_id, actor=actor)
118
114
  return source.to_pydantic()
@@ -122,7 +118,7 @@ class SourceManager:
122
118
  @enforce_types
123
119
  def get_source_by_name(self, source_name: str, actor: PydanticUser) -> Optional[PydanticSource]:
124
120
  """Retrieve a source by its name."""
125
- with self.session_maker() as session:
121
+ with db_registry.session() as session:
126
122
  sources = SourceModel.list(
127
123
  db_session=session,
128
124
  name=source_name,
@@ -141,7 +137,7 @@ class SourceManager:
141
137
  if db_file:
142
138
  return db_file
143
139
  else:
144
- with self.session_maker() as session:
140
+ with db_registry.session() as session:
145
141
  file_metadata.organization_id = actor.organization_id
146
142
  file_metadata = FileMetadataModel(**file_metadata.model_dump(to_orm=True, exclude_none=True))
147
143
  file_metadata.create(session, actor=actor)
@@ -151,7 +147,7 @@ class SourceManager:
151
147
  @enforce_types
152
148
  def get_file_by_id(self, file_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticFileMetadata]:
153
149
  """Retrieve a file by its ID."""
154
- with self.session_maker() as session:
150
+ with db_registry.session() as session:
155
151
  try:
156
152
  file = FileMetadataModel.read(db_session=session, identifier=file_id, actor=actor)
157
153
  return file.to_pydantic()
@@ -163,7 +159,7 @@ class SourceManager:
163
159
  self, source_id: str, actor: PydanticUser, after: Optional[str] = None, limit: Optional[int] = 50
164
160
  ) -> List[PydanticFileMetadata]:
165
161
  """List all files with optional pagination."""
166
- with self.session_maker() as session:
162
+ with db_registry.session() as session:
167
163
  files = FileMetadataModel.list(
168
164
  db_session=session, after=after, limit=limit, organization_id=actor.organization_id, source_id=source_id
169
165
  )
@@ -172,7 +168,7 @@ class SourceManager:
172
168
  @enforce_types
173
169
  def delete_file(self, file_id: str, actor: PydanticUser) -> PydanticFileMetadata:
174
170
  """Delete a file by its ID."""
175
- with self.session_maker() as session:
171
+ with db_registry.session() as session:
176
172
  file = FileMetadataModel.read(db_session=session, identifier=file_id)
177
173
  file.hard_delete(db_session=session, actor=actor)
178
174
  return file.to_pydantic()
@@ -11,17 +11,13 @@ from letta.orm.step import Step as StepModel
11
11
  from letta.schemas.openai.chat_completion_response import UsageStatistics
12
12
  from letta.schemas.step import Step as PydanticStep
13
13
  from letta.schemas.user import User as PydanticUser
14
+ from letta.server.db import db_registry
14
15
  from letta.tracing import get_trace_id
15
16
  from letta.utils import enforce_types
16
17
 
17
18
 
18
19
  class StepManager:
19
20
 
20
- def __init__(self):
21
- from letta.server.db import db_context
22
-
23
- self.session_maker = db_context
24
-
25
21
  @enforce_types
26
22
  def list_steps(
27
23
  self,
@@ -36,7 +32,7 @@ class StepManager:
36
32
  agent_id: Optional[str] = None,
37
33
  ) -> List[PydanticStep]:
38
34
  """List all jobs with optional pagination and status filter."""
39
- with self.session_maker() as session:
35
+ with db_registry.session() as session:
40
36
  filter_kwargs = {"organization_id": actor.organization_id}
41
37
  if model:
42
38
  filter_kwargs["model"] = model
@@ -85,7 +81,7 @@ class StepManager:
85
81
  "tid": None,
86
82
  "trace_id": get_trace_id(), # Get the current trace ID
87
83
  }
88
- with self.session_maker() as session:
84
+ with db_registry.session() as session:
89
85
  if job_id:
90
86
  self._verify_job_access(session, job_id, actor, access=["write"])
91
87
  new_step = StepModel(**step_data)
@@ -94,7 +90,7 @@ class StepManager:
94
90
 
95
91
  @enforce_types
96
92
  def get_step(self, step_id: str, actor: PydanticUser) -> PydanticStep:
97
- with self.session_maker() as session:
93
+ with db_registry.session() as session:
98
94
  step = StepModel.read(db_session=session, identifier=step_id, actor=actor)
99
95
  return step.to_pydantic()
100
96
 
@@ -113,7 +109,7 @@ class StepManager:
113
109
  Raises:
114
110
  NoResultFound: If the step does not exist
115
111
  """
116
- with self.session_maker() as session:
112
+ with db_registry.session() as session:
117
113
  step = session.get(StepModel, step_id)
118
114
  if not step:
119
115
  raise NoResultFound(f"Step with id {step_id} does not exist")