letta-nightly 0.11.7.dev20251006104136__py3-none-any.whl → 0.11.7.dev20251008104128__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 (145) hide show
  1. letta/adapters/letta_llm_adapter.py +1 -0
  2. letta/adapters/letta_llm_request_adapter.py +0 -1
  3. letta/adapters/letta_llm_stream_adapter.py +7 -2
  4. letta/adapters/simple_llm_request_adapter.py +88 -0
  5. letta/adapters/simple_llm_stream_adapter.py +192 -0
  6. letta/agents/agent_loop.py +6 -0
  7. letta/agents/ephemeral_summary_agent.py +2 -1
  8. letta/agents/helpers.py +142 -6
  9. letta/agents/letta_agent.py +13 -33
  10. letta/agents/letta_agent_batch.py +2 -4
  11. letta/agents/letta_agent_v2.py +87 -77
  12. letta/agents/letta_agent_v3.py +899 -0
  13. letta/agents/voice_agent.py +2 -6
  14. letta/constants.py +8 -4
  15. letta/errors.py +40 -0
  16. letta/functions/function_sets/base.py +84 -4
  17. letta/functions/function_sets/multi_agent.py +0 -3
  18. letta/functions/schema_generator.py +113 -71
  19. letta/groups/dynamic_multi_agent.py +3 -2
  20. letta/groups/helpers.py +1 -2
  21. letta/groups/round_robin_multi_agent.py +3 -2
  22. letta/groups/sleeptime_multi_agent.py +3 -2
  23. letta/groups/sleeptime_multi_agent_v2.py +1 -1
  24. letta/groups/sleeptime_multi_agent_v3.py +17 -17
  25. letta/groups/supervisor_multi_agent.py +84 -80
  26. letta/helpers/converters.py +3 -0
  27. letta/helpers/message_helper.py +4 -0
  28. letta/helpers/tool_rule_solver.py +92 -5
  29. letta/interfaces/anthropic_streaming_interface.py +409 -0
  30. letta/interfaces/gemini_streaming_interface.py +296 -0
  31. letta/interfaces/openai_streaming_interface.py +752 -1
  32. letta/llm_api/anthropic_client.py +126 -16
  33. letta/llm_api/bedrock_client.py +4 -2
  34. letta/llm_api/deepseek_client.py +4 -1
  35. letta/llm_api/google_vertex_client.py +123 -42
  36. letta/llm_api/groq_client.py +4 -1
  37. letta/llm_api/llm_api_tools.py +11 -4
  38. letta/llm_api/llm_client_base.py +6 -2
  39. letta/llm_api/openai.py +32 -2
  40. letta/llm_api/openai_client.py +423 -18
  41. letta/llm_api/xai_client.py +4 -1
  42. letta/main.py +9 -5
  43. letta/memory.py +1 -0
  44. letta/orm/__init__.py +1 -1
  45. letta/orm/agent.py +10 -0
  46. letta/orm/block.py +7 -16
  47. letta/orm/blocks_agents.py +8 -2
  48. letta/orm/files_agents.py +2 -0
  49. letta/orm/job.py +7 -5
  50. letta/orm/mcp_oauth.py +1 -0
  51. letta/orm/message.py +21 -6
  52. letta/orm/organization.py +2 -0
  53. letta/orm/provider.py +6 -2
  54. letta/orm/run.py +71 -0
  55. letta/orm/sandbox_config.py +7 -1
  56. letta/orm/sqlalchemy_base.py +0 -306
  57. letta/orm/step.py +6 -5
  58. letta/orm/step_metrics.py +5 -5
  59. letta/otel/tracing.py +28 -3
  60. letta/plugins/defaults.py +4 -4
  61. letta/prompts/system_prompts/__init__.py +2 -0
  62. letta/prompts/system_prompts/letta_v1.py +25 -0
  63. letta/schemas/agent.py +3 -2
  64. letta/schemas/agent_file.py +9 -3
  65. letta/schemas/block.py +23 -10
  66. letta/schemas/enums.py +21 -2
  67. letta/schemas/job.py +17 -4
  68. letta/schemas/letta_message_content.py +71 -2
  69. letta/schemas/letta_stop_reason.py +5 -5
  70. letta/schemas/llm_config.py +53 -3
  71. letta/schemas/memory.py +1 -1
  72. letta/schemas/message.py +504 -117
  73. letta/schemas/openai/responses_request.py +64 -0
  74. letta/schemas/providers/__init__.py +2 -0
  75. letta/schemas/providers/anthropic.py +16 -0
  76. letta/schemas/providers/ollama.py +115 -33
  77. letta/schemas/providers/openrouter.py +52 -0
  78. letta/schemas/providers/vllm.py +2 -1
  79. letta/schemas/run.py +48 -42
  80. letta/schemas/step.py +2 -2
  81. letta/schemas/step_metrics.py +1 -1
  82. letta/schemas/tool.py +15 -107
  83. letta/schemas/tool_rule.py +88 -5
  84. letta/serialize_schemas/marshmallow_agent.py +1 -0
  85. letta/server/db.py +86 -408
  86. letta/server/rest_api/app.py +61 -10
  87. letta/server/rest_api/dependencies.py +14 -0
  88. letta/server/rest_api/redis_stream_manager.py +19 -8
  89. letta/server/rest_api/routers/v1/agents.py +364 -292
  90. letta/server/rest_api/routers/v1/blocks.py +14 -20
  91. letta/server/rest_api/routers/v1/identities.py +45 -110
  92. letta/server/rest_api/routers/v1/internal_templates.py +21 -0
  93. letta/server/rest_api/routers/v1/jobs.py +23 -6
  94. letta/server/rest_api/routers/v1/messages.py +1 -1
  95. letta/server/rest_api/routers/v1/runs.py +126 -85
  96. letta/server/rest_api/routers/v1/sandbox_configs.py +10 -19
  97. letta/server/rest_api/routers/v1/tools.py +281 -594
  98. letta/server/rest_api/routers/v1/voice.py +1 -1
  99. letta/server/rest_api/streaming_response.py +29 -29
  100. letta/server/rest_api/utils.py +122 -64
  101. letta/server/server.py +160 -887
  102. letta/services/agent_manager.py +236 -919
  103. letta/services/agent_serialization_manager.py +16 -0
  104. letta/services/archive_manager.py +0 -100
  105. letta/services/block_manager.py +211 -168
  106. letta/services/file_manager.py +1 -1
  107. letta/services/files_agents_manager.py +24 -33
  108. letta/services/group_manager.py +0 -142
  109. letta/services/helpers/agent_manager_helper.py +7 -2
  110. letta/services/helpers/run_manager_helper.py +85 -0
  111. letta/services/job_manager.py +96 -411
  112. letta/services/lettuce/__init__.py +6 -0
  113. letta/services/lettuce/lettuce_client_base.py +86 -0
  114. letta/services/mcp_manager.py +38 -6
  115. letta/services/message_manager.py +165 -362
  116. letta/services/organization_manager.py +0 -36
  117. letta/services/passage_manager.py +0 -345
  118. letta/services/provider_manager.py +0 -80
  119. letta/services/run_manager.py +301 -0
  120. letta/services/sandbox_config_manager.py +0 -234
  121. letta/services/step_manager.py +62 -39
  122. letta/services/summarizer/summarizer.py +9 -7
  123. letta/services/telemetry_manager.py +0 -16
  124. letta/services/tool_executor/builtin_tool_executor.py +35 -0
  125. letta/services/tool_executor/core_tool_executor.py +397 -2
  126. letta/services/tool_executor/files_tool_executor.py +3 -3
  127. letta/services/tool_executor/multi_agent_tool_executor.py +30 -15
  128. letta/services/tool_executor/tool_execution_manager.py +6 -8
  129. letta/services/tool_executor/tool_executor_base.py +3 -3
  130. letta/services/tool_manager.py +85 -339
  131. letta/services/tool_sandbox/base.py +24 -13
  132. letta/services/tool_sandbox/e2b_sandbox.py +16 -1
  133. letta/services/tool_schema_generator.py +123 -0
  134. letta/services/user_manager.py +0 -99
  135. letta/settings.py +20 -4
  136. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/METADATA +3 -5
  137. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/RECORD +140 -132
  138. letta/agents/temporal/activities/__init__.py +0 -4
  139. letta/agents/temporal/activities/example_activity.py +0 -7
  140. letta/agents/temporal/activities/prepare_messages.py +0 -10
  141. letta/agents/temporal/temporal_agent_workflow.py +0 -56
  142. letta/agents/temporal/types.py +0 -25
  143. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/WHEEL +0 -0
  144. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/entry_points.txt +0 -0
  145. {letta_nightly-0.11.7.dev20251006104136.dist-info → letta_nightly-0.11.7.dev20251008104128.dist-info}/licenses/LICENSE +0 -0
@@ -7,7 +7,7 @@ from pydantic import Field
7
7
  from letta.data_sources.redis_client import NoopAsyncRedisClient, get_redis_client
8
8
  from letta.helpers.datetime_helpers import get_utc_time
9
9
  from letta.orm.errors import NoResultFound
10
- from letta.schemas.enums import JobStatus, JobType
10
+ from letta.schemas.enums import RunStatus
11
11
  from letta.schemas.letta_message import LettaMessageUnion
12
12
  from letta.schemas.letta_request import RetrieveStreamRequest
13
13
  from letta.schemas.letta_stop_reason import StopReasonType
@@ -22,81 +22,124 @@ from letta.server.rest_api.streaming_response import (
22
22
  cancellation_aware_stream_wrapper,
23
23
  )
24
24
  from letta.server.server import SyncServer
25
+ from letta.services.lettuce import LettuceClient
26
+ from letta.services.run_manager import RunManager
25
27
  from letta.settings import settings
26
28
 
27
29
  router = APIRouter(prefix="/runs", tags=["runs"])
28
30
 
29
31
 
32
+ def convert_statuses_to_enum(statuses: Optional[List[str]]) -> Optional[List[RunStatus]]:
33
+ """Convert a list of status strings to RunStatus enum values.
34
+
35
+ Args:
36
+ statuses: List of status strings or None
37
+
38
+ Returns:
39
+ List of RunStatus enum values or None if input is None
40
+ """
41
+ if statuses is None:
42
+ return None
43
+ return [RunStatus(status) for status in statuses]
44
+
45
+
30
46
  @router.get("/", response_model=List[Run], operation_id="list_runs")
31
- def list_runs(
47
+ async def list_runs(
32
48
  server: "SyncServer" = Depends(get_letta_server),
33
- agent_ids: Optional[List[str]] = Query(None, description="The unique identifier of the agent associated with the run."),
49
+ agent_id: Optional[str] = Query(None, description="The unique identifier of the agent associated with the run."),
50
+ agent_ids: Optional[List[str]] = Query(
51
+ None,
52
+ description="The unique identifiers of the agents associated with the run. Deprecated in favor of agent_id field.",
53
+ deprecated=True,
54
+ ),
55
+ statuses: Optional[List[str]] = Query(None, description="Filter runs by status. Can specify multiple statuses."),
34
56
  background: Optional[bool] = Query(None, description="If True, filters for runs that were created in background mode."),
35
57
  stop_reason: Optional[StopReasonType] = Query(None, description="Filter runs by stop reason."),
36
- after: Optional[str] = Query(None, description="Cursor for pagination"),
37
- before: Optional[str] = Query(None, description="Cursor for pagination"),
38
- limit: Optional[int] = Query(50, description="Maximum number of runs to return"),
58
+ before: Optional[str] = Query(
59
+ None, description="Run ID cursor for pagination. Returns runs that come before this run ID in the specified sort order"
60
+ ),
61
+ after: Optional[str] = Query(
62
+ None, description="Run ID cursor for pagination. Returns runs that come after this run ID in the specified sort order"
63
+ ),
64
+ limit: Optional[int] = Query(100, description="Maximum number of runs to return"),
65
+ order: Literal["asc", "desc"] = Query(
66
+ "desc", description="Sort order for runs by creation time. 'asc' for oldest first, 'desc' for newest first"
67
+ ),
68
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
39
69
  active: bool = Query(False, description="Filter for active runs."),
40
70
  ascending: bool = Query(
41
71
  False,
42
- description="Whether to sort agents oldest to newest (True) or newest to oldest (False, default)",
72
+ description="Whether to sort agents oldest to newest (True) or newest to oldest (False, default). Deprecated in favor of order field.",
73
+ deprecated=True,
43
74
  ),
44
75
  headers: HeaderParams = Depends(get_headers),
45
76
  ):
46
77
  """
47
78
  List all runs.
48
79
  """
49
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
50
- statuses = None
51
- if active:
52
- statuses = [JobStatus.created, JobStatus.running]
53
-
54
- runs = [
55
- Run.from_job(job)
56
- for job in server.job_manager.list_jobs(
57
- actor=actor,
58
- statuses=statuses,
59
- job_type=JobType.RUN,
60
- limit=limit,
61
- before=before,
62
- after=after,
63
- ascending=False,
64
- stop_reason=stop_reason,
65
- )
66
- ]
67
- if agent_ids:
68
- runs = [run for run in runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
69
- if background is not None:
70
- runs = [run for run in runs if "background" in run.metadata and run.metadata["background"] == background]
80
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
81
+ runs_manager = RunManager()
82
+
83
+ # Handle backwards compatibility: if statuses not provided but active=True, filter by active statuses
84
+ if statuses is None and active:
85
+ statuses = [RunStatus.created, RunStatus.running]
86
+
87
+ if agent_id:
88
+ # NOTE: we are deprecating agent_ids so this will the primary path soon
89
+ agent_ids = [agent_id]
90
+
91
+ # Handle backward compatibility: if ascending is explicitly set, use it; otherwise use order
92
+ if ascending is not False:
93
+ # ascending was explicitly set to True
94
+ sort_ascending = ascending
95
+ else:
96
+ # Use the new order parameter
97
+ sort_ascending = order == "asc"
98
+
99
+ # Convert string statuses to RunStatus enum
100
+ parsed_statuses = convert_statuses_to_enum(statuses)
101
+
102
+ runs = await runs_manager.list_runs(
103
+ actor=actor,
104
+ agent_ids=agent_ids,
105
+ statuses=parsed_statuses,
106
+ limit=limit,
107
+ before=before,
108
+ after=after,
109
+ ascending=sort_ascending,
110
+ stop_reason=stop_reason,
111
+ background=background,
112
+ )
71
113
  return runs
72
114
 
73
115
 
74
116
  @router.get("/active", response_model=List[Run], operation_id="list_active_runs", deprecated=True)
75
- def list_active_runs(
117
+ async def list_active_runs(
76
118
  server: "SyncServer" = Depends(get_letta_server),
77
- agent_ids: Optional[List[str]] = Query(None, description="The unique identifier of the agent associated with the run."),
119
+ agent_id: Optional[str] = Query(None, description="The unique identifier of the agent associated with the run."),
78
120
  background: Optional[bool] = Query(None, description="If True, filters for runs that were created in background mode."),
79
121
  headers: HeaderParams = Depends(get_headers),
80
122
  ):
81
123
  """
82
124
  List all active runs.
83
125
  """
84
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
85
-
86
- active_runs = server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running], job_type=JobType.RUN)
87
- active_runs = [Run.from_job(job) for job in active_runs]
126
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
127
+ runs_manager = RunManager()
88
128
 
89
- if agent_ids:
90
- active_runs = [run for run in active_runs if "agent_id" in run.metadata and run.metadata["agent_id"] in agent_ids]
129
+ if agent_id:
130
+ agent_ids = [agent_id]
131
+ else:
132
+ agent_ids = None
91
133
 
92
- if background is not None:
93
- active_runs = [run for run in active_runs if "background" in run.metadata and run.metadata["background"] == background]
134
+ active_runs = await runs_manager.list_runs(
135
+ actor=actor, statuses=[RunStatus.created, RunStatus.running], agent_ids=agent_ids, background=background
136
+ )
94
137
 
95
138
  return active_runs
96
139
 
97
140
 
98
141
  @router.get("/{run_id}", response_model=Run, operation_id="retrieve_run")
99
- def retrieve_run(
142
+ async def retrieve_run(
100
143
  run_id: str,
101
144
  headers: HeaderParams = Depends(get_headers),
102
145
  server: "SyncServer" = Depends(get_letta_server),
@@ -104,11 +147,29 @@ def retrieve_run(
104
147
  """
105
148
  Get the status of a run.
106
149
  """
107
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
150
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
151
+ runs_manager = RunManager()
108
152
 
109
153
  try:
110
- job = server.job_manager.get_job_by_id(job_id=run_id, actor=actor)
111
- return Run.from_job(job)
154
+ run = await runs_manager.get_run_by_id(run_id=run_id, actor=actor)
155
+
156
+ use_lettuce = run.metadata and run.metadata.get("lettuce")
157
+ if use_lettuce and run.status not in [RunStatus.completed, RunStatus.failed, RunStatus.cancelled]:
158
+ lettuce_client = await LettuceClient.create()
159
+ status = await lettuce_client.get_status()
160
+
161
+ # Map the status to our enum
162
+ run_status = run.status
163
+ if status == "RUNNING":
164
+ run_status = RunStatus.running
165
+ elif status == "COMPLETED":
166
+ run_status = RunStatus.completed
167
+ elif status == "FAILED":
168
+ run_status = RunStatus.failed
169
+ elif status == "CANCELLED":
170
+ run_status = RunStatus.cancelled
171
+ run.status = run_status
172
+ return run
112
173
  except NoResultFound:
113
174
  raise HTTPException(status_code=404, detail="Run not found")
114
175
 
@@ -137,26 +198,15 @@ async def list_run_messages(
137
198
  order: Literal["asc", "desc"] = Query(
138
199
  "asc", description="Sort order for messages by creation time. 'asc' for oldest first, 'desc' for newest first"
139
200
  ),
201
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
140
202
  ):
141
203
  """Get response messages associated with a run."""
142
204
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
143
-
144
- try:
145
- messages = server.job_manager.get_run_messages(
146
- run_id=run_id,
147
- actor=actor,
148
- limit=limit,
149
- before=before,
150
- after=after,
151
- ascending=(order == "asc"),
152
- )
153
- return messages
154
- except NoResultFound as e:
155
- raise HTTPException(status_code=404, detail=str(e))
205
+ return await server.run_manager.get_run_messages(run_id=run_id, actor=actor, before=before, after=after, limit=limit, order=order)
156
206
 
157
207
 
158
208
  @router.get("/{run_id}/usage", response_model=UsageStatistics, operation_id="retrieve_run_usage")
159
- def retrieve_run_usage(
209
+ async def retrieve_run_usage(
160
210
  run_id: str,
161
211
  headers: HeaderParams = Depends(get_headers),
162
212
  server: "SyncServer" = Depends(get_letta_server),
@@ -164,10 +214,11 @@ def retrieve_run_usage(
164
214
  """
165
215
  Get usage statistics for a run.
166
216
  """
167
- actor = server.user_manager.get_user_or_default(user_id=headers.actor_id)
217
+ actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
218
+ runs_manager = RunManager()
168
219
 
169
220
  try:
170
- usage = server.job_manager.get_job_usage(job_id=run_id, actor=actor)
221
+ usage = await runs_manager.get_run_usage(run_id=run_id, actor=actor)
171
222
  return usage
172
223
  except NoResultFound:
173
224
  raise HTTPException(status_code=404, detail=f"Run '{run_id}' not found")
@@ -185,31 +236,20 @@ async def list_run_steps(
185
236
  before: Optional[str] = Query(None, description="Cursor for pagination"),
186
237
  after: Optional[str] = Query(None, description="Cursor for pagination"),
187
238
  limit: Optional[int] = Query(100, description="Maximum number of messages to return"),
188
- order: str = Query(
189
- "desc", description="Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order."
239
+ order: Literal["asc", "desc"] = Query(
240
+ "desc", description="Sort order for steps by creation time. 'asc' for oldest first, 'desc' for newest first"
190
241
  ),
242
+ order_by: Literal["created_at"] = Query("created_at", description="Field to sort by"),
191
243
  ):
192
244
  """
193
- Get messages associated with a run with filtering options.
194
-
195
- Args:
196
- run_id: ID of the run
197
- before: A cursor for use in pagination. `before` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, starting with obj_foo, your subsequent call can include before=obj_foo in order to fetch the previous page of the list.
198
- after: A cursor for use in pagination. `after` is an object ID that defines your place in the list. For instance, if you make a list request and receive 100 objects, ending with obj_foo, your subsequent call can include after=obj_foo in order to fetch the next page of the list.
199
- limit: Maximum number of steps to return
200
- order: Sort order by the created_at timestamp of the objects. asc for ascending order and desc for descending order.
201
-
202
- Returns:
203
- A list of steps associated with the run.
245
+ Get steps associated with a run with filtering options.
204
246
  """
205
- if order not in ["asc", "desc"]:
206
- raise HTTPException(status_code=400, detail="Order must be 'asc' or 'desc'")
207
-
208
247
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
248
+ runs_manager = RunManager()
209
249
 
210
250
  try:
211
- steps = server.job_manager.get_job_steps(
212
- job_id=run_id,
251
+ steps = await runs_manager.get_run_steps(
252
+ run_id=run_id,
213
253
  actor=actor,
214
254
  limit=limit,
215
255
  before=before,
@@ -231,10 +271,11 @@ async def delete_run(
231
271
  Delete a run by its run_id.
232
272
  """
233
273
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
274
+ runs_manager = RunManager()
234
275
 
235
276
  try:
236
- job = await server.job_manager.delete_job_by_id_async(job_id=run_id, actor=actor)
237
- return Run.from_job(job)
277
+ run = await runs_manager.delete_run_by_id(run_id=run_id, actor=actor)
278
+ return run
238
279
  except NoResultFound:
239
280
  raise HTTPException(status_code=404, detail="Run not found")
240
281
 
@@ -278,14 +319,14 @@ async def retrieve_stream(
278
319
  server: "SyncServer" = Depends(get_letta_server),
279
320
  ):
280
321
  actor = await server.user_manager.get_actor_or_default_async(actor_id=headers.actor_id)
322
+ runs_manager = RunManager()
323
+
281
324
  try:
282
- job = server.job_manager.get_job_by_id(job_id=run_id, actor=actor)
325
+ run = await runs_manager.get_run_by_id(run_id=run_id, actor=actor)
283
326
  except NoResultFound:
284
327
  raise HTTPException(status_code=404, detail="Run not found")
285
328
 
286
- run = Run.from_job(job)
287
-
288
- if "background" not in run.metadata or not run.metadata["background"]:
329
+ if not run.background:
289
330
  raise HTTPException(status_code=400, detail="Run was not created in background mode, so it cannot be retrieved.")
290
331
 
291
332
  if run.created_at < get_utc_time() - timedelta(hours=3):
@@ -314,8 +355,8 @@ async def retrieve_stream(
314
355
  if settings.enable_cancellation_aware_streaming:
315
356
  stream = cancellation_aware_stream_wrapper(
316
357
  stream_generator=stream,
317
- job_manager=server.job_manager,
318
- job_id=run_id,
358
+ run_manager=server.run_manager,
359
+ run_id=run_id,
319
360
  actor=actor,
320
361
  )
321
362
 
@@ -2,8 +2,9 @@ import os
2
2
  import shutil
3
3
  from typing import List, Optional
4
4
 
5
- from fastapi import APIRouter, Depends, HTTPException, Query
5
+ from fastapi import APIRouter, Depends, Query
6
6
 
7
+ from letta.errors import LettaInvalidArgumentError
7
8
  from letta.log import get_logger
8
9
  from letta.schemas.enums import SandboxType
9
10
  from letta.schemas.environment_variables import (
@@ -68,9 +69,8 @@ async def create_custom_local_sandbox_config(
68
69
  """
69
70
  # Ensure the incoming config is of type LOCAL
70
71
  if local_sandbox_config.type != SandboxType.LOCAL:
71
- raise HTTPException(
72
- status_code=400,
73
- detail=f"Provided config must be of type '{SandboxType.LOCAL.value}'.",
72
+ raise LettaInvalidArgumentError(
73
+ f"Provided config must be of type '{SandboxType.LOCAL.value}'.", argument_name="local_sandbox_config.type"
74
74
  )
75
75
 
76
76
  # Retrieve the user (actor)
@@ -138,25 +138,16 @@ async def force_recreate_local_sandbox_venv(
138
138
 
139
139
  # Check if venv exists, and delete if necessary
140
140
  if os.path.isdir(venv_path):
141
- try:
142
- shutil.rmtree(venv_path)
143
- logger.info(f"Deleted existing virtual environment at: {venv_path}")
144
- except Exception as e:
145
- raise HTTPException(status_code=500, detail=f"Failed to delete existing venv: {e}")
141
+ shutil.rmtree(venv_path)
142
+ logger.info(f"Deleted existing virtual environment at: {venv_path}")
146
143
 
147
144
  # Recreate the virtual environment
148
- try:
149
- create_venv_for_local_sandbox(sandbox_dir_path=sandbox_dir, venv_path=str(venv_path), env=os.environ.copy(), force_recreate=True)
150
- logger.info(f"Successfully recreated virtual environment at: {venv_path}")
151
- except Exception as e:
152
- raise HTTPException(status_code=500, detail=f"Failed to recreate venv: {e}")
145
+ create_venv_for_local_sandbox(sandbox_dir_path=sandbox_dir, venv_path=str(venv_path), env=os.environ.copy(), force_recreate=True)
146
+ logger.info(f"Successfully recreated virtual environment at: {venv_path}")
153
147
 
154
148
  # Install pip requirements
155
- try:
156
- install_pip_requirements_for_sandbox(local_configs=local_configs, env=os.environ.copy())
157
- logger.info(f"Successfully installed pip requirements for venv at: {venv_path}")
158
- except Exception as e:
159
- raise HTTPException(status_code=500, detail=f"Failed to install pip requirements: {e}")
149
+ install_pip_requirements_for_sandbox(local_configs=local_configs, env=os.environ.copy())
150
+ logger.info(f"Successfully installed pip requirements for venv at: {venv_path}")
160
151
 
161
152
  return sbx_config
162
153