refactor: re-organize different runtime implementations into an impl folder (#4346)

Co-authored-by: Graham Neubig <neubig@gmail.com>
This commit is contained in:
Xingyao Wang 2024-10-23 05:10:03 -05:00 committed by GitHub
parent 9b6fd239d0
commit 2d5b360505
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 63 additions and 62 deletions

View File

@ -21,7 +21,7 @@ The OpenHands Runtime system uses a client-server architecture implemented with
graph TD
A[User-provided Custom Docker Image] --> B[OpenHands Backend]
B -->|Builds| C[OH Runtime Image]
C -->|Launches| D[Runtime Client]
C -->|Launches| D[Action Executor]
D -->|Initializes| E[Browser]
D -->|Initializes| F[Bash Shell]
D -->|Initializes| G[Plugins]
@ -49,10 +49,10 @@ graph TD
1. User Input: The user provides a custom base Docker image
2. Image Building: OpenHands builds a new Docker image (the "OH runtime image") based on the user-provided image. This new image includes OpenHands-specific code, primarily the "runtime client"
3. Container Launch: When OpenHands starts, it launches a Docker container using the OH runtime image
4. Client Initialization: The runtime client initializes inside the container, setting up necessary components like a bash shell and loading any specified plugins
5. Communication: The OpenHands backend (`runtime.py`) communicates with the runtime client over RESTful API, sending actions and receiving observations
4. Action Execution Server Initialization: The action execution server initializes an `ActionExecutor` inside the container, setting up necessary components like a bash shell and loading any specified plugins
5. Communication: The OpenHands backend (`openhands/runtime/impl/eventstream/eventstream_runtime.py`) communicates with the action execution server over RESTful API, sending actions and receiving observations
6. Action Execution: The runtime client receives actions from the backend, executes them in the sandboxed environment, and sends back observations
7. Observation Return: The client sends execution results back to the OpenHands backend as observations
7. Observation Return: The action execution server sends execution results back to the OpenHands backend as observations
The role of the client:

View File

@ -32,7 +32,7 @@ from openhands.core.logger import openhands_logger as logger
from openhands.core.main import create_runtime, run_controller
from openhands.events.action import AgentFinishAction, CmdRunAction, MessageAction
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
def get_config(

View File

@ -32,7 +32,7 @@ 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
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
# Configure visibility of unit tests to the Agent.
USE_UNIT_TESTS = os.environ.get('USE_UNIT_TESTS', 'false').lower() == 'true'

View File

@ -29,7 +29,7 @@ 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
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
'CodeActAgent': functools.partial(

View File

@ -32,7 +32,7 @@ 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
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
def codeact_user_response(state: State) -> str:

View File

@ -28,7 +28,7 @@ from openhands.core.logger import openhands_logger as logger
from openhands.core.main import create_runtime, run_controller
from openhands.events.action import AgentFinishAction, CmdRunAction, MessageAction
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
DATASET_CACHE_DIR = os.path.join(os.path.dirname(__file__), 'data')

View File

@ -37,7 +37,7 @@ 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
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
IMPORT_HELPER = {
'python': [

View File

@ -24,7 +24,7 @@ from openhands.core.config import (
from openhands.core.logger import openhands_logger as logger
from openhands.core.main import create_runtime, run_controller
from openhands.events.action import MessageAction
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
FAKE_RESPONSES = {
'CodeActAgent': codeact_user_response,

View File

@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
from pydantic import BaseModel
from openhands.events.event import Event
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
class TestResult(BaseModel):

View File

@ -4,7 +4,7 @@ import tempfile
from evaluation.integration_tests.tests.base import BaseIntegrationTest, TestResult
from openhands.events.action import CmdRunAction
from openhands.events.event import Event
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
class Test(BaseIntegrationTest):

View File

@ -2,7 +2,7 @@ from evaluation.integration_tests.tests.base import BaseIntegrationTest, TestRes
from evaluation.utils.shared import assert_and_raise
from openhands.events.action import CmdRunAction
from openhands.events.event import Event
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
class Test(BaseIntegrationTest):

View File

@ -2,7 +2,7 @@ from evaluation.integration_tests.tests.base import BaseIntegrationTest, TestRes
from evaluation.utils.shared import assert_and_raise
from openhands.events.action import CmdRunAction
from openhands.events.event import Event
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
class Test(BaseIntegrationTest):

View File

@ -2,7 +2,7 @@ from evaluation.integration_tests.tests.base import BaseIntegrationTest, TestRes
from evaluation.utils.shared import assert_and_raise
from openhands.events.action import CmdRunAction
from openhands.events.event import Event
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
class Test(BaseIntegrationTest):

View File

@ -6,7 +6,7 @@ from evaluation.utils.shared import assert_and_raise
from openhands.events.action import AgentFinishAction, CmdRunAction, MessageAction
from openhands.events.event import Event
from openhands.events.observation import AgentDelegateObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
HTML_FILE = """
<!DOCTYPE html>

View File

@ -29,7 +29,7 @@ from openhands.events.action import (
MessageAction,
)
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
'CodeActAgent': codeact_user_response,

View File

@ -30,11 +30,11 @@ from openhands.events.action import (
MessageAction,
)
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.base import Runtime
from openhands.runtime.browser.browser_env import (
BROWSER_EVAL_GET_GOAL_ACTION,
BROWSER_EVAL_GET_REWARDS_ACTION,
)
from openhands.runtime.runtime import Runtime
SUPPORTED_AGENT_CLS = {'BrowsingAgent'}

View File

@ -32,7 +32,7 @@ from openhands.events.action import (
MessageAction,
)
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
def codeact_user_response_mint(state: State, task: Task, task_config: dict[str, int]):

View File

@ -41,7 +41,7 @@ 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
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
config = load_app_config()

View File

@ -33,7 +33,7 @@ from openhands.core.main import create_runtime, run_controller
from openhands.events.action import CmdRunAction, MessageAction
from openhands.events.observation import CmdOutputObservation, ErrorObservation
from openhands.events.serialization.event import event_to_dict
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
from openhands.runtime.utils.shutdown_listener import sleep_if_should_continue
USE_HINT_TEXT = os.environ.get('USE_HINT_TEXT', 'false').lower() == 'true'

View File

@ -25,7 +25,7 @@ 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
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
'CodeActAgent': codeact_user_response,

View File

@ -30,11 +30,11 @@ from openhands.events.action import (
MessageAction,
)
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.base import Runtime
from openhands.runtime.browser.browser_env import (
BROWSER_EVAL_GET_GOAL_ACTION,
BROWSER_EVAL_GET_REWARDS_ACTION,
)
from openhands.runtime.runtime import Runtime
SUPPORTED_AGENT_CLS = {'BrowsingAgent'}

View File

@ -30,7 +30,7 @@ from openhands.events.observation import (
)
from openhands.llm.llm import LLM
from openhands.runtime import get_runtime_cls
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
from openhands.storage import get_file_store

View File

@ -26,7 +26,7 @@ from openhands.events.observation import AgentStateChangedObservation
from openhands.events.serialization.event import event_to_trajectory
from openhands.llm.llm import LLM
from openhands.runtime import get_runtime_cls
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
from openhands.storage import get_file_store

View File

@ -7,9 +7,9 @@ You can learn more about how the runtime works in the [EventStream Runtime](http
## Main Components
### 1. runtime.py
### 1. impl/*runtime.py
The `runtime.py` file defines the `Runtime` class, which serves as the primary interface for agent interactions with the external environment. It handles various operations including:
The `impl/*runtime.py` file defines the `Runtime` class, which serves as the primary [interface](./base.py) for agent interactions with the external environment. It handles various operations including:
- Bash sandbox execution
- Browser interactions
@ -23,11 +23,11 @@ Key features of the `Runtime` class:
- Action execution methods for different types of actions (run, read, write, browse, etc.)
- Abstract methods for file operations (to be implemented by subclasses)
### 2. client/client.py
### 2. action_execution_server.py
The `client.py` file contains the `RuntimeClient` class, which is responsible for executing actions received from the OpenHands backend and producing observations. This client runs inside a Docker sandbox.
The `action_executor_server.py` file contains the `ActionExecutor` class, which is responsible for executing actions received from the OpenHands backend and producing observations. This client runs inside a Docker sandbox.
Key features of the `RuntimeClient` class:
Key features of the `ActionExecutor` class:
- Initialization of user environment and bash shell
- Plugin management and initialization
- Execution of various action types (bash commands, IPython cells, file operations, browsing)
@ -59,7 +59,7 @@ Key features of the `RuntimeClient` class:
- Plugins like Jupyter and AgentSkills are initialized and integrated into the runtime.
6. **Sandbox Environment**:
- The `RuntimeClient` sets up a sandboxed environment inside a Docker container.
- The `ActionExecutor` sets up a sandboxed environment inside a Docker container.
- User environment and bash shell are initialized.
- Actions received from the OpenHands backend are executed in this sandboxed environment.
@ -96,7 +96,7 @@ This is the default runtime used within OpenHands.
The Remote Runtime is designed for execution in a remote environment:
- Connects to a remote server running the RuntimeClient
- Connects to a remote server running the ActionExecutor
- Executes actions by sending requests to the remote client
- Supports distributed execution and cloud-based deployments
- Ideal for production environments, scalability, and scenarios where local resource constraints are a concern

View File

@ -1,24 +1,26 @@
from openhands.core.logger import openhands_logger as logger
from openhands.runtime.e2b.sandbox import E2BBox
from openhands.runtime.impl.e2b.sandbox import E2BBox
def get_runtime_cls(name: str):
# Local imports to avoid circular imports
if name == 'eventstream':
from openhands.runtime.client.runtime import EventStreamRuntime
from openhands.runtime.impl.eventstream.eventstream_runtime import (
EventStreamRuntime,
)
return EventStreamRuntime
elif name == 'e2b':
from openhands.runtime.e2b.runtime import E2BRuntime
from openhands.runtime.impl.e2b.e2b_runtime import E2BRuntime
return E2BRuntime
elif name == 'remote':
from openhands.runtime.remote.runtime import RemoteRuntime
from openhands.runtime.impl.remote.remote_runtime import RemoteRuntime
return RemoteRuntime
elif name == 'modal':
logger.info('Using ModalRuntime')
from openhands.runtime.modal.runtime import ModalRuntime
from openhands.runtime.impl.modal.modal_runtime import ModalRuntime
return ModalRuntime
else:

View File

@ -78,8 +78,8 @@ def verify_api_key(api_key: str = Depends(api_key_header)):
return api_key
class RuntimeClient:
"""RuntimeClient is running inside docker sandbox.
class ActionExecutor:
"""ActionExecutor is running inside docker sandbox.
It is responsible for executing actions received from OpenHands backend and producing observations.
"""
@ -629,12 +629,12 @@ if __name__ == '__main__':
raise ValueError(f'Plugin {plugin} not found')
plugins_to_load.append(ALL_PLUGINS[plugin]()) # type: ignore
client: RuntimeClient | None = None
client: ActionExecutor | None = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global client
client = RuntimeClient(
client = ActionExecutor(
plugins_to_load,
work_dir=args.working_dir,
username=args.username,

View File

@ -12,10 +12,10 @@ from openhands.events.observation import (
Observation,
)
from openhands.events.stream import EventStream
from openhands.runtime.e2b.filestore import E2BFileStore
from openhands.runtime.e2b.sandbox import E2BSandbox
from openhands.runtime.base import Runtime
from openhands.runtime.impl.e2b.filestore import E2BFileStore
from openhands.runtime.impl.e2b.sandbox import E2BSandbox
from openhands.runtime.plugins import PluginRequirement
from openhands.runtime.runtime import Runtime
from openhands.runtime.utils.files import insert_lines, read_lines

View File

@ -7,7 +7,6 @@ from e2b import Sandbox as E2BSandbox
from e2b.sandbox.exception import (
TimeoutException,
)
from openhands.core.config import SandboxConfig
from openhands.core.logger import openhands_logger as logger

View File

@ -31,9 +31,9 @@ from openhands.events.observation import (
)
from openhands.events.serialization import event_to_dict, observation_from_dict
from openhands.events.serialization.action import ACTION_TYPE_TO_CLASS
from openhands.runtime.base import Runtime
from openhands.runtime.builder import DockerRuntimeBuilder
from openhands.runtime.plugins import PluginRequirement
from openhands.runtime.runtime import Runtime
from openhands.runtime.utils import find_available_tcp_port
from openhands.runtime.utils.request import send_request_with_retry
from openhands.runtime.utils.runtime_build import build_runtime_image
@ -304,7 +304,7 @@ class EventStreamRuntime(Runtime):
command=(
f'/openhands/micromamba/bin/micromamba run -n openhands '
f'poetry run '
f'python -u -m openhands.runtime.client.client {self._container_port} '
f'python -u -m openhands.runtime.action_execution_server {self._container_port} '
f'--working-dir "{sandbox_workspace_dir}" '
f'{plugin_arg}'
f'--username {"openhands" if self.config.run_as_openhands else "root"} '

View File

@ -28,9 +28,9 @@ from openhands.events.observation import (
)
from openhands.events.serialization import event_to_dict, observation_from_dict
from openhands.events.serialization.action import ACTION_TYPE_TO_CLASS
from openhands.runtime.base import Runtime
from openhands.runtime.builder.remote import RemoteRuntimeBuilder
from openhands.runtime.plugins import PluginRequirement
from openhands.runtime.runtime import Runtime
from openhands.runtime.utils.request import (
is_404_error,
is_503_error,
@ -212,7 +212,7 @@ class RemoteRuntime(Runtime):
'command': (
f'/openhands/micromamba/bin/micromamba run -n openhands '
'poetry run '
f'python -u -m openhands.runtime.client.client {self.port} '
f'python -u -m openhands.runtime.action_execution_server {self.port} '
f'--working-dir {self.config.workspace_mount_path_in_sandbox} '
f'{plugin_arg}'
f'--username {"openhands" if self.config.run_as_openhands else "root"} '

View File

@ -54,7 +54,7 @@ from openhands.events.observation import (
)
from openhands.events.serialization import event_to_dict
from openhands.llm import bedrock
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
from openhands.server.auth import get_sid_from_token, sign_token
from openhands.server.session import SessionManager

View File

@ -11,7 +11,7 @@ from openhands.events.action.agent import ChangeAgentStateAction
from openhands.events.event import EventSource
from openhands.events.stream import EventStream
from openhands.runtime import get_runtime_cls
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
from openhands.security import SecurityAnalyzer, options
from openhands.storage.files import FileStore
from openhands.utils.async_utils import call_sync_from_async

View File

@ -1,7 +1,7 @@
from openhands.core.config import AppConfig
from openhands.events.stream import EventStream
from openhands.runtime import get_runtime_cls
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
from openhands.security import SecurityAnalyzer, options
from openhands.storage.files import FileStore

View File

@ -11,10 +11,10 @@ from pytest import TempPathFactory
from openhands.core.config import load_app_config
from openhands.core.logger import openhands_logger as logger
from openhands.events import EventStream
from openhands.runtime.client.runtime import EventStreamRuntime
from openhands.runtime.base import Runtime
from openhands.runtime.impl.eventstream.eventstream_runtime import EventStreamRuntime
from openhands.runtime.impl.remote.remote_runtime import RemoteRuntime
from openhands.runtime.plugins import AgentSkillsRequirement, JupyterRequirement
from openhands.runtime.remote.runtime import RemoteRuntime
from openhands.runtime.runtime import Runtime
from openhands.storage import get_file_store
TEST_IN_CI = os.getenv('TEST_IN_CI', 'False').lower() in ['true', '1', 'yes']

View File

@ -1,4 +1,4 @@
"""Bash-related tests for the EventStreamRuntime, which connects to the RuntimeClient running in the sandbox."""
"""Bash-related tests for the EventStreamRuntime, which connects to the ActionExecutor running in the sandbox."""
import os
@ -13,7 +13,7 @@ from conftest import (
from openhands.core.logger import openhands_logger as logger
from openhands.events.action import CmdRunAction
from openhands.events.observation import CmdOutputObservation
from openhands.runtime.runtime import Runtime
from openhands.runtime.base import Runtime
# ============================================================================================================================
# Bash-specific tests

View File

@ -1,4 +1,4 @@
"""Browsing-related tests for the EventStreamRuntime, which connects to the RuntimeClient running in the sandbox."""
"""Browsing-related tests for the EventStreamRuntime, which connects to the ActionExecutor running in the sandbox."""
import json

View File

@ -1,4 +1,4 @@
"""Env vars related tests for the EventStreamRuntime, which connects to the RuntimeClient running in the sandbox."""
"""Env vars related tests for the EventStreamRuntime, which connects to the ActionExecutor running in the sandbox."""
import os
from unittest.mock import patch

View File

@ -1,4 +1,4 @@
"""Image-related tests for the EventStreamRuntime, which connects to the RuntimeClient running in the sandbox."""
"""Image-related tests for the EventStreamRuntime, which connects to the ActionExecutor running in the sandbox."""
import pytest
from conftest import _close_test_runtime, _load_runtime

View File

@ -1,4 +1,4 @@
"""Test the EventStreamRuntime, which connects to the RuntimeClient running in the sandbox."""
"""Test the EventStreamRuntime, which connects to the ActionExecutor running in the sandbox."""
import pytest
from conftest import (