Skip to content

Commit

Permalink
refactor: re-organize different runtime implementations into an impl …
Browse files Browse the repository at this point in the history
…folder (#4346)

Co-authored-by: Graham Neubig <neubig@gmail.com>
  • Loading branch information
xingyaoww and neubig authored Oct 23, 2024
1 parent 9b6fd23 commit 2d5b360
Show file tree
Hide file tree
Showing 43 changed files with 63 additions and 62 deletions.
8 changes: 4 additions & 4 deletions docs/modules/usage/architecture/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion evaluation/agent_bench/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
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(
Expand Down
2 changes: 1 addition & 1 deletion evaluation/aider_bench/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
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'
Expand Down
2 changes: 1 addition & 1 deletion evaluation/biocoder/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
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(
Expand Down
2 changes: 1 addition & 1 deletion evaluation/bird/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
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:
Expand Down
2 changes: 1 addition & 1 deletion evaluation/gaia/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
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')

Expand Down
2 changes: 1 addition & 1 deletion evaluation/humanevalfix/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
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': [
Expand Down
2 changes: 1 addition & 1 deletion evaluation/integration_tests/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
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,
Expand Down
2 changes: 1 addition & 1 deletion evaluation/integration_tests/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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):
Expand Down
2 changes: 1 addition & 1 deletion evaluation/integration_tests/tests/t01_fix_simple_typo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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):
Expand Down
2 changes: 1 addition & 1 deletion evaluation/integration_tests/tests/t02_add_bash_hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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):
Expand Down
2 changes: 1 addition & 1 deletion evaluation/integration_tests/tests/t04_git_staging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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):
Expand Down
2 changes: 1 addition & 1 deletion evaluation/integration_tests/tests/t05_simple_browsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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>
Expand Down
2 changes: 1 addition & 1 deletion evaluation/logic_reasoning/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
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,
Expand Down
2 changes: 1 addition & 1 deletion evaluation/miniwob/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
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'}

Expand Down
2 changes: 1 addition & 1 deletion evaluation/mint/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
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]):
Expand Down
2 changes: 1 addition & 1 deletion evaluation/ml_bench/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
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()

Expand Down
2 changes: 1 addition & 1 deletion evaluation/swe_bench/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
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'
Expand Down
2 changes: 1 addition & 1 deletion evaluation/toolqa/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
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,
Expand Down
2 changes: 1 addition & 1 deletion evaluation/webarena/run_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
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'}

Expand Down
2 changes: 1 addition & 1 deletion openhands/core/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
)
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


Expand Down
2 changes: 1 addition & 1 deletion openhands/core/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
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


Expand Down
14 changes: 7 additions & 7 deletions openhands/runtime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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
Expand Down
12 changes: 7 additions & 5 deletions openhands/runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""

Expand Down Expand Up @@ -629,12 +629,12 @@ def close(self):
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,
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
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


Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from e2b.sandbox.exception import (
TimeoutException,
)

from openhands.core.config import SandboxConfig
from openhands.core.logger import openhands_logger as logger

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
)
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
Expand Down Expand Up @@ -304,7 +304,7 @@ def _init_container(
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"} '
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
)
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,
Expand Down Expand Up @@ -212,7 +212,7 @@ def _start_runtime(self, plugins: list[PluginRequirement] | None):
'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"} '
Expand Down
Loading

0 comments on commit 2d5b360

Please sign in to comment.