feat(runtime): use micromamba instead of mamba and fix build issue (#4154)

This commit is contained in:
Xingyao Wang
2024-10-02 16:23:18 -05:00
committed by GitHub
parent c8a933590a
commit e81c5597d6
11 changed files with 51 additions and 50 deletions

View File

@@ -45,7 +45,7 @@ jobs:
- name: Run tests - name: Run tests
run: | run: |
set -e 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 - name: Check exit code
run: | run: |
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then

View File

@@ -293,7 +293,7 @@ jobs:
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \ SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \ TEST_IN_CI=true \
RUN_AS_OPENHANDS=false \ 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 - name: Upload coverage to Codecov
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v4
env: env:
@@ -371,7 +371,7 @@ jobs:
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \ SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
TEST_IN_CI=true \ TEST_IN_CI=true \
RUN_AS_OPENHANDS=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 - name: Upload coverage to Codecov
uses: codecov/codecov-action@v4 uses: codecov/codecov-action@v4
env: env:

View File

@@ -18,6 +18,7 @@ class SandboxConfig:
enable_auto_lint: Whether to enable auto-lint. enable_auto_lint: Whether to enable auto-lint.
use_host_network: Whether to use the host network. use_host_network: Whether to use the host network.
initialize_plugins: Whether to initialize plugins. 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). 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. 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). It can contain any valid shell commands (e.g., pip install numpy).
@@ -43,6 +44,7 @@ class SandboxConfig:
) )
use_host_network: bool = False use_host_network: bool = False
initialize_plugins: bool = True initialize_plugins: bool = True
force_rebuild_runtime: bool = False
runtime_extra_deps: str | None = None runtime_extra_deps: str | None = None
runtime_startup_env_vars: dict[str, str] = field(default_factory=dict) runtime_startup_env_vars: dict[str, str] = field(default_factory=dict)
browsergym_eval_env: str | None = None browsergym_eval_env: str | None = None

View File

@@ -113,8 +113,8 @@ class DockerRuntimeBuilder(RuntimeBuilder):
raise subprocess.CalledProcessError( raise subprocess.CalledProcessError(
return_code, return_code,
process.args, process.args,
output=None, output=process.stdout.read() if process.stdout else None,
stderr=None, stderr=process.stderr.read() if process.stderr else None,
) )
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:

View File

@@ -167,6 +167,7 @@ class EventStreamRuntime(Runtime):
self.base_container_image, self.base_container_image,
self.runtime_builder, self.runtime_builder,
extra_deps=self.config.sandbox.runtime_extra_deps, extra_deps=self.config.sandbox.runtime_extra_deps,
force_rebuild=self.config.sandbox.force_rebuild_runtime,
) )
self.container = self._init_container( self.container = self._init_container(
sandbox_workspace_dir=self.config.workspace_mount_path_in_sandbox, # e.g. /workspace sandbox_workspace_dir=self.config.workspace_mount_path_in_sandbox, # e.g. /workspace
@@ -273,7 +274,7 @@ class EventStreamRuntime(Runtime):
container = self.docker_client.containers.run( container = self.docker_client.containers.run(
self.runtime_container_image, self.runtime_container_image,
command=( command=(
f'/openhands/miniforge3/bin/mamba run --no-capture-output -n base ' f'/openhands/micromamba/bin/micromamba run -n openhands '
f'poetry run ' f'poetry run '
f'python -u -m openhands.runtime.client.client {self._container_port} ' f'python -u -m openhands.runtime.client.client {self._container_port} '
f'--working-dir "{sandbox_workspace_dir}" ' f'--working-dir "{sandbox_workspace_dir}" '

View File

@@ -28,7 +28,8 @@ class JupyterPlugin(Plugin):
'cd /openhands/code\n' 'cd /openhands/code\n'
'export POETRY_VIRTUALENVS_PATH=/openhands/poetry;\n' 'export POETRY_VIRTUALENVS_PATH=/openhands/poetry;\n'
'export PYTHONPATH=/openhands/code:$PYTHONPATH;\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 ' 'poetry run jupyter kernelgateway '
'--KernelGatewayApp.ip=0.0.0.0 ' '--KernelGatewayApp.ip=0.0.0.0 '
f'--KernelGatewayApp.port={self.kernel_gateway_port}\n' f'--KernelGatewayApp.port={self.kernel_gateway_port}\n'

View File

@@ -119,6 +119,7 @@ class RemoteRuntime(Runtime):
self.config.sandbox.base_container_image, self.config.sandbox.base_container_image,
self.runtime_builder, self.runtime_builder,
extra_deps=self.config.sandbox.runtime_extra_deps, extra_deps=self.config.sandbox.runtime_extra_deps,
force_rebuild=self.config.sandbox.force_rebuild_runtime,
) )
response = send_request( response = send_request(
@@ -144,8 +145,8 @@ class RemoteRuntime(Runtime):
start_request = { start_request = {
'image': self.container_image, 'image': self.container_image,
'command': ( 'command': (
f'/openhands/miniforge3/bin/mamba run --no-capture-output -n base ' f'/openhands/micromamba/bin/micromamba run -n openhands '
'PYTHONUNBUFFERED=1 poetry run ' 'poetry run '
f'python -u -m openhands.runtime.client.client {self.port} ' f'python -u -m openhands.runtime.client.client {self.port} '
f'--working-dir {self.config.workspace_mount_path_in_sandbox} ' f'--working-dir {self.config.workspace_mount_path_in_sandbox} '
f'{plugin_arg}' f'{plugin_arg}'

View File

@@ -1,11 +1,13 @@
{% if skip_init %}
FROM {{ base_image }} 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 # 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')) %} {% if 'ubuntu' in base_image and (base_image.endswith(':latest') or base_image.endswith(':24.04')) %}
{% set LIBGL_MESA = 'libgl1' %} {% set LIBGL_MESA = 'libgl1' %}
{% else %} {% else %}
@@ -14,7 +16,7 @@ FROM {{ base_image }}
# Install necessary packages and clean up in one layer # Install necessary packages and clean up in one layer
RUN apt-get update && \ 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 && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@@ -26,19 +28,16 @@ RUN mkdir -p /openhands && \
mkdir -p /openhands/logs && \ mkdir -p /openhands/logs && \
mkdir -p /openhands/poetry mkdir -p /openhands/poetry
# Directory containing subdirectories for virtual environment. # Install micromamba
ENV POETRY_VIRTUALENVS_PATH=/openhands/poetry 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 \ # Create the openhands virtual environment and install poetry and python
wget --progress=bar:force -O Miniforge3.sh "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" && \ RUN /openhands/micromamba/bin/micromamba create -n openhands -y && \
bash Miniforge3.sh -b -p /openhands/miniforge3 && \ /openhands/micromamba/bin/micromamba install -n openhands -c conda-forge poetry python=3.11 -y
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
# Install Python and Poetry
RUN /openhands/miniforge3/bin/mamba install conda-forge::poetry python=3.11 -y
# ================================================================ # ================================================================
# END: Build Runtime Image from Scratch # END: Build Runtime Image from Scratch
# ================================================================ # ================================================================
@@ -59,27 +58,28 @@ COPY ./code /openhands/code
# virtual environment are used by default. # virtual environment are used by default.
WORKDIR /openhands/code WORKDIR /openhands/code
RUN \ RUN \
/openhands/micromamba/bin/micromamba config set changeps1 False && \
# Configure Poetry and create virtual environment # Configure Poetry and create virtual environment
/openhands/miniforge3/bin/mamba run -n base poetry config virtualenvs.path /openhands/poetry && \ /openhands/micromamba/bin/micromamba run -n openhands 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 env use python3.11 && \
# Install project dependencies # 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 # Update and install additional tools
apt-get update && \ apt-get update && \
/openhands/miniforge3/bin/mamba run -n base poetry run pip install playwright && \ /openhands/micromamba/bin/micromamba run -n openhands 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 playwright install --with-deps chromium && \
# Set environment variables # 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 # Install extra dependencies if specified
{{ extra_deps }} {% if extra_deps %} && {% endif %} \ {{ extra_deps }} {% if extra_deps %} && {% endif %} \
# Clear caches # 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 # Set permissions
{% if not skip_init %}chmod -R g+rws /openhands/poetry && {% endif %} \ {% if not skip_init %}chmod -R g+rws /openhands/poetry && {% endif %} \
mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \ mkdir -p /openhands/workspace && chmod -R g+rws,o+rw /openhands/workspace && \
# Clean up # Clean up
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ 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 # END: Copy Project and Install/Update Dependencies
# ================================================================ # ================================================================

View File

@@ -208,6 +208,7 @@ def _load_runtime(
base_container_image: str | None = None, base_container_image: str | None = None,
browsergym_eval_env: str | None = None, browsergym_eval_env: str | None = None,
use_workspace: bool | None = None, use_workspace: bool | None = None,
force_rebuild_runtime: bool = False,
) -> Runtime: ) -> Runtime:
sid = 'rt_' + str(random.randint(100000, 999999)) sid = 'rt_' + str(random.randint(100000, 999999))
@@ -217,7 +218,7 @@ def _load_runtime(
config = load_app_config() config = load_app_config()
config.run_as_openhands = run_as_openhands config.run_as_openhands = run_as_openhands
config.sandbox.force_rebuild_runtime = force_rebuild_runtime
# Folder where all tests create their own folder # Folder where all tests create their own folder
global test_mount_path global test_mount_path
if use_workspace: if use_workspace:

View File

@@ -19,7 +19,7 @@ from openhands.events.observation import (
# Browsing tests # 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): 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 run_as_openhands=False, # need root permission to access file
base_container_image='xingyaoww/od-eval-miniwob:v1.0', base_container_image='xingyaoww/od-eval-miniwob:v1.0',
browsergym_eval_env='browsergym/miniwob.choose-list', browsergym_eval_env='browsergym/miniwob.choose-list',
force_rebuild_runtime=True,
) )
from openhands.runtime.browser.browser_env import ( from openhands.runtime.browser.browser_env import (
BROWSER_EVAL_GET_GOAL_ACTION, BROWSER_EVAL_GET_GOAL_ACTION,

View File

@@ -155,16 +155,14 @@ def test_generate_dockerfile_scratch():
) )
assert base_image in dockerfile_content assert base_image in dockerfile_content
assert 'apt-get update' in dockerfile_content assert 'apt-get update' in dockerfile_content
assert 'apt-get install -y wget sudo apt-utils' in dockerfile_content assert 'apt-get install -y wget curl sudo apt-utils' in dockerfile_content
assert ( assert 'poetry' in dockerfile_content and '-c conda-forge' in dockerfile_content
'RUN /openhands/miniforge3/bin/mamba install conda-forge::poetry python=3.11 -y' assert 'python=3.11' in dockerfile_content
in dockerfile_content
)
# Check the update command # Check the update command
assert 'COPY ./code /openhands/code' in dockerfile_content assert 'COPY ./code /openhands/code' in dockerfile_content
assert ( assert (
'/openhands/miniforge3/bin/mamba run -n base poetry install' '/openhands/micromamba/bin/micromamba run -n openhands poetry install'
in dockerfile_content 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 # 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 apt update && apt install -y wget sudo' not in dockerfile_content
assert ( assert '-c conda-forge' not in dockerfile_content
'RUN /openhands/miniforge3/bin/mamba install conda-forge::poetry python=3.11 -y' assert 'python=3.11' not in dockerfile_content
not in dockerfile_content assert 'https://micro.mamba.pm/install.sh' not in dockerfile_content
)
# These update commands SHOULD still in the dockerfile # These update commands SHOULD still in the dockerfile
assert 'COPY ./code /openhands/code' in dockerfile_content assert 'COPY ./code /openhands/code' in dockerfile_content
assert ( assert 'poetry install' in dockerfile_content
'/openhands/miniforge3/bin/mamba run -n base poetry install'
in dockerfile_content
)
def test_get_runtime_image_repo_and_tag_eventstream(): def test_get_runtime_image_repo_and_tag_eventstream():
@@ -353,7 +347,7 @@ def live_docker_image():
dockerfile_content = f""" dockerfile_content = f"""
# syntax=docker/dockerfile:1.4 # syntax=docker/dockerfile:1.4
FROM {DEFAULT_BASE_IMAGE} AS base 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 FROM base AS intermediate
RUN mkdir -p /openhands RUN mkdir -p /openhands