Skip to content

Commit 9b60320

Browse files
authored
fix: microsand box integration (#3101)
1 parent 67db33b commit 9b60320

File tree

5 files changed

+195
-130
lines changed

5 files changed

+195
-130
lines changed

camel/interpreters/microsandbox_interpreter.py

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
# limitations under the License.
1313
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
1414
import asyncio
15-
import shlex
1615
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Union
1716

1817
from camel.interpreters.base import BaseInterpreter
@@ -54,7 +53,7 @@ class MicrosandboxInterpreter(BaseInterpreter):
5453
5554
Note:
5655
The SDK handles parameter priority as: user parameter > environment
57-
variable > default value.
56+
variable > default value.
5857
"""
5958

6059
_CODE_TYPE_MAPPING: ClassVar[Dict[str, str]] = {
@@ -84,15 +83,10 @@ def __init__(
8483
sandbox_name: Optional[str] = None,
8584
timeout: int = 30,
8685
) -> None:
87-
try:
88-
from microsandbox import ( # type: ignore[import-not-found,import-untyped]
89-
NodeSandbox,
90-
PythonSandbox,
91-
)
92-
except ImportError as e:
93-
raise ImportError(
94-
"Please install microsandbox to use MicrosandboxInterpreter"
95-
) from e
86+
from microsandbox import (
87+
NodeSandbox,
88+
PythonSandbox,
89+
)
9690

9791
# Store parameters, let SDK handle defaults and environment variables
9892
self.require_confirm = require_confirm
@@ -205,7 +199,9 @@ async def _run_python_code(self, code: str) -> str:
205199
async with self._PythonSandbox.create(
206200
**self._sandbox_config
207201
) as sandbox:
208-
execution = await sandbox.run(code)
202+
execution = await asyncio.wait_for(
203+
sandbox.run(code), timeout=self.timeout
204+
)
209205
return await self._get_execution_output(execution)
210206

211207
async def _run_node_code(self, code: str) -> str:
@@ -218,7 +214,9 @@ async def _run_node_code(self, code: str) -> str:
218214
str: Execution output.
219215
"""
220216
async with self._NodeSandbox.create(**self._sandbox_config) as sandbox:
221-
execution = await sandbox.run(code)
217+
execution = await asyncio.wait_for(
218+
sandbox.run(code), timeout=self.timeout
219+
)
222220
return await self._get_execution_output(execution)
223221

224222
async def _run_shell_command(self, code: str) -> str:
@@ -234,7 +232,9 @@ async def _run_shell_command(self, code: str) -> str:
234232
async with self._PythonSandbox.create(
235233
**self._sandbox_config
236234
) as sandbox:
237-
execution = await sandbox.command.run("bash", ["-c", code])
235+
execution = await asyncio.wait_for(
236+
sandbox.command.run("bash", ["-c", code]), timeout=self.timeout
237+
)
238238
return await self._get_command_output(execution)
239239

240240
async def _get_execution_output(self, execution) -> str:
@@ -372,24 +372,13 @@ async def _execute_command_async(self, command: str) -> str:
372372
InterpreterError: If execution fails.
373373
"""
374374
try:
375-
# Parse the command string into command and arguments
376-
try:
377-
command_parts = shlex.split(command)
378-
if not command_parts:
379-
raise InterpreterError("Empty command")
380-
381-
cmd = command_parts[0]
382-
args = command_parts[1:] if len(command_parts) > 1 else []
383-
except ValueError as e:
384-
raise InterpreterError(f"Invalid command format: {e}")
385-
386-
# Create and use sandbox with context manager
387375
async with self._PythonSandbox.create(
388376
**self._sandbox_config
389377
) as sandbox:
390-
# Use the command interface with proper command and args format
391-
execution = await sandbox.command.run(cmd, args)
392-
378+
execution = await asyncio.wait_for(
379+
sandbox.command.run("bash", ["-c", command]),
380+
timeout=self.timeout,
381+
)
393382
return await self._get_command_output(execution)
394383

395384
except Exception as e:

camel/runtimes/daytona_runtime.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import json
1717
import os
1818
from functools import wraps
19-
from typing import Any, Dict, List, Optional, Union
19+
from typing import Any, Callable, Dict, List, Optional, Union
2020

2121
from pydantic import BaseModel
2222

@@ -49,15 +49,15 @@ def __init__(
4949
api_url: Optional[str] = None,
5050
language: Optional[str] = "python",
5151
):
52-
from daytona_sdk import Daytona, DaytonaConfig
52+
from daytona_sdk import Daytona, DaytonaConfig, Sandbox
5353

5454
super().__init__()
5555
self.api_key = api_key or os.environ.get('DAYTONA_API_KEY')
5656
self.api_url = api_url or os.environ.get('DAYTONA_API_URL')
5757
self.language = language
5858
self.config = DaytonaConfig(api_key=self.api_key, api_url=self.api_url)
5959
self.daytona = Daytona(self.config)
60-
self.sandbox = None
60+
self.sandbox: Optional[Sandbox] = None
6161
self.entrypoint: Dict[str, str] = dict()
6262

6363
def build(self) -> "DaytonaRuntime":
@@ -66,10 +66,10 @@ def build(self) -> "DaytonaRuntime":
6666
Returns:
6767
DaytonaRuntime: The current runtime.
6868
"""
69-
from daytona_sdk import CreateSandboxParams
69+
from daytona_sdk import CreateSandboxBaseParams
7070

7171
try:
72-
params = CreateSandboxParams(language=self.language)
72+
params = CreateSandboxBaseParams(language=self.language)
7373
self.sandbox = self.daytona.create(params)
7474
if self.sandbox is None:
7575
raise RuntimeError("Failed to create sandbox.")
@@ -83,7 +83,7 @@ def _cleanup(self):
8383
r"""Clean up the sandbox when exiting."""
8484
if self.sandbox:
8585
try:
86-
self.daytona.remove(self.sandbox)
86+
self.daytona.delete(self.sandbox)
8787
logger.info(f"Sandbox {self.sandbox.id} removed")
8888
self.sandbox = None
8989
except Exception as e:
@@ -112,7 +112,7 @@ def add(
112112
if arguments is not None:
113113
entrypoint += json.dumps(arguments, ensure_ascii=False)
114114

115-
def make_wrapper(inner_func, func_name, func_code):
115+
def make_wrapper(inner_func: Callable, func_name: str, func_code: str):
116116
r"""Creates a wrapper for a function to execute it in the
117117
Daytona sandbox.
118118
@@ -208,12 +208,11 @@ def info(self) -> str:
208208
RuntimeError: If the sandbox is not initialized.
209209
"""
210210
if self.sandbox is None:
211-
raise RuntimeError("Failed to create sandbox.")
212-
info = self.sandbox.info()
211+
raise RuntimeError("Sandbox not initialized.")
213212
return (
214-
f"Sandbox {info.name}:\n"
215-
f"State: {info.state}\n"
216-
f"Resources: {info.resources.cpu} CPU, {info.resources.memory} RAM"
213+
f"Sandbox {self.sandbox.id}:\n"
214+
f"State: {self.sandbox.state}\n"
215+
f"Resources: {self.sandbox.cpu} CPU, {self.sandbox.memory} RAM"
217216
)
218217

219218
def __del__(self):

camel/toolkits/video_analysis_toolkit.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,18 +195,24 @@ def __del__(self):
195195
destroyed.
196196
"""
197197
# Clean up temporary files
198-
for temp_file in self._temp_files:
199-
if os.path.exists(temp_file):
200-
try:
201-
os.remove(temp_file)
202-
logger.debug(f"Removed temporary file: {temp_file}")
203-
except OSError as e:
204-
logger.warning(
205-
f"Failed to remove temporary file {temp_file}: {e}"
206-
)
198+
if hasattr(self, '_temp_files'):
199+
for temp_file in self._temp_files:
200+
if os.path.exists(temp_file):
201+
try:
202+
os.remove(temp_file)
203+
logger.debug(f"Removed temporary file: {temp_file}")
204+
except OSError as e:
205+
logger.warning(
206+
f"Failed to remove temporary file {temp_file}: {e}"
207+
)
207208

208209
# Clean up temporary directory if needed
209-
if self._cleanup and os.path.exists(self._working_directory):
210+
if (
211+
hasattr(self, '_cleanup')
212+
and self._cleanup
213+
and hasattr(self, '_working_directory')
214+
and os.path.exists(self._working_directory)
215+
):
210216
try:
211217
import sys
212218

pyproject.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,12 @@ dev_tools = [
171171
"ipykernel>=6.0.0,<7",
172172
"agentops>=0.3.21,<0.4",
173173
"e2b-code-interpreter>=1.0.3,<2",
174-
"microsandbox>=0.1.0",
174+
"microsandbox>=0.1.8",
175175
"tree-sitter-python>=0.23.6,<0.24",
176176
"tree-sitter>=0.23.2,<0.24",
177177
"typer>=0.15.2",
178178
"mcp>=1.3.0",
179-
"daytona-sdk==0.20.0",
179+
"daytona-sdk>=0.20.0",
180180
"aci-sdk>=1.0.0b1",
181181
"langfuse>=2.60.5",
182182
]
@@ -251,7 +251,6 @@ owl = [
251251
"requests_oauthlib>=1.3.1,<2",
252252
"websockets>=13.0,<15.1",
253253
"e2b-code-interpreter>=1.0.3,<2",
254-
"microsandbox>=0.1.0",
255254
"typer>=0.15.2",
256255
"tree-sitter-python>=0.23.6,<0.24",
257256
"tree-sitter>=0.23.2,<0.24",
@@ -329,7 +328,7 @@ all = [
329328
"rouge>=1.0.1,<2",
330329
"aiosqlite>=0.20.0,<0.21",
331330
"e2b-code-interpreter>=1.0.3,<2",
332-
"microsandbox>=0.1.0",
331+
"microsandbox>=0.1.8",
333332
"firecrawl-py>=1.0.0,<2",
334333
"arxiv>=2.1.3,<3",
335334
"arxiv2text>=0.1.14,<0.2",
@@ -408,7 +407,7 @@ all = [
408407
"pyautogui>=0.9.54,<0.10",
409408
"pyobvector>=0.1.18",
410409
"scrapegraph-py>=1.12.0,<2",
411-
"daytona-sdk==0.20.0",
410+
"daytona-sdk>=0.20.0",
412411
"ibm-watsonx-ai>=1.3.11",
413412
"google-genai>=1.13.0",
414413
"aci-sdk>=1.0.0b1",
@@ -656,6 +655,7 @@ module = [
656655
"pgvector.*",
657656
"reportlab.*",
658657
"surrealdb.*",
658+
"microsandbox.*"
659659
]
660660
ignore_missing_imports = true
661661

0 commit comments

Comments
 (0)