Skip to content

Commit 43693ec

Browse files
committed
feat: add E2B sandbox support
Add a new E2BREPL environment that runs Python code in E2B sandboxes, following the same HTTP broker pattern as ModalREPL and PrimeREPL. Changes: - Add rlm/environments/e2b_repl.py with E2BREPL class - Add examples/e2b_repl_example.py demonstrating usage - Register "e2b" environment in environments/__init__.py - Add "e2b" to EnvironmentType in core/types.py - Add e2b optional dependency to pyproject.toml Key implementation details: - Uses e2b-code-interpreter SDK with Sandbox.create() API - Runs Flask broker on port 8889 (8888 is used by E2B's Jupyter) - Uses sandbox.get_host(port) for public URL access - Supports llm_query(), llm_query_batched(), FINAL_VAR()
1 parent db41d15 commit 43693ec

5 files changed

Lines changed: 617 additions & 4 deletions

File tree

‎examples/e2b_repl_example.py‎

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
Example usage of E2B REPL with code execution and LLM queries.
3+
4+
Run with: python -m examples.e2b_repl_example
5+
6+
Requires E2B API key set in environment:
7+
export E2B_API_KEY=your_api_key
8+
"""
9+
10+
from rlm.clients.base_lm import BaseLM
11+
from rlm.core.lm_handler import LMHandler
12+
from rlm.core.types import ModelUsageSummary, UsageSummary
13+
from rlm.environments.e2b_repl import E2BREPL
14+
15+
16+
class MockLM(BaseLM):
17+
"""Simple mock LM that echoes prompts."""
18+
19+
def __init__(self):
20+
super().__init__(model_name="mock-model")
21+
22+
def completion(self, prompt):
23+
return f"Mock response to: {prompt[:50]}"
24+
25+
async def acompletion(self, prompt):
26+
return self.completion(prompt)
27+
28+
def get_usage_summary(self):
29+
return UsageSummary(
30+
model_usage_summaries={
31+
"mock-model": ModelUsageSummary(
32+
total_calls=1, total_input_tokens=10, total_output_tokens=10
33+
)
34+
}
35+
)
36+
37+
def get_last_usage(self):
38+
return self.get_usage_summary()
39+
40+
41+
def main():
42+
print("=" * 60)
43+
print("E2B REPL Example")
44+
print("=" * 60)
45+
46+
# Example 1: Basic code execution
47+
print("\n[1] Basic code execution (no LLM handler)")
48+
print("-" * 40)
49+
50+
with E2BREPL(timeout=300) as repl:
51+
print(f"E2B sandbox created, ID: {repl.sandbox_id}")
52+
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 E2BREPL(
75+
timeout=300,
76+
lm_handler_address=handler.address,
77+
) as repl:
78+
print(f"E2B sandbox created, 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+
print(f"Stderr: {result.stderr or '(none)'}")
85+
86+
result = repl.execute_code("print(response)")
87+
print(f"Response: {result.stdout.strip()}")
88+
89+
# Batched LLM query
90+
result = repl.execute_code(
91+
'responses = llm_query_batched(["Question 1", "Question 2", "Question 3"])'
92+
)
93+
print("\nExecuted: responses = llm_query_batched([...])")
94+
95+
result = repl.execute_code("print(f'Got {len(responses)} responses')")
96+
print(f"Result: {result.stdout.strip()}")
97+
98+
result = repl.execute_code("print(responses[0])")
99+
print(f"First response: {result.stdout.strip()}")
100+
101+
print("\n" + "=" * 60)
102+
print("Done!")
103+
print("=" * 60)
104+
105+
106+
if __name__ == "__main__":
107+
main()

‎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+
e2b = ["e2b-code-interpreter>=0.0.11", "dill>=0.3.7"]
2324

2425
[build-system]
2526
requires = ["setuptools>=61.0"]

‎rlm/core/types.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
ClientBackend = Literal[
66
"openai", "portkey", "openrouter", "vllm", "litellm", "anthropic", "azure_openai", "gemini"
77
]
8-
EnvironmentType = Literal["local", "prime", "modal"]
8+
EnvironmentType = Literal["local", "docker", "modal", "prime", "e2b"]
99

1010

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

‎rlm/environments/__init__.py‎

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

66

77
def get_environment(
8-
environment: Literal["local", "modal", "docker"],
8+
environment: Literal["local", "modal", "docker", "e2b"],
99
environment_kwargs: dict[str, Any],
1010
) -> BaseEnv:
1111
"""
1212
Routes a specific environment and the args (as a dict) to the appropriate environment if supported.
13-
Currently supported environments: ['local', 'modal', 'docker']
13+
Currently supported environments: ['local', 'modal', 'docker', 'e2b']
1414
"""
1515
if environment == "local":
1616
return LocalREPL(**environment_kwargs)
@@ -22,7 +22,11 @@ def get_environment(
2222
from rlm.environments.docker_repl import DockerREPL
2323

2424
return DockerREPL(**environment_kwargs)
25+
elif environment == "e2b":
26+
from rlm.environments.e2b_repl import E2BREPL
27+
28+
return E2BREPL(**environment_kwargs)
2529
else:
2630
raise ValueError(
27-
f"Unknown environment: {environment}. Supported: ['local', 'modal', 'docker']"
31+
f"Unknown environment: {environment}. Supported: ['local', 'modal', 'docker', 'e2b']"
2832
)

0 commit comments

Comments
 (0)