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** 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/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/sandbox_config.py b/openhands/core/config/sandbox_config.py index 6655067980..e5699c28c7 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). @@ -46,6 +47,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/core/config/utils.py b/openhands/core/config/utils.py index faa4ca01e4..da89fc3f8b 100644 --- a/openhands/core/config/utils.py +++ b/openhands/core/config/utils.py @@ -289,6 +289,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', @@ -383,14 +389,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 24b4715e80..d79ffe918a 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/openhands/llm/llm.py b/openhands/llm/llm.py index 99ee45c62f..d8f20afc31 100644 --- a/openhands/llm/llm.py +++ b/openhands/llm/llm.py @@ -18,6 +18,7 @@ from litellm.caching import Cache from litellm.exceptions import ( APIConnectionError, + APIError, InternalServerError, RateLimitError, ServiceUnavailableError, @@ -39,6 +40,7 @@ # tuple of exceptions to retry on LLM_RETRY_EXCEPTIONS: tuple[type[Exception], ...] = ( APIConnectionError, + APIError, InternalServerError, RateLimitError, ServiceUnavailableError, 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/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} diff --git a/openhands/runtime/client/runtime.py b/openhands/runtime/client/runtime.py index f95b42dffa..76b7b2722c 100644 --- a/openhands/runtime/client/runtime.py +++ b/openhands/runtime/client/runtime.py @@ -177,11 +177,13 @@ def __init__( raise ValueError( 'Neither runtime container image nor base container image is set' ) + logger.info('Preparing container, this might take a few minutes...') self.send_status_message('STATUS$STARTING_CONTAINER') self.runtime_container_image = build_runtime_image( 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, @@ -302,7 +304,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}" ' @@ -450,13 +452,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 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 219bd5afa1..56661a6652 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 docker.io lsof && \ + apt-get install -y wget curl sudo apt-utils {{ LIBGL_MESA }} libasound2-plugins git docker.io lsof && \ 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.12 -y -# Install Python and Poetry -RUN /openhands/miniforge3/bin/mamba install conda-forge::poetry python=3.12 -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 docker && \ - /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 docker&& \ + /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/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') diff --git a/poetry.lock b/poetry.lock index c250334e47..ec01863ac9 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] @@ -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] @@ -3802,13 +3802,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] @@ -5405,13 +5405,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] @@ -7160,13 +7160,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] @@ -8052,13 +8052,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] @@ -8069,18 +8069,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)"] 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_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}' 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