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
@@ -17,6 +17,7 @@ from letta.schemas.llm_config import LLMConfig
17
17
  from letta.schemas.message import Message as PydanticMessage
18
18
  from letta.schemas.user import User as PydanticUser
19
19
  from letta.server.db import db_registry
20
+ from letta.tracing import trace_method
20
21
  from letta.utils import enforce_types
21
22
 
22
23
  logger = get_logger(__name__)
@@ -26,6 +27,7 @@ class LLMBatchManager:
26
27
  """Manager for handling both LLMBatchJob and LLMBatchItem operations."""
27
28
 
28
29
  @enforce_types
30
+ @trace_method
29
31
  async def create_llm_batch_job_async(
30
32
  self,
31
33
  llm_provider: ProviderType,
@@ -47,6 +49,7 @@ class LLMBatchManager:
47
49
  return batch.to_pydantic()
48
50
 
49
51
  @enforce_types
52
+ @trace_method
50
53
  async def get_llm_batch_job_by_id_async(self, llm_batch_id: str, actor: Optional[PydanticUser] = None) -> PydanticLLMBatchJob:
51
54
  """Retrieve a single batch job by ID."""
52
55
  async with db_registry.async_session() as session:
@@ -54,7 +57,8 @@ class LLMBatchManager:
54
57
  return batch.to_pydantic()
55
58
 
56
59
  @enforce_types
57
- def update_llm_batch_status(
60
+ @trace_method
61
+ async def update_llm_batch_status_async(
58
62
  self,
59
63
  llm_batch_id: str,
60
64
  status: JobStatus,
@@ -62,15 +66,15 @@ class LLMBatchManager:
62
66
  latest_polling_response: Optional[BetaMessageBatch] = None,
63
67
  ) -> PydanticLLMBatchJob:
64
68
  """Update a batch job’s status and optionally its polling response."""
65
- with db_registry.session() as session:
66
- batch = LLMBatchJob.read(db_session=session, identifier=llm_batch_id, actor=actor)
69
+ async with db_registry.async_session() as session:
70
+ batch = await LLMBatchJob.read_async(db_session=session, identifier=llm_batch_id, actor=actor)
67
71
  batch.status = status
68
72
  batch.latest_polling_response = latest_polling_response
69
73
  batch.last_polled_at = datetime.datetime.now(datetime.timezone.utc)
70
- batch = batch.update(db_session=session, actor=actor)
74
+ batch = await batch.update_async(db_session=session, actor=actor)
71
75
  return batch.to_pydantic()
72
76
 
73
- def bulk_update_llm_batch_statuses(
77
+ async def bulk_update_llm_batch_statuses_async(
74
78
  self,
75
79
  updates: List[BatchPollingResult],
76
80
  ) -> None:
@@ -81,7 +85,7 @@ class LLMBatchManager:
81
85
  """
82
86
  now = datetime.datetime.now(datetime.timezone.utc)
83
87
 
84
- with db_registry.session() as session:
88
+ async with db_registry.async_session() as session:
85
89
  mappings = []
86
90
  for llm_batch_id, status, response in updates:
87
91
  mappings.append(
@@ -93,17 +97,18 @@ class LLMBatchManager:
93
97
  }
94
98
  )
95
99
 
96
- session.bulk_update_mappings(LLMBatchJob, mappings)
97
- session.commit()
100
+ await session.run_sync(lambda ses: ses.bulk_update_mappings(LLMBatchJob, mappings))
101
+ await session.commit()
98
102
 
99
103
  @enforce_types
100
- def list_llm_batch_jobs(
104
+ @trace_method
105
+ async def list_llm_batch_jobs_async(
101
106
  self,
102
107
  letta_batch_id: str,
103
108
  limit: Optional[int] = None,
104
109
  actor: Optional[PydanticUser] = None,
105
110
  after: Optional[str] = None,
106
- ) -> List[PydanticLLMBatchItem]:
111
+ ) -> List[PydanticLLMBatchJob]:
107
112
  """
108
113
  List all batch items for a given llm_batch_id, optionally filtered by additional criteria and limited in count.
109
114
 
@@ -115,33 +120,35 @@ class LLMBatchManager:
115
120
 
116
121
  The results are ordered by their id in ascending order.
117
122
  """
118
- with db_registry.session() as session:
119
- query = session.query(LLMBatchJob).filter(LLMBatchJob.letta_batch_job_id == letta_batch_id)
123
+ async with db_registry.async_session() as session:
124
+ query = select(LLMBatchJob).where(LLMBatchJob.letta_batch_job_id == letta_batch_id)
120
125
 
121
126
  if actor is not None:
122
- query = query.filter(LLMBatchJob.organization_id == actor.organization_id)
127
+ query = query.where(LLMBatchJob.organization_id == actor.organization_id)
123
128
 
124
129
  # Additional optional filters
125
130
  if after is not None:
126
- query = query.filter(LLMBatchJob.id > after)
131
+ query = query.where(LLMBatchJob.id > after)
127
132
 
128
133
  query = query.order_by(LLMBatchJob.id.asc())
129
134
 
130
135
  if limit is not None:
131
136
  query = query.limit(limit)
132
137
 
133
- results = query.all()
134
- return [item.to_pydantic() for item in results]
138
+ results = await session.execute(query)
139
+ return [item.to_pydantic() for item in results.scalars().all()]
135
140
 
136
141
  @enforce_types
137
- def delete_llm_batch_request(self, llm_batch_id: str, actor: PydanticUser) -> None:
142
+ @trace_method
143
+ async def delete_llm_batch_request_async(self, llm_batch_id: str, actor: PydanticUser) -> None:
138
144
  """Hard delete a batch job by ID."""
139
- with db_registry.session() as session:
140
- batch = LLMBatchJob.read(db_session=session, identifier=llm_batch_id, actor=actor)
141
- batch.hard_delete(db_session=session, actor=actor)
145
+ async with db_registry.async_session() as session:
146
+ batch = await LLMBatchJob.read_async(db_session=session, identifier=llm_batch_id, actor=actor)
147
+ await batch.hard_delete_async(db_session=session, actor=actor)
142
148
 
143
149
  @enforce_types
144
- def get_messages_for_letta_batch(
150
+ @trace_method
151
+ async def get_messages_for_letta_batch_async(
145
152
  self,
146
153
  letta_batch_job_id: str,
147
154
  limit: int = 100,
@@ -154,12 +161,12 @@ class LLMBatchManager:
154
161
  Retrieve messages across all LLM batch jobs associated with a Letta batch job.
155
162
  Optimized for PostgreSQL performance using ID-based keyset pagination.
156
163
  """
157
- with db_registry.session() as session:
164
+ async with db_registry.async_session() as session:
158
165
  # If cursor is provided, get sequence_id for that message
159
166
  cursor_sequence_id = None
160
167
  if cursor:
161
- cursor_query = session.query(MessageModel.sequence_id).filter(MessageModel.id == cursor).limit(1)
162
- cursor_result = cursor_query.first()
168
+ cursor_query = select(MessageModel.sequence_id).where(MessageModel.id == cursor).limit(1)
169
+ cursor_result = await session.execute(cursor_query)
163
170
  if cursor_result:
164
171
  cursor_sequence_id = cursor_result[0]
165
172
  else:
@@ -167,24 +174,24 @@ class LLMBatchManager:
167
174
  pass
168
175
 
169
176
  query = (
170
- session.query(MessageModel)
177
+ select(MessageModel)
171
178
  .join(LLMBatchItem, MessageModel.batch_item_id == LLMBatchItem.id)
172
179
  .join(LLMBatchJob, LLMBatchItem.llm_batch_id == LLMBatchJob.id)
173
- .filter(LLMBatchJob.letta_batch_job_id == letta_batch_job_id)
180
+ .where(LLMBatchJob.letta_batch_job_id == letta_batch_job_id)
174
181
  )
175
182
 
176
183
  if actor is not None:
177
- query = query.filter(MessageModel.organization_id == actor.organization_id)
184
+ query = query.where(MessageModel.organization_id == actor.organization_id)
178
185
 
179
186
  if agent_id is not None:
180
- query = query.filter(MessageModel.agent_id == agent_id)
187
+ query = query.where(MessageModel.agent_id == agent_id)
181
188
 
182
189
  # Apply cursor-based pagination if cursor exists
183
190
  if cursor_sequence_id is not None:
184
191
  if sort_descending:
185
- query = query.filter(MessageModel.sequence_id < cursor_sequence_id)
192
+ query = query.where(MessageModel.sequence_id < cursor_sequence_id)
186
193
  else:
187
- query = query.filter(MessageModel.sequence_id > cursor_sequence_id)
194
+ query = query.where(MessageModel.sequence_id > cursor_sequence_id)
188
195
 
189
196
  if sort_descending:
190
197
  query = query.order_by(desc(MessageModel.sequence_id))
@@ -193,10 +200,11 @@ class LLMBatchManager:
193
200
 
194
201
  query = query.limit(limit)
195
202
 
196
- results = query.all()
197
- return [message.to_pydantic() for message in results]
203
+ results = await session.execute(query)
204
+ return [message.to_pydantic() for message in results.scalars().all()]
198
205
 
199
206
  @enforce_types
207
+ @trace_method
200
208
  async def list_running_llm_batches_async(self, actor: Optional[PydanticUser] = None) -> List[PydanticLLMBatchJob]:
201
209
  """Return all running LLM batch jobs, optionally filtered by actor's organization."""
202
210
  async with db_registry.async_session() as session:
@@ -209,7 +217,8 @@ class LLMBatchManager:
209
217
  return [batch.to_pydantic() for batch in results.scalars().all()]
210
218
 
211
219
  @enforce_types
212
- def create_llm_batch_item(
220
+ @trace_method
221
+ async def create_llm_batch_item_async(
213
222
  self,
214
223
  llm_batch_id: str,
215
224
  agent_id: str,
@@ -220,7 +229,7 @@ class LLMBatchManager:
220
229
  step_state: Optional[AgentStepState] = None,
221
230
  ) -> PydanticLLMBatchItem:
222
231
  """Create a new batch item."""
223
- with db_registry.session() as session:
232
+ async with db_registry.async_session() as session:
224
233
  item = LLMBatchItem(
225
234
  llm_batch_id=llm_batch_id,
226
235
  agent_id=agent_id,
@@ -230,10 +239,11 @@ class LLMBatchManager:
230
239
  step_state=step_state,
231
240
  organization_id=actor.organization_id,
232
241
  )
233
- item.create(session, actor=actor)
242
+ await item.create_async(session, actor=actor)
234
243
  return item.to_pydantic()
235
244
 
236
245
  @enforce_types
246
+ @trace_method
237
247
  async def create_llm_batch_items_bulk_async(
238
248
  self, llm_batch_items: List[PydanticLLMBatchItem], actor: PydanticUser
239
249
  ) -> List[PydanticLLMBatchItem]:
@@ -269,14 +279,16 @@ class LLMBatchManager:
269
279
  return [item.to_pydantic() for item in created_items]
270
280
 
271
281
  @enforce_types
272
- def get_llm_batch_item_by_id(self, item_id: str, actor: PydanticUser) -> PydanticLLMBatchItem:
282
+ @trace_method
283
+ async def get_llm_batch_item_by_id_async(self, item_id: str, actor: PydanticUser) -> PydanticLLMBatchItem:
273
284
  """Retrieve a single batch item by ID."""
274
- with db_registry.session() as session:
275
- item = LLMBatchItem.read(db_session=session, identifier=item_id, actor=actor)
285
+ async with db_registry.async_session() as session:
286
+ item = await LLMBatchItem.read_async(db_session=session, identifier=item_id, actor=actor)
276
287
  return item.to_pydantic()
277
288
 
278
289
  @enforce_types
279
- def update_llm_batch_item(
290
+ @trace_method
291
+ async def update_llm_batch_item_async(
280
292
  self,
281
293
  item_id: str,
282
294
  actor: PydanticUser,
@@ -286,8 +298,8 @@ class LLMBatchManager:
286
298
  step_state: Optional[AgentStepState] = None,
287
299
  ) -> PydanticLLMBatchItem:
288
300
  """Update fields on a batch item."""
289
- with db_registry.session() as session:
290
- item = LLMBatchItem.read(db_session=session, identifier=item_id, actor=actor)
301
+ async with db_registry.async_session() as session:
302
+ item = await LLMBatchItem.read_async(db_session=session, identifier=item_id, actor=actor)
291
303
 
292
304
  if request_status:
293
305
  item.request_status = request_status
@@ -298,9 +310,11 @@ class LLMBatchManager:
298
310
  if step_state:
299
311
  item.step_state = step_state
300
312
 
301
- return item.update(db_session=session, actor=actor).to_pydantic()
313
+ result = await item.update_async(db_session=session, actor=actor)
314
+ return result.to_pydantic()
302
315
 
303
316
  @enforce_types
317
+ @trace_method
304
318
  async def list_llm_batch_items_async(
305
319
  self,
306
320
  llm_batch_id: str,
@@ -346,7 +360,8 @@ class LLMBatchManager:
346
360
  results = await session.execute(query)
347
361
  return [item.to_pydantic() for item in results.scalars()]
348
362
 
349
- def bulk_update_llm_batch_items(
363
+ @trace_method
364
+ async def bulk_update_llm_batch_items_async(
350
365
  self, llm_batch_id_agent_id_pairs: List[Tuple[str, str]], field_updates: List[Dict[str, Any]], strict: bool = True
351
366
  ) -> None:
352
367
  """
@@ -364,13 +379,13 @@ class LLMBatchManager:
364
379
  if len(llm_batch_id_agent_id_pairs) != len(field_updates):
365
380
  raise ValueError("llm_batch_id_agent_id_pairs and field_updates must have the same length")
366
381
 
367
- with db_registry.session() as session:
382
+ async with db_registry.async_session() as session:
368
383
  # Lookup primary keys for all requested (batch_id, agent_id) pairs
369
- items = (
370
- session.query(LLMBatchItem.id, LLMBatchItem.llm_batch_id, LLMBatchItem.agent_id)
371
- .filter(tuple_(LLMBatchItem.llm_batch_id, LLMBatchItem.agent_id).in_(llm_batch_id_agent_id_pairs))
372
- .all()
384
+ query = select(LLMBatchItem.id, LLMBatchItem.llm_batch_id, LLMBatchItem.agent_id).filter(
385
+ tuple_(LLMBatchItem.llm_batch_id, LLMBatchItem.agent_id).in_(llm_batch_id_agent_id_pairs)
373
386
  )
387
+ result = await session.execute(query)
388
+ items = result.all()
374
389
  pair_to_pk = {(batch_id, agent_id): pk for pk, batch_id, agent_id in items}
375
390
 
376
391
  if strict:
@@ -395,11 +410,12 @@ class LLMBatchManager:
395
410
  mappings.append(update_fields)
396
411
 
397
412
  if mappings:
398
- session.bulk_update_mappings(LLMBatchItem, mappings)
399
- session.commit()
413
+ await session.run_sync(lambda ses: ses.bulk_update_mappings(LLMBatchItem, mappings))
414
+ await session.commit()
400
415
 
401
416
  @enforce_types
402
- def bulk_update_batch_llm_items_results_by_agent(self, updates: List[ItemUpdateInfo], strict: bool = True) -> None:
417
+ @trace_method
418
+ async def bulk_update_batch_llm_items_results_by_agent_async(self, updates: List[ItemUpdateInfo], strict: bool = True) -> None:
403
419
  """Update request status and batch results for multiple batch items."""
404
420
  batch_id_agent_id_pairs = [(update.llm_batch_id, update.agent_id) for update in updates]
405
421
  field_updates = [
@@ -410,33 +426,41 @@ class LLMBatchManager:
410
426
  for update in updates
411
427
  ]
412
428
 
413
- self.bulk_update_llm_batch_items(batch_id_agent_id_pairs, field_updates, strict=strict)
429
+ await self.bulk_update_llm_batch_items_async(batch_id_agent_id_pairs, field_updates, strict=strict)
414
430
 
415
431
  @enforce_types
416
- def bulk_update_llm_batch_items_step_status_by_agent(self, updates: List[StepStatusUpdateInfo], strict: bool = True) -> None:
432
+ @trace_method
433
+ async def bulk_update_llm_batch_items_step_status_by_agent_async(
434
+ self, updates: List[StepStatusUpdateInfo], strict: bool = True
435
+ ) -> None:
417
436
  """Update step status for multiple batch items."""
418
437
  batch_id_agent_id_pairs = [(update.llm_batch_id, update.agent_id) for update in updates]
419
438
  field_updates = [{"step_status": update.step_status} for update in updates]
420
439
 
421
- self.bulk_update_llm_batch_items(batch_id_agent_id_pairs, field_updates, strict=strict)
440
+ await self.bulk_update_llm_batch_items_async(batch_id_agent_id_pairs, field_updates, strict=strict)
422
441
 
423
442
  @enforce_types
424
- def bulk_update_llm_batch_items_request_status_by_agent(self, updates: List[RequestStatusUpdateInfo], strict: bool = True) -> None:
443
+ @trace_method
444
+ async def bulk_update_llm_batch_items_request_status_by_agent_async(
445
+ self, updates: List[RequestStatusUpdateInfo], strict: bool = True
446
+ ) -> None:
425
447
  """Update request status for multiple batch items."""
426
448
  batch_id_agent_id_pairs = [(update.llm_batch_id, update.agent_id) for update in updates]
427
449
  field_updates = [{"request_status": update.request_status} for update in updates]
428
450
 
429
- self.bulk_update_llm_batch_items(batch_id_agent_id_pairs, field_updates, strict=strict)
451
+ await self.bulk_update_llm_batch_items_async(batch_id_agent_id_pairs, field_updates, strict=strict)
430
452
 
431
453
  @enforce_types
432
- def delete_llm_batch_item(self, item_id: str, actor: PydanticUser) -> None:
454
+ @trace_method
455
+ async def delete_llm_batch_item_async(self, item_id: str, actor: PydanticUser) -> None:
433
456
  """Hard delete a batch item by ID."""
434
- with db_registry.session() as session:
435
- item = LLMBatchItem.read(db_session=session, identifier=item_id, actor=actor)
436
- item.hard_delete(db_session=session, actor=actor)
457
+ async with db_registry.async_session() as session:
458
+ item = await LLMBatchItem.read_async(db_session=session, identifier=item_id, actor=actor)
459
+ await item.hard_delete_async(db_session=session, actor=actor)
437
460
 
438
461
  @enforce_types
439
- def count_llm_batch_items(self, llm_batch_id: str) -> int:
462
+ @trace_method
463
+ async def count_llm_batch_items_async(self, llm_batch_id: str) -> int:
440
464
  """
441
465
  Efficiently count the number of batch items for a given llm_batch_id.
442
466
 
@@ -446,6 +470,6 @@ class LLMBatchManager:
446
470
  Returns:
447
471
  int: The total number of batch items associated with the given llm_batch_id.
448
472
  """
449
- with db_registry.session() as session:
450
- count = session.query(func.count(LLMBatchItem.id)).filter(LLMBatchItem.llm_batch_id == llm_batch_id).scalar()
451
- return count or 0
473
+ async with db_registry.async_session() as session:
474
+ count = await session.execute(select(func.count(LLMBatchItem.id)).where(LLMBatchItem.llm_batch_id == llm_batch_id))
475
+ return count.scalar() or 0
@@ -13,6 +13,7 @@ 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
15
  from letta.server.db import db_registry
16
+ from letta.tracing import trace_method
16
17
  from letta.utils import enforce_types
17
18
 
18
19
  logger = get_logger(__name__)
@@ -22,6 +23,7 @@ class MessageManager:
22
23
  """Manager class to handle business logic related to Messages."""
23
24
 
24
25
  @enforce_types
26
+ @trace_method
25
27
  def get_message_by_id(self, message_id: str, actor: PydanticUser) -> Optional[PydanticMessage]:
26
28
  """Fetch a message by ID."""
27
29
  with db_registry.session() as session:
@@ -32,6 +34,7 @@ class MessageManager:
32
34
  return None
33
35
 
34
36
  @enforce_types
37
+ @trace_method
35
38
  async def get_message_by_id_async(self, message_id: str, actor: PydanticUser) -> Optional[PydanticMessage]:
36
39
  """Fetch a message by ID."""
37
40
  async with db_registry.async_session() as session:
@@ -42,6 +45,7 @@ class MessageManager:
42
45
  return None
43
46
 
44
47
  @enforce_types
48
+ @trace_method
45
49
  def get_messages_by_ids(self, message_ids: List[str], actor: PydanticUser) -> List[PydanticMessage]:
46
50
  """Fetch messages by ID and return them in the requested order."""
47
51
  with db_registry.session() as session:
@@ -49,6 +53,7 @@ class MessageManager:
49
53
  return self._get_messages_by_id_postprocess(results, message_ids)
50
54
 
51
55
  @enforce_types
56
+ @trace_method
52
57
  async def get_messages_by_ids_async(self, message_ids: List[str], actor: PydanticUser) -> List[PydanticMessage]:
53
58
  """Fetch messages by ID and return them in the requested order. Async version of above function."""
54
59
  async with db_registry.async_session() as session:
@@ -71,6 +76,7 @@ class MessageManager:
71
76
  return list(filter(lambda x: x is not None, [result_dict.get(msg_id, None) for msg_id in message_ids]))
72
77
 
73
78
  @enforce_types
79
+ @trace_method
74
80
  def create_message(self, pydantic_msg: PydanticMessage, actor: PydanticUser) -> PydanticMessage:
75
81
  """Create a new message."""
76
82
  with db_registry.session() as session:
@@ -92,6 +98,7 @@ class MessageManager:
92
98
  return orm_messages
93
99
 
94
100
  @enforce_types
101
+ @trace_method
95
102
  def create_many_messages(self, pydantic_msgs: List[PydanticMessage], actor: PydanticUser) -> List[PydanticMessage]:
96
103
  """
97
104
  Create multiple messages in a single database transaction.
@@ -111,6 +118,7 @@ class MessageManager:
111
118
  return [msg.to_pydantic() for msg in created_messages]
112
119
 
113
120
  @enforce_types
121
+ @trace_method
114
122
  async def create_many_messages_async(self, pydantic_msgs: List[PydanticMessage], actor: PydanticUser) -> List[PydanticMessage]:
115
123
  """
116
124
  Create multiple messages in a single database transaction asynchronously.
@@ -131,6 +139,7 @@ class MessageManager:
131
139
  return [msg.to_pydantic() for msg in created_messages]
132
140
 
133
141
  @enforce_types
142
+ @trace_method
134
143
  def update_message_by_letta_message(
135
144
  self, message_id: str, letta_message_update: LettaMessageUpdateUnion, actor: PydanticUser
136
145
  ) -> PydanticMessage:
@@ -169,6 +178,7 @@ class MessageManager:
169
178
  raise ValueError(f"Message type got modified: {letta_message_update.message_type}")
170
179
 
171
180
  @enforce_types
181
+ @trace_method
172
182
  def update_message_by_letta_message(
173
183
  self, message_id: str, letta_message_update: LettaMessageUpdateUnion, actor: PydanticUser
174
184
  ) -> PydanticMessage:
@@ -207,6 +217,7 @@ class MessageManager:
207
217
  raise ValueError(f"Message type got modified: {letta_message_update.message_type}")
208
218
 
209
219
  @enforce_types
220
+ @trace_method
210
221
  def update_message_by_id(self, message_id: str, message_update: MessageUpdate, actor: PydanticUser) -> PydanticMessage:
211
222
  """
212
223
  Updates an existing record in the database with values from the provided record object.
@@ -224,6 +235,7 @@ class MessageManager:
224
235
  return message.to_pydantic()
225
236
 
226
237
  @enforce_types
238
+ @trace_method
227
239
  async def update_message_by_id_async(self, message_id: str, message_update: MessageUpdate, actor: PydanticUser) -> PydanticMessage:
228
240
  """
229
241
  Updates an existing record in the database with values from the provided record object.
@@ -267,6 +279,7 @@ class MessageManager:
267
279
  return message
268
280
 
269
281
  @enforce_types
282
+ @trace_method
270
283
  def delete_message_by_id(self, message_id: str, actor: PydanticUser) -> bool:
271
284
  """Delete a message."""
272
285
  with db_registry.session() as session:
@@ -281,6 +294,7 @@ class MessageManager:
281
294
  raise ValueError(f"Message with id {message_id} not found.")
282
295
 
283
296
  @enforce_types
297
+ @trace_method
284
298
  def size(
285
299
  self,
286
300
  actor: PydanticUser,
@@ -297,6 +311,7 @@ class MessageManager:
297
311
  return MessageModel.size(db_session=session, actor=actor, role=role, agent_id=agent_id)
298
312
 
299
313
  @enforce_types
314
+ @trace_method
300
315
  async def size_async(
301
316
  self,
302
317
  actor: PydanticUser,
@@ -312,6 +327,7 @@ class MessageManager:
312
327
  return await MessageModel.size_async(db_session=session, actor=actor, role=role, agent_id=agent_id)
313
328
 
314
329
  @enforce_types
330
+ @trace_method
315
331
  def list_user_messages_for_agent(
316
332
  self,
317
333
  agent_id: str,
@@ -334,6 +350,7 @@ class MessageManager:
334
350
  )
335
351
 
336
352
  @enforce_types
353
+ @trace_method
337
354
  def list_messages_for_agent(
338
355
  self,
339
356
  agent_id: str,
@@ -437,6 +454,7 @@ class MessageManager:
437
454
  return [msg.to_pydantic() for msg in results]
438
455
 
439
456
  @enforce_types
457
+ @trace_method
440
458
  async def list_messages_for_agent_async(
441
459
  self,
442
460
  agent_id: str,
@@ -538,6 +556,7 @@ class MessageManager:
538
556
  return [msg.to_pydantic() for msg in results]
539
557
 
540
558
  @enforce_types
559
+ @trace_method
541
560
  def delete_all_messages_for_agent(self, agent_id: str, actor: PydanticUser) -> int:
542
561
  """
543
562
  Efficiently deletes all messages associated with a given agent_id,
@@ -5,6 +5,7 @@ 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
7
  from letta.server.db import db_registry
8
+ from letta.tracing import trace_method
8
9
  from letta.utils import enforce_types
9
10
 
10
11
 
@@ -15,11 +16,13 @@ class OrganizationManager:
15
16
  DEFAULT_ORG_NAME = "default_org"
16
17
 
17
18
  @enforce_types
19
+ @trace_method
18
20
  def get_default_organization(self) -> PydanticOrganization:
19
21
  """Fetch the default organization."""
20
22
  return self.get_organization_by_id(self.DEFAULT_ORG_ID)
21
23
 
22
24
  @enforce_types
25
+ @trace_method
23
26
  def get_organization_by_id(self, org_id: str) -> Optional[PydanticOrganization]:
24
27
  """Fetch an organization by ID."""
25
28
  with db_registry.session() as session:
@@ -27,6 +30,7 @@ class OrganizationManager:
27
30
  return organization.to_pydantic()
28
31
 
29
32
  @enforce_types
33
+ @trace_method
30
34
  def create_organization(self, pydantic_org: PydanticOrganization) -> PydanticOrganization:
31
35
  """Create a new organization."""
32
36
  try:
@@ -36,6 +40,7 @@ class OrganizationManager:
36
40
  return self._create_organization(pydantic_org=pydantic_org)
37
41
 
38
42
  @enforce_types
43
+ @trace_method
39
44
  def _create_organization(self, pydantic_org: PydanticOrganization) -> PydanticOrganization:
40
45
  with db_registry.session() as session:
41
46
  org = OrganizationModel(**pydantic_org.model_dump(to_orm=True))
@@ -43,11 +48,13 @@ class OrganizationManager:
43
48
  return org.to_pydantic()
44
49
 
45
50
  @enforce_types
51
+ @trace_method
46
52
  def create_default_organization(self) -> PydanticOrganization:
47
53
  """Create the default organization."""
48
54
  return self.create_organization(PydanticOrganization(name=self.DEFAULT_ORG_NAME, id=self.DEFAULT_ORG_ID))
49
55
 
50
56
  @enforce_types
57
+ @trace_method
51
58
  def update_organization_name_using_id(self, org_id: str, name: Optional[str] = None) -> PydanticOrganization:
52
59
  """Update an organization."""
53
60
  with db_registry.session() as session:
@@ -58,6 +65,7 @@ class OrganizationManager:
58
65
  return org.to_pydantic()
59
66
 
60
67
  @enforce_types
68
+ @trace_method
61
69
  def update_organization(self, org_id: str, org_update: OrganizationUpdate) -> PydanticOrganization:
62
70
  """Update an organization."""
63
71
  with db_registry.session() as session:
@@ -70,6 +78,7 @@ class OrganizationManager:
70
78
  return org.to_pydantic()
71
79
 
72
80
  @enforce_types
81
+ @trace_method
73
82
  def delete_organization_by_id(self, org_id: str):
74
83
  """Delete an organization by marking it as deleted."""
75
84
  with db_registry.session() as session:
@@ -77,6 +86,7 @@ class OrganizationManager:
77
86
  organization.hard_delete(session)
78
87
 
79
88
  @enforce_types
89
+ @trace_method
80
90
  def list_organizations(self, after: Optional[str] = None, limit: Optional[int] = 50) -> List[PydanticOrganization]:
81
91
  """List all organizations with optional pagination."""
82
92
  with db_registry.session() as session:
@@ -11,6 +11,7 @@ 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
13
  from letta.server.db import db_registry
14
+ from letta.tracing import trace_method
14
15
  from letta.utils import enforce_types
15
16
 
16
17
 
@@ -18,6 +19,7 @@ class PassageManager:
18
19
  """Manager class to handle business logic related to Passages."""
19
20
 
20
21
  @enforce_types
22
+ @trace_method
21
23
  def get_passage_by_id(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
22
24
  """Fetch a passage by ID."""
23
25
  with db_registry.session() as session:
@@ -34,6 +36,7 @@ class PassageManager:
34
36
  raise NoResultFound(f"Passage with id {passage_id} not found in database.")
35
37
 
36
38
  @enforce_types
39
+ @trace_method
37
40
  def create_passage(self, pydantic_passage: PydanticPassage, actor: PydanticUser) -> PydanticPassage:
38
41
  """Create a new passage in the appropriate table based on whether it has agent_id or source_id."""
39
42
  # Common fields for both passage types
@@ -70,11 +73,13 @@ class PassageManager:
70
73
  return passage.to_pydantic()
71
74
 
72
75
  @enforce_types
76
+ @trace_method
73
77
  def create_many_passages(self, passages: List[PydanticPassage], actor: PydanticUser) -> List[PydanticPassage]:
74
78
  """Create multiple passages."""
75
79
  return [self.create_passage(p, actor) for p in passages]
76
80
 
77
81
  @enforce_types
82
+ @trace_method
78
83
  def insert_passage(
79
84
  self,
80
85
  agent_state: AgentState,
@@ -136,6 +141,7 @@ class PassageManager:
136
141
  raise e
137
142
 
138
143
  @enforce_types
144
+ @trace_method
139
145
  def update_passage_by_id(self, passage_id: str, passage: PydanticPassage, actor: PydanticUser, **kwargs) -> Optional[PydanticPassage]:
140
146
  """Update a passage."""
141
147
  if not passage_id:
@@ -170,6 +176,7 @@ class PassageManager:
170
176
  return curr_passage.to_pydantic()
171
177
 
172
178
  @enforce_types
179
+ @trace_method
173
180
  def delete_passage_by_id(self, passage_id: str, actor: PydanticUser) -> bool:
174
181
  """Delete a passage from either source or archival passages."""
175
182
  if not passage_id:
@@ -190,6 +197,8 @@ class PassageManager:
190
197
  except NoResultFound:
191
198
  raise NoResultFound(f"Passage with id {passage_id} not found.")
192
199
 
200
+ @enforce_types
201
+ @trace_method
193
202
  def delete_passages(
194
203
  self,
195
204
  actor: PydanticUser,
@@ -202,6 +211,7 @@ class PassageManager:
202
211
  return True
203
212
 
204
213
  @enforce_types
214
+ @trace_method
205
215
  def size(
206
216
  self,
207
217
  actor: PydanticUser,
@@ -217,6 +227,7 @@ class PassageManager:
217
227
  return AgentPassage.size(db_session=session, actor=actor, agent_id=agent_id)
218
228
 
219
229
  @enforce_types
230
+ @trace_method
220
231
  async def size_async(
221
232
  self,
222
233
  actor: PydanticUser,
@@ -230,6 +241,8 @@ class PassageManager:
230
241
  async with db_registry.async_session() as session:
231
242
  return await AgentPassage.size_async(db_session=session, actor=actor, agent_id=agent_id)
232
243
 
244
+ @enforce_types
245
+ @trace_method
233
246
  def estimate_embeddings_size(
234
247
  self,
235
248
  actor: PydanticUser,