letta-nightly 0.7.21.dev20250521233415__py3-none-any.whl → 0.7.22.dev20250523081403__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.
- letta/__init__.py +2 -2
- letta/agents/base_agent.py +4 -2
- letta/agents/letta_agent.py +3 -10
- letta/agents/letta_agent_batch.py +6 -6
- letta/cli/cli.py +0 -316
- letta/cli/cli_load.py +0 -52
- letta/client/client.py +2 -1554
- letta/data_sources/connectors.py +4 -2
- letta/functions/ast_parsers.py +33 -43
- letta/groups/sleeptime_multi_agent_v2.py +49 -13
- letta/jobs/llm_batch_job_polling.py +3 -3
- letta/jobs/scheduler.py +20 -19
- letta/llm_api/anthropic_client.py +3 -0
- letta/llm_api/google_vertex_client.py +5 -0
- letta/llm_api/openai_client.py +5 -0
- letta/main.py +2 -362
- letta/server/db.py +5 -0
- letta/server/rest_api/routers/v1/agents.py +72 -43
- letta/server/rest_api/routers/v1/llms.py +2 -2
- letta/server/rest_api/routers/v1/messages.py +5 -3
- letta/server/rest_api/routers/v1/sandbox_configs.py +18 -18
- letta/server/rest_api/routers/v1/sources.py +49 -36
- letta/server/server.py +53 -22
- letta/services/agent_manager.py +797 -124
- letta/services/block_manager.py +14 -62
- letta/services/group_manager.py +37 -0
- letta/services/identity_manager.py +9 -0
- letta/services/job_manager.py +17 -0
- letta/services/llm_batch_manager.py +88 -64
- letta/services/message_manager.py +19 -0
- letta/services/organization_manager.py +10 -0
- letta/services/passage_manager.py +13 -0
- letta/services/per_agent_lock_manager.py +4 -0
- letta/services/provider_manager.py +34 -0
- letta/services/sandbox_config_manager.py +130 -0
- letta/services/source_manager.py +59 -44
- letta/services/step_manager.py +8 -1
- letta/services/tool_manager.py +21 -0
- letta/services/tool_sandbox/e2b_sandbox.py +4 -2
- letta/services/tool_sandbox/local_sandbox.py +7 -3
- letta/services/user_manager.py +16 -0
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/METADATA +1 -1
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/RECORD +46 -50
- letta/__main__.py +0 -3
- letta/benchmark/benchmark.py +0 -98
- letta/benchmark/constants.py +0 -14
- letta/cli/cli_config.py +0 -227
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.21.dev20250521233415.dist-info → letta_nightly-0.7.22.dev20250523081403.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
|
-
|
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.
|
66
|
-
batch = LLMBatchJob.
|
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.
|
74
|
+
batch = await batch.update_async(db_session=session, actor=actor)
|
71
75
|
return batch.to_pydantic()
|
72
76
|
|
73
|
-
def
|
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.
|
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
|
-
|
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[
|
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.
|
119
|
-
query =
|
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.
|
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.
|
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 =
|
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
|
-
|
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.
|
140
|
-
batch = LLMBatchJob.
|
141
|
-
batch.
|
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
|
-
|
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.
|
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 =
|
162
|
-
cursor_result =
|
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
|
-
|
177
|
+
select(MessageModel)
|
171
178
|
.join(LLMBatchItem, MessageModel.batch_item_id == LLMBatchItem.id)
|
172
179
|
.join(LLMBatchJob, LLMBatchItem.llm_batch_id == LLMBatchJob.id)
|
173
|
-
.
|
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.
|
184
|
+
query = query.where(MessageModel.organization_id == actor.organization_id)
|
178
185
|
|
179
186
|
if agent_id is not None:
|
180
|
-
query = query.
|
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.
|
192
|
+
query = query.where(MessageModel.sequence_id < cursor_sequence_id)
|
186
193
|
else:
|
187
|
-
query = query.
|
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 =
|
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
|
-
|
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.
|
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.
|
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
|
-
|
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.
|
275
|
-
item = LLMBatchItem.
|
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
|
-
|
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.
|
290
|
-
item = LLMBatchItem.
|
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
|
-
|
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
|
-
|
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.
|
382
|
+
async with db_registry.async_session() as session:
|
368
383
|
# Lookup primary keys for all requested (batch_id, agent_id) pairs
|
369
|
-
|
370
|
-
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
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
|
-
|
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.
|
435
|
-
item = LLMBatchItem.
|
436
|
-
item.
|
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
|
-
|
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.
|
450
|
-
count = session.
|
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,
|