Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ Choose one of the following installation methods based on your needs.
--shm-size="10g" --cap-add=SYS_ADMIN \
-v .:/workspace/rllm -v /tmp:/tmp \
--name rllm-container rllm sleep infinity

docker start rllm-container
```
</Step>
Expand Down Expand Up @@ -192,7 +192,7 @@ rLLM provides additional optional dependencies for specific agent domains and fr

<Accordion title="Domain-specific tools">
- **web**: Web agents (BrowserGym, Selenium, Firecrawl)
- **code-tools**: Sandboxed code execution (E2B, Together)
- **code-tools**: Sandboxed code execution (Daytona, E2B, Together)
- **swe**: Software engineering tools (Docker, Kubernetes, SWEBench)
- **verifiers**: Verifiers integration for validation
</Accordion>
Expand Down Expand Up @@ -238,7 +238,7 @@ You should see the version number printed (e.g., `0.2.1`).
<Card title="Quick start" icon="rocket" href="/quickstart-cli">
Build your first math reasoning agent in 10 minutes
</Card>

<Card title="Core concepts" icon="book" href="/core-concepts/episodes-trajectories-steps">
Learn about the key components of rLLM
</Card>
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ agentcore = [
]

code-tools = [
"daytona",
"e2b_code_interpreter",
"together>=1.4",
]
Expand Down
2 changes: 2 additions & 0 deletions rllm/tools/code_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from rllm.tools.code_tools.daytona_tool import DaytonaPythonInterpreter
from rllm.tools.code_tools.e2b_tool import E2BPythonInterpreter
from rllm.tools.code_tools.lcb_tool import LCBPythonInterpreter
from rllm.tools.code_tools.python_interpreter import PythonInterpreter
from rllm.tools.code_tools.together_tool import TogetherCodeTool

__all__ = [
"PythonInterpreter", # New unified interpreter
"DaytonaPythonInterpreter",
"E2BPythonInterpreter", # Legacy interpreters for backward compatibility
"LCBPythonInterpreter",
"TogetherCodeTool",
Expand Down
154 changes: 154 additions & 0 deletions rllm/tools/code_tools/daytona_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import os
from typing import Any

try:
from daytona import (
CreateSandboxFromSnapshotParams,
Daytona,
DaytonaAuthenticationError,
DaytonaConfig,
)
except ImportError:
CreateSandboxFromSnapshotParams = None
Daytona = None
DaytonaAuthenticationError = None
DaytonaConfig = None

from rllm.tools.code_tools.code_tool import CodeTool, CodeToolOutput

DAYTONA_API_KEY = os.environ.get("DAYTONA_API_KEY", None)


class DaytonaPythonInterpreter(CodeTool):
"""Execute Python code in a Daytona sandbox"""

def __init__(
self,
n_sandboxes: int = 1,
api_key: str | None = DAYTONA_API_KEY,
api_url: str | None = None,
snapshot: str | None = None,
env_vars: dict[str, str] | None = None,
):
if Daytona is None:
raise ImportError("daytona is not installed. Please install it with `pip install daytona`.")
assert n_sandboxes > 0, "Number of sandboxes must be greater than 0"

super().__init__(
name="daytona_python",
description="A tool that executes python code in a Daytona sandbox and returns standard output/error.",
n_sandboxes=n_sandboxes,
)

self.api_key = api_key
self.api_url = api_url
self.snapshot = snapshot
self.env_vars = env_vars
self._init_sandbox()

def _init_sandbox(self):
"""Initialize multiple sandbox environments."""
config_kwargs: dict[str, Any] = {}
if self.api_key is not None:
config_kwargs["api_key"] = self.api_key
if self.api_url is not None:
config_kwargs["api_url"] = self.api_url
self._client = Daytona(DaytonaConfig(**config_kwargs))

params_kwargs: dict[str, Any] = {"language": "python"}
if self.snapshot is not None:
params_kwargs["snapshot"] = self.snapshot
if self.env_vars is not None:
params_kwargs["env_vars"] = dict(self.env_vars)
params = CreateSandboxFromSnapshotParams(**params_kwargs)

self.sandboxes = []
self.cur_sandbox_idx = 0
for _ in range(self.n_sandboxes):
self.sandboxes.append(self._client.create(params))

def _kill_sandbox(self):
"""Clean up all sandbox resources."""
for sandbox in getattr(self, "sandboxes", []):
try:
sandbox.delete()
except Exception as e:
print(f"Error deleting sandbox: {e}")
self.sandboxes = []

def forward(self, code: str, timeout: int = 20, **kwargs) -> CodeToolOutput:
"""
Execute Python code in one of the sandboxes using round-robin distribution.

Args:
code: Python code to execute
timeout: Maximum execution time in seconds
**kwargs: Additional parameters including id, max_retries

Returns:
CodeToolOutput containing execution results, stdout, and stderr
"""
id = kwargs.get("id", None)
max_retries = kwargs.get("max_retries", 3)

if id:
self.cur_sandbox_idx = id % self.n_sandboxes
else:
self.cur_sandbox_idx = (self.cur_sandbox_idx + 1) % self.n_sandboxes
sandbox = self.sandboxes[self.cur_sandbox_idx]

for _ in range(max_retries):
try:
response = sandbox.process.code_run(code, timeout=timeout)
break
except DaytonaAuthenticationError as e:
return CodeToolOutput(name=self.name or "daytona_python", error=f"Auth error: {e}")
except Exception:
continue
else:
return CodeToolOutput(name=self.name or "daytona_python", error="Sandbox error, please try again.")

stdout = None
stderr = None
error = None
if response.exit_code == 0:
stdout = response.result or None
else:
stderr = response.result or None
if response.result:
lines = response.result.strip().splitlines()
if lines:
error = lines[-1]

return CodeToolOutput(name=self.name or "daytona_python", stdout=stdout, stderr=stderr, error=error)

@property
def json(self) -> dict[str, Any]:
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Python code to execute in a Daytona sandbox environment.",
},
},
"required": ["code"],
},
},
}


if __name__ == "__main__":
from pprint import pprint

interpreter = DaytonaPythonInterpreter()
try:
pprint(interpreter(code="print('Hello, world!')\nimport math\nmath.sqrt(4)"))
pprint(interpreter(code="1/0"))
finally:
interpreter._kill_sandbox()
13 changes: 8 additions & 5 deletions rllm/tools/code_tools/python_interpreter.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
from typing import Any, Literal

from rllm.tools.code_tools.code_tool import CodeTool, CodeToolOutput
from rllm.tools.code_tools.daytona_tool import DaytonaPythonInterpreter
from rllm.tools.code_tools.e2b_tool import E2BPythonInterpreter
from rllm.tools.code_tools.lcb_tool import LCBPythonInterpreter
from rllm.tools.code_tools.together_tool import TogetherCodeTool

# Backend types
BackendType = Literal["local", "e2b", "together", "lcb"]
BackendType = Literal["local", "e2b", "together", "lcb", "daytona"]


class PythonInterpreter(CodeTool):
Expand All @@ -15,7 +16,7 @@ class PythonInterpreter(CodeTool):

This class provides a common interface for executing Python code using different
backend implementations, including local execution, E2B sandbox, Together API,
and LiveCodeBench environment.
Daytona sandboxes, and LiveCodeBench environment.
"""

def __init__(
Expand All @@ -30,9 +31,9 @@ def __init__(
Initialize the unified Python interpreter with the specified backend.

Args:
backend: The backend to use ("local", "e2b", "together", or "lcb")
backend: The backend to use ("local", "e2b", "together", "lcb", or "daytona")
n_sandboxes: Number of concurrent sandboxes/workers to use (for applicable backends)
api_key: API key for cloud-based backends (e2b, together)
api_key: API key for cloud-based backends (e2b, together, daytona)
name: The name of the tool
description: Description of what the tool does
"""
Expand All @@ -48,11 +49,13 @@ def __init__(
def _init_backend(self):
"""Initialize the selected backend interpreter."""
if self.backend_type == "local":
self.backend: LCBPythonInterpreter | E2BPythonInterpreter | TogetherCodeTool = LCBPythonInterpreter()
self.backend: LCBPythonInterpreter | E2BPythonInterpreter | TogetherCodeTool | DaytonaPythonInterpreter = LCBPythonInterpreter()
elif self.backend_type == "e2b":
self.backend = E2BPythonInterpreter(n_sandboxes=self.n_sandboxes, api_key=self.api_key)
elif self.backend_type == "together":
self.backend = TogetherCodeTool(api_key=self.api_key)
elif self.backend_type == "daytona":
self.backend = DaytonaPythonInterpreter(n_sandboxes=self.n_sandboxes, api_key=self.api_key)
else:
raise ValueError(f"Unsupported backend type: {self.backend_type}")

Expand Down