letta-nightly 0.12.1.dev20251024104217__py3-none-any.whl → 0.13.0.dev20251025104015__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.
- letta/__init__.py +2 -3
- letta/adapters/letta_llm_adapter.py +1 -0
- letta/adapters/simple_llm_request_adapter.py +8 -5
- letta/adapters/simple_llm_stream_adapter.py +22 -6
- letta/agents/agent_loop.py +10 -3
- letta/agents/base_agent.py +4 -1
- letta/agents/helpers.py +41 -9
- letta/agents/letta_agent.py +11 -10
- letta/agents/letta_agent_v2.py +47 -37
- letta/agents/letta_agent_v3.py +395 -300
- letta/agents/voice_agent.py +8 -6
- letta/agents/voice_sleeptime_agent.py +3 -3
- letta/constants.py +30 -7
- letta/errors.py +20 -0
- letta/functions/function_sets/base.py +55 -3
- letta/functions/mcp_client/types.py +33 -57
- letta/functions/schema_generator.py +135 -23
- letta/groups/sleeptime_multi_agent_v3.py +6 -11
- letta/groups/sleeptime_multi_agent_v4.py +227 -0
- letta/helpers/converters.py +78 -4
- letta/helpers/crypto_utils.py +6 -2
- letta/interfaces/anthropic_parallel_tool_call_streaming_interface.py +9 -11
- letta/interfaces/anthropic_streaming_interface.py +3 -4
- letta/interfaces/gemini_streaming_interface.py +4 -6
- letta/interfaces/openai_streaming_interface.py +63 -28
- letta/llm_api/anthropic_client.py +7 -4
- letta/llm_api/deepseek_client.py +6 -4
- letta/llm_api/google_ai_client.py +3 -12
- letta/llm_api/google_vertex_client.py +1 -1
- letta/llm_api/helpers.py +90 -61
- letta/llm_api/llm_api_tools.py +4 -1
- letta/llm_api/openai.py +12 -12
- letta/llm_api/openai_client.py +53 -16
- letta/local_llm/constants.py +4 -3
- letta/local_llm/json_parser.py +5 -2
- letta/local_llm/utils.py +2 -3
- letta/log.py +171 -7
- letta/orm/agent.py +43 -9
- letta/orm/archive.py +4 -0
- letta/orm/custom_columns.py +15 -0
- letta/orm/identity.py +11 -11
- letta/orm/mcp_server.py +9 -0
- letta/orm/message.py +6 -1
- letta/orm/run_metrics.py +7 -2
- letta/orm/sqlalchemy_base.py +2 -2
- letta/orm/tool.py +3 -0
- letta/otel/tracing.py +2 -0
- letta/prompts/prompt_generator.py +7 -2
- letta/schemas/agent.py +41 -10
- letta/schemas/agent_file.py +3 -0
- letta/schemas/archive.py +4 -2
- letta/schemas/block.py +2 -1
- letta/schemas/enums.py +36 -3
- letta/schemas/file.py +3 -3
- letta/schemas/folder.py +2 -1
- letta/schemas/group.py +2 -1
- letta/schemas/identity.py +18 -9
- letta/schemas/job.py +3 -1
- letta/schemas/letta_message.py +71 -12
- letta/schemas/letta_request.py +7 -3
- letta/schemas/letta_stop_reason.py +0 -25
- letta/schemas/llm_config.py +8 -2
- letta/schemas/mcp.py +80 -83
- letta/schemas/mcp_server.py +349 -0
- letta/schemas/memory.py +20 -8
- letta/schemas/message.py +212 -67
- letta/schemas/providers/anthropic.py +13 -6
- letta/schemas/providers/azure.py +6 -4
- letta/schemas/providers/base.py +8 -4
- letta/schemas/providers/bedrock.py +6 -2
- letta/schemas/providers/cerebras.py +7 -3
- letta/schemas/providers/deepseek.py +2 -1
- letta/schemas/providers/google_gemini.py +15 -6
- letta/schemas/providers/groq.py +2 -1
- letta/schemas/providers/lmstudio.py +9 -6
- letta/schemas/providers/mistral.py +2 -1
- letta/schemas/providers/openai.py +7 -2
- letta/schemas/providers/together.py +9 -3
- letta/schemas/providers/xai.py +7 -3
- letta/schemas/run.py +7 -2
- letta/schemas/run_metrics.py +2 -1
- letta/schemas/sandbox_config.py +2 -2
- letta/schemas/secret.py +3 -158
- letta/schemas/source.py +2 -2
- letta/schemas/step.py +2 -2
- letta/schemas/tool.py +24 -1
- letta/schemas/usage.py +0 -1
- letta/server/rest_api/app.py +123 -7
- letta/server/rest_api/dependencies.py +3 -0
- letta/server/rest_api/interface.py +7 -4
- letta/server/rest_api/redis_stream_manager.py +16 -1
- letta/server/rest_api/routers/v1/__init__.py +7 -0
- letta/server/rest_api/routers/v1/agents.py +332 -322
- letta/server/rest_api/routers/v1/archives.py +127 -40
- letta/server/rest_api/routers/v1/blocks.py +54 -6
- letta/server/rest_api/routers/v1/chat_completions.py +146 -0
- letta/server/rest_api/routers/v1/folders.py +27 -35
- letta/server/rest_api/routers/v1/groups.py +23 -35
- letta/server/rest_api/routers/v1/identities.py +24 -10
- letta/server/rest_api/routers/v1/internal_runs.py +107 -0
- letta/server/rest_api/routers/v1/internal_templates.py +162 -179
- letta/server/rest_api/routers/v1/jobs.py +15 -27
- letta/server/rest_api/routers/v1/mcp_servers.py +309 -0
- letta/server/rest_api/routers/v1/messages.py +23 -34
- letta/server/rest_api/routers/v1/organizations.py +6 -27
- letta/server/rest_api/routers/v1/providers.py +35 -62
- letta/server/rest_api/routers/v1/runs.py +30 -43
- letta/server/rest_api/routers/v1/sandbox_configs.py +6 -4
- letta/server/rest_api/routers/v1/sources.py +26 -42
- letta/server/rest_api/routers/v1/steps.py +16 -29
- letta/server/rest_api/routers/v1/tools.py +17 -13
- letta/server/rest_api/routers/v1/users.py +5 -17
- letta/server/rest_api/routers/v1/voice.py +18 -27
- letta/server/rest_api/streaming_response.py +5 -2
- letta/server/rest_api/utils.py +187 -25
- letta/server/server.py +27 -22
- letta/server/ws_api/server.py +5 -4
- letta/services/agent_manager.py +148 -26
- letta/services/agent_serialization_manager.py +6 -1
- letta/services/archive_manager.py +168 -15
- letta/services/block_manager.py +14 -4
- letta/services/file_manager.py +33 -29
- letta/services/group_manager.py +10 -0
- letta/services/helpers/agent_manager_helper.py +65 -11
- letta/services/identity_manager.py +105 -4
- letta/services/job_manager.py +11 -1
- letta/services/mcp/base_client.py +2 -2
- letta/services/mcp/oauth_utils.py +33 -8
- letta/services/mcp_manager.py +174 -78
- letta/services/mcp_server_manager.py +1331 -0
- letta/services/message_manager.py +109 -4
- letta/services/organization_manager.py +4 -4
- letta/services/passage_manager.py +9 -25
- letta/services/provider_manager.py +91 -15
- letta/services/run_manager.py +72 -15
- letta/services/sandbox_config_manager.py +45 -3
- letta/services/source_manager.py +15 -8
- letta/services/step_manager.py +24 -1
- letta/services/streaming_service.py +581 -0
- letta/services/summarizer/summarizer.py +1 -1
- letta/services/tool_executor/core_tool_executor.py +111 -0
- letta/services/tool_executor/files_tool_executor.py +5 -3
- letta/services/tool_executor/sandbox_tool_executor.py +2 -2
- letta/services/tool_executor/tool_execution_manager.py +1 -1
- letta/services/tool_manager.py +10 -3
- letta/services/tool_sandbox/base.py +61 -1
- letta/services/tool_sandbox/local_sandbox.py +1 -3
- letta/services/user_manager.py +2 -2
- letta/settings.py +49 -5
- letta/system.py +14 -5
- letta/utils.py +73 -1
- letta/validators.py +105 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/METADATA +4 -2
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/RECORD +157 -151
- letta/schemas/letta_ping.py +0 -28
- letta/server/rest_api/routers/openai/chat_completions/__init__.py +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/WHEEL +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.dist-info}/entry_points.txt +0 -0
- {letta_nightly-0.12.1.dev20251024104217.dist-info → letta_nightly-0.13.0.dev20251025104015.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,
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
310
|
-
|
|
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,
|
|
3
|
+
from fastapi import APIRouter, Depends, Query
|
|
4
4
|
|
|
5
|
-
from letta.
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
122
|
+
if existing_job.status.is_terminal:
|
|
123
|
+
return False
|
|
130
124
|
|
|
131
|
-
|
|
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:
|
|
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)
|