letta-nightly 0.7.20.dev20250521104258__py3-none-any.whl → 0.7.21.dev20250522104246__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- letta/__init__.py +1 -1
- letta/agent.py +290 -3
- letta/agents/base_agent.py +0 -55
- letta/agents/helpers.py +5 -0
- letta/agents/letta_agent.py +314 -64
- letta/agents/letta_agent_batch.py +102 -55
- letta/agents/voice_agent.py +5 -5
- letta/client/client.py +9 -18
- letta/constants.py +55 -1
- letta/functions/function_sets/builtin.py +27 -0
- letta/groups/sleeptime_multi_agent_v2.py +1 -1
- letta/interfaces/anthropic_streaming_interface.py +10 -1
- letta/interfaces/openai_streaming_interface.py +9 -2
- letta/llm_api/anthropic.py +21 -2
- letta/llm_api/anthropic_client.py +33 -6
- letta/llm_api/google_ai_client.py +136 -423
- letta/llm_api/google_vertex_client.py +173 -22
- letta/llm_api/llm_api_tools.py +27 -0
- letta/llm_api/llm_client.py +1 -1
- letta/llm_api/llm_client_base.py +32 -21
- letta/llm_api/openai.py +57 -0
- letta/llm_api/openai_client.py +7 -11
- letta/memory.py +0 -1
- letta/orm/__init__.py +1 -0
- letta/orm/enums.py +1 -0
- letta/orm/provider_trace.py +26 -0
- letta/orm/step.py +1 -0
- letta/schemas/provider_trace.py +43 -0
- letta/schemas/providers.py +210 -65
- letta/schemas/step.py +1 -0
- letta/schemas/tool.py +4 -0
- letta/server/db.py +37 -19
- letta/server/rest_api/routers/v1/__init__.py +2 -0
- letta/server/rest_api/routers/v1/agents.py +57 -34
- letta/server/rest_api/routers/v1/blocks.py +3 -3
- letta/server/rest_api/routers/v1/identities.py +24 -26
- letta/server/rest_api/routers/v1/jobs.py +3 -3
- letta/server/rest_api/routers/v1/llms.py +13 -8
- letta/server/rest_api/routers/v1/sandbox_configs.py +6 -6
- letta/server/rest_api/routers/v1/tags.py +3 -3
- letta/server/rest_api/routers/v1/telemetry.py +18 -0
- letta/server/rest_api/routers/v1/tools.py +6 -6
- letta/server/rest_api/streaming_response.py +105 -0
- letta/server/rest_api/utils.py +4 -0
- letta/server/server.py +140 -1
- letta/services/agent_manager.py +251 -18
- letta/services/block_manager.py +52 -37
- letta/services/helpers/noop_helper.py +10 -0
- letta/services/identity_manager.py +43 -38
- letta/services/job_manager.py +29 -0
- letta/services/message_manager.py +111 -0
- letta/services/sandbox_config_manager.py +36 -0
- letta/services/step_manager.py +146 -0
- letta/services/telemetry_manager.py +58 -0
- letta/services/tool_executor/tool_execution_manager.py +49 -5
- letta/services/tool_executor/tool_execution_sandbox.py +47 -0
- letta/services/tool_executor/tool_executor.py +236 -7
- letta/services/tool_manager.py +160 -1
- letta/services/tool_sandbox/e2b_sandbox.py +65 -3
- letta/settings.py +10 -2
- letta/tracing.py +5 -5
- {letta_nightly-0.7.20.dev20250521104258.dist-info → letta_nightly-0.7.21.dev20250522104246.dist-info}/METADATA +3 -2
- {letta_nightly-0.7.20.dev20250521104258.dist-info → letta_nightly-0.7.21.dev20250522104246.dist-info}/RECORD +66 -59
- {letta_nightly-0.7.20.dev20250521104258.dist-info → letta_nightly-0.7.21.dev20250522104246.dist-info}/LICENSE +0 -0
- {letta_nightly-0.7.20.dev20250521104258.dist-info → letta_nightly-0.7.21.dev20250522104246.dist-info}/WHEEL +0 -0
- {letta_nightly-0.7.20.dev20250521104258.dist-info → letta_nightly-0.7.21.dev20250522104246.dist-info}/entry_points.txt +0 -0
letta/services/step_manager.py
CHANGED
@@ -2,6 +2,7 @@ from datetime import datetime
|
|
2
2
|
from typing import List, Literal, Optional
|
3
3
|
|
4
4
|
from sqlalchemy import select
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
5
6
|
from sqlalchemy.orm import Session
|
6
7
|
|
7
8
|
from letta.orm.errors import NoResultFound
|
@@ -12,6 +13,7 @@ from letta.schemas.openai.chat_completion_response import UsageStatistics
|
|
12
13
|
from letta.schemas.step import Step as PydanticStep
|
13
14
|
from letta.schemas.user import User as PydanticUser
|
14
15
|
from letta.server.db import db_registry
|
16
|
+
from letta.services.helpers.noop_helper import singleton
|
15
17
|
from letta.tracing import get_trace_id
|
16
18
|
from letta.utils import enforce_types
|
17
19
|
|
@@ -57,12 +59,14 @@ class StepManager:
|
|
57
59
|
actor: PydanticUser,
|
58
60
|
agent_id: str,
|
59
61
|
provider_name: str,
|
62
|
+
provider_category: str,
|
60
63
|
model: str,
|
61
64
|
model_endpoint: Optional[str],
|
62
65
|
context_window_limit: int,
|
63
66
|
usage: UsageStatistics,
|
64
67
|
provider_id: Optional[str] = None,
|
65
68
|
job_id: Optional[str] = None,
|
69
|
+
step_id: Optional[str] = None,
|
66
70
|
) -> PydanticStep:
|
67
71
|
step_data = {
|
68
72
|
"origin": None,
|
@@ -70,6 +74,7 @@ class StepManager:
|
|
70
74
|
"agent_id": agent_id,
|
71
75
|
"provider_id": provider_id,
|
72
76
|
"provider_name": provider_name,
|
77
|
+
"provider_category": provider_category,
|
73
78
|
"model": model,
|
74
79
|
"model_endpoint": model_endpoint,
|
75
80
|
"context_window_limit": context_window_limit,
|
@@ -81,6 +86,8 @@ class StepManager:
|
|
81
86
|
"tid": None,
|
82
87
|
"trace_id": get_trace_id(), # Get the current trace ID
|
83
88
|
}
|
89
|
+
if step_id:
|
90
|
+
step_data["id"] = step_id
|
84
91
|
with db_registry.session() as session:
|
85
92
|
if job_id:
|
86
93
|
self._verify_job_access(session, job_id, actor, access=["write"])
|
@@ -88,6 +95,48 @@ class StepManager:
|
|
88
95
|
new_step.create(session)
|
89
96
|
return new_step.to_pydantic()
|
90
97
|
|
98
|
+
@enforce_types
|
99
|
+
async def log_step_async(
|
100
|
+
self,
|
101
|
+
actor: PydanticUser,
|
102
|
+
agent_id: str,
|
103
|
+
provider_name: str,
|
104
|
+
provider_category: str,
|
105
|
+
model: str,
|
106
|
+
model_endpoint: Optional[str],
|
107
|
+
context_window_limit: int,
|
108
|
+
usage: UsageStatistics,
|
109
|
+
provider_id: Optional[str] = None,
|
110
|
+
job_id: Optional[str] = None,
|
111
|
+
step_id: Optional[str] = None,
|
112
|
+
) -> PydanticStep:
|
113
|
+
step_data = {
|
114
|
+
"origin": None,
|
115
|
+
"organization_id": actor.organization_id,
|
116
|
+
"agent_id": agent_id,
|
117
|
+
"provider_id": provider_id,
|
118
|
+
"provider_name": provider_name,
|
119
|
+
"provider_category": provider_category,
|
120
|
+
"model": model,
|
121
|
+
"model_endpoint": model_endpoint,
|
122
|
+
"context_window_limit": context_window_limit,
|
123
|
+
"completion_tokens": usage.completion_tokens,
|
124
|
+
"prompt_tokens": usage.prompt_tokens,
|
125
|
+
"total_tokens": usage.total_tokens,
|
126
|
+
"job_id": job_id,
|
127
|
+
"tags": [],
|
128
|
+
"tid": None,
|
129
|
+
"trace_id": get_trace_id(), # Get the current trace ID
|
130
|
+
}
|
131
|
+
if step_id:
|
132
|
+
step_data["id"] = step_id
|
133
|
+
async with db_registry.async_session() as session:
|
134
|
+
if job_id:
|
135
|
+
await self._verify_job_access_async(session, job_id, actor, access=["write"])
|
136
|
+
new_step = StepModel(**step_data)
|
137
|
+
await new_step.create_async(session)
|
138
|
+
return new_step.to_pydantic()
|
139
|
+
|
91
140
|
@enforce_types
|
92
141
|
def get_step(self, step_id: str, actor: PydanticUser) -> PydanticStep:
|
93
142
|
with db_registry.session() as session:
|
@@ -147,3 +196,100 @@ class StepManager:
|
|
147
196
|
if not job:
|
148
197
|
raise NoResultFound(f"Job with id {job_id} does not exist or user does not have access")
|
149
198
|
return job
|
199
|
+
|
200
|
+
async def _verify_job_access_async(
|
201
|
+
self,
|
202
|
+
session: AsyncSession,
|
203
|
+
job_id: str,
|
204
|
+
actor: PydanticUser,
|
205
|
+
access: List[Literal["read", "write", "delete"]] = ["read"],
|
206
|
+
) -> JobModel:
|
207
|
+
"""
|
208
|
+
Verify that a job exists and the user has the required access asynchronously.
|
209
|
+
|
210
|
+
Args:
|
211
|
+
session: The async database session
|
212
|
+
job_id: The ID of the job to verify
|
213
|
+
actor: The user making the request
|
214
|
+
|
215
|
+
Returns:
|
216
|
+
The job if it exists and the user has access
|
217
|
+
|
218
|
+
Raises:
|
219
|
+
NoResultFound: If the job does not exist or user does not have access
|
220
|
+
"""
|
221
|
+
job_query = select(JobModel).where(JobModel.id == job_id)
|
222
|
+
job_query = JobModel.apply_access_predicate(job_query, actor, access, AccessType.USER)
|
223
|
+
result = await session.execute(job_query)
|
224
|
+
job = result.scalar_one_or_none()
|
225
|
+
if not job:
|
226
|
+
raise NoResultFound(f"Job with id {job_id} does not exist or user does not have access")
|
227
|
+
return job
|
228
|
+
|
229
|
+
|
230
|
+
@singleton
|
231
|
+
class NoopStepManager(StepManager):
|
232
|
+
"""
|
233
|
+
Noop implementation of StepManager.
|
234
|
+
Temporarily used for migrations, but allows for different implementations in the future.
|
235
|
+
Will not allow for writes, but will still allow for reads.
|
236
|
+
"""
|
237
|
+
|
238
|
+
@enforce_types
|
239
|
+
def log_step(
|
240
|
+
self,
|
241
|
+
actor: PydanticUser,
|
242
|
+
agent_id: str,
|
243
|
+
provider_name: str,
|
244
|
+
provider_category: str,
|
245
|
+
model: str,
|
246
|
+
model_endpoint: Optional[str],
|
247
|
+
context_window_limit: int,
|
248
|
+
usage: UsageStatistics,
|
249
|
+
provider_id: Optional[str] = None,
|
250
|
+
job_id: Optional[str] = None,
|
251
|
+
step_id: Optional[str] = None,
|
252
|
+
) -> PydanticStep:
|
253
|
+
return
|
254
|
+
|
255
|
+
@enforce_types
|
256
|
+
async def log_step_async(
|
257
|
+
self,
|
258
|
+
actor: PydanticUser,
|
259
|
+
agent_id: str,
|
260
|
+
provider_name: str,
|
261
|
+
provider_category: str,
|
262
|
+
model: str,
|
263
|
+
model_endpoint: Optional[str],
|
264
|
+
context_window_limit: int,
|
265
|
+
usage: UsageStatistics,
|
266
|
+
provider_id: Optional[str] = None,
|
267
|
+
job_id: Optional[str] = None,
|
268
|
+
step_id: Optional[str] = None,
|
269
|
+
) -> PydanticStep:
|
270
|
+
step_data = {
|
271
|
+
"origin": None,
|
272
|
+
"organization_id": actor.organization_id,
|
273
|
+
"agent_id": agent_id,
|
274
|
+
"provider_id": provider_id,
|
275
|
+
"provider_name": provider_name,
|
276
|
+
"provider_category": provider_category,
|
277
|
+
"model": model,
|
278
|
+
"model_endpoint": model_endpoint,
|
279
|
+
"context_window_limit": context_window_limit,
|
280
|
+
"completion_tokens": usage.completion_tokens,
|
281
|
+
"prompt_tokens": usage.prompt_tokens,
|
282
|
+
"total_tokens": usage.total_tokens,
|
283
|
+
"job_id": job_id,
|
284
|
+
"tags": [],
|
285
|
+
"tid": None,
|
286
|
+
"trace_id": get_trace_id(), # Get the current trace ID
|
287
|
+
}
|
288
|
+
if step_id:
|
289
|
+
step_data["id"] = step_id
|
290
|
+
async with db_registry.async_session() as session:
|
291
|
+
if job_id:
|
292
|
+
await self._verify_job_access_async(session, job_id, actor, access=["write"])
|
293
|
+
new_step = StepModel(**step_data)
|
294
|
+
await new_step.create_async(session)
|
295
|
+
return new_step.to_pydantic()
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from letta.helpers.json_helpers import json_dumps, json_loads
|
2
|
+
from letta.orm.provider_trace import ProviderTrace as ProviderTraceModel
|
3
|
+
from letta.schemas.provider_trace import ProviderTrace as PydanticProviderTrace
|
4
|
+
from letta.schemas.provider_trace import ProviderTraceCreate
|
5
|
+
from letta.schemas.step import Step as PydanticStep
|
6
|
+
from letta.schemas.user import User as PydanticUser
|
7
|
+
from letta.server.db import db_registry
|
8
|
+
from letta.services.helpers.noop_helper import singleton
|
9
|
+
from letta.utils import enforce_types
|
10
|
+
|
11
|
+
|
12
|
+
class TelemetryManager:
|
13
|
+
@enforce_types
|
14
|
+
async def get_provider_trace_by_step_id_async(
|
15
|
+
self,
|
16
|
+
step_id: str,
|
17
|
+
actor: PydanticUser,
|
18
|
+
) -> PydanticProviderTrace:
|
19
|
+
async with db_registry.async_session() as session:
|
20
|
+
provider_trace = await ProviderTraceModel.read_async(db_session=session, step_id=step_id, actor=actor)
|
21
|
+
return provider_trace.to_pydantic()
|
22
|
+
|
23
|
+
@enforce_types
|
24
|
+
async def create_provider_trace_async(self, actor: PydanticUser, provider_trace_create: ProviderTraceCreate) -> PydanticProviderTrace:
|
25
|
+
async with db_registry.async_session() as session:
|
26
|
+
provider_trace = ProviderTraceModel(**provider_trace_create.model_dump())
|
27
|
+
if provider_trace_create.request_json:
|
28
|
+
request_json_str = json_dumps(provider_trace_create.request_json)
|
29
|
+
provider_trace.request_json = json_loads(request_json_str)
|
30
|
+
|
31
|
+
if provider_trace_create.response_json:
|
32
|
+
response_json_str = json_dumps(provider_trace_create.response_json)
|
33
|
+
provider_trace.response_json = json_loads(response_json_str)
|
34
|
+
await provider_trace.create_async(session, actor=actor)
|
35
|
+
return provider_trace.to_pydantic()
|
36
|
+
|
37
|
+
@enforce_types
|
38
|
+
def create_provider_trace(self, actor: PydanticUser, provider_trace_create: ProviderTraceCreate) -> PydanticProviderTrace:
|
39
|
+
with db_registry.session() as session:
|
40
|
+
provider_trace = ProviderTraceModel(**provider_trace_create.model_dump())
|
41
|
+
provider_trace.create(session, actor=actor)
|
42
|
+
return provider_trace.to_pydantic()
|
43
|
+
|
44
|
+
|
45
|
+
@singleton
|
46
|
+
class NoopTelemetryManager(TelemetryManager):
|
47
|
+
"""
|
48
|
+
Noop implementation of TelemetryManager.
|
49
|
+
"""
|
50
|
+
|
51
|
+
async def create_provider_trace_async(self, actor: PydanticUser, provider_trace_create: ProviderTraceCreate) -> PydanticProviderTrace:
|
52
|
+
return
|
53
|
+
|
54
|
+
async def get_provider_trace_by_step_id_async(self, step_id: str, actor: PydanticUser) -> PydanticStep:
|
55
|
+
return
|
56
|
+
|
57
|
+
def create_provider_trace(self, actor: PydanticUser, provider_trace_create: ProviderTraceCreate) -> PydanticProviderTrace:
|
58
|
+
return
|
@@ -8,9 +8,14 @@ from letta.schemas.sandbox_config import SandboxConfig
|
|
8
8
|
from letta.schemas.tool import Tool
|
9
9
|
from letta.schemas.tool_execution_result import ToolExecutionResult
|
10
10
|
from letta.schemas.user import User
|
11
|
+
from letta.services.agent_manager import AgentManager
|
12
|
+
from letta.services.block_manager import BlockManager
|
13
|
+
from letta.services.message_manager import MessageManager
|
14
|
+
from letta.services.passage_manager import PassageManager
|
11
15
|
from letta.services.tool_executor.tool_executor import (
|
12
16
|
ExternalComposioToolExecutor,
|
13
17
|
ExternalMCPToolExecutor,
|
18
|
+
LettaBuiltinToolExecutor,
|
14
19
|
LettaCoreToolExecutor,
|
15
20
|
LettaMultiAgentToolExecutor,
|
16
21
|
SandboxToolExecutor,
|
@@ -28,15 +33,30 @@ class ToolExecutorFactory:
|
|
28
33
|
ToolType.LETTA_MEMORY_CORE: LettaCoreToolExecutor,
|
29
34
|
ToolType.LETTA_SLEEPTIME_CORE: LettaCoreToolExecutor,
|
30
35
|
ToolType.LETTA_MULTI_AGENT_CORE: LettaMultiAgentToolExecutor,
|
36
|
+
ToolType.LETTA_BUILTIN: LettaBuiltinToolExecutor,
|
31
37
|
ToolType.EXTERNAL_COMPOSIO: ExternalComposioToolExecutor,
|
32
38
|
ToolType.EXTERNAL_MCP: ExternalMCPToolExecutor,
|
33
39
|
}
|
34
40
|
|
35
41
|
@classmethod
|
36
|
-
def get_executor(
|
42
|
+
def get_executor(
|
43
|
+
cls,
|
44
|
+
tool_type: ToolType,
|
45
|
+
message_manager: MessageManager,
|
46
|
+
agent_manager: AgentManager,
|
47
|
+
block_manager: BlockManager,
|
48
|
+
passage_manager: PassageManager,
|
49
|
+
actor: User,
|
50
|
+
) -> ToolExecutor:
|
37
51
|
"""Get the appropriate executor for the given tool type."""
|
38
52
|
executor_class = cls._executor_map.get(tool_type, SandboxToolExecutor)
|
39
|
-
return executor_class(
|
53
|
+
return executor_class(
|
54
|
+
message_manager=message_manager,
|
55
|
+
agent_manager=agent_manager,
|
56
|
+
block_manager=block_manager,
|
57
|
+
passage_manager=passage_manager,
|
58
|
+
actor=actor,
|
59
|
+
)
|
40
60
|
|
41
61
|
|
42
62
|
class ToolExecutionManager:
|
@@ -44,11 +64,19 @@ class ToolExecutionManager:
|
|
44
64
|
|
45
65
|
def __init__(
|
46
66
|
self,
|
67
|
+
message_manager: MessageManager,
|
68
|
+
agent_manager: AgentManager,
|
69
|
+
block_manager: BlockManager,
|
70
|
+
passage_manager: PassageManager,
|
47
71
|
agent_state: AgentState,
|
48
72
|
actor: User,
|
49
73
|
sandbox_config: Optional[SandboxConfig] = None,
|
50
74
|
sandbox_env_vars: Optional[Dict[str, Any]] = None,
|
51
75
|
):
|
76
|
+
self.message_manager = message_manager
|
77
|
+
self.agent_manager = agent_manager
|
78
|
+
self.block_manager = block_manager
|
79
|
+
self.passage_manager = passage_manager
|
52
80
|
self.agent_state = agent_state
|
53
81
|
self.logger = get_logger(__name__)
|
54
82
|
self.actor = actor
|
@@ -68,7 +96,14 @@ class ToolExecutionManager:
|
|
68
96
|
Tuple containing the function response and sandbox run result (if applicable)
|
69
97
|
"""
|
70
98
|
try:
|
71
|
-
executor = ToolExecutorFactory.get_executor(
|
99
|
+
executor = ToolExecutorFactory.get_executor(
|
100
|
+
tool.tool_type,
|
101
|
+
message_manager=self.message_manager,
|
102
|
+
agent_manager=self.agent_manager,
|
103
|
+
block_manager=self.block_manager,
|
104
|
+
passage_manager=self.passage_manager,
|
105
|
+
actor=self.actor,
|
106
|
+
)
|
72
107
|
return executor.execute(
|
73
108
|
function_name,
|
74
109
|
function_args,
|
@@ -98,9 +133,18 @@ class ToolExecutionManager:
|
|
98
133
|
Execute a tool asynchronously and persist any state changes.
|
99
134
|
"""
|
100
135
|
try:
|
101
|
-
executor = ToolExecutorFactory.get_executor(
|
136
|
+
executor = ToolExecutorFactory.get_executor(
|
137
|
+
tool.tool_type,
|
138
|
+
message_manager=self.message_manager,
|
139
|
+
agent_manager=self.agent_manager,
|
140
|
+
block_manager=self.block_manager,
|
141
|
+
passage_manager=self.passage_manager,
|
142
|
+
actor=self.actor,
|
143
|
+
)
|
102
144
|
# TODO: Extend this async model to composio
|
103
|
-
if isinstance(
|
145
|
+
if isinstance(
|
146
|
+
executor, (SandboxToolExecutor, ExternalComposioToolExecutor, LettaBuiltinToolExecutor, LettaMultiAgentToolExecutor)
|
147
|
+
):
|
104
148
|
result = await executor.execute(function_name, function_args, self.agent_state, tool, self.actor)
|
105
149
|
else:
|
106
150
|
result = executor.execute(function_name, function_args, self.agent_state, tool, self.actor)
|
@@ -73,6 +73,7 @@ class ToolExecutionSandbox:
|
|
73
73
|
self.force_recreate = force_recreate
|
74
74
|
self.force_recreate_venv = force_recreate_venv
|
75
75
|
|
76
|
+
@trace_method
|
76
77
|
def run(
|
77
78
|
self,
|
78
79
|
agent_state: Optional[AgentState] = None,
|
@@ -321,6 +322,7 @@ class ToolExecutionSandbox:
|
|
321
322
|
|
322
323
|
# e2b sandbox specific functions
|
323
324
|
|
325
|
+
@trace_method
|
324
326
|
def run_e2b_sandbox(
|
325
327
|
self,
|
326
328
|
agent_state: Optional[AgentState] = None,
|
@@ -352,10 +354,22 @@ class ToolExecutionSandbox:
|
|
352
354
|
if additional_env_vars:
|
353
355
|
env_vars.update(additional_env_vars)
|
354
356
|
code = self.generate_execution_script(agent_state=agent_state)
|
357
|
+
log_event(
|
358
|
+
"e2b_execution_started",
|
359
|
+
{"tool": self.tool_name, "sandbox_id": sbx.sandbox_id, "code": code, "env_vars": env_vars},
|
360
|
+
)
|
355
361
|
execution = sbx.run_code(code, envs=env_vars)
|
356
362
|
|
357
363
|
if execution.results:
|
358
364
|
func_return, agent_state = self.parse_best_effort(execution.results[0].text)
|
365
|
+
log_event(
|
366
|
+
"e2b_execution_succeeded",
|
367
|
+
{
|
368
|
+
"tool": self.tool_name,
|
369
|
+
"sandbox_id": sbx.sandbox_id,
|
370
|
+
"func_return": func_return,
|
371
|
+
},
|
372
|
+
)
|
359
373
|
elif execution.error:
|
360
374
|
logger.error(f"Executing tool {self.tool_name} raised a {execution.error.name} with message: \n{execution.error.value}")
|
361
375
|
logger.error(f"Traceback from e2b sandbox: \n{execution.error.traceback}")
|
@@ -363,7 +377,25 @@ class ToolExecutionSandbox:
|
|
363
377
|
function_name=self.tool_name, exception_name=execution.error.name, exception_message=execution.error.value
|
364
378
|
)
|
365
379
|
execution.logs.stderr.append(execution.error.traceback)
|
380
|
+
log_event(
|
381
|
+
"e2b_execution_failed",
|
382
|
+
{
|
383
|
+
"tool": self.tool_name,
|
384
|
+
"sandbox_id": sbx.sandbox_id,
|
385
|
+
"error_type": execution.error.name,
|
386
|
+
"error_message": execution.error.value,
|
387
|
+
"func_return": func_return,
|
388
|
+
},
|
389
|
+
)
|
366
390
|
else:
|
391
|
+
log_event(
|
392
|
+
"e2b_execution_empty",
|
393
|
+
{
|
394
|
+
"tool": self.tool_name,
|
395
|
+
"sandbox_id": sbx.sandbox_id,
|
396
|
+
"status": "no_results_no_error",
|
397
|
+
},
|
398
|
+
)
|
367
399
|
raise ValueError(f"Tool {self.tool_name} returned execution with None")
|
368
400
|
|
369
401
|
return ToolExecutionResult(
|
@@ -395,16 +427,31 @@ class ToolExecutionSandbox:
|
|
395
427
|
|
396
428
|
return None
|
397
429
|
|
430
|
+
@trace_method
|
398
431
|
def create_e2b_sandbox_with_metadata_hash(self, sandbox_config: SandboxConfig) -> "Sandbox":
|
399
432
|
from e2b_code_interpreter import Sandbox
|
400
433
|
|
401
434
|
state_hash = sandbox_config.fingerprint()
|
402
435
|
e2b_config = sandbox_config.get_e2b_config()
|
436
|
+
log_event(
|
437
|
+
"e2b_sandbox_create_started",
|
438
|
+
{
|
439
|
+
"sandbox_fingerprint": state_hash,
|
440
|
+
"e2b_config": e2b_config.model_dump(),
|
441
|
+
},
|
442
|
+
)
|
403
443
|
if e2b_config.template:
|
404
444
|
sbx = Sandbox(sandbox_config.get_e2b_config().template, metadata={self.METADATA_CONFIG_STATE_KEY: state_hash})
|
405
445
|
else:
|
406
446
|
# no template
|
407
447
|
sbx = Sandbox(metadata={self.METADATA_CONFIG_STATE_KEY: state_hash}, **e2b_config.model_dump(exclude={"pip_requirements"}))
|
448
|
+
log_event(
|
449
|
+
"e2b_sandbox_create_finished",
|
450
|
+
{
|
451
|
+
"sandbox_id": sbx.sandbox_id,
|
452
|
+
"sandbox_fingerprint": state_hash,
|
453
|
+
},
|
454
|
+
)
|
408
455
|
|
409
456
|
# install pip requirements
|
410
457
|
if e2b_config.pip_requirements:
|