letta-nightly 0.12.1.dev20251023104211__py3-none-any.whl → 0.13.0.dev20251024223017__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.

Potentially problematic release.


This version of letta-nightly might be problematic. Click here for more details.

Files changed (159) hide show
  1. letta/__init__.py +2 -3
  2. letta/adapters/letta_llm_adapter.py +1 -0
  3. letta/adapters/simple_llm_request_adapter.py +8 -5
  4. letta/adapters/simple_llm_stream_adapter.py +22 -6
  5. letta/agents/agent_loop.py +10 -3
  6. letta/agents/base_agent.py +4 -1
  7. letta/agents/helpers.py +41 -9
  8. letta/agents/letta_agent.py +11 -10
  9. letta/agents/letta_agent_v2.py +47 -37
  10. letta/agents/letta_agent_v3.py +395 -300
  11. letta/agents/voice_agent.py +8 -6
  12. letta/agents/voice_sleeptime_agent.py +3 -3
  13. letta/constants.py +30 -7
  14. letta/errors.py +20 -0
  15. letta/functions/function_sets/base.py +55 -3
  16. letta/functions/mcp_client/types.py +33 -57
  17. letta/functions/schema_generator.py +135 -23
  18. letta/groups/sleeptime_multi_agent_v3.py +6 -11
  19. letta/groups/sleeptime_multi_agent_v4.py +227 -0
  20. letta/helpers/converters.py +78 -4
  21. letta/helpers/crypto_utils.py +6 -2
  22. letta/interfaces/anthropic_parallel_tool_call_streaming_interface.py +9 -11
  23. letta/interfaces/anthropic_streaming_interface.py +3 -4
  24. letta/interfaces/gemini_streaming_interface.py +4 -6
  25. letta/interfaces/openai_streaming_interface.py +63 -28
  26. letta/llm_api/anthropic_client.py +7 -4
  27. letta/llm_api/deepseek_client.py +6 -4
  28. letta/llm_api/google_ai_client.py +3 -12
  29. letta/llm_api/google_vertex_client.py +1 -1
  30. letta/llm_api/helpers.py +90 -61
  31. letta/llm_api/llm_api_tools.py +4 -1
  32. letta/llm_api/openai.py +12 -12
  33. letta/llm_api/openai_client.py +53 -16
  34. letta/local_llm/constants.py +4 -3
  35. letta/local_llm/json_parser.py +5 -2
  36. letta/local_llm/utils.py +2 -3
  37. letta/log.py +171 -7
  38. letta/orm/agent.py +43 -9
  39. letta/orm/archive.py +4 -0
  40. letta/orm/custom_columns.py +15 -0
  41. letta/orm/identity.py +11 -11
  42. letta/orm/mcp_server.py +9 -0
  43. letta/orm/message.py +6 -1
  44. letta/orm/run_metrics.py +7 -2
  45. letta/orm/sqlalchemy_base.py +2 -2
  46. letta/orm/tool.py +3 -0
  47. letta/otel/tracing.py +2 -0
  48. letta/prompts/prompt_generator.py +7 -2
  49. letta/schemas/agent.py +41 -10
  50. letta/schemas/agent_file.py +3 -0
  51. letta/schemas/archive.py +4 -2
  52. letta/schemas/block.py +2 -1
  53. letta/schemas/enums.py +36 -3
  54. letta/schemas/file.py +3 -3
  55. letta/schemas/folder.py +2 -1
  56. letta/schemas/group.py +2 -1
  57. letta/schemas/identity.py +18 -9
  58. letta/schemas/job.py +3 -1
  59. letta/schemas/letta_message.py +71 -12
  60. letta/schemas/letta_request.py +7 -3
  61. letta/schemas/letta_stop_reason.py +0 -25
  62. letta/schemas/llm_config.py +8 -2
  63. letta/schemas/mcp.py +80 -83
  64. letta/schemas/mcp_server.py +349 -0
  65. letta/schemas/memory.py +20 -8
  66. letta/schemas/message.py +212 -67
  67. letta/schemas/providers/anthropic.py +13 -6
  68. letta/schemas/providers/azure.py +6 -4
  69. letta/schemas/providers/base.py +8 -4
  70. letta/schemas/providers/bedrock.py +6 -2
  71. letta/schemas/providers/cerebras.py +7 -3
  72. letta/schemas/providers/deepseek.py +2 -1
  73. letta/schemas/providers/google_gemini.py +15 -6
  74. letta/schemas/providers/groq.py +2 -1
  75. letta/schemas/providers/lmstudio.py +9 -6
  76. letta/schemas/providers/mistral.py +2 -1
  77. letta/schemas/providers/openai.py +7 -2
  78. letta/schemas/providers/together.py +9 -3
  79. letta/schemas/providers/xai.py +7 -3
  80. letta/schemas/run.py +7 -2
  81. letta/schemas/run_metrics.py +2 -1
  82. letta/schemas/sandbox_config.py +2 -2
  83. letta/schemas/secret.py +3 -158
  84. letta/schemas/source.py +2 -2
  85. letta/schemas/step.py +2 -2
  86. letta/schemas/tool.py +24 -1
  87. letta/schemas/usage.py +0 -1
  88. letta/server/rest_api/app.py +123 -7
  89. letta/server/rest_api/dependencies.py +3 -0
  90. letta/server/rest_api/interface.py +7 -4
  91. letta/server/rest_api/redis_stream_manager.py +16 -1
  92. letta/server/rest_api/routers/v1/__init__.py +7 -0
  93. letta/server/rest_api/routers/v1/agents.py +332 -322
  94. letta/server/rest_api/routers/v1/archives.py +127 -40
  95. letta/server/rest_api/routers/v1/blocks.py +54 -6
  96. letta/server/rest_api/routers/v1/chat_completions.py +146 -0
  97. letta/server/rest_api/routers/v1/folders.py +27 -35
  98. letta/server/rest_api/routers/v1/groups.py +23 -35
  99. letta/server/rest_api/routers/v1/identities.py +24 -10
  100. letta/server/rest_api/routers/v1/internal_runs.py +107 -0
  101. letta/server/rest_api/routers/v1/internal_templates.py +162 -179
  102. letta/server/rest_api/routers/v1/jobs.py +15 -27
  103. letta/server/rest_api/routers/v1/mcp_servers.py +309 -0
  104. letta/server/rest_api/routers/v1/messages.py +23 -34
  105. letta/server/rest_api/routers/v1/organizations.py +6 -27
  106. letta/server/rest_api/routers/v1/providers.py +35 -62
  107. letta/server/rest_api/routers/v1/runs.py +30 -43
  108. letta/server/rest_api/routers/v1/sandbox_configs.py +6 -4
  109. letta/server/rest_api/routers/v1/sources.py +26 -42
  110. letta/server/rest_api/routers/v1/steps.py +16 -29
  111. letta/server/rest_api/routers/v1/tools.py +17 -13
  112. letta/server/rest_api/routers/v1/users.py +5 -17
  113. letta/server/rest_api/routers/v1/voice.py +18 -27
  114. letta/server/rest_api/streaming_response.py +5 -2
  115. letta/server/rest_api/utils.py +187 -25
  116. letta/server/server.py +27 -22
  117. letta/server/ws_api/server.py +5 -4
  118. letta/services/agent_manager.py +148 -26
  119. letta/services/agent_serialization_manager.py +6 -1
  120. letta/services/archive_manager.py +168 -15
  121. letta/services/block_manager.py +14 -4
  122. letta/services/file_manager.py +33 -29
  123. letta/services/group_manager.py +10 -0
  124. letta/services/helpers/agent_manager_helper.py +65 -11
  125. letta/services/identity_manager.py +105 -4
  126. letta/services/job_manager.py +11 -1
  127. letta/services/mcp/base_client.py +2 -2
  128. letta/services/mcp/oauth_utils.py +33 -8
  129. letta/services/mcp_manager.py +174 -78
  130. letta/services/mcp_server_manager.py +1331 -0
  131. letta/services/message_manager.py +109 -4
  132. letta/services/organization_manager.py +4 -4
  133. letta/services/passage_manager.py +9 -25
  134. letta/services/provider_manager.py +91 -15
  135. letta/services/run_manager.py +72 -15
  136. letta/services/sandbox_config_manager.py +45 -3
  137. letta/services/source_manager.py +15 -8
  138. letta/services/step_manager.py +24 -1
  139. letta/services/streaming_service.py +581 -0
  140. letta/services/summarizer/summarizer.py +1 -1
  141. letta/services/tool_executor/core_tool_executor.py +111 -0
  142. letta/services/tool_executor/files_tool_executor.py +5 -3
  143. letta/services/tool_executor/sandbox_tool_executor.py +2 -2
  144. letta/services/tool_executor/tool_execution_manager.py +1 -1
  145. letta/services/tool_manager.py +10 -3
  146. letta/services/tool_sandbox/base.py +61 -1
  147. letta/services/tool_sandbox/local_sandbox.py +1 -3
  148. letta/services/user_manager.py +2 -2
  149. letta/settings.py +49 -5
  150. letta/system.py +14 -5
  151. letta/utils.py +73 -1
  152. letta/validators.py +105 -0
  153. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/METADATA +4 -2
  154. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/RECORD +157 -151
  155. letta/schemas/letta_ping.py +0 -28
  156. letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
  157. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/WHEEL +0 -0
  158. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/entry_points.txt +0 -0
  159. {letta_nightly-0.12.1.dev20251023104211.dist-info → letta_nightly-0.13.0.dev20251024223017.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,6 @@
1
1
  from typing import List, Optional
2
2
 
3
- from fastapi import APIRouter, Body, Depends, HTTPException, Query
3
+ from fastapi import APIRouter, Body, Depends, Query
4
4
  from pydantic import BaseModel
5
5
 
6
6
  from letta.schemas.agent import AgentState, InternalTemplateAgentCreate
@@ -21,11 +21,8 @@ async def create_group(
21
21
  """
22
22
  Create a new multi-agent group with the specified configuration.
23
23
  """
24
- try:
25
- actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
26
- return await server.group_manager.create_group_async(group, actor=actor)
27
- except Exception as e:
28
- raise HTTPException(status_code=500, detail=str(e))
24
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
25
+ return await server.group_manager.create_group_async(group, actor=actor)
29
26
 
30
27
 
31
28
  @router.post("/agents", response_model=AgentState, operation_id="create_internal_template_agent")
@@ -37,11 +34,9 @@ async def create_agent(
37
34
  """
38
35
  Create a new agent with template-related fields.
39
36
  """
40
- try:
41
- actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
42
- return await server.agent_manager.create_agent_async(agent, actor=actor)
43
- except Exception as e:
44
- raise HTTPException(status_code=500, detail=str(e))
37
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
38
+ # Default to ignore_invalid_tools=True for template-based agent creation
39
+ return await server.agent_manager.create_agent_async(agent, actor=actor, ignore_invalid_tools=True)
45
40
 
46
41
 
47
42
  @router.post("/blocks", response_model=Block, operation_id="create_internal_template_block")
@@ -53,12 +48,9 @@ async def create_block(
53
48
  """
54
49
  Create a new block with template-related fields.
55
50
  """
56
- try:
57
- actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
58
- block_obj = Block(**block.model_dump())
59
- return await server.block_manager.create_or_update_block_async(block_obj, actor=actor)
60
- except Exception as e:
61
- raise HTTPException(status_code=500, detail=str(e))
51
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
52
+ block_obj = Block(**block.model_dump())
53
+ return await server.block_manager.create_or_update_block_async(block_obj, actor=actor)
62
54
 
63
55
 
64
56
  @router.post("/blocks/batch", response_model=List[Block], operation_id="create_internal_template_blocks_batch")
@@ -70,16 +62,13 @@ async def create_blocks_batch(
70
62
  """
71
63
  Create multiple blocks with template-related fields.
72
64
  """
73
- try:
74
- actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
75
- created_blocks = []
76
- for block in blocks:
77
- block_obj = Block(**block.model_dump())
78
- created_block = await server.block_manager.create_or_update_block_async(block_obj, actor=actor)
79
- created_blocks.append(created_block)
80
- return created_blocks
81
- except Exception as e:
82
- raise HTTPException(status_code=500, detail=str(e))
65
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
66
+ created_blocks = []
67
+ for block in blocks:
68
+ block_obj = Block(**block.model_dump())
69
+ created_block = await server.block_manager.create_or_update_block_async(block_obj, actor=actor)
70
+ created_blocks.append(created_block)
71
+ return created_blocks
83
72
 
84
73
 
85
74
  class DeploymentEntity(BaseModel):
@@ -122,132 +111,33 @@ async def list_deployment_entities(
122
111
  List all entities (blocks, agents, groups) with the specified deployment_id.
123
112
  Optionally filter by entity types.
124
113
  """
125
- try:
126
- actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
127
-
128
- entities = []
129
-
130
- # Parse entity_types filter - support both array and comma-separated string
131
- allowed_types = {"block", "agent", "group"}
132
- if entity_types is None:
133
- # If no filter specified, include all types
134
- types_to_include = allowed_types
135
- else:
136
- # Handle comma-separated strings in a single item
137
- if len(entity_types) == 1 and "," in entity_types[0]:
138
- entity_types = [t.strip() for t in entity_types[0].split(",")]
139
-
140
- # Validate and filter types
141
- types_to_include = {t.lower() for t in entity_types if t.lower() in allowed_types}
142
- if not types_to_include:
143
- types_to_include = allowed_types # Default to all if invalid types provided
144
-
145
- # Query blocks if requested
146
- if "block" in types_to_include:
147
- from sqlalchemy import select
148
-
149
- from letta.orm.block import Block as BlockModel
150
- from letta.server.db import db_registry
151
-
152
- async with db_registry.async_session() as session:
153
- block_query = select(BlockModel).where(
154
- BlockModel.deployment_id == deployment_id, BlockModel.organization_id == actor.organization_id
155
- )
156
- result = await session.execute(block_query)
157
- blocks = result.scalars().all()
158
-
159
- for block in blocks:
160
- entities.append(
161
- DeploymentEntity(
162
- id=block.id,
163
- type="block",
164
- name=getattr(block, "template_name", None) or getattr(block, "label", None),
165
- description=block.description,
166
- entity_id=getattr(block, "entity_id", None),
167
- project_id=getattr(block, "project_id", None),
168
- )
169
- )
170
-
171
- # Query agents if requested
172
- if "agent" in types_to_include:
173
- from letta.orm.agent import Agent as AgentModel
174
-
175
- async with db_registry.async_session() as session:
176
- agent_query = select(AgentModel).where(
177
- AgentModel.deployment_id == deployment_id, AgentModel.organization_id == actor.organization_id
178
- )
179
- result = await session.execute(agent_query)
180
- agents = result.scalars().all()
181
-
182
- for agent in agents:
183
- entities.append(
184
- DeploymentEntity(
185
- id=agent.id,
186
- type="agent",
187
- name=agent.name,
188
- description=agent.description,
189
- entity_id=getattr(agent, "entity_id", None),
190
- project_id=getattr(agent, "project_id", None),
191
- )
192
- )
193
-
194
- # Query groups if requested
195
- if "group" in types_to_include:
196
- from letta.orm.group import Group as GroupModel
197
-
198
- async with db_registry.async_session() as session:
199
- group_query = select(GroupModel).where(
200
- GroupModel.deployment_id == deployment_id, GroupModel.organization_id == actor.organization_id
201
- )
202
- result = await session.execute(group_query)
203
- groups = result.scalars().all()
204
-
205
- for group in groups:
206
- entities.append(
207
- DeploymentEntity(
208
- id=group.id,
209
- type="group",
210
- name=None, # Groups don't have a name field
211
- description=group.description,
212
- entity_id=getattr(group, "entity_id", None),
213
- project_id=getattr(group, "project_id", None),
214
- )
215
- )
216
-
217
- message = f"Found {len(entities)} entities for deployment {deployment_id}"
218
- if entity_types:
219
- message += f" (filtered by types: {', '.join(types_to_include)})"
220
-
221
- return ListDeploymentEntitiesResponse(entities=entities, total_count=len(entities), deployment_id=deployment_id, message=message)
222
- except Exception as e:
223
- raise HTTPException(status_code=500, detail=str(e))
224
-
225
-
226
- @router.delete("/deployment/{deployment_id}", response_model=DeleteDeploymentResponse, operation_id="delete_deployment")
227
- async def delete_deployment(
228
- deployment_id: str,
229
- server: "SyncServer" = Depends(get_letta_server),
230
- headers: HeaderParams = Depends(get_headers),
231
- ):
232
- """
233
- Delete all entities (blocks, agents, groups) with the specified deployment_id.
234
- Deletion order: blocks -> agents -> groups to maintain referential integrity.
235
- """
236
- try:
237
- actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
238
-
239
- deleted_blocks = []
240
- deleted_agents = []
241
- deleted_groups = []
242
-
243
- # First delete blocks
114
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
115
+
116
+ entities = []
117
+
118
+ # Parse entity_types filter - support both array and comma-separated string
119
+ allowed_types = {"block", "agent", "group"}
120
+ if entity_types is None:
121
+ # If no filter specified, include all types
122
+ types_to_include = allowed_types
123
+ else:
124
+ # Handle comma-separated strings in a single item
125
+ if len(entity_types) == 1 and "," in entity_types[0]:
126
+ entity_types = [t.strip() for t in entity_types[0].split(",")]
127
+
128
+ # Validate and filter types
129
+ types_to_include = {t.lower() for t in entity_types if t.lower() in allowed_types}
130
+ if not types_to_include:
131
+ types_to_include = allowed_types # Default to all if invalid types provided
132
+
133
+ # Query blocks if requested
134
+ if "block" in types_to_include:
244
135
  from sqlalchemy import select
245
136
 
246
137
  from letta.orm.block import Block as BlockModel
247
138
  from letta.server.db import db_registry
248
139
 
249
140
  async with db_registry.async_session() as session:
250
- # Get all blocks with the deployment_id
251
141
  block_query = select(BlockModel).where(
252
142
  BlockModel.deployment_id == deployment_id, BlockModel.organization_id == actor.organization_id
253
143
  )
@@ -255,18 +145,22 @@ async def delete_deployment(
255
145
  blocks = result.scalars().all()
256
146
 
257
147
  for block in blocks:
258
- try:
259
- await server.block_manager.delete_block_async(block.id, actor)
260
- deleted_blocks.append(block.id)
261
- except Exception as e:
262
- # Continue deleting other blocks even if one fails
263
- print(f"Failed to delete block {block.id}: {e}")
264
-
265
- # Then delete agents
148
+ entities.append(
149
+ DeploymentEntity(
150
+ id=block.id,
151
+ type="block",
152
+ name=getattr(block, "template_name", None) or getattr(block, "label", None),
153
+ description=block.description,
154
+ entity_id=getattr(block, "entity_id", None),
155
+ project_id=getattr(block, "project_id", None),
156
+ )
157
+ )
158
+
159
+ # Query agents if requested
160
+ if "agent" in types_to_include:
266
161
  from letta.orm.agent import Agent as AgentModel
267
162
 
268
163
  async with db_registry.async_session() as session:
269
- # Get all agents with the deployment_id
270
164
  agent_query = select(AgentModel).where(
271
165
  AgentModel.deployment_id == deployment_id, AgentModel.organization_id == actor.organization_id
272
166
  )
@@ -274,18 +168,22 @@ async def delete_deployment(
274
168
  agents = result.scalars().all()
275
169
 
276
170
  for agent in agents:
277
- try:
278
- await server.agent_manager.delete_agent_async(agent.id, actor)
279
- deleted_agents.append(agent.id)
280
- except Exception as e:
281
- # Continue deleting other agents even if one fails
282
- print(f"Failed to delete agent {agent.id}: {e}")
283
-
284
- # Finally delete groups
171
+ entities.append(
172
+ DeploymentEntity(
173
+ id=agent.id,
174
+ type="agent",
175
+ name=agent.name,
176
+ description=agent.description,
177
+ entity_id=getattr(agent, "entity_id", None),
178
+ project_id=getattr(agent, "project_id", None),
179
+ )
180
+ )
181
+
182
+ # Query groups if requested
183
+ if "group" in types_to_include:
285
184
  from letta.orm.group import Group as GroupModel
286
185
 
287
186
  async with db_registry.async_session() as session:
288
- # Get all groups with the deployment_id
289
187
  group_query = select(GroupModel).where(
290
188
  GroupModel.deployment_id == deployment_id, GroupModel.organization_id == actor.organization_id
291
189
  )
@@ -293,18 +191,103 @@ async def delete_deployment(
293
191
  groups = result.scalars().all()
294
192
 
295
193
  for group in groups:
296
- try:
297
- await server.group_manager.delete_group_async(group.id, actor)
298
- deleted_groups.append(group.id)
299
- except Exception as e:
300
- # Continue deleting other groups even if one fails
301
- print(f"Failed to delete group {group.id}: {e}")
302
-
303
- total_deleted = len(deleted_blocks) + len(deleted_agents) + len(deleted_groups)
304
- message = f"Successfully deleted {total_deleted} entities from deployment {deployment_id}"
305
-
306
- return DeleteDeploymentResponse(
307
- deleted_blocks=deleted_blocks, deleted_agents=deleted_agents, deleted_groups=deleted_groups, message=message
194
+ entities.append(
195
+ DeploymentEntity(
196
+ id=group.id,
197
+ type="group",
198
+ name=None, # Groups don't have a name field
199
+ description=group.description,
200
+ entity_id=getattr(group, "entity_id", None),
201
+ project_id=getattr(group, "project_id", None),
202
+ )
203
+ )
204
+
205
+ message = f"Found {len(entities)} entities for deployment {deployment_id}"
206
+ if entity_types:
207
+ message += f" (filtered by types: {', '.join(types_to_include)})"
208
+
209
+ return ListDeploymentEntitiesResponse(entities=entities, total_count=len(entities), deployment_id=deployment_id, message=message)
210
+
211
+
212
+ @router.delete("/deployment/{deployment_id}", response_model=DeleteDeploymentResponse, operation_id="delete_deployment")
213
+ async def delete_deployment(
214
+ deployment_id: str,
215
+ server: "SyncServer" = Depends(get_letta_server),
216
+ headers: HeaderParams = Depends(get_headers),
217
+ ):
218
+ """
219
+ Delete all entities (blocks, agents, groups) with the specified deployment_id.
220
+ Deletion order: blocks -> agents -> groups to maintain referential integrity.
221
+ """
222
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
223
+
224
+ deleted_blocks = []
225
+ deleted_agents = []
226
+ deleted_groups = []
227
+
228
+ # First delete blocks
229
+ from sqlalchemy import select
230
+
231
+ from letta.orm.block import Block as BlockModel
232
+ from letta.server.db import db_registry
233
+
234
+ async with db_registry.async_session() as session:
235
+ # Get all blocks with the deployment_id
236
+ block_query = select(BlockModel).where(
237
+ BlockModel.deployment_id == deployment_id, BlockModel.organization_id == actor.organization_id
238
+ )
239
+ result = await session.execute(block_query)
240
+ blocks = result.scalars().all()
241
+
242
+ for block in blocks:
243
+ try:
244
+ await server.block_manager.delete_block_async(block.id, actor)
245
+ deleted_blocks.append(block.id)
246
+ except Exception as e:
247
+ # Continue deleting other blocks even if one fails
248
+ print(f"Failed to delete block {block.id}: {e}")
249
+
250
+ # Then delete agents
251
+ from letta.orm.agent import Agent as AgentModel
252
+
253
+ async with db_registry.async_session() as session:
254
+ # Get all agents with the deployment_id
255
+ agent_query = select(AgentModel).where(
256
+ AgentModel.deployment_id == deployment_id, AgentModel.organization_id == actor.organization_id
257
+ )
258
+ result = await session.execute(agent_query)
259
+ agents = result.scalars().all()
260
+
261
+ for agent in agents:
262
+ try:
263
+ await server.agent_manager.delete_agent_async(agent.id, actor)
264
+ deleted_agents.append(agent.id)
265
+ except Exception as e:
266
+ # Continue deleting other agents even if one fails
267
+ print(f"Failed to delete agent {agent.id}: {e}")
268
+
269
+ # Finally delete groups
270
+ from letta.orm.group import Group as GroupModel
271
+
272
+ async with db_registry.async_session() as session:
273
+ # Get all groups with the deployment_id
274
+ group_query = select(GroupModel).where(
275
+ GroupModel.deployment_id == deployment_id, GroupModel.organization_id == actor.organization_id
308
276
  )
309
- except Exception as e:
310
- raise HTTPException(status_code=500, detail=str(e))
277
+ result = await session.execute(group_query)
278
+ groups = result.scalars().all()
279
+
280
+ for group in groups:
281
+ try:
282
+ await server.group_manager.delete_group_async(group.id, actor)
283
+ deleted_groups.append(group.id)
284
+ except Exception as e:
285
+ # Continue deleting other groups even if one fails
286
+ print(f"Failed to delete group {group.id}: {e}")
287
+
288
+ total_deleted = len(deleted_blocks) + len(deleted_agents) + len(deleted_groups)
289
+ message = f"Successfully deleted {total_deleted} entities from deployment {deployment_id}"
290
+
291
+ return DeleteDeploymentResponse(
292
+ deleted_blocks=deleted_blocks, deleted_agents=deleted_agents, deleted_groups=deleted_groups, message=message
293
+ )
@@ -1,13 +1,14 @@
1
1
  from typing import List, Literal, Optional
2
2
 
3
- from fastapi import APIRouter, Depends, HTTPException, Query
3
+ from fastapi import APIRouter, Depends, Query
4
4
 
5
- from letta.orm.errors import NoResultFound
5
+ from letta.errors import LettaInvalidArgumentError
6
6
  from letta.schemas.enums import JobStatus
7
- from letta.schemas.job import Job
7
+ from letta.schemas.job import Job, JobBase
8
8
  from letta.server.rest_api.dependencies import HeaderParams, get_headers, get_letta_server
9
9
  from letta.server.server import SyncServer
10
10
  from letta.settings import settings
11
+ from letta.validators import JobId
11
12
 
12
13
  router = APIRouter(prefix="/jobs", tags=["jobs"])
13
14
 
@@ -88,7 +89,7 @@ async def list_active_jobs(
88
89
 
89
90
  @router.get("/{job_id}", response_model=Job, operation_id="retrieve_job")
90
91
  async def retrieve_job(
91
- job_id: str,
92
+ job_id: JobId,
92
93
  headers: HeaderParams = Depends(get_headers),
93
94
  server: "SyncServer" = Depends(get_letta_server),
94
95
  ):
@@ -96,16 +97,12 @@ async def retrieve_job(
96
97
  Get the status of a job.
97
98
  """
98
99
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
99
-
100
- try:
101
- return await server.job_manager.get_job_by_id_async(job_id=job_id, actor=actor)
102
- except NoResultFound:
103
- raise HTTPException(status_code=404, detail="Job not found")
100
+ return await server.job_manager.get_job_by_id_async(job_id=job_id, actor=actor)
104
101
 
105
102
 
106
103
  @router.patch("/{job_id}/cancel", response_model=Job, operation_id="cancel_job")
107
104
  async def cancel_job(
108
- job_id: str,
105
+ job_id: JobId,
109
106
  headers: HeaderParams = Depends(get_headers),
110
107
  server: "SyncServer" = Depends(get_letta_server),
111
108
  ):
@@ -117,24 +114,20 @@ async def cancel_job(
117
114
  """
118
115
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
119
116
  if not settings.track_agent_run:
120
- raise HTTPException(status_code=400, detail="Agent run tracking is disabled")
121
-
122
- try:
123
- # First check if the job exists and is in a cancellable state
124
- existing_job = await server.job_manager.get_job_by_id_async(job_id=job_id, actor=actor)
117
+ raise LettaInvalidArgumentError("Agent run tracking is disabled")
125
118
 
126
- if existing_job.status.is_terminal:
127
- return False
119
+ # First check if the job exists and is in a cancellable state
120
+ existing_job = await server.job_manager.get_job_by_id_async(job_id=job_id, actor=actor)
128
121
 
129
- return await server.job_manager.safe_update_job_status_async(job_id=job_id, new_status=JobStatus.cancelled, actor=actor)
122
+ if existing_job.status.is_terminal:
123
+ return False
130
124
 
131
- except NoResultFound:
132
- raise HTTPException(status_code=404, detail="Job not found")
125
+ return await server.job_manager.safe_update_job_status_async(job_id=job_id, new_status=JobStatus.cancelled, actor=actor)
133
126
 
134
127
 
135
128
  @router.delete("/{job_id}", response_model=Job, operation_id="delete_job")
136
129
  async def delete_job(
137
- job_id: str,
130
+ job_id: JobId,
138
131
  headers: HeaderParams = Depends(get_headers),
139
132
  server: "SyncServer" = Depends(get_letta_server),
140
133
  ):
@@ -142,9 +135,4 @@ async def delete_job(
142
135
  Delete a job by its job_id.
143
136
  """
144
137
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
145
-
146
- try:
147
- job = await server.job_manager.delete_job_by_id_async(job_id=job_id, actor=actor)
148
- return job
149
- except NoResultFound:
150
- raise HTTPException(status_code=404, detail="Job not found")
138
+ return await server.job_manager.delete_job_by_id_async(job_id=job_id, actor=actor)