mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Document various runtimes (#4536)
Co-authored-by: Xingyao Wang <xingyao@all-hands.dev>
This commit is contained in:
parent
3ae4bc0f8e
commit
2e50a5bef5
2
.github/workflows/ghcr-build.yml
vendored
2
.github/workflows/ghcr-build.yml
vendored
@ -1,5 +1,5 @@
|
||||
# Workflow that builds, tests and then pushes the OpenHands and runtime docker images to the ghcr.io repository
|
||||
name: Build, Test and Publish RT Image
|
||||
name: Docker
|
||||
|
||||
# Always run on "main"
|
||||
# Always run on tags
|
||||
|
||||
24
README.md
24
README.md
@ -40,30 +40,28 @@ See the [Installation](https://docs.all-hands.dev/modules/usage/installation) gu
|
||||
system requirements and more information.
|
||||
|
||||
```bash
|
||||
export WORKSPACE_BASE=$(pwd)/workspace
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik
|
||||
|
||||
docker pull ghcr.io/all-hands-ai/runtime:0.11-nikolaik
|
||||
|
||||
docker run -it --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.11-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
ghcr.io/all-hands-ai/openhands:0.11
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.11
|
||||
```
|
||||
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
|
||||
|
||||
You'll need a model provider and API key. One option that works well: [Claude 3.5 Sonnet](https://www.anthropic.com/api), but you have [many options](https://docs.all-hands.dev/modules/usage/llms).
|
||||
You'll need a model provider and API key.
|
||||
[Anthropic's Claude 3.5 Sonnet (`anthropic/claude-3-5-sonnet-20241022`)](https://www.anthropic.com/api)
|
||||
works best, but you have [many options](https://docs.all-hands.dev/modules/usage/llms).
|
||||
|
||||
---
|
||||
|
||||
You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
|
||||
or as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode).
|
||||
You can also [connect OpenHands to your local filesystem](https://docs.all-hands.dev/modules/usage/runtimes),
|
||||
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
|
||||
or interact with it via a [friendly CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode).
|
||||
|
||||
Visit [Installation](https://docs.all-hands.dev/modules/usage/installation) for more information and setup instructions.
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ ENV SANDBOX_LOCAL_RUNTIME_URL=http://host.docker.internal
|
||||
ENV USE_HOST_NETWORK=false
|
||||
ENV WORKSPACE_BASE=/opt/workspace_base
|
||||
ENV OPENHANDS_BUILD_VERSION=$OPENHANDS_BUILD_VERSION
|
||||
ENV SANDBOX_USER_ID=0
|
||||
RUN mkdir -p $WORKSPACE_BASE
|
||||
|
||||
RUN apt-get update -y \
|
||||
|
||||
@ -18,6 +18,11 @@ if [ -z "$SANDBOX_USER_ID" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$WORKSPACE_MOUNT_PATH" ]; then
|
||||
# This is set to /opt/workspace in the Dockerfile. But if the user isn't mounting, we want to unset it so that OpenHands doesn't mount at all
|
||||
unset WORKSPACE_BASE
|
||||
fi
|
||||
|
||||
if [[ "$SANDBOX_USER_ID" -eq 0 ]]; then
|
||||
echo "Running OpenHands as root"
|
||||
export RUN_AS_OPENHANDS=false
|
||||
|
||||
@ -57,7 +57,7 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
ghcr.io/all-hands-ai/openhands:0.11 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.11 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@ -51,6 +51,6 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
ghcr.io/all-hands-ai/openhands:0.11 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.11 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
@ -150,7 +150,7 @@ metadata:
|
||||
spec:
|
||||
containers:
|
||||
- name: openhands-app-2024
|
||||
image: ghcr.io/all-hands-ai/openhands:main
|
||||
image: docker.all-hands.dev/all-hands-ai/openhands:main
|
||||
env:
|
||||
- name: SANDBOX_USER_ID
|
||||
value: "1000"
|
||||
@ -164,7 +164,7 @@ spec:
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
- name: openhands-sandbox-2024
|
||||
image: ghcr.io/all-hands-ai/sandbox:main
|
||||
image: docker.all-hands.dev/all-hands-ai/runtime:main
|
||||
ports:
|
||||
- containerPort: 51963
|
||||
command: ["/usr/sbin/sshd", "-D", "-p 51963", "-o", "PermitRootLogin=yes"]
|
||||
@ -205,10 +205,10 @@ LAST SEEN TYPE REASON OBJECT
|
||||
9s Normal SuccessfulAttachVolume pod/openhands-app-2024 AttachVolume.Attach succeeded for volume "pvc-2b1d223a-1c8f-4990-8e3d-68061a9ae252"
|
||||
9s Normal SuccessfulAttachVolume pod/openhands-app-2024 AttachVolume.Attach succeeded for volume "pvc-31f15b25-faad-4665-a25f-201a530379af"
|
||||
6s Normal AddedInterface pod/openhands-app-2024 Add eth0 [10.128.2.48/23] from openshift-sdn
|
||||
6s Normal Pulled pod/openhands-app-2024 Container image "ghcr.io/all-hands-ai/openhands:main" already present on machine
|
||||
6s Normal Pulled pod/openhands-app-2024 Container image "docker.all-hands.dev/all-hands-ai/openhands:main" already present on machine
|
||||
6s Normal Created pod/openhands-app-2024 Created container openhands-app-2024
|
||||
6s Normal Started pod/openhands-app-2024 Started container openhands-app-2024
|
||||
6s Normal Pulled pod/openhands-app-2024 Container image "ghcr.io/all-hands-ai/sandbox:main" already present on machine
|
||||
6s Normal Pulled pod/openhands-app-2024 Container image "docker.all-hands.dev/all-hands-ai/sandbox:main" already present on machine
|
||||
5s Normal Created pod/openhands-app-2024 Created container openhands-sandbox-2024
|
||||
5s Normal Started pod/openhands-app-2024 Started container openhands-sandbox-2024
|
||||
83s Normal WaitForFirstConsumer persistentvolumeclaim/workspace-pvc waiting for first consumer to be created before binding
|
||||
@ -334,7 +334,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: openhands-app-2024
|
||||
image: ghcr.io/all-hands-ai/openhands:main
|
||||
image: docker.all-hands.dev/all-hands-ai/openhands:main
|
||||
env:
|
||||
- name: SANDBOX_USER_ID
|
||||
value: "1000"
|
||||
@ -356,7 +356,7 @@ spec:
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
- name: openhands-sandbox-2024
|
||||
image: ghcr.io/opendevin/sandbox:main
|
||||
image: docker.all-hands.dev/all-hands-ai/runtime:main
|
||||
# securityContext:
|
||||
# privileged: true # Add this to allow privileged access
|
||||
ports:
|
||||
|
||||
@ -8,24 +8,18 @@
|
||||
|
||||
## Start the app
|
||||
|
||||
The easiest way to run OpenHands is in Docker. You can change `WORKSPACE_BASE` below to point OpenHands to
|
||||
existing code that you'd like to modify.
|
||||
The easiest way to run OpenHands is in Docker.
|
||||
|
||||
```bash
|
||||
export WORKSPACE_BASE=$(pwd)/workspace
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik
|
||||
|
||||
docker pull ghcr.io/all-hands-ai/runtime:0.11-nikolaik
|
||||
|
||||
docker run -it --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.11-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
ghcr.io/all-hands-ai/openhands:0.11
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.11
|
||||
```
|
||||
|
||||
You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), or using the [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
@ -34,9 +28,6 @@ You can also run OpenHands in a scriptable [headless mode](https://docs.all-hand
|
||||
|
||||
After running the command above, you'll find OpenHands running at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
The agent will have access to the `./workspace` folder to do its work. You can copy existing code here, or change `WORKSPACE_BASE` in the
|
||||
command to point to an existing folder.
|
||||
|
||||
Upon launching OpenHands, you'll see a settings modal. You **must** select an `LLM Provider` and `LLM Model` and enter a corresponding `API Key`.
|
||||
These can be changed at any time by selecting the `Settings` button (gear icon) in the UI.
|
||||
|
||||
@ -52,9 +43,9 @@ The `Advanced Options` also allow you to specify a `Base URL` if required.
|
||||
## Versions
|
||||
|
||||
The command above pulls the most recent stable release of OpenHands. You have other options as well:
|
||||
- For a specific release, use `ghcr.io/all-hands-ai/openhands:$VERSION`, replacing $VERSION with the version number.
|
||||
- For a specific release, use `docker.all-hands.dev/all-hands-ai/openhands:$VERSION`, replacing $VERSION with the version number.
|
||||
- We use semver, and release major, minor, and patch tags. So `0.9` will automatically point to the latest `0.9.x` release, and `0` will point to the latest `0.x.x` release.
|
||||
- For the most up-to-date development version, you can use `ghcr.io/all-hands-ai/openhands:main`. This version is unstable and is recommended for testing or development purposes only.
|
||||
- For the most up-to-date development version, you can use `docker.all-hands.dev/all-hands-ai/openhands:main`. This version is unstable and is recommended for testing or development purposes only.
|
||||
|
||||
You can choose the tag that best suits your needs based on stability requirements and desired features.
|
||||
|
||||
|
||||
@ -35,32 +35,15 @@ Use the instructions [here](../getting-started) to start OpenHands using Docker.
|
||||
But when running `docker run`, you'll need to add a few more arguments:
|
||||
|
||||
```bash
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
-e LLM_OLLAMA_BASE_URL="http://host.docker.internal:11434" \
|
||||
```
|
||||
|
||||
LLM_OLLAMA_BASE_URL is optional. If you set it, it will be used to show the available installed models in the UI.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
# The directory you want OpenHands to modify. MUST be an absolute path!
|
||||
export WORKSPACE_BASE=$(pwd)/workspace
|
||||
|
||||
docker run \
|
||||
-it \
|
||||
--pull=always \
|
||||
docker run # ...
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e LLM_OLLAMA_BASE_URL="http://host.docker.internal:11434" \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
ghcr.io/all-hands-ai/openhands:main
|
||||
# ...
|
||||
```
|
||||
|
||||
You should now be able to connect to `http://localhost:3000/`
|
||||
LLM_OLLAMA_BASE_URL is optional. If you set it, it will be used to show
|
||||
the available installed models in the UI.
|
||||
|
||||
|
||||
### Configure the Web Application
|
||||
|
||||
@ -176,18 +159,11 @@ CUSTOM_LLM_PROVIDER="openai"
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-it \
|
||||
--pull=always \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
docker run # ...
|
||||
-e LLM_MODEL="openai/lmstudio" \
|
||||
-e LLM_BASE_URL="http://host.docker.internal:1234/v1" \
|
||||
-e CUSTOM_LLM_PROVIDER="openai" \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
ghcr.io/all-hands-ai/openhands:main
|
||||
# ...
|
||||
```
|
||||
|
||||
You should now be able to connect to `http://localhost:3000/`
|
||||
|
||||
79
docs/modules/usage/runtimes.md
Normal file
79
docs/modules/usage/runtimes.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Runtime Configuration
|
||||
|
||||
A Runtime is an environment where the OpenHands agent can edit files and run
|
||||
commands.
|
||||
|
||||
By default, OpenHands uses a Docker-based runtime, running on your local computer.
|
||||
This means you only have to pay for the LLM you're using, and your code is only ever sent to the LLM.
|
||||
|
||||
We also support "remote" runtimes, which are typically managed by third-parties.
|
||||
They can make setup a bit simpler and more scalable, especially
|
||||
if you're running many OpenHands conversations in parallel (e.g. to do evaluation).
|
||||
|
||||
## Docker Runtime
|
||||
This is the default Runtime that's used when you start OpenHands. You might notice
|
||||
some flags being passed to `docker run` that make this possible:
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
The `SANDBOX_RUNTIME_CONTAINER_IMAGE` from nikolaik is a pre-built runtime image
|
||||
that contains our Runtime server, as well as some basic utilities for Python and NodeJS.
|
||||
You can also [build your own runtime image](how-to/custom-sandbox-guide).
|
||||
|
||||
### Connecting to Your filesystem
|
||||
One useful feature here is the ability to connect to your local filesystem.
|
||||
|
||||
To mount your filesystem into the runtime, add the following options to
|
||||
the `docker run` command:
|
||||
|
||||
```bash
|
||||
export WORKSPACE_BASE=/path/to/your/code
|
||||
|
||||
docker run # ...
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
# ...
|
||||
```
|
||||
|
||||
Be careful! There's nothing stopping the OpenHands agent from deleting or modifying
|
||||
any files that are mounted into its workspace.
|
||||
|
||||
This setup can cause some issues with file permissions (hence the `SANDBOX_USER_ID` variable)
|
||||
but seems to work well on most systems.
|
||||
|
||||
## All Hands Runtime
|
||||
The All Hands Runtime is currently in beta. You can request access by joining
|
||||
the #remote-runtime-limited-beta channel on Slack (see the README for an invite).
|
||||
|
||||
To use the All Hands Runtime, set the following environment variables when
|
||||
starting OpenHands:
|
||||
|
||||
```bash
|
||||
docker run # ...
|
||||
-e RUNTIME=remote \
|
||||
-e SANDBOX_REMOTE_RUNTIME_API_URL="https://runtime.app.all-hands.dev" \
|
||||
-e SANDBOX_API_KEY="your-all-hands-api-key" \
|
||||
-e SANDBOX_KEEP_REMOTE_RUNTIME_ALIVE="true" \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
|
||||
# ...
|
||||
```
|
||||
|
||||
## Modal Runtime
|
||||
Our partners at [Modal](https://modal.com/) have also provided a runtime for OpenHands.
|
||||
|
||||
To use the Modal Runtime, create an account, and then [create an API key](https://modal.com/settings)
|
||||
|
||||
You'll then need to set the following environment variables when starting OpenHands:
|
||||
```bash
|
||||
docker run # ...
|
||||
-e RUNTIME=modal \
|
||||
-e MODAL_API_TOKEN_ID="your-id" \
|
||||
-e MODAL_API_TOKEN_SECRET="your-secret" \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.11-nikolaik \
|
||||
```
|
||||
@ -90,6 +90,11 @@ const sidebars: SidebarsConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Runtime Configuration',
|
||||
id: 'usage/runtimes',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Custom Sandbox',
|
||||
|
||||
4691
docs/yarn.lock
4691
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,6 @@ from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.config_utils import (
|
||||
OH_DEFAULT_AGENT,
|
||||
OH_MAX_ITERATIONS,
|
||||
UndefinedString,
|
||||
get_field_info,
|
||||
)
|
||||
from openhands.core.config.llm_config import LLMConfig
|
||||
@ -22,7 +21,6 @@ from openhands.core.config.utils import (
|
||||
__all__ = [
|
||||
'OH_DEFAULT_AGENT',
|
||||
'OH_MAX_ITERATIONS',
|
||||
'UndefinedString',
|
||||
'AgentConfig',
|
||||
'AppConfig',
|
||||
'LLMConfig',
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import os
|
||||
import uuid
|
||||
from dataclasses import dataclass, field, fields, is_dataclass
|
||||
from typing import ClassVar
|
||||
@ -8,7 +7,6 @@ from openhands.core.config.agent_config import AgentConfig
|
||||
from openhands.core.config.config_utils import (
|
||||
OH_DEFAULT_AGENT,
|
||||
OH_MAX_ITERATIONS,
|
||||
UndefinedString,
|
||||
get_field_info,
|
||||
)
|
||||
from openhands.core.config.llm_config import LLMConfig
|
||||
@ -55,11 +53,8 @@ class AppConfig:
|
||||
file_store: str = 'memory'
|
||||
file_store_path: str = '/tmp/file_store'
|
||||
trajectories_path: str | None = None
|
||||
# TODO: clean up workspace path after the removal of ServerRuntime
|
||||
workspace_base: str = os.path.join(os.getcwd(), 'workspace')
|
||||
workspace_mount_path: str | None = (
|
||||
UndefinedString.UNDEFINED # this path should always be set when config is fully loaded
|
||||
) # when set to None, do not mount the workspace
|
||||
workspace_base: str | None = None
|
||||
workspace_mount_path: str | None = None
|
||||
workspace_mount_path_in_sandbox: str = '/workspace'
|
||||
workspace_mount_rewrite: str | None = None
|
||||
cache_dir: str = '/tmp/cache'
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from enum import Enum
|
||||
from types import UnionType
|
||||
from typing import get_args, get_origin
|
||||
|
||||
@ -6,10 +5,6 @@ OH_DEFAULT_AGENT = 'CodeActAgent'
|
||||
OH_MAX_ITERATIONS = 100
|
||||
|
||||
|
||||
class UndefinedString(str, Enum):
|
||||
UNDEFINED = 'UNDEFINED'
|
||||
|
||||
|
||||
def get_field_info(f):
|
||||
"""Extract information about a dataclass field: type, optional, and default.
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@ from openhands.core.config.app_config import AppConfig
|
||||
from openhands.core.config.config_utils import (
|
||||
OH_DEFAULT_AGENT,
|
||||
OH_MAX_ITERATIONS,
|
||||
UndefinedString,
|
||||
)
|
||||
from openhands.core.config.llm_config import LLMConfig
|
||||
from openhands.core.config.sandbox_config import SandboxConfig
|
||||
@ -191,16 +190,15 @@ def load_from_toml(cfg: AppConfig, toml_file: str = 'config.toml'):
|
||||
|
||||
def finalize_config(cfg: AppConfig):
|
||||
"""More tweaks to the config after it's been loaded."""
|
||||
cfg.workspace_base = os.path.abspath(cfg.workspace_base)
|
||||
# Set workspace_mount_path if not set by the user
|
||||
if cfg.workspace_mount_path is UndefinedString.UNDEFINED:
|
||||
cfg.workspace_mount_path = cfg.workspace_base
|
||||
if cfg.workspace_base is not None:
|
||||
cfg.workspace_base = os.path.abspath(cfg.workspace_base)
|
||||
if cfg.workspace_mount_path is None:
|
||||
cfg.workspace_mount_path = cfg.workspace_base
|
||||
|
||||
if cfg.workspace_mount_rewrite: # and not config.workspace_mount_path:
|
||||
# TODO why do we need to check if workspace_mount_path is None?
|
||||
base = cfg.workspace_base or os.getcwd()
|
||||
parts = cfg.workspace_mount_rewrite.split(':')
|
||||
cfg.workspace_mount_path = base.replace(parts[0], parts[1])
|
||||
if cfg.workspace_mount_rewrite:
|
||||
base = cfg.workspace_base or os.getcwd()
|
||||
parts = cfg.workspace_mount_rewrite.split(':')
|
||||
cfg.workspace_mount_path = base.replace(parts[0], parts[1])
|
||||
|
||||
for llm in cfg.llms.values():
|
||||
if llm.embedding_base_url is None:
|
||||
|
||||
@ -205,11 +205,7 @@ class EventStreamRuntime(Runtime):
|
||||
self.log(
|
||||
'info', f'Starting runtime with image: {self.runtime_container_image}'
|
||||
)
|
||||
self._init_container(
|
||||
sandbox_workspace_dir=self.config.workspace_mount_path_in_sandbox, # e.g. /workspace
|
||||
mount_dir=self.config.workspace_mount_path, # e.g. /opt/openhands/_test_workspace
|
||||
plugins=self.plugins,
|
||||
)
|
||||
self._init_container()
|
||||
self.log('info', f'Container started: {self.container_name}')
|
||||
|
||||
else:
|
||||
@ -246,19 +242,14 @@ class EventStreamRuntime(Runtime):
|
||||
stop=tenacity.stop_after_attempt(5) | stop_if_should_exit(),
|
||||
wait=tenacity.wait_exponential(multiplier=1, min=4, max=60),
|
||||
)
|
||||
def _init_container(
|
||||
self,
|
||||
sandbox_workspace_dir: str,
|
||||
mount_dir: str | None = None,
|
||||
plugins: list[PluginRequirement] | None = None,
|
||||
):
|
||||
def _init_container(self):
|
||||
try:
|
||||
self.log('debug', 'Preparing to start container...')
|
||||
self.send_status_message('STATUS$PREPARING_CONTAINER')
|
||||
plugin_arg = ''
|
||||
if plugins is not None and len(plugins) > 0:
|
||||
if self.plugins is not None and len(self.plugins) > 0:
|
||||
plugin_arg = (
|
||||
f'--plugins {" ".join([plugin.name for plugin in plugins])} '
|
||||
f'--plugins {" ".join([plugin.name for plugin in self.plugins])} '
|
||||
)
|
||||
|
||||
self._host_port = self._find_available_port()
|
||||
@ -294,17 +285,27 @@ class EventStreamRuntime(Runtime):
|
||||
environment['DEBUG'] = 'true'
|
||||
|
||||
self.log('debug', f'Workspace Base: {self.config.workspace_base}')
|
||||
if mount_dir is not None and sandbox_workspace_dir is not None:
|
||||
if (
|
||||
self.config.workspace_mount_path is not None
|
||||
and self.config.workspace_mount_path_in_sandbox is not None
|
||||
):
|
||||
# e.g. result would be: {"/home/user/openhands/workspace": {'bind': "/workspace", 'mode': 'rw'}}
|
||||
volumes = {mount_dir: {'bind': sandbox_workspace_dir, 'mode': 'rw'}}
|
||||
self.log('debug', f'Mount dir: {mount_dir}')
|
||||
volumes = {
|
||||
self.config.workspace_mount_path: {
|
||||
'bind': self.config.workspace_mount_path_in_sandbox,
|
||||
'mode': 'rw',
|
||||
}
|
||||
}
|
||||
logger.debug(f'Mount dir: {self.config.workspace_mount_path}')
|
||||
else:
|
||||
self.log(
|
||||
'warn',
|
||||
'Warning: Mount dir is not set, will not mount the workspace directory to the container!\n',
|
||||
logger.debug(
|
||||
'Mount dir is not set, will not mount the workspace directory to the container'
|
||||
)
|
||||
volumes = None
|
||||
self.log('debug', f'Sandbox workspace: {sandbox_workspace_dir}')
|
||||
self.log(
|
||||
'debug',
|
||||
f'Sandbox workspace: {self.config.workspace_mount_path_in_sandbox}'
|
||||
)
|
||||
|
||||
if self.config.sandbox.browsergym_eval_env is not None:
|
||||
browsergym_arg = (
|
||||
@ -319,7 +320,7 @@ class EventStreamRuntime(Runtime):
|
||||
f'/openhands/micromamba/bin/micromamba run -n openhands '
|
||||
f'poetry run '
|
||||
f'python -u -m openhands.runtime.action_execution_server {self._container_port} '
|
||||
f'--working-dir "{sandbox_workspace_dir}" '
|
||||
f'--working-dir "{self.config.workspace_mount_path_in_sandbox}" '
|
||||
f'{plugin_arg}'
|
||||
f'--username {"openhands" if self.config.run_as_openhands else "root"} '
|
||||
f'--user-id {self.config.sandbox.user_id} '
|
||||
|
||||
@ -6,7 +6,6 @@ from openhands.core.config import (
|
||||
AgentConfig,
|
||||
AppConfig,
|
||||
LLMConfig,
|
||||
UndefinedString,
|
||||
finalize_config,
|
||||
get_llm_config_arg,
|
||||
load_from_env,
|
||||
@ -82,12 +81,8 @@ def test_load_from_old_style_env(monkeypatch, default_config):
|
||||
assert default_config.get_agent_config().memory_enabled is True
|
||||
assert default_config.default_agent == 'PlannerAgent'
|
||||
assert default_config.workspace_base == '/opt/files/workspace'
|
||||
assert (
|
||||
default_config.workspace_mount_path is UndefinedString.UNDEFINED
|
||||
) # before finalize_config
|
||||
assert (
|
||||
default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
|
||||
)
|
||||
assert default_config.workspace_mount_path is None # before finalize_config
|
||||
assert default_config.workspace_mount_path_in_sandbox is not None
|
||||
assert default_config.sandbox.base_container_image == 'custom_image'
|
||||
|
||||
|
||||
@ -148,11 +143,8 @@ default_agent = "TestAgent"
|
||||
assert default_config.workspace_base == '/opt/files2/workspace'
|
||||
assert default_config.sandbox.timeout == 1
|
||||
|
||||
# before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
|
||||
assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
|
||||
assert (
|
||||
default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
|
||||
)
|
||||
assert default_config.workspace_mount_path is None
|
||||
assert default_config.workspace_mount_path_in_sandbox is not None
|
||||
assert default_config.workspace_mount_path_in_sandbox == '/workspace'
|
||||
|
||||
finalize_config(default_config)
|
||||
@ -231,8 +223,7 @@ sandbox_user_id = 1001
|
||||
|
||||
load_from_toml(default_config, temp_toml_file)
|
||||
|
||||
# before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
|
||||
assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
|
||||
assert default_config.workspace_mount_path is None
|
||||
|
||||
load_from_env(default_config, os.environ)
|
||||
|
||||
@ -244,11 +235,9 @@ sandbox_user_id = 1001
|
||||
|
||||
# after we set workspace_base to 'UNDEFINED' in the environment,
|
||||
# workspace_base should be set to that
|
||||
# workspace_mount path is still UndefinedString.UNDEFINED
|
||||
assert default_config.workspace_base is not UndefinedString.UNDEFINED
|
||||
assert default_config.workspace_base is not None
|
||||
assert default_config.workspace_base == 'UNDEFINED'
|
||||
assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
|
||||
assert default_config.workspace_mount_path == 'UNDEFINED'
|
||||
assert default_config.workspace_mount_path is None
|
||||
|
||||
assert default_config.disable_color is True
|
||||
assert default_config.sandbox.timeout == 1000
|
||||
@ -284,8 +273,7 @@ user_id = 1001
|
||||
|
||||
load_from_toml(default_config, temp_toml_file)
|
||||
|
||||
# before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
|
||||
assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
|
||||
assert default_config.workspace_mount_path is None
|
||||
|
||||
# before load_from_env, values are set to the values from the toml file
|
||||
assert default_config.get_llm_config().api_key == 'toml-api-key'
|
||||
@ -338,9 +326,7 @@ user_id = 1001
|
||||
def test_defaults_dict_after_updates(default_config):
|
||||
# Test that `defaults_dict` retains initial values after updates.
|
||||
initial_defaults = default_config.defaults_dict
|
||||
assert (
|
||||
initial_defaults['workspace_mount_path']['default'] is UndefinedString.UNDEFINED
|
||||
)
|
||||
assert initial_defaults['workspace_mount_path']['default'] is None
|
||||
assert initial_defaults['default_agent']['default'] == 'CodeActAgent'
|
||||
|
||||
updated_config = AppConfig()
|
||||
@ -352,10 +338,7 @@ def test_defaults_dict_after_updates(default_config):
|
||||
|
||||
defaults_after_updates = updated_config.defaults_dict
|
||||
assert defaults_after_updates['default_agent']['default'] == 'CodeActAgent'
|
||||
assert (
|
||||
defaults_after_updates['workspace_mount_path']['default']
|
||||
is UndefinedString.UNDEFINED
|
||||
)
|
||||
assert defaults_after_updates['workspace_mount_path']['default'] is None
|
||||
assert defaults_after_updates['sandbox']['timeout']['default'] == 120
|
||||
assert (
|
||||
defaults_after_updates['sandbox']['base_container_image']['default']
|
||||
@ -384,17 +367,16 @@ def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
|
||||
|
||||
def test_finalize_config(default_config):
|
||||
# Test finalize config
|
||||
assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
|
||||
assert default_config.workspace_mount_path is None
|
||||
default_config.workspace_base = None
|
||||
finalize_config(default_config)
|
||||
|
||||
assert default_config.workspace_mount_path == os.path.abspath(
|
||||
default_config.workspace_base
|
||||
)
|
||||
assert default_config.workspace_mount_path is None
|
||||
|
||||
|
||||
# tests for workspace, mount path, path in sandbox, cache dir
|
||||
def test_workspace_mount_path_default(default_config):
|
||||
assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
|
||||
assert default_config.workspace_mount_path is None
|
||||
default_config.workspace_base = '/home/user/project'
|
||||
finalize_config(default_config)
|
||||
assert default_config.workspace_mount_path == os.path.abspath(
|
||||
default_config.workspace_base
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user