letta-nightly 0.8.0.dev20250606195656__py3-none-any.whl → 0.8.3.dev20250607000559__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 (105) hide show
  1. letta/__init__.py +1 -1
  2. letta/agent.py +16 -12
  3. letta/agents/base_agent.py +1 -1
  4. letta/agents/helpers.py +13 -2
  5. letta/agents/letta_agent.py +72 -34
  6. letta/agents/letta_agent_batch.py +1 -2
  7. letta/agents/voice_agent.py +19 -13
  8. letta/agents/voice_sleeptime_agent.py +23 -6
  9. letta/constants.py +18 -0
  10. letta/data_sources/__init__.py +0 -0
  11. letta/data_sources/redis_client.py +282 -0
  12. letta/errors.py +0 -4
  13. letta/functions/function_sets/files.py +58 -0
  14. letta/functions/schema_generator.py +18 -1
  15. letta/groups/sleeptime_multi_agent_v2.py +13 -3
  16. letta/helpers/datetime_helpers.py +47 -3
  17. letta/helpers/decorators.py +69 -0
  18. letta/{services/helpers/noop_helper.py → helpers/singleton.py} +5 -0
  19. letta/interfaces/anthropic_streaming_interface.py +43 -24
  20. letta/interfaces/openai_streaming_interface.py +21 -19
  21. letta/llm_api/anthropic.py +1 -1
  22. letta/llm_api/anthropic_client.py +30 -16
  23. letta/llm_api/google_vertex_client.py +1 -1
  24. letta/llm_api/helpers.py +36 -30
  25. letta/llm_api/llm_api_tools.py +1 -1
  26. letta/llm_api/llm_client_base.py +29 -1
  27. letta/llm_api/openai.py +1 -1
  28. letta/llm_api/openai_client.py +6 -8
  29. letta/local_llm/chat_completion_proxy.py +1 -1
  30. letta/memory.py +1 -1
  31. letta/orm/enums.py +1 -0
  32. letta/orm/file.py +80 -3
  33. letta/orm/files_agents.py +13 -0
  34. letta/orm/passage.py +2 -0
  35. letta/orm/sqlalchemy_base.py +34 -11
  36. letta/otel/__init__.py +0 -0
  37. letta/otel/context.py +25 -0
  38. letta/otel/events.py +0 -0
  39. letta/otel/metric_registry.py +122 -0
  40. letta/otel/metrics.py +66 -0
  41. letta/otel/resource.py +26 -0
  42. letta/{tracing.py → otel/tracing.py} +55 -78
  43. letta/plugins/README.md +22 -0
  44. letta/plugins/__init__.py +0 -0
  45. letta/plugins/defaults.py +11 -0
  46. letta/plugins/plugins.py +72 -0
  47. letta/schemas/enums.py +8 -0
  48. letta/schemas/file.py +12 -0
  49. letta/schemas/letta_request.py +6 -0
  50. letta/schemas/passage.py +1 -0
  51. letta/schemas/tool.py +4 -0
  52. letta/server/db.py +7 -7
  53. letta/server/rest_api/app.py +8 -6
  54. letta/server/rest_api/routers/v1/agents.py +46 -37
  55. letta/server/rest_api/routers/v1/groups.py +3 -3
  56. letta/server/rest_api/routers/v1/sources.py +26 -3
  57. letta/server/rest_api/routers/v1/tools.py +7 -2
  58. letta/server/rest_api/utils.py +9 -6
  59. letta/server/server.py +25 -13
  60. letta/services/agent_manager.py +186 -194
  61. letta/services/block_manager.py +1 -1
  62. letta/services/context_window_calculator/context_window_calculator.py +1 -1
  63. letta/services/context_window_calculator/token_counter.py +3 -2
  64. letta/services/file_processor/chunker/line_chunker.py +34 -0
  65. letta/services/file_processor/file_processor.py +43 -12
  66. letta/services/file_processor/parser/mistral_parser.py +11 -1
  67. letta/services/files_agents_manager.py +96 -7
  68. letta/services/group_manager.py +6 -6
  69. letta/services/helpers/agent_manager_helper.py +404 -3
  70. letta/services/identity_manager.py +1 -1
  71. letta/services/job_manager.py +1 -1
  72. letta/services/llm_batch_manager.py +1 -1
  73. letta/services/mcp/stdio_client.py +5 -1
  74. letta/services/mcp_manager.py +4 -4
  75. letta/services/message_manager.py +1 -1
  76. letta/services/organization_manager.py +1 -1
  77. letta/services/passage_manager.py +604 -19
  78. letta/services/per_agent_lock_manager.py +1 -1
  79. letta/services/provider_manager.py +1 -1
  80. letta/services/sandbox_config_manager.py +1 -1
  81. letta/services/source_manager.py +178 -19
  82. letta/services/step_manager.py +2 -2
  83. letta/services/summarizer/summarizer.py +1 -1
  84. letta/services/telemetry_manager.py +1 -1
  85. letta/services/tool_executor/builtin_tool_executor.py +117 -0
  86. letta/services/tool_executor/composio_tool_executor.py +53 -0
  87. letta/services/tool_executor/core_tool_executor.py +474 -0
  88. letta/services/tool_executor/files_tool_executor.py +138 -0
  89. letta/services/tool_executor/mcp_tool_executor.py +45 -0
  90. letta/services/tool_executor/multi_agent_tool_executor.py +123 -0
  91. letta/services/tool_executor/tool_execution_manager.py +34 -14
  92. letta/services/tool_executor/tool_execution_sandbox.py +1 -1
  93. letta/services/tool_executor/tool_executor.py +3 -802
  94. letta/services/tool_executor/tool_executor_base.py +43 -0
  95. letta/services/tool_manager.py +55 -59
  96. letta/services/tool_sandbox/e2b_sandbox.py +1 -1
  97. letta/services/tool_sandbox/local_sandbox.py +6 -3
  98. letta/services/user_manager.py +6 -3
  99. letta/settings.py +23 -2
  100. letta/utils.py +7 -2
  101. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/METADATA +4 -2
  102. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/RECORD +105 -83
  103. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/LICENSE +0 -0
  104. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/WHEEL +0 -0
  105. {letta_nightly-0.8.0.dev20250606195656.dist-info → letta_nightly-0.8.3.dev20250607000559.dist-info}/entry_points.txt +0 -0
@@ -11,11 +11,12 @@ from letta.constants import MAX_EMBEDDING_DIM
11
11
  from letta.embeddings import embedding_model, parse_and_chunk_text
12
12
  from letta.orm.errors import NoResultFound
13
13
  from letta.orm.passage import AgentPassage, SourcePassage
14
+ from letta.otel.tracing import trace_method
14
15
  from letta.schemas.agent import AgentState
16
+ from letta.schemas.file import FileMetadata as PydanticFileMetadata
15
17
  from letta.schemas.passage import Passage as PydanticPassage
16
18
  from letta.schemas.user import User as PydanticUser
17
19
  from letta.server.db import db_registry
18
- from letta.tracing import trace_method
19
20
  from letta.utils import enforce_types
20
21
 
21
22
 
@@ -42,10 +43,65 @@ async def get_openai_embedding_async(text: str, model: str, endpoint: str) -> Li
42
43
  class PassageManager:
43
44
  """Manager class to handle business logic related to Passages."""
44
45
 
46
+ # AGENT PASSAGE METHODS
47
+ @enforce_types
48
+ @trace_method
49
+ def get_agent_passage_by_id(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
50
+ """Fetch an agent passage by ID."""
51
+ with db_registry.session() as session:
52
+ try:
53
+ passage = AgentPassage.read(db_session=session, identifier=passage_id, actor=actor)
54
+ return passage.to_pydantic()
55
+ except NoResultFound:
56
+ raise NoResultFound(f"Agent passage with id {passage_id} not found in database.")
57
+
58
+ @enforce_types
59
+ @trace_method
60
+ async def get_agent_passage_by_id_async(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
61
+ """Fetch an agent passage by ID."""
62
+ async with db_registry.async_session() as session:
63
+ try:
64
+ passage = await AgentPassage.read_async(db_session=session, identifier=passage_id, actor=actor)
65
+ return passage.to_pydantic()
66
+ except NoResultFound:
67
+ raise NoResultFound(f"Agent passage with id {passage_id} not found in database.")
68
+
69
+ # SOURCE PASSAGE METHODS
70
+ @enforce_types
71
+ @trace_method
72
+ def get_source_passage_by_id(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
73
+ """Fetch a source passage by ID."""
74
+ with db_registry.session() as session:
75
+ try:
76
+ passage = SourcePassage.read(db_session=session, identifier=passage_id, actor=actor)
77
+ return passage.to_pydantic()
78
+ except NoResultFound:
79
+ raise NoResultFound(f"Source passage with id {passage_id} not found in database.")
80
+
81
+ @enforce_types
82
+ @trace_method
83
+ async def get_source_passage_by_id_async(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
84
+ """Fetch a source passage by ID."""
85
+ async with db_registry.async_session() as session:
86
+ try:
87
+ passage = await SourcePassage.read_async(db_session=session, identifier=passage_id, actor=actor)
88
+ return passage.to_pydantic()
89
+ except NoResultFound:
90
+ raise NoResultFound(f"Source passage with id {passage_id} not found in database.")
91
+
92
+ # DEPRECATED - Use specific methods above
45
93
  @enforce_types
46
94
  @trace_method
47
95
  def get_passage_by_id(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
48
- """Fetch a passage by ID."""
96
+ """DEPRECATED: Use get_agent_passage_by_id() or get_source_passage_by_id() instead."""
97
+ import warnings
98
+
99
+ warnings.warn(
100
+ "get_passage_by_id is deprecated. Use get_agent_passage_by_id() or get_source_passage_by_id() instead.",
101
+ DeprecationWarning,
102
+ stacklevel=2,
103
+ )
104
+
49
105
  with db_registry.session() as session:
50
106
  # Try source passages first
51
107
  try:
@@ -62,7 +118,15 @@ class PassageManager:
62
118
  @enforce_types
63
119
  @trace_method
64
120
  async def get_passage_by_id_async(self, passage_id: str, actor: PydanticUser) -> Optional[PydanticPassage]:
65
- """Fetch a passage by ID."""
121
+ """DEPRECATED: Use get_agent_passage_by_id_async() or get_source_passage_by_id_async() instead."""
122
+ import warnings
123
+
124
+ warnings.warn(
125
+ "get_passage_by_id_async is deprecated. Use get_agent_passage_by_id_async() or get_source_passage_by_id_async() instead.",
126
+ DeprecationWarning,
127
+ stacklevel=2,
128
+ )
129
+
66
130
  async with db_registry.async_session() as session:
67
131
  # Try source passages first
68
132
  try:
@@ -76,10 +140,137 @@ class PassageManager:
76
140
  except NoResultFound:
77
141
  raise NoResultFound(f"Passage with id {passage_id} not found in database.")
78
142
 
143
+ @enforce_types
144
+ @trace_method
145
+ def create_agent_passage(self, pydantic_passage: PydanticPassage, actor: PydanticUser) -> PydanticPassage:
146
+ """Create a new agent passage."""
147
+ if not pydantic_passage.agent_id:
148
+ raise ValueError("Agent passage must have agent_id")
149
+ if pydantic_passage.source_id:
150
+ raise ValueError("Agent passage cannot have source_id")
151
+
152
+ data = pydantic_passage.model_dump(to_orm=True)
153
+ common_fields = {
154
+ "id": data.get("id"),
155
+ "text": data["text"],
156
+ "embedding": data["embedding"],
157
+ "embedding_config": data["embedding_config"],
158
+ "organization_id": data["organization_id"],
159
+ "metadata_": data.get("metadata", {}),
160
+ "is_deleted": data.get("is_deleted", False),
161
+ "created_at": data.get("created_at", datetime.now(timezone.utc)),
162
+ }
163
+ agent_fields = {"agent_id": data["agent_id"]}
164
+ passage = AgentPassage(**common_fields, **agent_fields)
165
+
166
+ with db_registry.session() as session:
167
+ passage.create(session, actor=actor)
168
+ return passage.to_pydantic()
169
+
170
+ @enforce_types
171
+ @trace_method
172
+ async def create_agent_passage_async(self, pydantic_passage: PydanticPassage, actor: PydanticUser) -> PydanticPassage:
173
+ """Create a new agent passage."""
174
+ if not pydantic_passage.agent_id:
175
+ raise ValueError("Agent passage must have agent_id")
176
+ if pydantic_passage.source_id:
177
+ raise ValueError("Agent passage cannot have source_id")
178
+
179
+ data = pydantic_passage.model_dump(to_orm=True)
180
+ common_fields = {
181
+ "id": data.get("id"),
182
+ "text": data["text"],
183
+ "embedding": data["embedding"],
184
+ "embedding_config": data["embedding_config"],
185
+ "organization_id": data["organization_id"],
186
+ "metadata_": data.get("metadata", {}),
187
+ "is_deleted": data.get("is_deleted", False),
188
+ "created_at": data.get("created_at", datetime.now(timezone.utc)),
189
+ }
190
+ agent_fields = {"agent_id": data["agent_id"]}
191
+ passage = AgentPassage(**common_fields, **agent_fields)
192
+
193
+ async with db_registry.async_session() as session:
194
+ passage = await passage.create_async(session, actor=actor)
195
+ return passage.to_pydantic()
196
+
197
+ @enforce_types
198
+ @trace_method
199
+ def create_source_passage(
200
+ self, pydantic_passage: PydanticPassage, file_metadata: PydanticFileMetadata, actor: PydanticUser
201
+ ) -> PydanticPassage:
202
+ """Create a new source passage."""
203
+ if not pydantic_passage.source_id:
204
+ raise ValueError("Source passage must have source_id")
205
+ if pydantic_passage.agent_id:
206
+ raise ValueError("Source passage cannot have agent_id")
207
+
208
+ data = pydantic_passage.model_dump(to_orm=True)
209
+ common_fields = {
210
+ "id": data.get("id"),
211
+ "text": data["text"],
212
+ "embedding": data["embedding"],
213
+ "embedding_config": data["embedding_config"],
214
+ "organization_id": data["organization_id"],
215
+ "metadata_": data.get("metadata", {}),
216
+ "is_deleted": data.get("is_deleted", False),
217
+ "created_at": data.get("created_at", datetime.now(timezone.utc)),
218
+ }
219
+ source_fields = {
220
+ "source_id": data["source_id"],
221
+ "file_id": data.get("file_id"),
222
+ "file_name": file_metadata.file_name,
223
+ }
224
+ passage = SourcePassage(**common_fields, **source_fields)
225
+
226
+ with db_registry.session() as session:
227
+ passage.create(session, actor=actor)
228
+ return passage.to_pydantic()
229
+
230
+ @enforce_types
231
+ @trace_method
232
+ async def create_source_passage_async(
233
+ self, pydantic_passage: PydanticPassage, file_metadata: PydanticFileMetadata, actor: PydanticUser
234
+ ) -> PydanticPassage:
235
+ """Create a new source passage."""
236
+ if not pydantic_passage.source_id:
237
+ raise ValueError("Source passage must have source_id")
238
+ if pydantic_passage.agent_id:
239
+ raise ValueError("Source passage cannot have agent_id")
240
+
241
+ data = pydantic_passage.model_dump(to_orm=True)
242
+ common_fields = {
243
+ "id": data.get("id"),
244
+ "text": data["text"],
245
+ "embedding": data["embedding"],
246
+ "embedding_config": data["embedding_config"],
247
+ "organization_id": data["organization_id"],
248
+ "metadata_": data.get("metadata", {}),
249
+ "is_deleted": data.get("is_deleted", False),
250
+ "created_at": data.get("created_at", datetime.now(timezone.utc)),
251
+ }
252
+ source_fields = {
253
+ "source_id": data["source_id"],
254
+ "file_id": data.get("file_id"),
255
+ "file_name": file_metadata.file_name,
256
+ }
257
+ passage = SourcePassage(**common_fields, **source_fields)
258
+
259
+ async with db_registry.async_session() as session:
260
+ passage = await passage.create_async(session, actor=actor)
261
+ return passage.to_pydantic()
262
+
263
+ # DEPRECATED - Use specific methods above
79
264
  @enforce_types
80
265
  @trace_method
81
266
  def create_passage(self, pydantic_passage: PydanticPassage, actor: PydanticUser) -> PydanticPassage:
82
- """Create a new passage in the appropriate table based on whether it has agent_id or source_id."""
267
+ """DEPRECATED: Use create_agent_passage() or create_source_passage() instead."""
268
+ import warnings
269
+
270
+ warnings.warn(
271
+ "create_passage is deprecated. Use create_agent_passage() or create_source_passage() instead.", DeprecationWarning, stacklevel=2
272
+ )
273
+
83
274
  passage = self._preprocess_passage_for_creation(pydantic_passage=pydantic_passage)
84
275
 
85
276
  with db_registry.session() as session:
@@ -89,7 +280,15 @@ class PassageManager:
89
280
  @enforce_types
90
281
  @trace_method
91
282
  async def create_passage_async(self, pydantic_passage: PydanticPassage, actor: PydanticUser) -> PydanticPassage:
92
- """Create a new passage in the appropriate table based on whether it has agent_id or source_id."""
283
+ """DEPRECATED: Use create_agent_passage_async() or create_source_passage_async() instead."""
284
+ import warnings
285
+
286
+ warnings.warn(
287
+ "create_passage_async is deprecated. Use create_agent_passage_async() or create_source_passage_async() instead.",
288
+ DeprecationWarning,
289
+ stacklevel=2,
290
+ )
291
+
93
292
  # Common fields for both passage types
94
293
  passage = self._preprocess_passage_for_creation(pydantic_passage=pydantic_passage)
95
294
  async with db_registry.async_session() as session:
@@ -128,16 +327,110 @@ class PassageManager:
128
327
 
129
328
  return passage
130
329
 
330
+ @enforce_types
331
+ @trace_method
332
+ def create_many_agent_passages(self, passages: List[PydanticPassage], actor: PydanticUser) -> List[PydanticPassage]:
333
+ """Create multiple agent passages."""
334
+ return [self.create_agent_passage(p, actor) for p in passages]
335
+
336
+ @enforce_types
337
+ @trace_method
338
+ async def create_many_agent_passages_async(self, passages: List[PydanticPassage], actor: PydanticUser) -> List[PydanticPassage]:
339
+ """Create multiple agent passages."""
340
+ agent_passages = []
341
+ for p in passages:
342
+ if not p.agent_id:
343
+ raise ValueError("Agent passage must have agent_id")
344
+ if p.source_id:
345
+ raise ValueError("Agent passage cannot have source_id")
346
+
347
+ data = p.model_dump(to_orm=True)
348
+ common_fields = {
349
+ "id": data.get("id"),
350
+ "text": data["text"],
351
+ "embedding": data["embedding"],
352
+ "embedding_config": data["embedding_config"],
353
+ "organization_id": data["organization_id"],
354
+ "metadata_": data.get("metadata", {}),
355
+ "is_deleted": data.get("is_deleted", False),
356
+ "created_at": data.get("created_at", datetime.now(timezone.utc)),
357
+ }
358
+ agent_fields = {"agent_id": data["agent_id"]}
359
+ agent_passages.append(AgentPassage(**common_fields, **agent_fields))
360
+
361
+ async with db_registry.async_session() as session:
362
+ agent_created = await AgentPassage.batch_create_async(items=agent_passages, db_session=session, actor=actor)
363
+ return [p.to_pydantic() for p in agent_created]
364
+
365
+ @enforce_types
366
+ @trace_method
367
+ def create_many_source_passages(
368
+ self, passages: List[PydanticPassage], file_metadata: PydanticFileMetadata, actor: PydanticUser
369
+ ) -> List[PydanticPassage]:
370
+ """Create multiple source passages."""
371
+ return [self.create_source_passage(p, file_metadata, actor) for p in passages]
372
+
373
+ @enforce_types
374
+ @trace_method
375
+ async def create_many_source_passages_async(
376
+ self, passages: List[PydanticPassage], file_metadata: PydanticFileMetadata, actor: PydanticUser
377
+ ) -> List[PydanticPassage]:
378
+ """Create multiple source passages."""
379
+ source_passages = []
380
+ for p in passages:
381
+ if not p.source_id:
382
+ raise ValueError("Source passage must have source_id")
383
+ if p.agent_id:
384
+ raise ValueError("Source passage cannot have agent_id")
385
+
386
+ data = p.model_dump(to_orm=True)
387
+ common_fields = {
388
+ "id": data.get("id"),
389
+ "text": data["text"],
390
+ "embedding": data["embedding"],
391
+ "embedding_config": data["embedding_config"],
392
+ "organization_id": data["organization_id"],
393
+ "metadata_": data.get("metadata", {}),
394
+ "is_deleted": data.get("is_deleted", False),
395
+ "created_at": data.get("created_at", datetime.now(timezone.utc)),
396
+ }
397
+ source_fields = {
398
+ "source_id": data["source_id"],
399
+ "file_id": data.get("file_id"),
400
+ "file_name": file_metadata.file_name,
401
+ }
402
+ source_passages.append(SourcePassage(**common_fields, **source_fields))
403
+
404
+ async with db_registry.async_session() as session:
405
+ source_created = await SourcePassage.batch_create_async(items=source_passages, db_session=session, actor=actor)
406
+ return [p.to_pydantic() for p in source_created]
407
+
408
+ # DEPRECATED - Use specific methods above
131
409
  @enforce_types
132
410
  @trace_method
133
411
  def create_many_passages(self, passages: List[PydanticPassage], actor: PydanticUser) -> List[PydanticPassage]:
134
- """Create multiple passages."""
412
+ """DEPRECATED: Use create_many_agent_passages() or create_many_source_passages() instead."""
413
+ import warnings
414
+
415
+ warnings.warn(
416
+ "create_many_passages is deprecated. Use create_many_agent_passages() or create_many_source_passages() instead.",
417
+ DeprecationWarning,
418
+ stacklevel=2,
419
+ )
135
420
  return [self.create_passage(p, actor) for p in passages]
136
421
 
137
422
  @enforce_types
138
423
  @trace_method
139
424
  async def create_many_passages_async(self, passages: List[PydanticPassage], actor: PydanticUser) -> List[PydanticPassage]:
140
- """Create multiple passages."""
425
+ """DEPRECATED: Use create_many_agent_passages_async() or create_many_source_passages_async() instead."""
426
+ import warnings
427
+
428
+ warnings.warn(
429
+ "create_many_passages_async is deprecated. Use create_many_agent_passages_async() or create_many_source_passages_async() instead.",
430
+ DeprecationWarning,
431
+ stacklevel=2,
432
+ )
433
+
141
434
  async with db_registry.async_session() as session:
142
435
  agent_passages = []
143
436
  source_passages = []
@@ -203,7 +496,7 @@ class PassageManager:
203
496
  raise TypeError(
204
497
  f"Got back an unexpected payload from text embedding function, type={type(embedding)}, value={embedding}"
205
498
  )
206
- passage = self.create_passage(
499
+ passage = self.create_agent_passage(
207
500
  PydanticPassage(
208
501
  organization_id=actor.organization_id,
209
502
  agent_id=agent_id,
@@ -251,7 +544,7 @@ class PassageManager:
251
544
  for chunk_text, embedding in zip(text_chunks, embeddings)
252
545
  ]
253
546
 
254
- passages = await self.create_many_passages_async(passages=passages, actor=actor)
547
+ passages = await self.create_many_agent_passages_async(passages=passages, actor=actor)
255
548
 
256
549
  return passages
257
550
 
@@ -292,10 +585,191 @@ class PassageManager:
292
585
 
293
586
  return processed_embeddings
294
587
 
588
+ @enforce_types
589
+ @trace_method
590
+ def update_agent_passage_by_id(
591
+ self, passage_id: str, passage: PydanticPassage, actor: PydanticUser, **kwargs
592
+ ) -> Optional[PydanticPassage]:
593
+ """Update an agent passage."""
594
+ if not passage_id:
595
+ raise ValueError("Passage ID must be provided.")
596
+
597
+ with db_registry.session() as session:
598
+ try:
599
+ curr_passage = AgentPassage.read(
600
+ db_session=session,
601
+ identifier=passage_id,
602
+ actor=actor,
603
+ )
604
+ except NoResultFound:
605
+ raise ValueError(f"Agent passage with id {passage_id} does not exist.")
606
+
607
+ # Update the database record with values from the provided record
608
+ update_data = passage.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
609
+ for key, value in update_data.items():
610
+ setattr(curr_passage, key, value)
611
+
612
+ # Commit changes
613
+ curr_passage.update(session, actor=actor)
614
+ return curr_passage.to_pydantic()
615
+
616
+ @enforce_types
617
+ @trace_method
618
+ async def update_agent_passage_by_id_async(
619
+ self, passage_id: str, passage: PydanticPassage, actor: PydanticUser, **kwargs
620
+ ) -> Optional[PydanticPassage]:
621
+ """Update an agent passage."""
622
+ if not passage_id:
623
+ raise ValueError("Passage ID must be provided.")
624
+
625
+ async with db_registry.async_session() as session:
626
+ try:
627
+ curr_passage = await AgentPassage.read_async(
628
+ db_session=session,
629
+ identifier=passage_id,
630
+ actor=actor,
631
+ )
632
+ except NoResultFound:
633
+ raise ValueError(f"Agent passage with id {passage_id} does not exist.")
634
+
635
+ # Update the database record with values from the provided record
636
+ update_data = passage.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
637
+ for key, value in update_data.items():
638
+ setattr(curr_passage, key, value)
639
+
640
+ # Commit changes
641
+ await curr_passage.update_async(session, actor=actor)
642
+ return curr_passage.to_pydantic()
643
+
644
+ @enforce_types
645
+ @trace_method
646
+ def update_source_passage_by_id(
647
+ self, passage_id: str, passage: PydanticPassage, actor: PydanticUser, **kwargs
648
+ ) -> Optional[PydanticPassage]:
649
+ """Update a source passage."""
650
+ if not passage_id:
651
+ raise ValueError("Passage ID must be provided.")
652
+
653
+ with db_registry.session() as session:
654
+ try:
655
+ curr_passage = SourcePassage.read(
656
+ db_session=session,
657
+ identifier=passage_id,
658
+ actor=actor,
659
+ )
660
+ except NoResultFound:
661
+ raise ValueError(f"Source passage with id {passage_id} does not exist.")
662
+
663
+ # Update the database record with values from the provided record
664
+ update_data = passage.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
665
+ for key, value in update_data.items():
666
+ setattr(curr_passage, key, value)
667
+
668
+ # Commit changes
669
+ curr_passage.update(session, actor=actor)
670
+ return curr_passage.to_pydantic()
671
+
672
+ @enforce_types
673
+ @trace_method
674
+ async def update_source_passage_by_id_async(
675
+ self, passage_id: str, passage: PydanticPassage, actor: PydanticUser, **kwargs
676
+ ) -> Optional[PydanticPassage]:
677
+ """Update a source passage."""
678
+ if not passage_id:
679
+ raise ValueError("Passage ID must be provided.")
680
+
681
+ async with db_registry.async_session() as session:
682
+ try:
683
+ curr_passage = await SourcePassage.read_async(
684
+ db_session=session,
685
+ identifier=passage_id,
686
+ actor=actor,
687
+ )
688
+ except NoResultFound:
689
+ raise ValueError(f"Source passage with id {passage_id} does not exist.")
690
+
691
+ # Update the database record with values from the provided record
692
+ update_data = passage.model_dump(to_orm=True, exclude_unset=True, exclude_none=True)
693
+ for key, value in update_data.items():
694
+ setattr(curr_passage, key, value)
695
+
696
+ # Commit changes
697
+ await curr_passage.update_async(session, actor=actor)
698
+ return curr_passage.to_pydantic()
699
+
700
+ @enforce_types
701
+ @trace_method
702
+ def delete_agent_passage_by_id(self, passage_id: str, actor: PydanticUser) -> bool:
703
+ """Delete an agent passage."""
704
+ if not passage_id:
705
+ raise ValueError("Passage ID must be provided.")
706
+
707
+ with db_registry.session() as session:
708
+ try:
709
+ passage = AgentPassage.read(db_session=session, identifier=passage_id, actor=actor)
710
+ passage.hard_delete(session, actor=actor)
711
+ return True
712
+ except NoResultFound:
713
+ raise NoResultFound(f"Agent passage with id {passage_id} not found.")
714
+
715
+ @enforce_types
716
+ @trace_method
717
+ async def delete_agent_passage_by_id_async(self, passage_id: str, actor: PydanticUser) -> bool:
718
+ """Delete an agent passage."""
719
+ if not passage_id:
720
+ raise ValueError("Passage ID must be provided.")
721
+
722
+ async with db_registry.async_session() as session:
723
+ try:
724
+ passage = await AgentPassage.read_async(db_session=session, identifier=passage_id, actor=actor)
725
+ await passage.hard_delete_async(session, actor=actor)
726
+ return True
727
+ except NoResultFound:
728
+ raise NoResultFound(f"Agent passage with id {passage_id} not found.")
729
+
730
+ @enforce_types
731
+ @trace_method
732
+ def delete_source_passage_by_id(self, passage_id: str, actor: PydanticUser) -> bool:
733
+ """Delete a source passage."""
734
+ if not passage_id:
735
+ raise ValueError("Passage ID must be provided.")
736
+
737
+ with db_registry.session() as session:
738
+ try:
739
+ passage = SourcePassage.read(db_session=session, identifier=passage_id, actor=actor)
740
+ passage.hard_delete(session, actor=actor)
741
+ return True
742
+ except NoResultFound:
743
+ raise NoResultFound(f"Source passage with id {passage_id} not found.")
744
+
745
+ @enforce_types
746
+ @trace_method
747
+ async def delete_source_passage_by_id_async(self, passage_id: str, actor: PydanticUser) -> bool:
748
+ """Delete a source passage."""
749
+ if not passage_id:
750
+ raise ValueError("Passage ID must be provided.")
751
+
752
+ async with db_registry.async_session() as session:
753
+ try:
754
+ passage = await SourcePassage.read_async(db_session=session, identifier=passage_id, actor=actor)
755
+ await passage.hard_delete_async(session, actor=actor)
756
+ return True
757
+ except NoResultFound:
758
+ raise NoResultFound(f"Source passage with id {passage_id} not found.")
759
+
760
+ # DEPRECATED - Use specific methods above
295
761
  @enforce_types
296
762
  @trace_method
297
763
  def update_passage_by_id(self, passage_id: str, passage: PydanticPassage, actor: PydanticUser, **kwargs) -> Optional[PydanticPassage]:
298
- """Update a passage."""
764
+ """DEPRECATED: Use update_agent_passage_by_id() or update_source_passage_by_id() instead."""
765
+ import warnings
766
+
767
+ warnings.warn(
768
+ "update_passage_by_id is deprecated. Use update_agent_passage_by_id() or update_source_passage_by_id() instead.",
769
+ DeprecationWarning,
770
+ stacklevel=2,
771
+ )
772
+
299
773
  if not passage_id:
300
774
  raise ValueError("Passage ID must be provided.")
301
775
 
@@ -330,7 +804,15 @@ class PassageManager:
330
804
  @enforce_types
331
805
  @trace_method
332
806
  def delete_passage_by_id(self, passage_id: str, actor: PydanticUser) -> bool:
333
- """Delete a passage from either source or archival passages."""
807
+ """DEPRECATED: Use delete_agent_passage_by_id() or delete_source_passage_by_id() instead."""
808
+ import warnings
809
+
810
+ warnings.warn(
811
+ "delete_passage_by_id is deprecated. Use delete_agent_passage_by_id() or delete_source_passage_by_id() instead.",
812
+ DeprecationWarning,
813
+ stacklevel=2,
814
+ )
815
+
334
816
  if not passage_id:
335
817
  raise ValueError("Passage ID must be provided.")
336
818
 
@@ -352,7 +834,15 @@ class PassageManager:
352
834
  @enforce_types
353
835
  @trace_method
354
836
  async def delete_passage_by_id_async(self, passage_id: str, actor: PydanticUser) -> bool:
355
- """Delete a passage from either source or archival passages."""
837
+ """DEPRECATED: Use delete_agent_passage_by_id_async() or delete_source_passage_by_id_async() instead."""
838
+ import warnings
839
+
840
+ warnings.warn(
841
+ "delete_passage_by_id_async is deprecated. Use delete_agent_passage_by_id_async() or delete_source_passage_by_id_async() instead.",
842
+ DeprecationWarning,
843
+ stacklevel=2,
844
+ )
845
+
356
846
  if not passage_id:
357
847
  raise ValueError("Passage ID must be provided.")
358
848
 
@@ -373,15 +863,42 @@ class PassageManager:
373
863
 
374
864
  @enforce_types
375
865
  @trace_method
376
- def delete_passages(
866
+ def delete_agent_passages(
377
867
  self,
378
868
  actor: PydanticUser,
379
869
  passages: List[PydanticPassage],
380
870
  ) -> bool:
871
+ """Delete multiple agent passages."""
381
872
  # TODO: This is very inefficient
382
873
  # TODO: We should have a base `delete_all_matching_filters`-esque function
383
874
  for passage in passages:
384
- self.delete_passage_by_id(passage_id=passage.id, actor=actor)
875
+ self.delete_agent_passage_by_id(passage_id=passage.id, actor=actor)
876
+ return True
877
+
878
+ @enforce_types
879
+ @trace_method
880
+ async def delete_agent_passages_async(
881
+ self,
882
+ actor: PydanticUser,
883
+ passages: List[PydanticPassage],
884
+ ) -> bool:
885
+ """Delete multiple agent passages."""
886
+ async with db_registry.async_session() as session:
887
+ await AgentPassage.bulk_hard_delete_async(db_session=session, identifiers=[p.id for p in passages], actor=actor)
888
+ return True
889
+
890
+ @enforce_types
891
+ @trace_method
892
+ def delete_source_passages(
893
+ self,
894
+ actor: PydanticUser,
895
+ passages: List[PydanticPassage],
896
+ ) -> bool:
897
+ """Delete multiple source passages."""
898
+ # TODO: This is very inefficient
899
+ # TODO: We should have a base `delete_all_matching_filters`-esque function
900
+ for passage in passages:
901
+ self.delete_source_passage_by_id(passage_id=passage.id, actor=actor)
385
902
  return True
386
903
 
387
904
  @enforce_types
@@ -395,14 +912,36 @@ class PassageManager:
395
912
  await SourcePassage.bulk_hard_delete_async(db_session=session, identifiers=[p.id for p in passages], actor=actor)
396
913
  return True
397
914
 
915
+ # DEPRECATED - Use specific methods above
398
916
  @enforce_types
399
917
  @trace_method
400
- def size(
918
+ def delete_passages(
919
+ self,
920
+ actor: PydanticUser,
921
+ passages: List[PydanticPassage],
922
+ ) -> bool:
923
+ """DEPRECATED: Use delete_agent_passages() or delete_source_passages() instead."""
924
+ import warnings
925
+
926
+ warnings.warn(
927
+ "delete_passages is deprecated. Use delete_agent_passages() or delete_source_passages() instead.",
928
+ DeprecationWarning,
929
+ stacklevel=2,
930
+ )
931
+ # TODO: This is very inefficient
932
+ # TODO: We should have a base `delete_all_matching_filters`-esque function
933
+ for passage in passages:
934
+ self.delete_passage_by_id(passage_id=passage.id, actor=actor)
935
+ return True
936
+
937
+ @enforce_types
938
+ @trace_method
939
+ def agent_passage_size(
401
940
  self,
402
941
  actor: PydanticUser,
403
942
  agent_id: Optional[str] = None,
404
943
  ) -> int:
405
- """Get the total count of messages with optional filters.
944
+ """Get the total count of agent passages with optional filters.
406
945
 
407
946
  Args:
408
947
  actor: The user requesting the count
@@ -411,14 +950,29 @@ class PassageManager:
411
950
  with db_registry.session() as session:
412
951
  return AgentPassage.size(db_session=session, actor=actor, agent_id=agent_id)
413
952
 
953
+ # DEPRECATED - Use agent_passage_size() instead since this only counted agent passages anyway
954
+ @enforce_types
955
+ @trace_method
956
+ def size(
957
+ self,
958
+ actor: PydanticUser,
959
+ agent_id: Optional[str] = None,
960
+ ) -> int:
961
+ """DEPRECATED: Use agent_passage_size() instead (this only counted agent passages anyway)."""
962
+ import warnings
963
+
964
+ warnings.warn("size is deprecated. Use agent_passage_size() instead.", DeprecationWarning, stacklevel=2)
965
+ with db_registry.session() as session:
966
+ return AgentPassage.size(db_session=session, actor=actor, agent_id=agent_id)
967
+
414
968
  @enforce_types
415
969
  @trace_method
416
- async def size_async(
970
+ async def agent_passage_size_async(
417
971
  self,
418
972
  actor: PydanticUser,
419
973
  agent_id: Optional[str] = None,
420
974
  ) -> int:
421
- """Get the total count of messages with optional filters.
975
+ """Get the total count of agent passages with optional filters.
422
976
  Args:
423
977
  actor: The user requesting the count
424
978
  agent_id: The agent ID of the messages
@@ -426,6 +980,37 @@ class PassageManager:
426
980
  async with db_registry.async_session() as session:
427
981
  return await AgentPassage.size_async(db_session=session, actor=actor, agent_id=agent_id)
428
982
 
983
+ @enforce_types
984
+ @trace_method
985
+ def source_passage_size(
986
+ self,
987
+ actor: PydanticUser,
988
+ source_id: Optional[str] = None,
989
+ ) -> int:
990
+ """Get the total count of source passages with optional filters.
991
+
992
+ Args:
993
+ actor: The user requesting the count
994
+ source_id: The source ID of the passages
995
+ """
996
+ with db_registry.session() as session:
997
+ return SourcePassage.size(db_session=session, actor=actor, source_id=source_id)
998
+
999
+ @enforce_types
1000
+ @trace_method
1001
+ async def source_passage_size_async(
1002
+ self,
1003
+ actor: PydanticUser,
1004
+ source_id: Optional[str] = None,
1005
+ ) -> int:
1006
+ """Get the total count of source passages with optional filters.
1007
+ Args:
1008
+ actor: The user requesting the count
1009
+ source_id: The source ID of the passages
1010
+ """
1011
+ async with db_registry.async_session() as session:
1012
+ return await SourcePassage.size_async(db_session=session, actor=actor, source_id=source_id)
1013
+
429
1014
  @enforce_types
430
1015
  @trace_method
431
1016
  async def estimate_embeddings_size_async(
@@ -448,7 +1033,7 @@ class PassageManager:
448
1033
  raise ValueError(f"Invalid storage unit: {storage_unit}. Must be one of {list(BYTES_PER_STORAGE_UNIT.keys())}.")
449
1034
  BYTES_PER_EMBEDDING_DIM = 4
450
1035
  GB_PER_EMBEDDING = BYTES_PER_EMBEDDING_DIM / BYTES_PER_STORAGE_UNIT[storage_unit] * MAX_EMBEDDING_DIM
451
- return await self.size_async(actor=actor, agent_id=agent_id) * GB_PER_EMBEDDING
1036
+ return await self.agent_passage_size_async(actor=actor, agent_id=agent_id) * GB_PER_EMBEDDING
452
1037
 
453
1038
  @enforce_types
454
1039
  @trace_method