mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
[refactor]: call load_openhands_config in resolver (#8641)
This commit is contained in:
parent
9fb5d2109a
commit
28dbb1fc74
@ -11,12 +11,12 @@ from argparse import Namespace
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import SecretStr
|
||||
from termcolor import colored
|
||||
|
||||
import openhands
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import AgentConfig, LLMConfig, OpenHandsConfig, SandboxConfig
|
||||
from openhands.core.config import AgentConfig, OpenHandsConfig, SandboxConfig
|
||||
from openhands.core.config.utils import load_openhands_config
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction, MessageAction
|
||||
@ -71,13 +71,6 @@ class IssueResolver:
|
||||
base_domain: The base domain for the git server.
|
||||
"""
|
||||
|
||||
# Setup and validate container images
|
||||
self.sandbox_config = self._setup_sandbox_config(
|
||||
args.base_container_image,
|
||||
args.runtime_container_image,
|
||||
args.is_experimental,
|
||||
)
|
||||
|
||||
parts = args.selected_repo.rsplit('/', 1)
|
||||
if len(parts) < 2:
|
||||
raise ValueError('Invalid repository format. Expected owner/repo')
|
||||
@ -98,32 +91,6 @@ class IssueResolver:
|
||||
args.base_domain,
|
||||
)
|
||||
|
||||
api_key = args.llm_api_key or os.environ['LLM_API_KEY']
|
||||
model = args.llm_model or os.environ['LLM_MODEL']
|
||||
base_url = args.llm_base_url or os.environ.get('LLM_BASE_URL', None)
|
||||
api_version = os.environ.get('LLM_API_VERSION', None)
|
||||
llm_num_retries = int(os.environ.get('LLM_NUM_RETRIES', '4'))
|
||||
llm_retry_min_wait = int(os.environ.get('LLM_RETRY_MIN_WAIT', '5'))
|
||||
llm_retry_max_wait = int(os.environ.get('LLM_RETRY_MAX_WAIT', '30'))
|
||||
llm_retry_multiplier = int(os.environ.get('LLM_RETRY_MULTIPLIER', 2))
|
||||
llm_timeout = int(os.environ.get('LLM_TIMEOUT', 0))
|
||||
|
||||
# Create LLMConfig instance
|
||||
llm_config = LLMConfig(
|
||||
model=model,
|
||||
api_key=SecretStr(api_key) if api_key else None,
|
||||
base_url=base_url,
|
||||
num_retries=llm_num_retries,
|
||||
retry_min_wait=llm_retry_min_wait,
|
||||
retry_max_wait=llm_retry_max_wait,
|
||||
retry_multiplier=llm_retry_multiplier,
|
||||
timeout=llm_timeout,
|
||||
)
|
||||
|
||||
# Only set api_version if it was explicitly provided, otherwise let LLMConfig handle it
|
||||
if api_version is not None:
|
||||
llm_config.api_version = api_version
|
||||
|
||||
repo_instruction = None
|
||||
if args.repo_instruction_file:
|
||||
with open(args.repo_instruction_file, 'r') as f:
|
||||
@ -156,19 +123,33 @@ class IssueResolver:
|
||||
'github.com' if platform == ProviderType.GITHUB else 'gitlab.com'
|
||||
)
|
||||
|
||||
self.output_dir = args.output_dir
|
||||
self.issue_type = issue_type
|
||||
self.issue_number = args.issue_number
|
||||
|
||||
self.workspace_base = self.build_workspace_base(
|
||||
self.output_dir, self.issue_type, self.issue_number
|
||||
)
|
||||
|
||||
self.max_iterations = args.max_iterations
|
||||
|
||||
self.app_config = self.update_openhands_config(
|
||||
load_openhands_config(),
|
||||
self.max_iterations,
|
||||
self.workspace_base,
|
||||
args.base_container_image,
|
||||
args.runtime_container_image,
|
||||
args.is_experimental,
|
||||
)
|
||||
|
||||
self.owner = owner
|
||||
self.repo = repo
|
||||
self.platform = platform
|
||||
self.max_iterations = args.max_iterations
|
||||
self.output_dir = args.output_dir
|
||||
self.llm_config = llm_config
|
||||
self.user_instructions_prompt_template = user_instructions_prompt_template
|
||||
self.conversation_instructions_prompt_template = (
|
||||
conversation_instructions_prompt_template
|
||||
)
|
||||
self.issue_type = issue_type
|
||||
self.repo_instruction = repo_instruction
|
||||
self.issue_number = args.issue_number
|
||||
self.comment_id = args.comment_id
|
||||
|
||||
factory = IssueHandlerFactory(
|
||||
@ -179,17 +160,47 @@ class IssueResolver:
|
||||
platform=self.platform,
|
||||
base_domain=base_domain,
|
||||
issue_type=self.issue_type,
|
||||
llm_config=self.llm_config,
|
||||
llm_config=self.app_config.get_llm_config(),
|
||||
)
|
||||
self.issue_handler = factory.create()
|
||||
|
||||
@classmethod
|
||||
def _setup_sandbox_config(
|
||||
def update_openhands_config(
|
||||
cls,
|
||||
config: OpenHandsConfig,
|
||||
max_iterations: int,
|
||||
workspace_base: str,
|
||||
base_container_image: str | None,
|
||||
runtime_container_image: str | None,
|
||||
is_experimental: bool,
|
||||
) -> SandboxConfig:
|
||||
) -> OpenHandsConfig:
|
||||
config.default_agent = 'CodeActAgent'
|
||||
config.runtime = 'docker'
|
||||
config.max_budget_per_task = 4
|
||||
config.max_iterations = max_iterations
|
||||
|
||||
# do not mount workspace
|
||||
config.workspace_base = workspace_base
|
||||
config.workspace_mount_path = workspace_base
|
||||
config.agents = {'CodeActAgent': AgentConfig(disabled_microagents=['github'])}
|
||||
|
||||
cls.update_sandbox_config(
|
||||
config,
|
||||
base_container_image,
|
||||
runtime_container_image,
|
||||
is_experimental,
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def update_sandbox_config(
|
||||
cls,
|
||||
openhands_config: OpenHandsConfig,
|
||||
base_container_image: str | None,
|
||||
runtime_container_image: str | None,
|
||||
is_experimental: bool,
|
||||
) -> None:
|
||||
if runtime_container_image is not None and base_container_image is not None:
|
||||
raise ValueError('Cannot provide both runtime and base container images.')
|
||||
|
||||
@ -229,7 +240,17 @@ class IssueResolver:
|
||||
if user_id == 0:
|
||||
sandbox_config.user_id = get_unique_uid()
|
||||
|
||||
return sandbox_config
|
||||
openhands_config.sandbox.base_container_image = (
|
||||
sandbox_config.base_container_image
|
||||
)
|
||||
openhands_config.sandbox.runtime_container_image = (
|
||||
sandbox_config.runtime_container_image
|
||||
)
|
||||
openhands_config.sandbox.enable_auto_lint = sandbox_config.enable_auto_lint
|
||||
openhands_config.sandbox.use_host_network = sandbox_config.use_host_network
|
||||
openhands_config.sandbox.timeout = sandbox_config.timeout
|
||||
openhands_config.sandbox.local_runtime_url = sandbox_config.local_runtime_url
|
||||
openhands_config.sandbox.user_id = sandbox_config.user_id
|
||||
|
||||
def initialize_runtime(
|
||||
self,
|
||||
@ -352,6 +373,15 @@ class IssueResolver:
|
||||
logger.info('-' * 30)
|
||||
return {'git_patch': git_patch}
|
||||
|
||||
@staticmethod
|
||||
def build_workspace_base(
|
||||
output_dir: str, issue_type: str, issue_number: int
|
||||
) -> str:
|
||||
workspace_base = os.path.join(
|
||||
output_dir, 'workspace', f'{issue_type}_{issue_number}'
|
||||
)
|
||||
return os.path.abspath(workspace_base)
|
||||
|
||||
async def process_issue(
|
||||
self,
|
||||
issue: Issue,
|
||||
@ -366,31 +396,12 @@ class IssueResolver:
|
||||
else:
|
||||
logger.info(f'Starting fixing issue {issue.number}.')
|
||||
|
||||
workspace_base = os.path.join(
|
||||
self.output_dir, 'workspace', f'{issue_handler.issue_type}_{issue.number}'
|
||||
)
|
||||
|
||||
# Get the absolute path of the workspace base
|
||||
workspace_base = os.path.abspath(workspace_base)
|
||||
# write the repo to the workspace
|
||||
if os.path.exists(workspace_base):
|
||||
shutil.rmtree(workspace_base)
|
||||
shutil.copytree(os.path.join(self.output_dir, 'repo'), workspace_base)
|
||||
if os.path.exists(self.workspace_base):
|
||||
shutil.rmtree(self.workspace_base)
|
||||
shutil.copytree(os.path.join(self.output_dir, 'repo'), self.workspace_base)
|
||||
|
||||
config = OpenHandsConfig(
|
||||
default_agent='CodeActAgent',
|
||||
runtime='docker',
|
||||
max_budget_per_task=4,
|
||||
max_iterations=self.max_iterations,
|
||||
sandbox=self.sandbox_config,
|
||||
# do not mount workspace
|
||||
workspace_base=workspace_base,
|
||||
workspace_mount_path=workspace_base,
|
||||
agents={'CodeActAgent': AgentConfig(disabled_microagents=['github'])},
|
||||
)
|
||||
config.set_llm_config(self.llm_config)
|
||||
|
||||
runtime = create_runtime(config)
|
||||
runtime = create_runtime(self.app_config)
|
||||
await runtime.connect()
|
||||
|
||||
def on_event(evt: Event) -> None:
|
||||
@ -414,7 +425,7 @@ class IssueResolver:
|
||||
action = MessageAction(content=instruction, image_urls=images_urls)
|
||||
try:
|
||||
state: State | None = await run_controller(
|
||||
config=config,
|
||||
config=self.app_config,
|
||||
initial_user_action=action,
|
||||
runtime=runtime,
|
||||
fake_user_response_fn=codeact_user_response,
|
||||
@ -492,16 +503,7 @@ class IssueResolver:
|
||||
)
|
||||
return output
|
||||
|
||||
async def resolve_issue(
|
||||
self,
|
||||
reset_logger: bool = False,
|
||||
) -> None:
|
||||
"""Resolve a single issue.
|
||||
|
||||
Args:
|
||||
reset_logger: Whether to reset the logger for multiprocessing.
|
||||
"""
|
||||
|
||||
def extract_issue(self) -> Issue:
|
||||
# Load dataset
|
||||
issues: list[Issue] = self.issue_handler.get_converted_issues(
|
||||
issue_numbers=[self.issue_number], comment_id=self.comment_id
|
||||
@ -515,7 +517,19 @@ class IssueResolver:
|
||||
f'3. The repository name is spelled correctly'
|
||||
)
|
||||
|
||||
issue = issues[0]
|
||||
return issues[0]
|
||||
|
||||
async def resolve_issue(
|
||||
self,
|
||||
reset_logger: bool = False,
|
||||
) -> None:
|
||||
"""Resolve a single issue.
|
||||
|
||||
Args:
|
||||
reset_logger: Whether to reset the logger for multiprocessing.
|
||||
"""
|
||||
|
||||
issue = self.extract_issue()
|
||||
|
||||
if self.comment_id is not None:
|
||||
if (
|
||||
@ -534,7 +548,7 @@ class IssueResolver:
|
||||
)
|
||||
|
||||
# TEST METADATA
|
||||
model_name = self.llm_config.model.split('/')[-1]
|
||||
model_name = self.app_config.get_llm_config().model.split('/')[-1]
|
||||
|
||||
pathlib.Path(self.output_dir).mkdir(parents=True, exist_ok=True)
|
||||
pathlib.Path(os.path.join(self.output_dir, 'infer_logs')).mkdir(
|
||||
|
||||
@ -2,7 +2,7 @@ from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from openhands.core.config import SandboxConfig
|
||||
from openhands.core.config import SandboxConfig,OpenHandsConfig
|
||||
from openhands.events.action import CmdRunAction
|
||||
from openhands.resolver.issue_resolver import IssueResolver
|
||||
|
||||
@ -26,14 +26,17 @@ def assert_sandbox_config(
|
||||
def test_setup_sandbox_config_default():
|
||||
"""Test default configuration when no images provided and not experimental"""
|
||||
with mock.patch('openhands.__version__', 'mock'):
|
||||
config = IssueResolver._setup_sandbox_config(
|
||||
openhands_config = OpenHandsConfig()
|
||||
|
||||
IssueResolver.update_sandbox_config(
|
||||
openhands_config=openhands_config,
|
||||
base_container_image=None,
|
||||
runtime_container_image=None,
|
||||
is_experimental=False,
|
||||
)
|
||||
|
||||
assert_sandbox_config(
|
||||
config, runtime_container_image='ghcr.io/all-hands-ai/runtime:mock-nikolaik'
|
||||
openhands_config.sandbox, runtime_container_image='ghcr.io/all-hands-ai/runtime:mock-nikolaik'
|
||||
)
|
||||
|
||||
|
||||
@ -42,7 +45,10 @@ def test_setup_sandbox_config_both_images():
|
||||
with pytest.raises(
|
||||
ValueError, match='Cannot provide both runtime and base container images.'
|
||||
):
|
||||
IssueResolver._setup_sandbox_config(
|
||||
openhands_config = OpenHandsConfig()
|
||||
|
||||
IssueResolver.update_sandbox_config(
|
||||
openhands_config=openhands_config,
|
||||
base_container_image='base-image',
|
||||
runtime_container_image='runtime-image',
|
||||
is_experimental=False,
|
||||
@ -52,39 +58,48 @@ def test_setup_sandbox_config_both_images():
|
||||
def test_setup_sandbox_config_base_only():
|
||||
"""Test configuration when only base_container_image is provided"""
|
||||
base_image = 'custom-base-image'
|
||||
config = IssueResolver._setup_sandbox_config(
|
||||
openhands_config = OpenHandsConfig()
|
||||
|
||||
IssueResolver.update_sandbox_config(
|
||||
openhands_config=openhands_config,
|
||||
base_container_image=base_image,
|
||||
runtime_container_image=None,
|
||||
is_experimental=False,
|
||||
)
|
||||
|
||||
assert_sandbox_config(
|
||||
config, base_container_image=base_image, runtime_container_image=None
|
||||
openhands_config.sandbox, base_container_image=base_image, runtime_container_image=None
|
||||
)
|
||||
|
||||
|
||||
def test_setup_sandbox_config_runtime_only():
|
||||
"""Test configuration when only runtime_container_image is provided"""
|
||||
runtime_image = 'custom-runtime-image'
|
||||
config = IssueResolver._setup_sandbox_config(
|
||||
openhands_config = OpenHandsConfig()
|
||||
|
||||
IssueResolver.update_sandbox_config(
|
||||
openhands_config=openhands_config,
|
||||
base_container_image=None,
|
||||
runtime_container_image=runtime_image,
|
||||
is_experimental=False,
|
||||
)
|
||||
|
||||
assert_sandbox_config(config, runtime_container_image=runtime_image)
|
||||
assert_sandbox_config(openhands_config.sandbox, runtime_container_image=runtime_image)
|
||||
|
||||
|
||||
def test_setup_sandbox_config_experimental():
|
||||
"""Test configuration when experimental mode is enabled"""
|
||||
with mock.patch('openhands.__version__', 'mock'):
|
||||
config = IssueResolver._setup_sandbox_config(
|
||||
openhands_config = OpenHandsConfig()
|
||||
|
||||
IssueResolver.update_sandbox_config(
|
||||
openhands_config=openhands_config,
|
||||
base_container_image=None,
|
||||
runtime_container_image=None,
|
||||
is_experimental=True,
|
||||
)
|
||||
|
||||
assert_sandbox_config(config, runtime_container_image=None)
|
||||
assert_sandbox_config(openhands_config.sandbox, runtime_container_image=None)
|
||||
|
||||
|
||||
@mock.patch('openhands.resolver.issue_resolver.os.getuid', return_value=0)
|
||||
@ -93,13 +108,16 @@ def test_setup_sandbox_config_gitlab_ci(mock_get_unique_uid, mock_getuid):
|
||||
"""Test GitLab CI specific configuration when running as root"""
|
||||
with mock.patch('openhands.__version__', 'mock'):
|
||||
with mock.patch.object(IssueResolver, 'GITLAB_CI', True):
|
||||
config = IssueResolver._setup_sandbox_config(
|
||||
openhands_config = OpenHandsConfig()
|
||||
|
||||
IssueResolver.update_sandbox_config(
|
||||
openhands_config=openhands_config,
|
||||
base_container_image=None,
|
||||
runtime_container_image=None,
|
||||
is_experimental=False,
|
||||
)
|
||||
|
||||
assert_sandbox_config(config, local_runtime_url='http://localhost')
|
||||
assert_sandbox_config(openhands_config.sandbox, local_runtime_url='http://localhost')
|
||||
|
||||
|
||||
@mock.patch('openhands.resolver.issue_resolver.os.getuid', return_value=1000)
|
||||
@ -107,13 +125,16 @@ def test_setup_sandbox_config_gitlab_ci_non_root(mock_getuid):
|
||||
"""Test GitLab CI configuration when not running as root"""
|
||||
with mock.patch('openhands.__version__', 'mock'):
|
||||
with mock.patch.object(IssueResolver, 'GITLAB_CI', True):
|
||||
config = IssueResolver._setup_sandbox_config(
|
||||
openhands_config = OpenHandsConfig()
|
||||
|
||||
IssueResolver.update_sandbox_config(
|
||||
openhands_config=openhands_config,
|
||||
base_container_image=None,
|
||||
runtime_container_image=None,
|
||||
is_experimental=False,
|
||||
)
|
||||
|
||||
assert_sandbox_config(config, local_runtime_url='http://localhost')
|
||||
assert_sandbox_config(openhands_config.sandbox, local_runtime_url='http://localhost')
|
||||
|
||||
|
||||
@mock.patch('openhands.events.observation.CmdOutputObservation')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user