From d518ca08b7b60ca4a757551dcc31236b302aabdf Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Wed, 2 Oct 2024 09:42:17 -0500 Subject: [PATCH 01/13] standardize error message across remote runtime and eventstream runtime (#4159) --- openhands/runtime/client/runtime.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openhands/runtime/client/runtime.py b/openhands/runtime/client/runtime.py index 32b002f3ea..c665f668b1 100644 --- a/openhands/runtime/client/runtime.py +++ b/openhands/runtime/client/runtime.py @@ -429,13 +429,15 @@ def run_action(self, action: Action) -> Observation: logger.debug(f'response: {response}') error_message = response.text logger.error(f'Error from server: {error_message}') - obs = ErrorObservation(f'Command execution failed: {error_message}') + obs = ErrorObservation(f'Action execution failed: {error_message}') except requests.Timeout: logger.error('No response received within the timeout period.') - obs = ErrorObservation('Command execution timed out') + obs = ErrorObservation( + f'Action execution timed out after {action.timeout} seconds.' + ) except Exception as e: - logger.error(f'Error during command execution: {e}') - obs = ErrorObservation(f'Command execution failed: {str(e)}') + logger.error(f'Error during action execution: {e}') + obs = ErrorObservation(f'Action execution failed: {str(e)}') self._refresh_logs() return obs From 240b500acf546b9847227b5369d09444e71d60c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:28:43 +0200 Subject: [PATCH 02/13] chore(deps-dev): bump openai from 1.50.2 to 1.51.0 (#4171) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 401792ae4e..90f6e9de66 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5365,13 +5365,13 @@ sympy = "*" [[package]] name = "openai" -version = "1.50.2" +version = "1.51.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.7.1" files = [ - {file = "openai-1.50.2-py3-none-any.whl", hash = "sha256:822dd2051baa3393d0d5406990611975dd6f533020dc9375a34d4fe67e8b75f7"}, - {file = "openai-1.50.2.tar.gz", hash = "sha256:3987ae027152fc8bea745d60b02c8f4c4a76e1b5c70e73565fa556db6f78c9e6"}, + {file = "openai-1.51.0-py3-none-any.whl", hash = "sha256:d9affafb7e51e5a27dce78589d4964ce4d6f6d560307265933a94b2e3f3c5d2c"}, + {file = "openai-1.51.0.tar.gz", hash = "sha256:8dc4f9d75ccdd5466fc8c99a952186eddceb9fd6ba694044773f3736a847149d"}, ] [package.dependencies] From a1d09c4437e1778768d2d38e83403833bdda6db6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:40:16 +0000 Subject: [PATCH 03/13] chore(deps): bump google-cloud-aiplatform from 1.68.0 to 1.69.0 (#4172) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 90f6e9de66..d8e9844c99 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2240,13 +2240,13 @@ httplib2 = ">=0.19.0" [[package]] name = "google-cloud-aiplatform" -version = "1.68.0" +version = "1.69.0" description = "Vertex AI API client library" optional = false python-versions = ">=3.8" files = [ - {file = "google-cloud-aiplatform-1.68.0.tar.gz", hash = "sha256:d74e9f33707c7a14c6a32a7cfe9acd32b90975dfba9fac487d105c8ba5197f40"}, - {file = "google_cloud_aiplatform-1.68.0-py2.py3-none-any.whl", hash = "sha256:24dacc34457665ab6054bdf47e2475793dcf2d865b568420a909b452a477b3e6"}, + {file = "google-cloud-aiplatform-1.69.0.tar.gz", hash = "sha256:08be3a4432fd87d9cc86db83ba626f988d13597197bc53c6808e1c4c65a25bb0"}, + {file = "google_cloud_aiplatform-1.69.0-py2.py3-none-any.whl", hash = "sha256:6e21c29bf4506ed3bfb00cfe47ab1ad1788854b18f0ded2458016837c917e520"}, ] [package.dependencies] From 471867859fbb08b848506f37287e11c7e67183c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:50:47 +0000 Subject: [PATCH 04/13] chore(deps): bump boto3 from 1.35.30 to 1.35.31 (#4174) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/poetry.lock b/poetry.lock index d8e9844c99..cf24d09e73 100644 --- a/poetry.lock +++ b/poetry.lock @@ -571,17 +571,17 @@ files = [ [[package]] name = "boto3" -version = "1.35.30" +version = "1.35.31" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.30-py3-none-any.whl", hash = "sha256:d89c3459db89c5408e83219ab849ffd0146bc4285e75cdc67c6e45d390a12df2"}, - {file = "boto3-1.35.30.tar.gz", hash = "sha256:d2851aec8e9dc6937977acbe9a5124ecc31b3ad5f50a10cd9ae52636da3f52fa"}, + {file = "boto3-1.35.31-py3-none-any.whl", hash = "sha256:2e9af74d10d8af7610a8d8468d2914961f116912a024fce17351825260385a52"}, + {file = "boto3-1.35.31.tar.gz", hash = "sha256:8c593af260c4ea3eb6f079c09908f94494ca2222aa4e40a7ff490fab1cee8b39"}, ] [package.dependencies] -botocore = ">=1.35.30,<1.36.0" +botocore = ">=1.35.31,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -590,13 +590,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.30" +version = "1.35.31" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.30-py3-none-any.whl", hash = "sha256:3bb9f9dde001608671ea74681ac3cec06bbbb10cba8cb8c1387a25e843075ce0"}, - {file = "botocore-1.35.30.tar.gz", hash = "sha256:ab5350e8a50e48d371fa2d517d65c29a40c43788cb9a15387f93eac5a23df0fd"}, + {file = "botocore-1.35.31-py3-none-any.whl", hash = "sha256:4cee814875bc78656aef4011d3d6b2231e96f53ea3661ee428201afb579d5c31"}, + {file = "botocore-1.35.31.tar.gz", hash = "sha256:f7bfa910cf2cbcc8c2307c1cf7b93495d614c2d699883417893e0a337fe4eb63"}, ] [package.dependencies] From bb151655cc54bd3d8cc343c94db5da9465ab8f18 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:06:58 +0000 Subject: [PATCH 05/13] chore(deps-dev): bump streamlit from 1.38.0 to 1.39.0 (#4175) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/poetry.lock b/poetry.lock index cf24d09e73..0b8b376fc3 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8012,13 +8012,13 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.7 [[package]] name = "streamlit" -version = "1.38.0" +version = "1.39.0" description = "A faster way to build and share data apps" optional = false python-versions = "!=3.9.7,>=3.8" files = [ - {file = "streamlit-1.38.0-py2.py3-none-any.whl", hash = "sha256:0653ecfe86fef0f1608e3e082aef7eb335d8713f6f31e9c3b19486d1c67d7c41"}, - {file = "streamlit-1.38.0.tar.gz", hash = "sha256:c4bf36b3ef871499ed4594574834583113f93f077dd3035d516d295786f2ad63"}, + {file = "streamlit-1.39.0-py2.py3-none-any.whl", hash = "sha256:a359fc54ed568b35b055ff1d453c320735539ad12e264365a36458aef55a5fba"}, + {file = "streamlit-1.39.0.tar.gz", hash = "sha256:fef9de7983c4ee65c08e85607d7ffccb56b00482b1041fa62f90e4815d39df3a"}, ] [package.dependencies] @@ -8029,18 +8029,18 @@ click = ">=7.0,<9" gitpython = ">=3.0.7,<3.1.19 || >3.1.19,<4" numpy = ">=1.20,<3" packaging = ">=20,<25" -pandas = ">=1.3.0,<3" +pandas = ">=1.4.0,<3" pillow = ">=7.1.0,<11" protobuf = ">=3.20,<6" pyarrow = ">=7.0" pydeck = ">=0.8.0b4,<1" requests = ">=2.27,<3" rich = ">=10.14.0,<14" -tenacity = ">=8.1.0,<9" +tenacity = ">=8.1.0,<10" toml = ">=0.10.1,<2" tornado = ">=6.0.3,<7" typing-extensions = ">=4.3.0,<5" -watchdog = {version = ">=2.1.5,<5", markers = "platform_system != \"Darwin\""} +watchdog = {version = ">=2.1.5,<6", markers = "platform_system != \"Darwin\""} [package.extras] snowflake = ["snowflake-connector-python (>=2.8.0)", "snowflake-snowpark-python[modin] (>=1.17.0)"] From 14a4e1018abaa818d6c4e365bf7f234c716a8a13 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 16:15:15 +0000 Subject: [PATCH 06/13] chore(deps): bump litellm from 1.48.7 to 1.48.9 (#4176) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0b8b376fc3..19f86aaeb8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3762,13 +3762,13 @@ types-tqdm = "*" [[package]] name = "litellm" -version = "1.48.7" +version = "1.48.9" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.48.7-py3-none-any.whl", hash = "sha256:4971a9e681188635c2ee6dc44fe35bb2774586e9018682adcccdbb516b839c64"}, - {file = "litellm-1.48.7.tar.gz", hash = "sha256:ff1fef7049e9afa09598f98d1e510a6d5f252ec65c0526b8bfaf13eadfcf65e5"}, + {file = "litellm-1.48.9-py3-none-any.whl", hash = "sha256:9608f510e82c27b15bab7bcfab5e1308055f0c457e7881ccfff91c189bf2c055"}, + {file = "litellm-1.48.9.tar.gz", hash = "sha256:02dd2f66fab24f388692694401bbabd34de5a62a16d064b3f15726a550a65cd3"}, ] [package.dependencies] From e93db807692f968a08e5b923566f9baaf0a09800 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 18:28:34 +0200 Subject: [PATCH 07/13] chore(deps-dev): bump reportlab from 4.2.4 to 4.2.5 (#4170) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 19f86aaeb8..1515fe2efe 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7120,13 +7120,13 @@ files = [ [[package]] name = "reportlab" -version = "4.2.4" +version = "4.2.5" description = "The Reportlab Toolkit" optional = false python-versions = "<4,>=3.7" files = [ - {file = "reportlab-4.2.4-py3-none-any.whl", hash = "sha256:6e4d86647b8bfd772f475a58f9b0dcba4b340b1969f0db36333089f6ca9ab362"}, - {file = "reportlab-4.2.4.tar.gz", hash = "sha256:a00b57292e156a7bda84edf31d60c25578153076c8fb96331d0c59eddda052c8"}, + {file = "reportlab-4.2.5-py3-none-any.whl", hash = "sha256:eb2745525a982d9880babb991619e97ac3f661fae30571b7d50387026ca765ee"}, + {file = "reportlab-4.2.5.tar.gz", hash = "sha256:5cf35b8fd609b68080ac7bbb0ae1e376104f7d5f7b2d3914c7adc63f2593941f"}, ] [package.dependencies] From e0f8a5d508633ed9712af85ac33cc87182636464 Mon Sep 17 00:00:00 2001 From: tofarr Date: Wed, 2 Oct 2024 10:51:12 -0600 Subject: [PATCH 08/13] Fix: Add timeout on websocket accept (#4169) --- openhands/server/listen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openhands/server/listen.py b/openhands/server/listen.py index 5a3e725da6..771e207a24 100644 --- a/openhands/server/listen.py +++ b/openhands/server/listen.py @@ -1,3 +1,4 @@ +import asyncio import os import re import tempfile @@ -257,7 +258,7 @@ async def websocket_endpoint(websocket: WebSocket): {"action": "finish", "args": {}} ``` """ - await websocket.accept() + await asyncio.wait_for(websocket.accept(), 10) if websocket.query_params.get('token'): token = websocket.query_params.get('token') From dd228c07e05b6908bc1d15dde8f8025284a9ef47 Mon Sep 17 00:00:00 2001 From: mamoodi Date: Wed, 2 Oct 2024 13:30:53 -0400 Subject: [PATCH 09/13] Small reordering of PR template (#4173) --- .github/pull_request_template.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 113f681a6c..dace0fffec 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,6 @@ -- [ ] Include this change in the Release Notes. If checked, you must provide an **end-user friendly** description for your change below - **End-user friendly description of the problem this fixes or functionality that this introduces** - +- [ ] Include this change in the Release Notes. If checked, you must provide an **end-user friendly** description for your change below --- **Give a summary of what the PR does, explaining any non-trivial design decisions** From c8a933590ac9bd55aa333940bacd4e323eff34bc Mon Sep 17 00:00:00 2001 From: Rehan Ganapathy <79349712+rehanganapathy@users.noreply.github.com> Date: Thu, 3 Oct 2024 02:00:12 +0530 Subject: [PATCH 10/13] (feat) allow specification of config.toml location via args (solves #3947) (#4168) Co-authored-by: Rehan Ganapathy --- openhands/core/cli.py | 2 +- openhands/core/config/utils.py | 15 ++++++++++++--- openhands/core/main.py | 2 +- tests/unit/test_arg_parser.py | 3 ++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/openhands/core/cli.py b/openhands/core/cli.py index 7c4380a27a..fafad7e116 100644 --- a/openhands/core/cli.py +++ b/openhands/core/cli.py @@ -91,7 +91,7 @@ async def main(): return logger.setLevel(logging.WARNING) - config = load_app_config() + config = load_app_config(config_file=args.config_file) sid = 'cli' agent_cls: Type[Agent] = Agent.get_cls(config.default_agent) diff --git a/openhands/core/config/utils.py b/openhands/core/config/utils.py index 1026d26c01..15b64eb6d6 100644 --- a/openhands/core/config/utils.py +++ b/openhands/core/config/utils.py @@ -281,6 +281,12 @@ def get_llm_config_arg( def get_parser() -> argparse.ArgumentParser: """Get the parser for the command line arguments.""" parser = argparse.ArgumentParser(description='Run an agent with a specific task') + parser.add_argument( + '--config-file', + type=str, + default='config.toml', + help='Path to the config file (default: config.toml in the current directory)', + ) parser.add_argument( '-d', '--directory', @@ -375,14 +381,17 @@ def parse_arguments() -> argparse.Namespace: return parsed_args -def load_app_config(set_logging_levels: bool = True) -> AppConfig: - """Load the configuration from the config.toml file and environment variables. +def load_app_config( + set_logging_levels: bool = True, config_file: str = 'config.toml' +) -> AppConfig: + """Load the configuration from the specified config file and environment variables. Args: set_logger_levels: Whether to set the global variables for logging levels. + config_file: Path to the config file. Defaults to 'config.toml' in the current directory. """ config = AppConfig() - load_from_toml(config) + load_from_toml(config, config_file) load_from_env(config, os.environ) finalize_config(config) if set_logging_levels: diff --git a/openhands/core/main.py b/openhands/core/main.py index 2b03f2f7a9..2e34dc643f 100644 --- a/openhands/core/main.py +++ b/openhands/core/main.py @@ -228,7 +228,7 @@ def generate_sid(config: AppConfig, session_name: str | None = None) -> str: # Load the app config # this will load config from config.toml in the current directory # as well as from the environment variables - config = load_app_config() + config = load_app_config(config_file=args.config_file) # Override default LLM configs ([llm] section in config.toml) if args.llm_config: diff --git a/tests/unit/test_arg_parser.py b/tests/unit/test_arg_parser.py index 22b22708fd..6788904ced 100644 --- a/tests/unit/test_arg_parser.py +++ b/tests/unit/test_arg_parser.py @@ -123,10 +123,11 @@ def test_help_message(capsys): '--eval-ids EVAL_IDS', '-l LLM_CONFIG, --llm-config LLM_CONFIG', '-n NAME, --name NAME', + '--config-file CONFIG_FILE', ] for element in expected_elements: assert element in help_output, f"Expected '{element}' to be in the help message" option_count = help_output.count(' -') - assert option_count == 14, f'Expected 14 options, found {option_count}' + assert option_count == 15, f'Expected 15 options, found {option_count}' From e81c5597d6696531cafb88373b331d6f8f9d5e9a Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Wed, 2 Oct 2024 16:23:18 -0500 Subject: [PATCH 11/13] feat(runtime): use micromamba instead of mamba and fix build issue (#4154) --- .github/workflows/dummy-agent-test.yml | 2 +- .github/workflows/ghcr-build.yml | 4 +- openhands/core/config/sandbox_config.py | 2 + openhands/runtime/builder/docker.py | 4 +- openhands/runtime/client/runtime.py | 3 +- openhands/runtime/plugins/jupyter/__init__.py | 3 +- openhands/runtime/remote/runtime.py | 5 +- .../utils/runtime_templates/Dockerfile.j2 | 48 +++++++++---------- tests/runtime/conftest.py | 3 +- tests/runtime/test_browsing.py | 3 +- tests/unit/test_runtime_build.py | 24 ++++------ 11 files changed, 51 insertions(+), 50 deletions(-) diff --git a/.github/workflows/dummy-agent-test.yml b/.github/workflows/dummy-agent-test.yml index 517af6fea1..795391e5b2 100644 --- a/.github/workflows/dummy-agent-test.yml +++ b/.github/workflows/dummy-agent-test.yml @@ -45,7 +45,7 @@ jobs: - name: Run tests run: | set -e - poetry run python3 openhands/core/main.py -t "do a flip" -d ./workspace/ -c DummyAgent + SANDBOX_FORCE_REBUILD_RUNTIME=True poetry run python3 openhands/core/main.py -t "do a flip" -d ./workspace/ -c DummyAgent - name: Check exit code run: | if [ $? -ne 0 ]; then diff --git a/.github/workflows/ghcr-build.yml b/.github/workflows/ghcr-build.yml index 3482477790..82c30d988e 100644 --- a/.github/workflows/ghcr-build.yml +++ b/.github/workflows/ghcr-build.yml @@ -293,7 +293,7 @@ jobs: SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \ TEST_IN_CI=true \ RUN_AS_OPENHANDS=false \ - poetry run pytest -n 3 -raR --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime + poetry run pytest -n 3 -raRs --reruns 2 --reruns-delay 5 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 env: @@ -371,7 +371,7 @@ jobs: SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \ TEST_IN_CI=true \ RUN_AS_OPENHANDS=true \ - poetry run pytest -n 3 -raR --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime + poetry run pytest -n 3 -raRs --reruns 2 --reruns-delay 5 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 env: diff --git a/openhands/core/config/sandbox_config.py b/openhands/core/config/sandbox_config.py index e6dc72d2cb..3f535a5fe8 100644 --- a/openhands/core/config/sandbox_config.py +++ b/openhands/core/config/sandbox_config.py @@ -18,6 +18,7 @@ class SandboxConfig: enable_auto_lint: Whether to enable auto-lint. use_host_network: Whether to use the host network. initialize_plugins: Whether to initialize plugins. + force_rebuild_runtime: Whether to force rebuild the runtime image. runtime_extra_deps: The extra dependencies to install in the runtime image (typically used for evaluation). This will be rendered into the end of the Dockerfile that builds the runtime image. It can contain any valid shell commands (e.g., pip install numpy). @@ -43,6 +44,7 @@ class SandboxConfig: ) use_host_network: bool = False initialize_plugins: bool = True + force_rebuild_runtime: bool = False runtime_extra_deps: str | None = None runtime_startup_env_vars: dict[str, str] = field(default_factory=dict) browsergym_eval_env: str | None = None diff --git a/openhands/runtime/builder/docker.py b/openhands/runtime/builder/docker.py index 729955108e..56a759df7d 100644 --- a/openhands/runtime/builder/docker.py +++ b/openhands/runtime/builder/docker.py @@ -113,8 +113,8 @@ def build( raise subprocess.CalledProcessError( return_code, process.args, - output=None, - stderr=None, + output=process.stdout.read() if process.stdout else None, + stderr=process.stderr.read() if process.stderr else None, ) except subprocess.CalledProcessError as e: diff --git a/openhands/runtime/client/runtime.py b/openhands/runtime/client/runtime.py index c665f668b1..c6bb301904 100644 --- a/openhands/runtime/client/runtime.py +++ b/openhands/runtime/client/runtime.py @@ -167,6 +167,7 @@ def __init__( self.base_container_image, self.runtime_builder, extra_deps=self.config.sandbox.runtime_extra_deps, + force_rebuild=self.config.sandbox.force_rebuild_runtime, ) self.container = self._init_container( sandbox_workspace_dir=self.config.workspace_mount_path_in_sandbox, # e.g. /workspace @@ -273,7 +274,7 @@ def _init_container( container = self.docker_client.containers.run( self.runtime_container_image, command=( - f'/openhands/miniforge3/bin/mamba run --no-capture-output -n base ' + f'/openhands/micromamba/bin/micromamba run -n openhands ' f'poetry run ' f'python -u -m openhands.runtime.client.client {self._container_port} ' f'--working-dir "{sandbox_workspace_dir}" ' diff --git a/openhands/runtime/plugins/jupyter/__init__.py b/openhands/runtime/plugins/jupyter/__init__.py index b46714c242..48ee21dbbb 100644 --- a/openhands/runtime/plugins/jupyter/__init__.py +++ b/openhands/runtime/plugins/jupyter/__init__.py @@ -28,7 +28,8 @@ async def initialize(self, username: str, kernel_id: str = 'openhands-default'): 'cd /openhands/code\n' 'export POETRY_VIRTUALENVS_PATH=/openhands/poetry;\n' 'export PYTHONPATH=/openhands/code:$PYTHONPATH;\n' - '/openhands/miniforge3/bin/mamba run -n base ' + 'export MAMBA_ROOT_PREFIX=/openhands/micromamba;\n' + '/openhands/micromamba/bin/micromamba run -n openhands ' 'poetry run jupyter kernelgateway ' '--KernelGatewayApp.ip=0.0.0.0 ' f'--KernelGatewayApp.port={self.kernel_gateway_port}\n' diff --git a/openhands/runtime/remote/runtime.py b/openhands/runtime/remote/runtime.py index c121021e86..be4a19bc5a 100644 --- a/openhands/runtime/remote/runtime.py +++ b/openhands/runtime/remote/runtime.py @@ -119,6 +119,7 @@ def __init__( self.config.sandbox.base_container_image, self.runtime_builder, extra_deps=self.config.sandbox.runtime_extra_deps, + force_rebuild=self.config.sandbox.force_rebuild_runtime, ) response = send_request( @@ -144,8 +145,8 @@ def __init__( start_request = { 'image': self.container_image, 'command': ( - f'/openhands/miniforge3/bin/mamba run --no-capture-output -n base ' - 'PYTHONUNBUFFERED=1 poetry run ' + f'/openhands/micromamba/bin/micromamba run -n openhands ' + 'poetry run ' f'python -u -m openhands.runtime.client.client {self.port} ' f'--working-dir {self.config.workspace_mount_path_in_sandbox} ' f'{plugin_arg}' diff --git a/openhands/runtime/utils/runtime_templates/Dockerfile.j2 b/openhands/runtime/utils/runtime_templates/Dockerfile.j2 index 57ea62dba2..3395847161 100644 --- a/openhands/runtime/utils/runtime_templates/Dockerfile.j2 +++ b/openhands/runtime/utils/runtime_templates/Dockerfile.j2 @@ -1,11 +1,13 @@ -{% if skip_init %} FROM {{ base_image }} -{% else %} + +# Shared environment variables (regardless of init or not) +ENV POETRY_VIRTUALENVS_PATH=/openhands/poetry +ENV MAMBA_ROOT_PREFIX=/openhands/micromamba + +{% if not skip_init %} # ================================================================ # START: Build Runtime Image from Scratch # ================================================================ -FROM {{ base_image }} - {% if 'ubuntu' in base_image and (base_image.endswith(':latest') or base_image.endswith(':24.04')) %} {% set LIBGL_MESA = 'libgl1' %} {% else %} @@ -14,7 +16,7 @@ FROM {{ base_image }} # Install necessary packages and clean up in one layer RUN apt-get update && \ - apt-get install -y wget sudo apt-utils {{ LIBGL_MESA }} libasound2-plugins git && \ + apt-get install -y wget curl sudo apt-utils {{ LIBGL_MESA }} libasound2-plugins git && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -26,19 +28,16 @@ RUN mkdir -p /openhands && \ mkdir -p /openhands/logs && \ mkdir -p /openhands/poetry -# Directory containing subdirectories for virtual environment. -ENV POETRY_VIRTUALENVS_PATH=/openhands/poetry +# Install micromamba +RUN mkdir -p /openhands/micromamba/bin && \ + /bin/bash -c "PREFIX_LOCATION=/openhands/micromamba BIN_FOLDER=/openhands/micromamba/bin INIT_YES=no CONDA_FORGE_YES=yes $(curl -L https://micro.mamba.pm/install.sh)" && \ + /openhands/micromamba/bin/micromamba config remove channels defaults && \ + /openhands/micromamba/bin/micromamba config list -RUN if [ ! -d /openhands/miniforge3 ]; then \ - wget --progress=bar:force -O Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" && \ - bash Miniforge3.sh -b -p /openhands/miniforge3 && \ - rm Miniforge3.sh && \ - chmod -R g+w /openhands/miniforge3 && \ - bash -c ". /openhands/miniforge3/etc/profile.d/conda.sh && conda config --set changeps1 False && conda config --append channels conda-forge"; \ - fi +# Create the openhands virtual environment and install poetry and python +RUN /openhands/micromamba/bin/micromamba create -n openhands -y && \ + /openhands/micromamba/bin/micromamba install -n openhands -c conda-forge poetry python=3.11 -y -# Install Python and Poetry -RUN /openhands/miniforge3/bin/mamba install conda-forge::poetry python=3.11 -y # ================================================================ # END: Build Runtime Image from Scratch # ================================================================ @@ -59,27 +58,28 @@ COPY ./code /openhands/code # virtual environment are used by default. WORKDIR /openhands/code RUN \ + /openhands/micromamba/bin/micromamba config set changeps1 False && \ # Configure Poetry and create virtual environment - /openhands/miniforge3/bin/mamba run -n base poetry config virtualenvs.path /openhands/poetry && \ - /openhands/miniforge3/bin/mamba run -n base poetry env use python3.11 && \ + /openhands/micromamba/bin/micromamba run -n openhands poetry config virtualenvs.path /openhands/poetry && \ + /openhands/micromamba/bin/micromamba run -n openhands poetry env use python3.11 && \ # Install project dependencies - /openhands/miniforge3/bin/mamba run -n base poetry install --only main,runtime --no-interaction --no-root && \ + /openhands/micromamba/bin/micromamba run -n openhands poetry install --only main,runtime --no-interaction --no-root && \ # Update and install additional tools apt-get update && \ - /openhands/miniforge3/bin/mamba run -n base poetry run pip install playwright && \ - /openhands/miniforge3/bin/mamba run -n base poetry run playwright install --with-deps chromium && \ + /openhands/micromamba/bin/micromamba run -n openhands poetry run pip install playwright && \ + /openhands/micromamba/bin/micromamba run -n openhands poetry run playwright install --with-deps chromium && \ # Set environment variables - echo "OH_INTERPRETER_PATH=$(/openhands/miniforge3/bin/mamba run -n base poetry run python -c "import sys; print(sys.executable)")" >> /etc/environment && \ + echo "OH_INTERPRETER_PATH=$(/openhands/micromamba/bin/micromamba run -n openhands poetry run python -c "import sys; print(sys.executable)")" >> /etc/environment && \ # Install extra dependencies if specified {{ extra_deps }} {% if extra_deps %} && {% endif %} \ # Clear caches - /openhands/miniforge3/bin/mamba run -n base poetry cache clear --all . && \ + /openhands/micromamba/bin/micromamba run -n openhands poetry cache clear --all . && \ # Set permissions {% if not skip_init %}chmod -R g+rws /openhands/poetry && {% endif %} \ mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \ # Clean up apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ - /openhands/miniforge3/bin/mamba clean --all + /openhands/micromamba/bin/micromamba clean --all # ================================================================ # END: Copy Project and Install/Update Dependencies # ================================================================ diff --git a/tests/runtime/conftest.py b/tests/runtime/conftest.py index 2308244fb3..9b5bebab54 100644 --- a/tests/runtime/conftest.py +++ b/tests/runtime/conftest.py @@ -208,6 +208,7 @@ def _load_runtime( base_container_image: str | None = None, browsergym_eval_env: str | None = None, use_workspace: bool | None = None, + force_rebuild_runtime: bool = False, ) -> Runtime: sid = 'rt_' + str(random.randint(100000, 999999)) @@ -217,7 +218,7 @@ def _load_runtime( config = load_app_config() config.run_as_openhands = run_as_openhands - + config.sandbox.force_rebuild_runtime = force_rebuild_runtime # Folder where all tests create their own folder global test_mount_path if use_workspace: diff --git a/tests/runtime/test_browsing.py b/tests/runtime/test_browsing.py index c0ea3e1810..1d3a42131b 100644 --- a/tests/runtime/test_browsing.py +++ b/tests/runtime/test_browsing.py @@ -19,7 +19,7 @@ # Browsing tests # ============================================================================================================================ -PY3_FOR_TESTING = '/openhands/miniforge3/bin/mamba run -n base python3' +PY3_FOR_TESTING = '/openhands/micromamba/bin/micromamba run -n openhands python3' def test_simple_browse(temp_dir, box_class, run_as_openhands): @@ -75,6 +75,7 @@ def test_browsergym_eval_env(box_class, temp_dir): run_as_openhands=False, # need root permission to access file base_container_image='xingyaoww/od-eval-miniwob:v1.0', browsergym_eval_env='browsergym/miniwob.choose-list', + force_rebuild_runtime=True, ) from openhands.runtime.browser.browser_env import ( BROWSER_EVAL_GET_GOAL_ACTION, diff --git a/tests/unit/test_runtime_build.py b/tests/unit/test_runtime_build.py index 0b448f2b54..0031f08160 100644 --- a/tests/unit/test_runtime_build.py +++ b/tests/unit/test_runtime_build.py @@ -155,16 +155,14 @@ def test_generate_dockerfile_scratch(): ) assert base_image in dockerfile_content assert 'apt-get update' in dockerfile_content - assert 'apt-get install -y wget sudo apt-utils' in dockerfile_content - assert ( - 'RUN /openhands/miniforge3/bin/mamba install conda-forge::poetry python=3.11 -y' - in dockerfile_content - ) + assert 'apt-get install -y wget curl sudo apt-utils' in dockerfile_content + assert 'poetry' in dockerfile_content and '-c conda-forge' in dockerfile_content + assert 'python=3.11' in dockerfile_content # Check the update command assert 'COPY ./code /openhands/code' in dockerfile_content assert ( - '/openhands/miniforge3/bin/mamba run -n base poetry install' + '/openhands/micromamba/bin/micromamba run -n openhands poetry install' in dockerfile_content ) @@ -178,17 +176,13 @@ def test_generate_dockerfile_skip_init(): # These commands SHOULD NOT include in the dockerfile if skip_init is True assert 'RUN apt update && apt install -y wget sudo' not in dockerfile_content - assert ( - 'RUN /openhands/miniforge3/bin/mamba install conda-forge::poetry python=3.11 -y' - not in dockerfile_content - ) + assert '-c conda-forge' not in dockerfile_content + assert 'python=3.11' not in dockerfile_content + assert 'https://micro.mamba.pm/install.sh' not in dockerfile_content # These update commands SHOULD still in the dockerfile assert 'COPY ./code /openhands/code' in dockerfile_content - assert ( - '/openhands/miniforge3/bin/mamba run -n base poetry install' - in dockerfile_content - ) + assert 'poetry install' in dockerfile_content def test_get_runtime_image_repo_and_tag_eventstream(): @@ -353,7 +347,7 @@ def live_docker_image(): dockerfile_content = f""" # syntax=docker/dockerfile:1.4 FROM {DEFAULT_BASE_IMAGE} AS base - RUN apt-get update && apt-get install -y wget sudo apt-utils + RUN apt-get update && apt-get install -y wget curl sudo apt-utils FROM base AS intermediate RUN mkdir -p /openhands From e0594432e27df9198c163180af7df6eb1868c7c1 Mon Sep 17 00:00:00 2001 From: Xingyao Wang Date: Wed, 2 Oct 2024 17:25:10 -0500 Subject: [PATCH 12/13] fix: build shutdown listener (#4147) --- openhands/runtime/builder/remote.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openhands/runtime/builder/remote.py b/openhands/runtime/builder/remote.py index a62872c2db..801496e8ff 100644 --- a/openhands/runtime/builder/remote.py +++ b/openhands/runtime/builder/remote.py @@ -9,7 +9,7 @@ from openhands.runtime.builder import RuntimeBuilder from openhands.runtime.utils.request import send_request from openhands.runtime.utils.shutdown_listener import ( - should_exit, + should_continue, sleep_if_should_continue, ) @@ -60,8 +60,8 @@ def build(self, path: str, tags: list[str]) -> str: # Poll /build_status until the build is complete start_time = time.time() timeout = 30 * 60 # 20 minutes in seconds - while True: - if should_exit() or time.time() - start_time > timeout: + while should_continue(): + if time.time() - start_time > timeout: logger.error('Build timed out after 30 minutes') raise RuntimeError('Build timed out after 30 minutes') @@ -101,6 +101,8 @@ def build(self, path: str, tags: list[str]) -> str: # Wait before polling again sleep_if_should_continue(30) + raise RuntimeError('Build interrupted (likely received SIGTERM or SIGINT).') + def image_exists(self, image_name: str, pull_from_repo: bool = True) -> bool: """Checks if an image exists in the remote registry using the /image_exists endpoint.""" params = {'image': image_name} From 1abfd3b8082aa11b4eb4ec2e463ceb7cf6912c6e Mon Sep 17 00:00:00 2001 From: Engel Nyst Date: Thu, 3 Oct 2024 01:54:49 +0200 Subject: [PATCH 13/13] Retry on litellm's APIError, which includes 502 (#4167) --- openhands/llm/llm.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openhands/llm/llm.py b/openhands/llm/llm.py index c9088b3955..11a34a5121 100644 --- a/openhands/llm/llm.py +++ b/openhands/llm/llm.py @@ -14,6 +14,7 @@ from litellm import completion_cost as litellm_completion_cost from litellm.exceptions import ( APIConnectionError, + APIError, InternalServerError, RateLimitError, ServiceUnavailableError, @@ -31,6 +32,7 @@ # tuple of exceptions to retry on LLM_RETRY_EXCEPTIONS: tuple[type[Exception], ...] = ( APIConnectionError, + APIError, InternalServerError, RateLimitError, ServiceUnavailableError,