letta-nightly 0.7.21.dev20250522104246__py3-none-any.whl → 0.7.22.dev20250523104244__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 (50) hide show
  1. letta/__init__.py +2 -2
  2. letta/agents/base_agent.py +4 -2
  3. letta/agents/letta_agent.py +3 -10
  4. letta/agents/letta_agent_batch.py +6 -6
  5. letta/cli/cli.py +0 -316
  6. letta/cli/cli_load.py +0 -52
  7. letta/client/client.py +2 -1554
  8. letta/data_sources/connectors.py +4 -2
  9. letta/functions/ast_parsers.py +33 -43
  10. letta/groups/sleeptime_multi_agent_v2.py +49 -13
  11. letta/jobs/llm_batch_job_polling.py +3 -3
  12. letta/jobs/scheduler.py +20 -19
  13. letta/llm_api/anthropic_client.py +3 -0
  14. letta/llm_api/google_vertex_client.py +5 -0
  15. letta/llm_api/openai_client.py +5 -0
  16. letta/main.py +2 -362
  17. letta/server/db.py +5 -0
  18. letta/server/rest_api/routers/v1/agents.py +72 -43
  19. letta/server/rest_api/routers/v1/llms.py +2 -2
  20. letta/server/rest_api/routers/v1/messages.py +5 -3
  21. letta/server/rest_api/routers/v1/sandbox_configs.py +18 -18
  22. letta/server/rest_api/routers/v1/sources.py +49 -36
  23. letta/server/server.py +53 -22
  24. letta/services/agent_manager.py +797 -124
  25. letta/services/block_manager.py +14 -62
  26. letta/services/group_manager.py +37 -0
  27. letta/services/identity_manager.py +9 -0
  28. letta/services/job_manager.py +17 -0
  29. letta/services/llm_batch_manager.py +88 -64
  30. letta/services/message_manager.py +19 -0
  31. letta/services/organization_manager.py +10 -0
  32. letta/services/passage_manager.py +13 -0
  33. letta/services/per_agent_lock_manager.py +4 -0
  34. letta/services/provider_manager.py +34 -0
  35. letta/services/sandbox_config_manager.py +130 -0
  36. letta/services/source_manager.py +59 -44
  37. letta/services/step_manager.py +8 -1
  38. letta/services/tool_manager.py +21 -0
  39. letta/services/tool_sandbox/e2b_sandbox.py +4 -2
  40. letta/services/tool_sandbox/local_sandbox.py +7 -3
  41. letta/services/user_manager.py +16 -0
  42. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/METADATA +1 -1
  43. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/RECORD +46 -50
  44. letta/__main__.py +0 -3
  45. letta/benchmark/benchmark.py +0 -98
  46. letta/benchmark/constants.py +0 -14
  47. letta/cli/cli_config.py +0 -227
  48. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/LICENSE +0 -0
  49. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/WHEEL +0 -0
  50. {letta_nightly-0.7.21.dev20250522104246.dist-info → letta_nightly-0.7.22.dev20250523104244.dist-info}/entry_points.txt +0 -0
@@ -14,6 +14,7 @@ from letta.schemas.block import Block as PydanticBlock
14
14
  from letta.schemas.block import BlockUpdate
15
15
  from letta.schemas.user import User as PydanticUser
16
16
  from letta.server.db import db_registry
17
+ from letta.tracing import trace_method
17
18
  from letta.utils import enforce_types
18
19
 
19
20
  logger = get_logger(__name__)
@@ -22,6 +23,7 @@ logger = get_logger(__name__)
22
23
  class BlockManager:
23
24
  """Manager class to handle business logic related to Blocks."""
24
25
 
26
+ @trace_method
25
27
  @enforce_types
26
28
  def create_or_update_block(self, block: PydanticBlock, actor: PydanticUser) -> PydanticBlock:
27
29
  """Create a new block based on the Block schema."""
@@ -36,6 +38,7 @@ class BlockManager:
36
38
  block.create(session, actor=actor)
37
39
  return block.to_pydantic()
38
40
 
41
+ @trace_method
39
42
  @enforce_types
40
43
  def batch_create_blocks(self, blocks: List[PydanticBlock], actor: PydanticUser) -> List[PydanticBlock]:
41
44
  """
@@ -59,6 +62,7 @@ class BlockManager:
59
62
  # Convert back to Pydantic
60
63
  return [m.to_pydantic() for m in created_models]
61
64
 
65
+ @trace_method
62
66
  @enforce_types
63
67
  def update_block(self, block_id: str, block_update: BlockUpdate, actor: PydanticUser) -> PydanticBlock:
64
68
  """Update a block by its ID with the given BlockUpdate object."""
@@ -74,6 +78,7 @@ class BlockManager:
74
78
  block.update(db_session=session, actor=actor)
75
79
  return block.to_pydantic()
76
80
 
81
+ @trace_method
77
82
  @enforce_types
78
83
  def delete_block(self, block_id: str, actor: PydanticUser) -> PydanticBlock:
79
84
  """Delete a block by its ID."""
@@ -82,6 +87,7 @@ class BlockManager:
82
87
  block.hard_delete(db_session=session, actor=actor)
83
88
  return block.to_pydantic()
84
89
 
90
+ @trace_method
85
91
  @enforce_types
86
92
  async def get_blocks_async(
87
93
  self,
@@ -144,68 +150,7 @@ class BlockManager:
144
150
 
145
151
  return [block.to_pydantic() for block in blocks]
146
152
 
147
- @enforce_types
148
- async def get_blocks_async(
149
- self,
150
- actor: PydanticUser,
151
- label: Optional[str] = None,
152
- is_template: Optional[bool] = None,
153
- template_name: Optional[str] = None,
154
- identity_id: Optional[str] = None,
155
- identifier_keys: Optional[List[str]] = None,
156
- limit: Optional[int] = 50,
157
- ) -> List[PydanticBlock]:
158
- """Async version of get_blocks method. Retrieve blocks based on various optional filters."""
159
- from sqlalchemy import select
160
- from sqlalchemy.orm import noload
161
-
162
- from letta.orm.sqlalchemy_base import AccessType
163
-
164
- async with db_registry.async_session() as session:
165
- # Start with a basic query
166
- query = select(BlockModel)
167
-
168
- # Explicitly avoid loading relationships
169
- query = query.options(noload(BlockModel.agents), noload(BlockModel.identities), noload(BlockModel.groups))
170
-
171
- # Apply access control
172
- query = BlockModel.apply_access_predicate(query, actor, ["read"], AccessType.ORGANIZATION)
173
-
174
- # Add filters
175
- query = query.where(BlockModel.organization_id == actor.organization_id)
176
- if label:
177
- query = query.where(BlockModel.label == label)
178
-
179
- if is_template is not None:
180
- query = query.where(BlockModel.is_template == is_template)
181
-
182
- if template_name:
183
- query = query.where(BlockModel.template_name == template_name)
184
-
185
- if identifier_keys:
186
- query = (
187
- query.join(BlockModel.identities)
188
- .filter(BlockModel.identities.property.mapper.class_.identifier_key.in_(identifier_keys))
189
- .distinct(BlockModel.id)
190
- )
191
-
192
- if identity_id:
193
- query = (
194
- query.join(BlockModel.identities)
195
- .filter(BlockModel.identities.property.mapper.class_.id == identity_id)
196
- .distinct(BlockModel.id)
197
- )
198
-
199
- # Add limit
200
- if limit:
201
- query = query.limit(limit)
202
-
203
- # Execute the query
204
- result = await session.execute(query)
205
- blocks = result.scalars().all()
206
-
207
- return [block.to_pydantic() for block in blocks]
208
-
153
+ @trace_method
209
154
  @enforce_types
210
155
  def get_block_by_id(self, block_id: str, actor: Optional[PydanticUser] = None) -> Optional[PydanticBlock]:
211
156
  """Retrieve a block by its name."""
@@ -216,6 +161,7 @@ class BlockManager:
216
161
  except NoResultFound:
217
162
  return None
218
163
 
164
+ @trace_method
219
165
  @enforce_types
220
166
  async def get_all_blocks_by_ids_async(self, block_ids: List[str], actor: Optional[PydanticUser] = None) -> List[PydanticBlock]:
221
167
  """Retrieve blocks by their ids without loading unnecessary relationships. Async implementation."""
@@ -263,6 +209,7 @@ class BlockManager:
263
209
 
264
210
  return pydantic_blocks
265
211
 
212
+ @trace_method
266
213
  @enforce_types
267
214
  async def get_agents_for_block_async(self, block_id: str, actor: PydanticUser) -> List[PydanticAgentState]:
268
215
  """
@@ -273,6 +220,7 @@ class BlockManager:
273
220
  agents_orm = block.agents
274
221
  return await asyncio.gather(*[agent.to_pydantic_async() for agent in agents_orm])
275
222
 
223
+ @trace_method
276
224
  @enforce_types
277
225
  def size(
278
226
  self,
@@ -286,6 +234,7 @@ class BlockManager:
286
234
 
287
235
  # Block History Functions
288
236
 
237
+ @trace_method
289
238
  @enforce_types
290
239
  def checkpoint_block(
291
240
  self,
@@ -389,6 +338,7 @@ class BlockManager:
389
338
  updated_block = block.update(db_session=session, actor=actor, no_commit=True)
390
339
  return updated_block
391
340
 
341
+ @trace_method
392
342
  @enforce_types
393
343
  def undo_checkpoint_block(self, block_id: str, actor: PydanticUser, use_preloaded_block: Optional[BlockModel] = None) -> PydanticBlock:
394
344
  """
@@ -431,6 +381,7 @@ class BlockManager:
431
381
  session.commit()
432
382
  return block.to_pydantic()
433
383
 
384
+ @trace_method
434
385
  @enforce_types
435
386
  def redo_checkpoint_block(self, block_id: str, actor: PydanticUser, use_preloaded_block: Optional[BlockModel] = None) -> PydanticBlock:
436
387
  """
@@ -469,6 +420,7 @@ class BlockManager:
469
420
  session.commit()
470
421
  return block.to_pydantic()
471
422
 
423
+ @trace_method
472
424
  @enforce_types
473
425
  async def bulk_update_block_values_async(
474
426
  self, updates: Dict[str, str], actor: PydanticUser, return_hydrated: bool = False
@@ -12,11 +12,13 @@ from letta.schemas.letta_message import LettaMessage
12
12
  from letta.schemas.message import Message as PydanticMessage
13
13
  from letta.schemas.user import User as PydanticUser
14
14
  from letta.server.db import db_registry
15
+ from letta.tracing import trace_method
15
16
  from letta.utils import enforce_types
16
17
 
17
18
 
18
19
  class GroupManager:
19
20
 
21
+ @trace_method
20
22
  @enforce_types
21
23
  def list_groups(
22
24
  self,
@@ -42,12 +44,14 @@ class GroupManager:
42
44
  )
43
45
  return [group.to_pydantic() for group in groups]
44
46
 
47
+ @trace_method
45
48
  @enforce_types
46
49
  def retrieve_group(self, group_id: str, actor: PydanticUser) -> PydanticGroup:
47
50
  with db_registry.session() as session:
48
51
  group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
49
52
  return group.to_pydantic()
50
53
 
54
+ @trace_method
51
55
  @enforce_types
52
56
  def create_group(self, group: GroupCreate, actor: PydanticUser) -> PydanticGroup:
53
57
  with db_registry.session() as session:
@@ -93,6 +97,7 @@ class GroupManager:
93
97
  new_group.create(session, actor=actor)
94
98
  return new_group.to_pydantic()
95
99
 
100
+ @trace_method
96
101
  @enforce_types
97
102
  def modify_group(self, group_id: str, group_update: GroupUpdate, actor: PydanticUser) -> PydanticGroup:
98
103
  with db_registry.session() as session:
@@ -155,6 +160,7 @@ class GroupManager:
155
160
  group.update(session, actor=actor)
156
161
  return group.to_pydantic()
157
162
 
163
+ @trace_method
158
164
  @enforce_types
159
165
  def delete_group(self, group_id: str, actor: PydanticUser) -> None:
160
166
  with db_registry.session() as session:
@@ -162,6 +168,7 @@ class GroupManager:
162
168
  group = GroupModel.read(db_session=session, identifier=group_id, actor=actor)
163
169
  group.hard_delete(session)
164
170
 
171
+ @trace_method
165
172
  @enforce_types
166
173
  def list_group_messages(
167
174
  self,
@@ -198,6 +205,7 @@ class GroupManager:
198
205
 
199
206
  return messages
200
207
 
208
+ @trace_method
201
209
  @enforce_types
202
210
  def reset_messages(self, group_id: str, actor: PydanticUser) -> None:
203
211
  with db_registry.session() as session:
@@ -211,6 +219,7 @@ class GroupManager:
211
219
 
212
220
  session.commit()
213
221
 
222
+ @trace_method
214
223
  @enforce_types
215
224
  def bump_turns_counter(self, group_id: str, actor: PydanticUser) -> int:
216
225
  with db_registry.session() as session:
@@ -222,6 +231,18 @@ class GroupManager:
222
231
  group.update(session, actor=actor)
223
232
  return group.turns_counter
224
233
 
234
+ @trace_method
235
+ @enforce_types
236
+ async def bump_turns_counter_async(self, group_id: str, actor: PydanticUser) -> int:
237
+ async with db_registry.async_session() as session:
238
+ # Ensure group is loadable by user
239
+ group = await GroupModel.read_async(session, identifier=group_id, actor=actor)
240
+
241
+ # Update turns counter
242
+ group.turns_counter = (group.turns_counter + 1) % group.sleeptime_agent_frequency
243
+ await group.update_async(session, actor=actor)
244
+ return group.turns_counter
245
+
225
246
  @enforce_types
226
247
  def get_last_processed_message_id_and_update(self, group_id: str, last_processed_message_id: str, actor: PydanticUser) -> str:
227
248
  with db_registry.session() as session:
@@ -235,6 +256,22 @@ class GroupManager:
235
256
 
236
257
  return prev_last_processed_message_id
237
258
 
259
+ @trace_method
260
+ @enforce_types
261
+ async def get_last_processed_message_id_and_update_async(
262
+ self, group_id: str, last_processed_message_id: str, actor: PydanticUser
263
+ ) -> str:
264
+ async with db_registry.async_session() as session:
265
+ # Ensure group is loadable by user
266
+ group = await GroupModel.read_async(session, identifier=group_id, actor=actor)
267
+
268
+ # Update last processed message id
269
+ prev_last_processed_message_id = group.last_processed_message_id
270
+ group.last_processed_message_id = last_processed_message_id
271
+ await group.update_async(session, actor=actor)
272
+
273
+ return prev_last_processed_message_id
274
+
238
275
  @enforce_types
239
276
  def size(
240
277
  self,
@@ -12,12 +12,14 @@ from letta.schemas.identity import Identity as PydanticIdentity
12
12
  from letta.schemas.identity import IdentityCreate, IdentityProperty, IdentityType, IdentityUpdate, IdentityUpsert
13
13
  from letta.schemas.user import User as PydanticUser
14
14
  from letta.server.db import db_registry
15
+ from letta.tracing import trace_method
15
16
  from letta.utils import enforce_types
16
17
 
17
18
 
18
19
  class IdentityManager:
19
20
 
20
21
  @enforce_types
22
+ @trace_method
21
23
  async def list_identities_async(
22
24
  self,
23
25
  name: Optional[str] = None,
@@ -48,12 +50,14 @@ class IdentityManager:
48
50
  return [identity.to_pydantic() for identity in identities]
49
51
 
50
52
  @enforce_types
53
+ @trace_method
51
54
  async def get_identity_async(self, identity_id: str, actor: PydanticUser) -> PydanticIdentity:
52
55
  async with db_registry.async_session() as session:
53
56
  identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
54
57
  return identity.to_pydantic()
55
58
 
56
59
  @enforce_types
60
+ @trace_method
57
61
  async def create_identity_async(self, identity: IdentityCreate, actor: PydanticUser) -> PydanticIdentity:
58
62
  async with db_registry.async_session() as session:
59
63
  new_identity = IdentityModel(**identity.model_dump(exclude={"agent_ids", "block_ids"}, exclude_unset=True))
@@ -78,6 +82,7 @@ class IdentityManager:
78
82
  return new_identity.to_pydantic()
79
83
 
80
84
  @enforce_types
85
+ @trace_method
81
86
  async def upsert_identity_async(self, identity: IdentityUpsert, actor: PydanticUser) -> PydanticIdentity:
82
87
  async with db_registry.async_session() as session:
83
88
  existing_identity = await IdentityModel.read_async(
@@ -103,6 +108,7 @@ class IdentityManager:
103
108
  )
104
109
 
105
110
  @enforce_types
111
+ @trace_method
106
112
  async def update_identity_async(
107
113
  self, identity_id: str, identity: IdentityUpdate, actor: PydanticUser, replace: bool = False
108
114
  ) -> PydanticIdentity:
@@ -165,6 +171,7 @@ class IdentityManager:
165
171
  return existing_identity.to_pydantic()
166
172
 
167
173
  @enforce_types
174
+ @trace_method
168
175
  async def upsert_identity_properties_async(
169
176
  self, identity_id: str, properties: List[IdentityProperty], actor: PydanticUser
170
177
  ) -> PydanticIdentity:
@@ -181,6 +188,7 @@ class IdentityManager:
181
188
  )
182
189
 
183
190
  @enforce_types
191
+ @trace_method
184
192
  async def delete_identity_async(self, identity_id: str, actor: PydanticUser) -> None:
185
193
  async with db_registry.async_session() as session:
186
194
  identity = await IdentityModel.read_async(db_session=session, identifier=identity_id, actor=actor)
@@ -192,6 +200,7 @@ class IdentityManager:
192
200
  await session.commit()
193
201
 
194
202
  @enforce_types
203
+ @trace_method
195
204
  async def size_async(
196
205
  self,
197
206
  actor: PydanticUser,
@@ -25,6 +25,7 @@ from letta.schemas.step import Step as PydanticStep
25
25
  from letta.schemas.usage import LettaUsageStatistics
26
26
  from letta.schemas.user import User as PydanticUser
27
27
  from letta.server.db import db_registry
28
+ from letta.tracing import trace_method
28
29
  from letta.utils import enforce_types
29
30
 
30
31
 
@@ -32,6 +33,7 @@ class JobManager:
32
33
  """Manager class to handle business logic related to Jobs."""
33
34
 
34
35
  @enforce_types
36
+ @trace_method
35
37
  def create_job(
36
38
  self, pydantic_job: Union[PydanticJob, PydanticRun, PydanticBatchJob], actor: PydanticUser
37
39
  ) -> Union[PydanticJob, PydanticRun, PydanticBatchJob]:
@@ -45,6 +47,7 @@ class JobManager:
45
47
  return job.to_pydantic()
46
48
 
47
49
  @enforce_types
50
+ @trace_method
48
51
  async def create_job_async(
49
52
  self, pydantic_job: Union[PydanticJob, PydanticRun, PydanticBatchJob], actor: PydanticUser
50
53
  ) -> Union[PydanticJob, PydanticRun, PydanticBatchJob]:
@@ -58,6 +61,7 @@ class JobManager:
58
61
  return job.to_pydantic()
59
62
 
60
63
  @enforce_types
64
+ @trace_method
61
65
  def update_job_by_id(self, job_id: str, job_update: JobUpdate, actor: PydanticUser) -> PydanticJob:
62
66
  """Update a job by its ID with the given JobUpdate object."""
63
67
  with db_registry.session() as session:
@@ -82,6 +86,7 @@ class JobManager:
82
86
  return job.to_pydantic()
83
87
 
84
88
  @enforce_types
89
+ @trace_method
85
90
  async def update_job_by_id_async(self, job_id: str, job_update: JobUpdate, actor: PydanticUser) -> PydanticJob:
86
91
  """Update a job by its ID with the given JobUpdate object asynchronously."""
87
92
  async with db_registry.async_session() as session:
@@ -106,6 +111,7 @@ class JobManager:
106
111
  return job.to_pydantic()
107
112
 
108
113
  @enforce_types
114
+ @trace_method
109
115
  def get_job_by_id(self, job_id: str, actor: PydanticUser) -> PydanticJob:
110
116
  """Fetch a job by its ID."""
111
117
  with db_registry.session() as session:
@@ -114,6 +120,7 @@ class JobManager:
114
120
  return job.to_pydantic()
115
121
 
116
122
  @enforce_types
123
+ @trace_method
117
124
  async def get_job_by_id_async(self, job_id: str, actor: PydanticUser) -> PydanticJob:
118
125
  """Fetch a job by its ID asynchronously."""
119
126
  async with db_registry.async_session() as session:
@@ -122,6 +129,7 @@ class JobManager:
122
129
  return job.to_pydantic()
123
130
 
124
131
  @enforce_types
132
+ @trace_method
125
133
  def list_jobs(
126
134
  self,
127
135
  actor: PydanticUser,
@@ -151,6 +159,7 @@ class JobManager:
151
159
  return [job.to_pydantic() for job in jobs]
152
160
 
153
161
  @enforce_types
162
+ @trace_method
154
163
  async def list_jobs_async(
155
164
  self,
156
165
  actor: PydanticUser,
@@ -180,6 +189,7 @@ class JobManager:
180
189
  return [job.to_pydantic() for job in jobs]
181
190
 
182
191
  @enforce_types
192
+ @trace_method
183
193
  def delete_job_by_id(self, job_id: str, actor: PydanticUser) -> PydanticJob:
184
194
  """Delete a job by its ID."""
185
195
  with db_registry.session() as session:
@@ -188,6 +198,7 @@ class JobManager:
188
198
  return job.to_pydantic()
189
199
 
190
200
  @enforce_types
201
+ @trace_method
191
202
  def get_job_messages(
192
203
  self,
193
204
  job_id: str,
@@ -238,6 +249,7 @@ class JobManager:
238
249
  return [message.to_pydantic() for message in messages]
239
250
 
240
251
  @enforce_types
252
+ @trace_method
241
253
  def get_job_steps(
242
254
  self,
243
255
  job_id: str,
@@ -283,6 +295,7 @@ class JobManager:
283
295
  return [step.to_pydantic() for step in steps]
284
296
 
285
297
  @enforce_types
298
+ @trace_method
286
299
  def add_message_to_job(self, job_id: str, message_id: str, actor: PydanticUser) -> None:
287
300
  """
288
301
  Associate a message with a job by creating a JobMessage record.
@@ -306,6 +319,7 @@ class JobManager:
306
319
  session.commit()
307
320
 
308
321
  @enforce_types
322
+ @trace_method
309
323
  def get_job_usage(self, job_id: str, actor: PydanticUser) -> LettaUsageStatistics:
310
324
  """
311
325
  Get usage statistics for a job.
@@ -343,6 +357,7 @@ class JobManager:
343
357
  )
344
358
 
345
359
  @enforce_types
360
+ @trace_method
346
361
  def add_job_usage(
347
362
  self,
348
363
  job_id: str,
@@ -383,6 +398,7 @@ class JobManager:
383
398
  session.commit()
384
399
 
385
400
  @enforce_types
401
+ @trace_method
386
402
  def get_run_messages(
387
403
  self,
388
404
  run_id: str,
@@ -434,6 +450,7 @@ class JobManager:
434
450
  return messages
435
451
 
436
452
  @enforce_types
453
+ @trace_method
437
454
  def get_step_messages(
438
455
  self,
439
456
  run_id: str,