Skip to content

Commit 6eb5f6b

Browse files
feat: add Daytona sandbox support (#36)
* feat: add Daytona REPL environment support - Introduced a new Daytona REPL environment for executing Python code in Daytona sandboxes. - Updated `pyproject.toml` to include `daytona` as an optional dependency. - Added example usage of Daytona REPL in `examples/daytona_repl_example.py`. - Modified existing code to support the new `daytona` environment in `rlm/environments/__init__.py` and `rlm/core/types.py`. - Enhanced the `RLM` initialization in `examples/quickstart.py` to utilize the Daytona environment. This implementation allows users to run code in isolated Daytona sandboxes, facilitating LLM queries and state management. * feat: enhance Daytona REPL examples and documentation * revert quickstart.py * fixed merge conflict * updated daytona repl to take the right default key * updated example --------- Co-authored-by: az <alex.lx.zhang@gmail.com>
1 parent 99f4045 commit 6eb5f6b

File tree

7 files changed

+1057
-10
lines changed

7 files changed

+1057
-10
lines changed

examples/daytona_repl_example.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
"""
2+
Example usage of Daytona REPL with code execution and LLM queries.
3+
4+
Run with: python -m examples.daytona_repl_example
5+
"""
6+
7+
from rlm.clients.base_lm import BaseLM
8+
from rlm.core.lm_handler import LMHandler
9+
from rlm.core.types import ModelUsageSummary, UsageSummary
10+
from rlm.environments.daytona_repl import DaytonaREPL
11+
12+
13+
class MockLM(BaseLM):
14+
"""Simple mock LM that echoes prompts."""
15+
16+
def __init__(self):
17+
super().__init__(model_name="mock-model")
18+
19+
def completion(self, prompt):
20+
return f"Mock response to: {prompt[:50]}"
21+
22+
async def acompletion(self, prompt):
23+
return self.completion(prompt)
24+
25+
def get_usage_summary(self):
26+
return UsageSummary(
27+
model_usage_summaries={
28+
"mock-model": ModelUsageSummary(
29+
total_calls=1, total_input_tokens=10, total_output_tokens=10
30+
)
31+
}
32+
)
33+
34+
def get_last_usage(self):
35+
return self.get_usage_summary()
36+
37+
38+
def main():
39+
print("=" * 60)
40+
print("Daytona REPL Example")
41+
print("=" * 60)
42+
43+
# Note: Requires DAYTONA_API_KEY environment variable to be set
44+
# or passed explicitly to DaytonaREPL(api_key="...")
45+
46+
# Example 1: Basic code execution
47+
print("\n[1] Basic code execution (no LLM handler)")
48+
print("-" * 40)
49+
50+
try:
51+
with DaytonaREPL(name="rlm-example") as repl:
52+
print(f"Daytona sandbox started with ID: {repl.sandbox.id}")
53+
result = repl.execute_code("x = 1 + 2")
54+
print("Executed: x = 1 + 2")
55+
print(f"Locals: {result.locals}")
56+
57+
result = repl.execute_code("print(x * 2)")
58+
print("Executed: print(x * 2)")
59+
print(f"Stdout: {result.stdout.strip()}")
60+
61+
result = repl.execute_code("answer = 42")
62+
result = repl.execute_code('print(FINAL_VAR("answer"))')
63+
print(f"FINAL_VAR('answer'): {result.stdout.strip()}")
64+
65+
# Example 2: With LLM handler
66+
print("\n[2] Code execution with LLM handler")
67+
print("-" * 40)
68+
69+
mock_client = MockLM()
70+
71+
with LMHandler(client=mock_client) as handler:
72+
print(f"LM Handler started at {handler.address}")
73+
74+
with DaytonaREPL(
75+
name="rlm-example-handler",
76+
lm_handler_address=handler.address,
77+
) as repl:
78+
print(f"Daytona sandbox started with ID: {repl.sandbox.id}")
79+
print(f"Broker URL: {repl.broker_url}")
80+
81+
# Single LLM query
82+
result = repl.execute_code('response = llm_query("What is 2+2?")')
83+
print("Executed: response = llm_query('What is 2+2?')")
84+
if result.stderr:
85+
print(f"Stderr: {result.stderr}")
86+
87+
result = repl.execute_code("print(response)")
88+
print(f"Response: {result.stdout.strip()}")
89+
90+
# Batched LLM query
91+
result = repl.execute_code(
92+
'responses = llm_query_batched(["Question 1", "Question 2", "Question 3"])'
93+
)
94+
print("\nExecuted: responses = llm_query_batched([...])")
95+
96+
result = repl.execute_code("print(f'Got {len(responses)} responses')")
97+
print(f"Result: {result.stdout.strip()}")
98+
99+
result = repl.execute_code("print(responses[0])")
100+
print(f"First response: {result.stdout.strip()}")
101+
102+
except Exception as e:
103+
print(f"Error: {e}")
104+
print("\nMake sure Daytona is configured correctly and DAYTONA_API_KEY is set.")
105+
106+
print("\n" + "=" * 60)
107+
print("Done!")
108+
print("=" * 60)
109+
110+
111+
if __name__ == "__main__":
112+
main()

examples/quickstart.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
"model_name": "gpt-5-nano",
1616
"api_key": os.getenv("OPENAI_API_KEY"),
1717
},
18-
environment="local",
18+
environment="docker",
1919
environment_kwargs={},
2020
max_depth=1,
2121
logger=logger,
2222
verbose=True, # For printing to console with rich, disabled by default.
2323
)
2424

25-
result = rlm.completion("Print me the first 100 powers of two, each on a newline.")
25+
result = rlm.completion("Print me the first 5 powers of two, each on a newline.")
2626

2727
print(result)

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies = [
2020

2121
[project.optional-dependencies]
2222
modal = ["modal>=0.73.0", "dill>=0.3.7"]
23+
daytona = ["daytona>=0.128.1", "dill>=0.3.7"]
2324
prime = ["prime-sandboxes>=0.2.0", "dill>=0.3.7"]
2425

2526
[build-system]

rlm/core/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"azure_openai",
1414
"gemini",
1515
]
16-
EnvironmentType = Literal["local", "docker", "modal", "prime"]
16+
EnvironmentType = Literal["local", "docker", "modal", "prime", "daytona"]
1717

1818

1919
def _serialize_value(value: Any) -> Any:

rlm/environments/__init__.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88

99
def get_environment(
10-
environment: Literal["local", "modal", "docker", "prime"],
10+
environment: Literal["local", "modal", "docker", "daytona", "prime"],
1111
environment_kwargs: dict[str, Any],
1212
) -> BaseEnv:
1313
"""
1414
Routes a specific environment and the args (as a dict) to the appropriate environment if supported.
15-
Currently supported environments: ['local', 'modal', 'docker', 'prime']
15+
Currently supported environments: ['local', 'modal', 'docker', 'daytona', 'prime']
1616
"""
1717
if environment == "local":
1818
return LocalREPL(**environment_kwargs)
@@ -24,11 +24,15 @@ def get_environment(
2424
from rlm.environments.docker_repl import DockerREPL
2525

2626
return DockerREPL(**environment_kwargs)
27+
elif environment == "daytona":
28+
from rlm.environments.daytona_repl import DaytonaREPL
29+
30+
return DaytonaREPL(**environment_kwargs)
2731
elif environment == "prime":
2832
from rlm.environments.prime_repl import PrimeREPL
2933

3034
return PrimeREPL(**environment_kwargs)
3135
else:
3236
raise ValueError(
33-
f"Unknown environment: {environment}. Supported: ['local', 'modal', 'docker', 'prime']"
37+
f"Unknown environment: {environment}. Supported: ['local', 'modal', 'docker', 'daytona', 'prime']"
3438
)

0 commit comments

Comments
 (0)