Skip to content

Commit 143f38d

Browse files
SmartManojenyst
andauthored
Refactored sandbox config and added fast boot (#2455)
* Refactored sandbox config and added fastboot * added tests * fixed tests * fixed tests * intimate user about breaking change * remove default config from eval * check for lowercase env * add test * Revert Migration * migrate old sandbox configs * resolve merge conflict * revert migration 2 * Revert "remove default config from eval" This reverts commit de57c58. * change type to box_type * fix var name * linted * lint * lint comments * fix tests * fix tests * fix typo * fix box_type, remove fast_boot * add tests for sandbox config * fix test * update eval docs * small removal comments * adapt toml template * old fields shouldn't be in the app dataclass * fix old keys in app config * clean up exec box --------- Co-authored-by: Engel Nyst <[email protected]>
1 parent 82f4860 commit 143f38d

File tree

22 files changed

+332
-96
lines changed

22 files changed

+332
-96
lines changed

.github/workflows/ghcr.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ jobs:
156156

157157
- name: Load sandbox image and run integration tests
158158
env:
159-
SANDBOX_TYPE: ${{ matrix.sandbox }}
159+
SANDBOX_BOX_TYPE: ${{ matrix.sandbox }}
160160
run: |
161161
# Load the Docker image and capture the output
162162
output=$(docker load -i /tmp/sandbox_image_amd64.tar)

.github/workflows/review-pr.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
env:
5656
LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
5757
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
58-
SANDBOX_TYPE: ssh
58+
SANDBOX_BOX_TYPE: ssh
5959
run: |
6060
# Append path to launch poetry
6161
export PATH="/github/home/.local/bin:$PATH"

.github/workflows/solve-issue.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
ISSUE_BODY: ${{ github.event.issue.body }}
5151
LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
5252
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
53-
SANDBOX_TYPE: ssh
53+
SANDBOX_BOX_TYPE: ssh
5454
run: |
5555
# Append path to launch poetry
5656
export PATH="/github/home/.local/bin:$PATH"

agenthub/micro/commit_writer/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
CommitWriterAgent can help write git commit message. Example:
44

55
```bash
6-
WORKSPACE_MOUNT_PATH="`PWD`" SANDBOX_TYPE="ssh" \
6+
WORKSPACE_MOUNT_PATH="`PWD`" SANDBOX_BOX_TYPE="ssh" \
77
poetry run python opendevin/core/main.py -t "dummy task" -c CommitWriterAgent -d ./
88
```
99

agenthub/monologue_agent/utils/prompts.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from opendevin.core.config import config
22
from opendevin.core.utils import json
3-
from opendevin.events.observation import (
4-
CmdOutputObservation,
5-
)
63
from opendevin.events.action import (
74
Action,
85
)
9-
6+
from opendevin.events.observation import (
7+
CmdOutputObservation,
8+
)
109
from opendevin.events.serialization.action import action_from_dict
10+
1111
ACTION_PROMPT = """
1212
You're a thoughtful robot. Your main task is this:
1313
%(task)s
@@ -206,7 +206,7 @@ def get_request_action_prompt(
206206
'background_commands': bg_commands_message,
207207
'hint': hint,
208208
'user': user,
209-
'timeout': config.sandbox_timeout,
209+
'timeout': config.sandbox.timeout,
210210
'WORKSPACE_MOUNT_PATH_IN_SANDBOX': config.workspace_mount_path_in_sandbox,
211211
}
212212

@@ -242,4 +242,4 @@ def parse_summary_response(response: str) -> list[dict]:
242242
- list[dict]: The list of summaries output by the model
243243
"""
244244
parsed = json.loads(response)
245-
return parsed['new_monologue']
245+
return parsed['new_monologue']

config.template.toml

+17-13
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ workspace_base = "./workspace"
1919
# Cache directory path
2020
#cache_dir = "/tmp/cache"
2121

22-
# Container image to use for the sandbox
23-
#sandbox_container_image = "ghcr.io/opendevin/sandbox:main"
24-
2522
# Debugging enabled
2623
#debug = false
2724

@@ -79,15 +76,6 @@ persist_sandbox = false
7976
# SSH port for the sandbox
8077
#ssh_port = 63710
8178

82-
# Sandbox timeout in seconds
83-
#sandbox_timeout = 120
84-
85-
# Sandbox type (ssh, exec, e2b, local)
86-
#sandbox_type = "ssh"
87-
88-
# Sandbox user ID
89-
#sandbox_user_id = 1000
90-
9179
# Use host network
9280
#use_host_network = false
9381

@@ -174,7 +162,23 @@ model = "gpt-4o"
174162
# Name of the agent
175163
#name = "CodeActAgent"
176164

165+
#################################### Sandbox ###################################
166+
# Configuration for the sandbox
167+
##############################################################################
168+
[sandbox]
169+
# Sandbox timeout in seconds
170+
#timeout = 120
171+
172+
# Sandbox type (ssh, e2b, local)
173+
#box_type = "ssh"
174+
175+
# Sandbox user ID
176+
#user_id = 1000
177+
178+
# Container image to use for the sandbox
179+
#container_image = "ghcr.io/opendevin/sandbox:main"
180+
177181
#################################### Eval ####################################
178182
# Configuration for the evaluation, please refer to the specific evaluation
179183
# plugin for the available options
180-
##############################################################################
184+
##############################################################################

evaluation/TUTORIAL.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ workspace_base = "/path/to/your/workspace"
3131
workspace_mount_path = "/path/to/your/workspace"
3232
# ==========================
3333

34-
sandbox_type = "ssh"
35-
sandbox_timeout = 120
3634
ssh_hostname = "localhost"
3735

3836
# SWEBench eval specific - but you can tweak it to your needs
@@ -41,6 +39,10 @@ run_as_devin = false
4139
# linting python after editing helps LLM fix indentations
4240
enable_auto_lint = true
4341

42+
[sandbox]
43+
box_type = "ssh"
44+
timeout = 120
45+
4446
[llm]
4547
# IMPORTANT: add your API key here, and set the model to the one you want to evaluate
4648
model = "gpt-4o-2024-05-13"

evaluation/agent_bench/README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ cache_dir = "/path/to/cache"
1818
workspace_base = "/path/to/workspace"
1919
workspace_mount_path = "/path/to/workspace"
2020

21-
sandbox_type = "ssh"
22-
sandbox_timeout = 120
2321
ssh_hostname = "localhost"
2422

2523
use_host_network = false
2624
# AgentBench specific
2725
run_as_devin = true
2826
enable_auto_lint = true
2927

28+
[sandbox]
29+
box_type = "ssh"
30+
timeout = 120
31+
3032
[eval_gpt35_turbo]
3133
model = "gpt-3.5-turbo"
3234
api_key = "sk-123"

evaluation/miniwob/README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ Add the following configurations:
1616
[core]
1717
max_iterations = 100
1818
cache_dir = "/tmp/cache"
19-
sandbox_type = "ssh"
2019
ssh_hostname = "localhost"
21-
sandbox_timeout = 120
20+
21+
[sandbox]
22+
box_type = "ssh"
23+
timeout = 120
2224

2325
# TODO: Change these to the model you want to evaluate
2426
[eval_gpt4_1106_preview]

evaluation/swe_bench/README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@ Add the following configurations:
4444
[core]
4545
max_iterations = 100
4646
cache_dir = "/tmp/cache"
47-
sandbox_type = "ssh"
4847
ssh_hostname = "localhost"
49-
sandbox_timeout = 120
48+
49+
[sandbox]
50+
box_type = "ssh"
51+
timeout = 120
5052

5153
# SWEBench eval specific
5254
use_host_network = false

evaluation/webarena/README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ Add the following configurations:
1616
[core]
1717
max_iterations = 100
1818
cache_dir = "/tmp/cache"
19-
sandbox_type = "ssh"
2019
ssh_hostname = "localhost"
21-
sandbox_timeout = 120
20+
21+
[sandbox]
22+
box_type = "ssh"
23+
timeout = 120
2224

2325
# TODO: Change these to the model you want to evaluate
2426
[eval_gpt4_1106_preview]

opendevin/core/config.py

+78-15
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,51 @@ def defaults_to_dict(self) -> dict:
125125
return result
126126

127127

128+
@dataclass
129+
class SandboxConfig(metaclass=Singleton):
130+
"""
131+
Configuration for the sandbox.
132+
133+
Attributes:
134+
box_type: The type of sandbox to use. Options are: ssh, e2b, local.
135+
container_image: The container image to use for the sandbox.
136+
user_id: The user ID for the sandbox.
137+
timeout: The timeout for the sandbox.
138+
139+
"""
140+
141+
box_type: str = 'ssh'
142+
container_image: str = 'ghcr.io/opendevin/sandbox' + (
143+
f':{os.getenv("OPEN_DEVIN_BUILD_VERSION")}'
144+
if os.getenv('OPEN_DEVIN_BUILD_VERSION')
145+
else ':main'
146+
)
147+
user_id: int = os.getuid() if hasattr(os, 'getuid') else 1000
148+
timeout: int = 120
149+
150+
def defaults_to_dict(self) -> dict:
151+
"""
152+
Serialize fields to a dict for the frontend, including type hints, defaults, and whether it's optional.
153+
"""
154+
dict = {}
155+
for f in fields(self):
156+
dict[f.name] = get_field_info(f)
157+
return dict
158+
159+
def __str__(self):
160+
attr_str = []
161+
for f in fields(self):
162+
attr_name = f.name
163+
attr_value = getattr(self, f.name)
164+
165+
attr_str.append(f'{attr_name}={repr(attr_value)}')
166+
167+
return f"SandboxConfig({', '.join(attr_str)})"
168+
169+
def __repr__(self):
170+
return self.__str__()
171+
172+
128173
class UndefinedString(str, Enum):
129174
UNDEFINED = 'UNDEFINED'
130175

@@ -137,6 +182,7 @@ class AppConfig(metaclass=Singleton):
137182
Attributes:
138183
llm: The LLM configuration.
139184
agent: The agent configuration.
185+
sandbox: The sandbox configuration.
140186
runtime: The runtime environment.
141187
file_store: The file store to use.
142188
file_store_path: The path to the file store.
@@ -145,17 +191,14 @@ class AppConfig(metaclass=Singleton):
145191
workspace_mount_path_in_sandbox: The path to mount the workspace in the sandbox. Defaults to /workspace.
146192
workspace_mount_rewrite: The path to rewrite the workspace mount path to.
147193
cache_dir: The path to the cache directory. Defaults to /tmp/cache.
148-
sandbox_container_image: The container image to use for the sandbox.
149194
run_as_devin: Whether to run as devin.
150195
max_iterations: The maximum number of iterations.
151196
max_budget_per_task: The maximum budget allowed per task, beyond which the agent will stop.
152197
e2b_api_key: The E2B API key.
153-
sandbox_type: The type of sandbox to use. Options are: ssh, exec, e2b, local.
154198
use_host_network: Whether to use the host network.
155199
ssh_hostname: The SSH hostname.
156200
disable_color: Whether to disable color. For terminals that don't support color.
157-
sandbox_user_id: The user ID for the sandbox.
158-
sandbox_timeout: The timeout for the sandbox.
201+
initialize_plugins: Whether to initialize plugins.
159202
debug: Whether to enable debugging.
160203
enable_auto_lint: Whether to enable auto linting. This is False by default, for regular runs of the app. For evaluation, please set this to True.
161204
enable_cli_session: Whether to enable saving and restoring the session when run from CLI.
@@ -166,6 +209,7 @@ class AppConfig(metaclass=Singleton):
166209

167210
llm: LLMConfig = field(default_factory=LLMConfig)
168211
agent: AgentConfig = field(default_factory=AgentConfig)
212+
sandbox: SandboxConfig = field(default_factory=SandboxConfig)
169213
runtime: str = 'server'
170214
file_store: str = 'memory'
171215
file_store_path: str = '/tmp/file_store'
@@ -176,21 +220,13 @@ class AppConfig(metaclass=Singleton):
176220
workspace_mount_path_in_sandbox: str = '/workspace'
177221
workspace_mount_rewrite: str | None = None
178222
cache_dir: str = '/tmp/cache'
179-
sandbox_container_image: str = 'ghcr.io/opendevin/sandbox' + (
180-
f':{os.getenv("OPEN_DEVIN_BUILD_VERSION")}'
181-
if os.getenv('OPEN_DEVIN_BUILD_VERSION')
182-
else ':main'
183-
)
184223
run_as_devin: bool = True
185224
max_iterations: int = 100
186225
max_budget_per_task: float | None = None
187226
e2b_api_key: str = ''
188-
sandbox_type: str = 'ssh' # Can be 'ssh', 'exec', or 'e2b'
189227
use_host_network: bool = False
190228
ssh_hostname: str = 'localhost'
191229
disable_color: bool = False
192-
sandbox_user_id: int = os.getuid() if hasattr(os, 'getuid') else 1000
193-
sandbox_timeout: int = 120
194230
initialize_plugins: bool = True
195231
persist_sandbox: bool = False
196232
ssh_port: int = 63710
@@ -287,7 +323,7 @@ def get_field_info(f):
287323

288324
def load_from_env(cfg: AppConfig, env_or_toml_dict: dict | MutableMapping[str, str]):
289325
"""Reads the env-style vars and sets config attributes based on env vars or a config.toml dict.
290-
Compatibility with vars like LLM_BASE_URL, AGENT_MEMORY_ENABLED and others.
326+
Compatibility with vars like LLM_BASE_URL, AGENT_MEMORY_ENABLED, SANDBOX_TIMEOUT and others.
291327
292328
Args:
293329
cfg: The AppConfig object to set attributes on.
@@ -335,6 +371,9 @@ def set_attr_from_env(sub_config: Any, prefix=''):
335371
f'Error setting env var {env_var_name}={value}: check that the value is of the right type'
336372
)
337373

374+
if 'SANDBOX_TYPE' in env_or_toml_dict:
375+
logger.error('SANDBOX_TYPE is deprecated. Please use SANDBOX_BOX_TYPE instead.')
376+
env_or_toml_dict['SANDBOX_BOX_TYPE'] = env_or_toml_dict.pop('SANDBOX_TYPE')
338377
# Start processing from the root of the config object
339378
set_attr_from_env(cfg)
340379

@@ -380,8 +419,32 @@ def load_from_toml(cfg: AppConfig, toml_file: str = 'config.toml'):
380419
if 'agent' in toml_config:
381420
agent_config = AgentConfig(**toml_config['agent'])
382421

422+
# set sandbox config from the toml file
423+
sandbox_config = config.sandbox
424+
425+
# migrate old sandbox configs from [core] section to sandbox config
426+
keys_to_migrate = [key for key in core_config if key.startswith('sandbox_')]
427+
for key in keys_to_migrate:
428+
new_key = key.replace('sandbox_', '')
429+
if new_key == 'type':
430+
new_key = 'box_type'
431+
if new_key in sandbox_config.__annotations__:
432+
# read the key in sandbox and remove it from core
433+
setattr(sandbox_config, new_key, core_config.pop(key))
434+
else:
435+
logger.warning(f'Unknown sandbox config: {key}')
436+
437+
# the new style values override the old style values
438+
if 'sandbox' in toml_config:
439+
sandbox_config = SandboxConfig(**toml_config['sandbox'])
440+
383441
# update the config object with the new values
384-
AppConfig(llm=llm_config, agent=agent_config, **core_config)
442+
AppConfig(
443+
llm=llm_config,
444+
agent=agent_config,
445+
sandbox=sandbox_config,
446+
**core_config,
447+
)
385448
except (TypeError, KeyError) as e:
386449
logger.warning(
387450
f'Cannot parse config from toml, toml values have not been applied.\nError: {e}',
@@ -400,7 +463,7 @@ def finalize_config(cfg: AppConfig):
400463
cfg.workspace_base = os.path.abspath(cfg.workspace_base)
401464

402465
# In local there is no sandbox, the workspace will have the same pwd as the host
403-
if cfg.sandbox_type == 'local':
466+
if cfg.sandbox.box_type == 'local':
404467
cfg.workspace_mount_path_in_sandbox = cfg.workspace_mount_path
405468

406469
if cfg.workspace_mount_rewrite: # and not config.workspace_mount_path:

opendevin/core/schema/config.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class ConfigType(str, Enum):
3434
MAX_ITERATIONS = 'MAX_ITERATIONS'
3535
AGENT = 'AGENT'
3636
E2B_API_KEY = 'E2B_API_KEY'
37-
SANDBOX_TYPE = 'SANDBOX_TYPE'
37+
SANDBOX_BOX_TYPE = 'SANDBOX_BOX_TYPE'
3838
SANDBOX_USER_ID = 'SANDBOX_USER_ID'
3939
SANDBOX_TIMEOUT = 'SANDBOX_TIMEOUT'
4040
USE_HOST_NETWORK = 'USE_HOST_NETWORK'

opendevin/runtime/docker/local_box.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727

2828
class LocalBox(Sandbox):
29-
def __init__(self, timeout: int = config.sandbox_timeout):
29+
def __init__(self, timeout: int = config.sandbox.timeout):
3030
os.makedirs(config.workspace_base, exist_ok=True)
3131
self.timeout = timeout
3232
self.background_commands: dict[int, Process] = {}

0 commit comments

Comments
 (0)