From 50426edaa19c7c41270cdfba0478dba4c80ca651 Mon Sep 17 00:00:00 2001 From: Tetsuuuuuuu Date: Mon, 21 Apr 2025 23:01:03 +0900 Subject: [PATCH] feat: add argument --base-container-image to resolver (#7612) Co-authored-by: Xingyao Wang --- openhands/core/config/sandbox_config.py | 2 +- openhands/resolver/resolve_all_issues.py | 26 +++++++++++++++++-- openhands/resolver/resolve_issue.py | 23 +++++++++++++++- openhands/runtime/builder/docker.py | 8 ++++++ .../runtime/impl/remote/remote_runtime.py | 5 +++- poetry.lock | 4 +-- .../resolver/github/test_resolve_issues.py | 3 +++ .../gitlab/test_gitlab_resolve_issues.py | 3 +++ 8 files changed, 67 insertions(+), 7 deletions(-) diff --git a/openhands/core/config/sandbox_config.py b/openhands/core/config/sandbox_config.py index 5cdecea8ef..9fee660713 100644 --- a/openhands/core/config/sandbox_config.py +++ b/openhands/core/config/sandbox_config.py @@ -46,7 +46,7 @@ class SandboxConfig(BaseModel): pause_closed_runtimes: bool = Field(default=True) rm_all_containers: bool = Field(default=False) api_key: str | None = Field(default=None) - base_container_image: str = Field( + base_container_image: str | None = Field( default='nikolaik/python-nodejs:python3.12-nodejs22' ) runtime_container_image: str | None = Field(default=None) diff --git a/openhands/resolver/resolve_all_issues.py b/openhands/resolver/resolve_all_issues.py index 286f5cc2d2..9d99010f4e 100644 --- a/openhands/resolver/resolve_all_issues.py +++ b/openhands/resolver/resolve_all_issues.py @@ -11,6 +11,7 @@ from typing import Any, Awaitable, TextIO from pydantic import SecretStr from tqdm import tqdm +import openhands from openhands.core.config import LLMConfig from openhands.core.logger import openhands_logger as logger from openhands.integrations.service_types import ProviderType @@ -59,6 +60,7 @@ async def resolve_issues( num_workers: int, output_dir: str, llm_config: LLMConfig, + base_container_image: str | None, runtime_container_image: str, prompt_template: str, issue_type: str, @@ -199,6 +201,7 @@ async def resolve_issues( max_iterations, llm_config, output_dir, + base_container_image, runtime_container_image, prompt_template, issue_handler, @@ -249,6 +252,12 @@ def main() -> None: default=None, help='Github or Gitlab username to access the repository.', ) + parser.add_argument( + '--base-container-image', + type=str, + default=None, + help='Base container image to use.', + ) parser.add_argument( '--runtime-container-image', type=str, @@ -331,9 +340,21 @@ def main() -> None: my_args = parser.parse_args() + base_container_image = my_args.base_container_image + runtime_container_image = my_args.runtime_container_image - if runtime_container_image is None: - runtime_container_image = 'ghcr.io/all-hands-ai/runtime:0.33.0-nikolaik' + + if runtime_container_image is not None and base_container_image is not None: + raise ValueError('Cannot provide both runtime and base container images.') + + if ( + runtime_container_image is None + and base_container_image is None + and not my_args.is_experimental + ): + runtime_container_image = ( + f'ghcr.io/all-hands-ai/runtime:{openhands.__version__}-nikolaik' + ) owner, repo = my_args.selected_repo.split('/') token = my_args.token or os.getenv('GITHUB_TOKEN') or os.getenv('GITLAB_TOKEN') @@ -386,6 +407,7 @@ def main() -> None: token=token, username=username, platform=platform, + base_container_image=base_container_image, runtime_container_image=runtime_container_image, max_iterations=my_args.max_iterations, limit_issues=my_args.limit_issues, diff --git a/openhands/resolver/resolve_issue.py b/openhands/resolver/resolve_issue.py index 0b9b25e3f9..7758b46fdd 100644 --- a/openhands/resolver/resolve_issue.py +++ b/openhands/resolver/resolve_issue.py @@ -167,6 +167,7 @@ async def process_issue( max_iterations: int, llm_config: LLMConfig, output_dir: str, + base_container_image: str | None, runtime_container_image: str | None, prompt_template: str, issue_handler: ServiceContextIssue | ServiceContextPR, @@ -195,6 +196,7 @@ async def process_issue( # they're set by default if nothing else overrides them # FIXME we should remove them here sandbox_config = SandboxConfig( + base_container_image=base_container_image, runtime_container_image=runtime_container_image, enable_auto_lint=False, use_host_network=False, @@ -360,6 +362,7 @@ async def resolve_issue( max_iterations: int, output_dir: str, llm_config: LLMConfig, + base_container_image: str | None, runtime_container_image: str | None, prompt_template: str, issue_type: str, @@ -525,6 +528,7 @@ async def resolve_issue( max_iterations, llm_config, output_dir, + base_container_image, runtime_container_image, prompt_template, issue_handler, @@ -567,6 +571,12 @@ def main() -> None: default=None, help='username to access the repository.', ) + parser.add_argument( + '--base-container-image', + type=str, + default=None, + help='base container image to use.', + ) parser.add_argument( '--runtime-container-image', type=str, @@ -649,8 +659,18 @@ def main() -> None: my_args = parser.parse_args() + base_container_image = my_args.base_container_image + runtime_container_image = my_args.runtime_container_image - if runtime_container_image is None and not my_args.is_experimental: + + if runtime_container_image is not None and base_container_image is not None: + raise ValueError('Cannot provide both runtime and base container images.') + + if ( + runtime_container_image is None + and base_container_image is None + and not my_args.is_experimental + ): runtime_container_image = ( f'ghcr.io/all-hands-ai/runtime:{openhands.__version__}-nikolaik' ) @@ -714,6 +734,7 @@ def main() -> None: token=token, username=username, platform=platform, + base_container_image=base_container_image, runtime_container_image=runtime_container_image, max_iterations=my_args.max_iterations, output_dir=my_args.output_dir, diff --git a/openhands/runtime/builder/docker.py b/openhands/runtime/builder/docker.py index 050471a67e..618fe913c0 100644 --- a/openhands/runtime/builder/docker.py +++ b/openhands/runtime/builder/docker.py @@ -159,6 +159,14 @@ class DockerRuntimeBuilder(RuntimeBuilder): f'================ {buildx_cmd[0].upper()} BUILD STARTED ================' ) + builder_cmd = ['docker', 'buildx', 'use', 'default'] + subprocess.Popen( + builder_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) + try: process = subprocess.Popen( buildx_cmd, diff --git a/openhands/runtime/impl/remote/remote_runtime.py b/openhands/runtime/impl/remote/remote_runtime.py index eb2e9b6f67..161b571914 100644 --- a/openhands/runtime/impl/remote/remote_runtime.py +++ b/openhands/runtime/impl/remote/remote_runtime.py @@ -194,7 +194,10 @@ class RemoteRuntime(ActionExecutionClient): 'debug', f'Runtime image repo: {os.environ["OH_RUNTIME_RUNTIME_IMAGE_REPO"]}', ) - + if self.config.sandbox.base_container_image is None: + raise ValueError( + 'base_container_image is required to build the runtime image. ' + ) if self.config.sandbox.runtime_extra_deps: self.log( 'debug', diff --git a/poetry.lock b/poetry.lock index 46bb02dcce..d180442a8e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2663,7 +2663,7 @@ grpcio = {version = ">=1.49.1,<2.0dev", optional = true, markers = "python_versi grpcio-status = {version = ">=1.49.1,<2.0.dev0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""} proto-plus = [ {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, - {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""}, + {version = ">=1.22.3,<2.0.0dev"}, ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0.dev0" requests = ">=2.18.0,<3.0.0.dev0" @@ -2877,7 +2877,7 @@ google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" grpc-google-iam-v1 = ">=0.14.0,<1.0.0dev" proto-plus = [ {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, - {version = ">=1.22.3,<2.0.0dev"}, + {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""}, ] protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<6.0.0dev" diff --git a/tests/unit/resolver/github/test_resolve_issues.py b/tests/unit/resolver/github/test_resolve_issues.py index d90e91ff01..01706ee1bd 100644 --- a/tests/unit/resolver/github/test_resolve_issues.py +++ b/tests/unit/resolver/github/test_resolve_issues.py @@ -112,6 +112,7 @@ async def test_resolve_issue_no_issues_found(): max_iterations=5, output_dir='/tmp', llm_config=LLMConfig(model='test', api_key='test'), + base_container_image='test-image', runtime_container_image='test-image', prompt_template='test-template', issue_type='pr', @@ -344,6 +345,7 @@ async def test_process_issue(mock_output_dir, mock_prompt_template): repo_instruction = 'Resolve this repo' max_iterations = 5 llm_config = LLMConfig(model='test_model', api_key='test_api_key') + base_container_image = 'test_image:latest' runtime_container_image = 'test_image:latest' # Test cases for different scenarios @@ -449,6 +451,7 @@ async def test_process_issue(mock_output_dir, mock_prompt_template): max_iterations, llm_config, mock_output_dir, + base_container_image, runtime_container_image, mock_prompt_template, handler_instance, diff --git a/tests/unit/resolver/gitlab/test_gitlab_resolve_issues.py b/tests/unit/resolver/gitlab/test_gitlab_resolve_issues.py index 19d8478d23..ff9b061074 100644 --- a/tests/unit/resolver/gitlab/test_gitlab_resolve_issues.py +++ b/tests/unit/resolver/gitlab/test_gitlab_resolve_issues.py @@ -132,6 +132,7 @@ async def test_resolve_issue_no_issues_found(): max_iterations=5, output_dir='/tmp', llm_config=LLMConfig(model='test', api_key='test'), + base_container_image='test-image', runtime_container_image='test-image', prompt_template='test-template', issue_type='pr', @@ -384,6 +385,7 @@ async def test_process_issue(mock_output_dir, mock_prompt_template): repo_instruction = 'Resolve this repo' max_iterations = 5 llm_config = LLMConfig(model='test_model', api_key='test_api_key') + base_container_image = 'test_image:latest' runtime_container_image = 'test_image:latest' # Test cases for different scenarios @@ -489,6 +491,7 @@ async def test_process_issue(mock_output_dir, mock_prompt_template): max_iterations, llm_config, mock_output_dir, + base_container_image, runtime_container_image, mock_prompt_template, handler_instance,