Compare commits
220 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e398d6362 | |||
| 1d052818ae | |||
| ee284bae8f | |||
| 1b1d8f0b02 | |||
| c32cec7f89 | |||
| 7b2b1eff57 | |||
| 2f1b537471 | |||
| f2a71eb388 | |||
| 63c5d74169 | |||
| 5d77aec90b | |||
| a66e738957 | |||
| 582f07f9c9 | |||
| 83b7fcc3ed | |||
| 8f565c8740 | |||
| df28a7a5b9 | |||
| 4743eb4c35 | |||
| 4a6898bbda | |||
| a4ddac4f2c | |||
| 33422f1a4a | |||
| dc418e7b71 | |||
| dd3d1497f6 | |||
| 8ea2d61ff2 | |||
| 73ded7de10 | |||
| 3f6aa0d1f1 | |||
| 2556767ccb | |||
| fbef93b762 | |||
| 3a93fd4c64 | |||
| a12e8cf06a | |||
| 69479852ff | |||
| 7024e973d4 | |||
| a260cc8dc8 | |||
| 7e53c96b14 | |||
| 96b23d2e4c | |||
| b97aa10b66 | |||
| b61455042f | |||
| c84495830e | |||
| 3435f1e5d8 | |||
| 714e46f29a | |||
| e0608af0b3 | |||
| 402a03cb9a | |||
| 01462e11d7 | |||
| ebd93977cd | |||
| ef189d52a5 | |||
| 3c77cc80dc | |||
| 31b189c9af | |||
| a7630c399a | |||
| 86521c971b | |||
| 6682e0f1dd | |||
| 031b91457a | |||
| b13ed017d8 | |||
| 72ca1690a7 | |||
| 45066f19dc | |||
| 809903bad4 | |||
| 071f1f0e13 | |||
| 08a505406b | |||
| 1756b85675 | |||
| e8ab8f1146 | |||
| a6ea4c2a60 | |||
| 1e7f398376 | |||
| 0f6fb0f80e | |||
| bd1f0f1671 | |||
| 2842efa0e8 | |||
| 620526b8b4 | |||
| 31dbd3d02e | |||
| dd7174e559 | |||
| 8fdfece059 | |||
| ad0b549d8b | |||
| 47f60b8275 | |||
| 5d7f2fd4ae | |||
| 22e885736b | |||
| f9589a552e | |||
| 043fb2771d | |||
| b95740cc55 | |||
| c864715b43 | |||
| a7f1bca586 | |||
| b4408b41c9 | |||
| e3be71f523 | |||
| dbe767325f | |||
| ec0fe35d48 | |||
| 883f36b005 | |||
| dc6b4b7296 | |||
| fe0a8eb036 | |||
| c3117e8c39 | |||
| f7ebc1cf1f | |||
| 8a419b5c45 | |||
| 9787a31ba1 | |||
| 31296624d1 | |||
| 656222f416 | |||
| a4e61faf56 | |||
| f4657edc48 | |||
| ef09f0fe37 | |||
| f996b31d64 | |||
| fa0d9cfa42 | |||
| 07a094e701 | |||
| 52c5abccbf | |||
| 29b0e62cd7 | |||
| 2a10ff1374 | |||
| 592c9580d2 | |||
| 804674bb9f | |||
| 41a54378dc | |||
| 9d4eb7d19d | |||
| d886bf7e2b | |||
| dcede97f68 | |||
| 531aec404a | |||
| b7e050eaf6 | |||
| 1cc1016768 | |||
| d3c2a022b3 | |||
| 88abf1b5d8 | |||
| 834296f7b4 | |||
| 243cb492aa | |||
| f11e767eb5 | |||
| 0db664986d | |||
| a33f61c025 | |||
| a45b20a406 | |||
| f6709d08ef | |||
| 2b3925278d | |||
| ecf4aed28b | |||
| a97ef34139 | |||
| 554636cf2a | |||
| 57390eb26b | |||
| 379f2b6f23 | |||
| 26cc1670ad | |||
| cbeae3e612 | |||
| 434951af23 | |||
| 900d819394 | |||
| 3a1b8c093b | |||
| cea6b6e30e | |||
| 0233cd7848 | |||
| 95edc1b5cf | |||
| 6111f530c2 | |||
| 78c5f58adc | |||
| 7506b20087 | |||
| bff9296d68 | |||
| 4b5240cdf7 | |||
| 9ef79cbe22 | |||
| 1f13d80ddc | |||
| 58de5221f5 | |||
| 9bbb35ec18 | |||
| 797f02ff6f | |||
| 47d9621742 | |||
| 8c12f5b67d | |||
| 6132a58367 | |||
| 2fe2f4c530 | |||
| 4d8f812f88 | |||
| e5cb80d59d | |||
| ee9bea393f | |||
| 940de86caa | |||
| fe5ecb6da8 | |||
| 41b8f3e4a7 | |||
| 93ecd82e61 | |||
| 052149ccf3 | |||
| 3aba722a59 | |||
| 4738640f4e | |||
| bf6f7b9943 | |||
| 0f20965999 | |||
| e6034b301b | |||
| 9e50b88f9f | |||
| fd0ecdd4e0 | |||
| c6105f264f | |||
| 93f271579c | |||
| 1976763152 | |||
| 516ca701d4 | |||
| f3b2085f9b | |||
| 97a03faf33 | |||
| 06ed142191 | |||
| dbd0786345 | |||
| 3bc9a485b8 | |||
| 2b845d9568 | |||
| d7e0db0b35 | |||
| 508681691a | |||
| 1c9b4ad78a | |||
| ecb1b9b2a0 | |||
| e4f8708656 | |||
| 822de89394 | |||
| c5e89be6de | |||
| 386688da5f | |||
| 6c01d25976 | |||
| 05e2b0c352 | |||
| 5066468c36 | |||
| 9349009074 | |||
| 5ffff742de | |||
| 50dc17c65c | |||
| f4d6b3262d | |||
| cb3168da12 | |||
| 2b7517e542 | |||
| dadada18ce | |||
| ab3851593d | |||
| 5100d12cea | |||
| a9cf7e6ee6 | |||
| 8392a3fb6b | |||
| c376b81505 | |||
| 8440604dd1 | |||
| a4d75cd190 | |||
| 4db929b986 | |||
| b6b38fcd37 | |||
| d619be96d1 | |||
| 6f44ea0115 | |||
| 0f118df910 | |||
| 43c4a7fff4 | |||
| 57187417b7 | |||
| 03b5b03bb2 | |||
| 681276f27c | |||
| 82a154f7e7 | |||
| 5b7ab28511 | |||
| ca3f39e918 | |||
| 6c2630e506 | |||
| 260e41486e | |||
| 9b0fb8f81a | |||
| cd360ef6aa | |||
| 5bb46525a4 | |||
| f80e4a9e5d | |||
| 60c5fd41ec | |||
| 688068a44e | |||
| ee158feb15 | |||
| ec25abd98b | |||
| bb28dea51f | |||
| 2a99aa6679 | |||
| add4653335 | |||
| 2557c18fb1 | |||
| bc31fb15fe |
@@ -1 +1 @@
|
||||
The files in this directory configure a development container for GitHub Codespaces.
|
||||
The files in this directory configure a development container for GitHub Codespaces.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Workflow that builds, tests and then pushes the runtime docker images to the ghcr.io repository
|
||||
name: Build, Test and Publish Runtime Image
|
||||
name: Build, Test and Publish RT Image
|
||||
|
||||
# Only run one workflow of the same group at a time.
|
||||
# There can be at most one running and one pending job in a concurrency group at any time.
|
||||
@@ -38,10 +38,6 @@ jobs:
|
||||
base_image:
|
||||
- image: 'nikolaik/python-nodejs:python3.11-nodejs22'
|
||||
tag: nikolaik
|
||||
- image: 'python:3.11-bookworm'
|
||||
tag: python
|
||||
- image: 'node:22-bookworm'
|
||||
tag: node
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -70,85 +66,162 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'poetry'
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: make install-python-dependencies
|
||||
- name: Create source distribution and Dockerfile
|
||||
run: poetry run python3 openhands/runtime/utils/runtime_build.py --base_image ${{ matrix.base_image.image }} --build_folder containers/runtime --force_rebuild
|
||||
- name: Build and push runtime image ${{ matrix.base_image.image }}
|
||||
if: "!github.event.pull_request.head.repo.fork"
|
||||
if: github.event.pull_request.head.repo.fork != true
|
||||
run: |
|
||||
./containers/build.sh runtime ${{ github.repository_owner }} --push ${{ matrix.base_image.tag }}
|
||||
# Forked repos can't push to GHCR, so we need to upload the image as an artifact
|
||||
- name: Build runtime image ${{ matrix.base_image.image }} for fork
|
||||
if: "github.event.pull_request.head.repo.fork"
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
tags: ghcr.io/all-hands-ai/runtime:${{ github.sha }}-${{ matrix.base_image.tag }}
|
||||
outputs: type=docker,dest=/tmp/runtime-${{ matrix.base_image.tag }}.tar
|
||||
context: containers/runtime
|
||||
- name: Upload runtime image for fork
|
||||
if: "github.event.pull_request.head.repo.fork"
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: runtime-${{ matrix.base_image.tag }}
|
||||
path: /tmp/runtime-${{ matrix.base_image.tag }}.tar
|
||||
|
||||
# Run unit tests with the EventStream runtime Docker images
|
||||
test_runtime:
|
||||
name: Test Runtime
|
||||
runs-on: ubuntu-latest
|
||||
# Run unit tests with the EventStream runtime Docker images as root
|
||||
test_runtime_root:
|
||||
name: RT Unit Tests (Root)
|
||||
needs: [ghcr_build_runtime]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
base_image: ['nikolaik', 'python', 'node']
|
||||
base_image: ['nikolaik']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
tool-cache: true
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
swap-storage: true
|
||||
# Forked repos can't push to GHCR, so we need to download the image as an artifact
|
||||
- name: Download runtime image for fork
|
||||
if: "github.event.pull_request.head.repo.fork"
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runtime-${{ matrix.base_image }}
|
||||
path: /tmp
|
||||
- name: Load runtime image for fork
|
||||
if: "github.event.pull_request.head.repo.fork"
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
run: |
|
||||
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'poetry'
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: make install-python-dependencies
|
||||
- name: Run runtime tests
|
||||
run: |
|
||||
# We install pytest-xdist in order to run tests across CPUs
|
||||
poetry run pip install pytest-xdist
|
||||
|
||||
# Install to be able to retry on failures for flaky tests
|
||||
poetry run pip install pytest-rerunfailures
|
||||
|
||||
image_name=ghcr.io/${{ github.repository_owner }}/runtime:${{ github.sha }}-${{ matrix.base_image }}
|
||||
image_name=$(echo $image_name | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
SKIP_CONTAINER_LOGS=true \
|
||||
TEST_RUNTIME=eventstream \
|
||||
SANDBOX_USER_ID=$(id -u) \
|
||||
SANDBOX_BASE_CONTAINER_IMAGE=$image_name \
|
||||
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
|
||||
TEST_IN_CI=true \
|
||||
poetry run pytest --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
|
||||
RUN_AS_OPENHANDS=false \
|
||||
poetry run pytest -n 3 -raR --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
# Run unit tests with the EventStream runtime Docker images as openhands user
|
||||
test_runtime_oh:
|
||||
name: RT Unit Tests (openhands)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ghcr_build_runtime]
|
||||
strategy:
|
||||
matrix:
|
||||
base_image: ['nikolaik']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Forked repos can't push to GHCR, so we need to download the image as an artifact
|
||||
- name: Download runtime image for fork
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runtime-${{ matrix.base_image }}
|
||||
path: /tmp
|
||||
- name: Load runtime image for fork
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
run: |
|
||||
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: make install-python-dependencies
|
||||
- name: Run runtime tests
|
||||
run: |
|
||||
# We install pytest-xdist in order to run tests across CPUs
|
||||
poetry run pip install pytest-xdist
|
||||
|
||||
# Install to be able to retry on failures for flaky tests
|
||||
poetry run pip install pytest-rerunfailures
|
||||
|
||||
image_name=ghcr.io/${{ github.repository_owner }}/runtime:${{ github.sha }}-${{ matrix.base_image }}
|
||||
image_name=$(echo $image_name | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
SKIP_CONTAINER_LOGS=true \
|
||||
TEST_RUNTIME=eventstream \
|
||||
SANDBOX_USER_ID=$(id -u) \
|
||||
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
|
||||
TEST_IN_CI=true \
|
||||
RUN_AS_OPENHANDS=true \
|
||||
poetry run pytest -n 3 -raR --reruns 1 --reruns-delay 3 --cov=agenthub --cov=openhands --cov-report=xml -s ./tests/runtime
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
@@ -156,33 +229,41 @@ jobs:
|
||||
|
||||
# Run integration tests with the eventstream runtime Docker image
|
||||
runtime_integration_tests_on_linux:
|
||||
name: Runtime Integration Tests on Linux
|
||||
name: RT Integration Tests (Linux)
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ghcr_build_runtime]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
base_image: ['nikolaik', 'python', 'node']
|
||||
base_image: ['nikolaik']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# Forked repos can't push to GHCR, so we need to download the image as an artifact
|
||||
- name: Download runtime image for fork
|
||||
if: "github.event.pull_request.head.repo.fork"
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: runtime-${{ matrix.base_image }}
|
||||
path: /tmp
|
||||
- name: Load runtime image for fork
|
||||
if: "github.event.pull_request.head.repo.fork"
|
||||
if: github.event.pull_request.head.repo.fork
|
||||
run: |
|
||||
docker load --input /tmp/runtime-${{ matrix.base_image }}.tar
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'poetry'
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: make install-python-dependencies
|
||||
- name: Run integration tests
|
||||
@@ -192,7 +273,7 @@ jobs:
|
||||
|
||||
TEST_RUNTIME=eventstream \
|
||||
SANDBOX_USER_ID=$(id -u) \
|
||||
SANDBOX_BASE_CONTAINER_IMAGE=$image_name \
|
||||
SANDBOX_RUNTIME_CONTAINER_IMAGE=$image_name \
|
||||
TEST_IN_CI=true \
|
||||
TEST_ONLY=true \
|
||||
./tests/integration/regenerate.sh
|
||||
@@ -201,11 +282,26 @@ jobs:
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
# Checks that all runtime tests have passed
|
||||
all_runtime_tests_passed:
|
||||
# The two following jobs (named identically) are to check whether all the runtime tests have passed as the
|
||||
# "All Runtime Tests Passed" is a required job for PRs to merge
|
||||
# Due to this bug: https://github.com/actions/runner/issues/2566, we want to create a job that runs when the
|
||||
# prerequisites have been cancelled or failed so merging is disallowed, otherwise Github considers "skipped" as "success"
|
||||
runtime_tests_check_success:
|
||||
name: All Runtime Tests Passed
|
||||
if: ${{ !cancelled() && !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test_runtime, runtime_integration_tests_on_linux]
|
||||
needs: [test_runtime_root, test_runtime_oh, runtime_integration_tests_on_linux]
|
||||
steps:
|
||||
- name: All tests passed
|
||||
run: echo "All runtime tests have passed successfully!"
|
||||
|
||||
runtime_tests_check_fail:
|
||||
name: All Runtime Tests Passed
|
||||
if: ${{ cancelled() || contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test_runtime_root, test_runtime_oh, runtime_integration_tests_on_linux]
|
||||
steps:
|
||||
- name: Some tests failed
|
||||
run: |
|
||||
echo "Some runtime tests failed or were cancelled"
|
||||
exit 1
|
||||
|
||||
@@ -22,13 +22,21 @@ jobs:
|
||||
python-version: ['3.11']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'poetry'
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: poetry install --without evaluation,llama-index
|
||||
- name: Install & Start Docker
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
name: Release workflow
|
||||
# Publishes the OpenHands PyPi package
|
||||
name: Publish PyPi Package
|
||||
|
||||
# Triggered manually
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-9]+.[0-9]+.[0-9]+*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reason:
|
||||
description: 'Reason for manual trigger'
|
||||
required: true
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
release:
|
||||
|
||||
@@ -3,6 +3,23 @@ name: Regenerate Integration Tests
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
debug:
|
||||
description: 'Enable debug mode'
|
||||
type: boolean
|
||||
default: true
|
||||
log_to_file:
|
||||
description: 'Enable logging to file'
|
||||
type: boolean
|
||||
default: true
|
||||
force_regenerate_tests:
|
||||
description: 'Force regeneration of tests'
|
||||
type: boolean
|
||||
default: false
|
||||
force_use_llm:
|
||||
description: 'Force use of LLM'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
regenerate_integration_tests:
|
||||
@@ -12,21 +29,32 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'poetry'
|
||||
python-version: "3.11"
|
||||
- name: Cache Poetry dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cache/pypoetry
|
||||
~/.virtualenvs
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: poetry install --without evaluation,llama-index
|
||||
run: make install-python-dependencies
|
||||
- name: Build Environment
|
||||
run: make build
|
||||
|
||||
- name: Regenerate integration tests
|
||||
run: ./tests/integration/regenerate.sh
|
||||
|
||||
run: |
|
||||
DEBUG=${{ inputs.debug }} \
|
||||
LOG_TO_FILE=${{ inputs.log_to_file }} \
|
||||
FORCE_REGENERATE_TESTS=${{ inputs.force_regenerate_tests }} \
|
||||
FORCE_USE_LLM=${{ inputs.force_use_llm }} \
|
||||
./tests/integration/regenerate.sh
|
||||
- name: Commit changes
|
||||
run: |
|
||||
if git diff --quiet --exit-code; then
|
||||
@@ -37,5 +65,6 @@ jobs:
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add .
|
||||
git commit -m "Regenerate integration tests"
|
||||
# run it twice in case pre-commit makes changes
|
||||
git commit -am "Regenerate integration tests" || git commit -am "Regenerate integration tests"
|
||||
git push
|
||||
|
||||
@@ -228,3 +228,4 @@ runtime_*.tar
|
||||
# docker build
|
||||
containers/runtime/Dockerfile
|
||||
containers/runtime/project.tar.gz
|
||||
containers/runtime/code
|
||||
|
||||
@@ -97,3 +97,28 @@ Please refer to [this README](./tests/integration/README.md) for details.
|
||||
### 9. Add or update dependency
|
||||
1. Add your dependency in `pyproject.toml` or use `poetry add xxx`
|
||||
2. Update the poetry.lock file via `poetry lock --no-update`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
TL;DR
|
||||
|
||||
```bash
|
||||
make docker-dev
|
||||
```
|
||||
|
||||
See more details [here](./containers/dev/README.md)
|
||||
|
||||
If you are just interested in running `OpenHands` without installing all the required tools on your host.
|
||||
|
||||
```bash
|
||||
make docker-run
|
||||
```
|
||||
|
||||
If you do not have `make` on your host, run:
|
||||
|
||||
```bash
|
||||
cd ./containers/dev
|
||||
./dev.sh
|
||||
```
|
||||
|
||||
You do need [Docker](https://docs.docker.com/engine/install/) installed on your host though.
|
||||
|
||||
@@ -2,8 +2,9 @@ SHELL=/bin/bash
|
||||
# Makefile for OpenHands project
|
||||
|
||||
# Variables
|
||||
BACKEND_HOST ?= "127.0.0.1"
|
||||
BACKEND_PORT = 3000
|
||||
BACKEND_HOST = "127.0.0.1:$(BACKEND_PORT)"
|
||||
BACKEND_HOST_PORT = "$(BACKEND_HOST):$(BACKEND_PORT)"
|
||||
FRONTEND_PORT = 3001
|
||||
DEFAULT_WORKSPACE_DIR = "./workspace"
|
||||
DEFAULT_MODEL = "gpt-4o"
|
||||
@@ -189,12 +190,12 @@ build-frontend:
|
||||
# Start backend
|
||||
start-backend:
|
||||
@echo "$(YELLOW)Starting backend...$(RESET)"
|
||||
@poetry run uvicorn openhands.server.listen:app --port $(BACKEND_PORT) --reload --reload-exclude "workspace/*"
|
||||
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "workspace/*"
|
||||
|
||||
# Start frontend
|
||||
start-frontend:
|
||||
@echo "$(YELLOW)Starting frontend...$(RESET)"
|
||||
@cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start
|
||||
@cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST_PORT) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start
|
||||
|
||||
# Common setup for running the app (non-callable)
|
||||
_run_setup:
|
||||
@@ -204,7 +205,7 @@ _run_setup:
|
||||
fi
|
||||
@mkdir -p logs
|
||||
@echo "$(YELLOW)Starting backend server...$(RESET)"
|
||||
@poetry run uvicorn openhands.server.listen:app --port $(BACKEND_PORT) &
|
||||
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) &
|
||||
@echo "$(YELLOW)Waiting for the backend to start...$(RESET)"
|
||||
@until nc -z localhost $(BACKEND_PORT); do sleep 0.1; done
|
||||
@echo "$(GREEN)Backend started successfully.$(RESET)"
|
||||
@@ -216,6 +217,20 @@ run:
|
||||
@cd frontend && echo "$(BLUE)Starting frontend with npm...$(RESET)" && npm run start -- --port $(FRONTEND_PORT)
|
||||
@echo "$(GREEN)Application started successfully.$(RESET)"
|
||||
|
||||
# Run the app (in docker)
|
||||
docker-run: WORKSPACE_BASE ?= $(PWD)/workspace
|
||||
docker-run:
|
||||
@if [ -f /.dockerenv ]; then \
|
||||
echo "Running inside a Docker container. Exiting..."; \
|
||||
exit 0; \
|
||||
else \
|
||||
echo "$(YELLOW)Running the app in Docker $(OPTIONS)...$(RESET)"; \
|
||||
export WORKSPACE_BASE=${WORKSPACE_BASE}; \
|
||||
export SANDBOX_USER_ID=$(shell id -u); \
|
||||
export DATE=$(shell date +%Y%m%d%H%M%S); \
|
||||
docker compose up $(OPTIONS); \
|
||||
fi
|
||||
|
||||
# Run the app (WSL mode)
|
||||
run-wsl:
|
||||
@echo "$(YELLOW)Running the app in WSL mode...$(RESET)"
|
||||
@@ -280,6 +295,16 @@ setup-config-prompts:
|
||||
fi
|
||||
|
||||
|
||||
# Develop in container
|
||||
docker-dev:
|
||||
@if [ -f /.dockerenv ]; then \
|
||||
echo "Running inside a Docker container. Exiting..."; \
|
||||
exit 0; \
|
||||
else \
|
||||
echo "$(YELLOW)Build and run in Docker $(OPTIONS)...$(RESET)"; \
|
||||
./containers/dev/dev.sh $(OPTIONS); \
|
||||
fi
|
||||
|
||||
# Clean up all caches
|
||||
clean:
|
||||
@echo "$(YELLOW)Cleaning up caches...$(RESET)"
|
||||
@@ -298,7 +323,10 @@ help:
|
||||
@echo " $(GREEN)start-frontend$(RESET) - Start the frontend server for the OpenHands project."
|
||||
@echo " $(GREEN)run$(RESET) - Run the OpenHands application, starting both backend and frontend servers."
|
||||
@echo " Backend Log file will be stored in the 'logs' directory."
|
||||
@echo " $(GREEN)docker-dev$(RESET) - Build and run the OpenHands application in Docker."
|
||||
@echo " $(GREEN)docker-run$(RESET) - Run the OpenHands application, starting both backend and frontend servers in Docker."
|
||||
@echo " $(GREEN)help$(RESET) - Display this help message, providing information on available targets."
|
||||
|
||||
# Phony targets
|
||||
.PHONY: build check-dependencies check-python check-npm check-docker check-poetry install-python-dependencies install-frontend-dependencies install-pre-commit-hooks lint start-backend start-frontend run run-wsl setup-config setup-config-prompts help
|
||||
.PHONY: docker-dev docker-run
|
||||
|
||||
@@ -1,72 +1,49 @@
|
||||
<a name="readme-top"></a>
|
||||
|
||||
<!--
|
||||
*** Thanks for checking out the Best-README-Template. If you have a suggestion
|
||||
*** that would make this better, please fork the repo and create a pull request
|
||||
*** or simply open an issue with the tag "enhancement".
|
||||
*** Don't forget to give the project a star!
|
||||
*** Thanks again! Now go create something AMAZING! :D
|
||||
-->
|
||||
<div align="center">
|
||||
<img src="./docs/static/img/logo.png" alt="Logo" width="200">
|
||||
<h1 align="center">OpenHands: Code Less, Make More</h1>
|
||||
</div>
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
<!--
|
||||
*** I'm using markdown "reference style" links for readability.
|
||||
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
||||
*** See the bottom of this document for the declaration of the reference variables
|
||||
*** for contributors-url, forks-url, etc. This is an optional, concise syntax you may use.
|
||||
*** https://www.markdownguide.org/basic-syntax/#reference-style-links
|
||||
-->
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/8648">
|
||||
<img src="https://trendshift.io/api/badge/repositories/8648" alt="GitHub Trend" style="display: block; margin: auto;">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/graphs/contributors"><img src="https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Contributors"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/network/members"><img src="https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Forks"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/issues"><img src="https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Issues"></a>
|
||||
<a href="https://codecov.io/github/All-Hands-AI/OpenHands?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/All-Hands-AI/OpenHands?style=for-the-badge&color=blue"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=blue" alt="Credits"></a>
|
||||
<br/>
|
||||
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community"></a>
|
||||
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community"></a>
|
||||
<a href="https://codecov.io/github/All-Hands-AI/OpenHands?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/All-Hands-AI/OpenHands?style=for-the-badge"></a>
|
||||
</div>
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<div align="center">
|
||||
<img src="./docs/static/img/logo.png" alt="Logo" width="200" height="200">
|
||||
<h1 align="center">OpenHands: Code Less, Make More</h1>
|
||||
<a href="https://docs.all-hands.dev/modules/usage/intro"><img src="https://img.shields.io/badge/Documentation-OpenHands-blue?logo=googledocs&logoColor=white&style=for-the-badge" alt="Check out the documentation"></a>
|
||||
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper-%20on%20Arxiv-red?logo=arxiv&style=for-the-badge" alt="Paper on Arxiv"></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="Credits"></a>
|
||||
<br/>
|
||||
<a href="https://huggingface.co/spaces/OpenDevin/evaluation"><img src="https://img.shields.io/badge/Evaluation-Benchmark%20on%20HF%20Space-green?logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark"></a>
|
||||
<a href="https://docs.all-hands.dev/modules/usage/getting-started"><img src="https://img.shields.io/badge/Documentation-000?logo=googledocs&logoColor=FFE165&style=for-the-badge" alt="Check out the documentation"></a>
|
||||
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper%20on%20Arxiv-000?logoColor=FFE165&logo=arxiv&style=for-the-badge" alt="Paper on Arxiv"></a>
|
||||
<a href="https://huggingface.co/spaces/OpenHands/evaluation"><img src="https://img.shields.io/badge/Benchmark%20score-000?logoColor=FFE165&logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark Score"></a>
|
||||
<hr>
|
||||
</div>
|
||||
<hr>
|
||||
|
||||
Welcome to OpenHands (formerly OpenDevin), a platform for software development agents powered by AI.
|
||||
|
||||
Welcome to OpenHands, a platform for autonomous software engineers, powered by AI and LLMs (previously called "OpenDevin").
|
||||
OpenHands agents can do anything a human developer can: modify code, run commands, browse the web,
|
||||
call APIs, and yes—even copy code snippets from StackOverflow.
|
||||
|
||||
OpenHands agents collaborate with human developers to write code, fix bugs, and ship features.
|
||||
Learn more at [docs.all-hands.dev](https://docs.all-hands.dev), or jump to the [Quick Start](#-quick-start).
|
||||
|
||||

|
||||
|
||||
## ⚡ Getting Started
|
||||
OpenHands works best with Docker version 26.0.0+ (Docker Desktop 4.31.0+).
|
||||
You must be using Linux, Mac OS, or WSL on Windows.
|
||||
## ⚡ Quick Start
|
||||
|
||||
To start OpenHands in a docker container, run the following commands in your terminal:
|
||||
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.
|
||||
|
||||
> [!WARNING]
|
||||
> When you run the following command, files in `./workspace` may be modified or deleted.
|
||||
See the [Getting Started](https://docs.all-hands.dev/modules/usage/getting-started) guide for
|
||||
system requirements and more information.
|
||||
|
||||
```bash
|
||||
WORKSPACE_BASE=$(pwd)/workspace
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
export WORKSPACE_BASE=$(pwd)/workspace
|
||||
|
||||
docker run -it --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.9-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
@@ -77,29 +54,23 @@ docker run -it \
|
||||
ghcr.io/all-hands-ai/openhands:0.9
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> This command pulls the `0.9` tag, which represents the most recent stable release of OpenHands. You have other options as well:
|
||||
> - For a specific release version, use `ghcr.io/all-hands-ai/openhands:<OpenHands_version>` (replace <OpenHands_version> with the desired version number).
|
||||
> - For the most up-to-date development version, use `ghcr.io/all-hands-ai/openhands:main`. This version may be **(unstable!)** and is recommended for testing or development purposes only.
|
||||
>
|
||||
> Choose the tag that best suits your needs based on stability requirements and desired features.
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
|
||||
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000) with access to `./workspace`. To have OpenHands operate on your code, place it in `./workspace`.
|
||||
OpenHands will only have access to this workspace folder. The rest of your system will not be affected as it runs in a secured docker sandbox.
|
||||
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).
|
||||
|
||||
Upon opening OpenHands, you must select the appropriate `Model` and enter the `API Key` within the settings that should pop up automatically. These can be set at any time by selecting
|
||||
the `Settings` button (gear icon) in the UI. If the required `Model` does not exist in the list, you can manually enter it in the text box.
|
||||
Visit [Getting Started](https://docs.all-hands.dev/modules/usage/getting-started) for more information and setup instructions.
|
||||
|
||||
For the development workflow, see [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
If you want to modify the OpenHands source code, check out [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
|
||||
Are you having trouble? Check out our [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting).
|
||||
Having issues? The [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting) can help.
|
||||
|
||||
## 🚀 Documentation
|
||||
## 📖 Documentation
|
||||
|
||||
To learn more about the project, and for tips on using OpenHands,
|
||||
**check out our [documentation](https://docs.all-hands.dev/modules/usage/intro)**.
|
||||
**check out our [documentation](https://docs.all-hands.dev/modules/usage/getting-started)**.
|
||||
|
||||
There you'll find resources on how to use different LLM providers (like ollama and Anthropic's Claude),
|
||||
There you'll find resources on how to use different LLM providers,
|
||||
troubleshooting resources, and advanced configuration options.
|
||||
|
||||
## 🤝 How to Contribute
|
||||
@@ -134,17 +105,6 @@ Let's make software engineering better together!
|
||||
|
||||
Distributed under the MIT License. See [`LICENSE`](./LICENSE) for more information.
|
||||
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[contributors-url]: https://github.com/All-Hands-AI/OpenHands/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[forks-url]: https://github.com/All-Hands-AI/OpenHands/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[stars-url]: https://github.com/All-Hands-AI/OpenHands/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[issues-url]: https://github.com/All-Hands-AI/OpenHands/issues
|
||||
[license-shield]: https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[license-url]: https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE
|
||||
|
||||
## 🙏 Acknowledgements
|
||||
|
||||
OpenHands is built by a large number of contributors, and every contribution is greatly appreciated! We also build upon other open source projects, and we are deeply thankful for their work.
|
||||
|
||||
@@ -65,10 +65,15 @@ In order to accomplish my goal I need to send the information asked back to the
|
||||
"""
|
||||
|
||||
|
||||
def get_prompt(error_prefix: str, cur_axtree_txt: str, prev_action_str: str) -> str:
|
||||
def get_prompt(
|
||||
error_prefix: str, cur_url: str, cur_axtree_txt: str, prev_action_str: str
|
||||
) -> str:
|
||||
prompt = f"""\
|
||||
{error_prefix}
|
||||
|
||||
# Current Page URL:
|
||||
{cur_url}
|
||||
|
||||
# Current Accessibility Tree:
|
||||
{cur_axtree_txt}
|
||||
|
||||
@@ -139,6 +144,7 @@ class BrowsingAgent(Agent):
|
||||
"""
|
||||
messages: list[Message] = []
|
||||
prev_actions = []
|
||||
cur_url = ''
|
||||
cur_axtree_txt = ''
|
||||
error_prefix = ''
|
||||
last_obs = None
|
||||
@@ -179,6 +185,9 @@ class BrowsingAgent(Agent):
|
||||
self.error_accumulator += 1
|
||||
if self.error_accumulator > 5:
|
||||
return MessageAction('Too many errors encountered. Task failed.')
|
||||
|
||||
cur_url = last_obs.url
|
||||
|
||||
try:
|
||||
cur_axtree_txt = flatten_axtree_to_str(
|
||||
last_obs.axtree_object,
|
||||
@@ -204,11 +213,11 @@ class BrowsingAgent(Agent):
|
||||
|
||||
messages.append(Message(role='system', content=[TextContent(text=system_msg)]))
|
||||
|
||||
prompt = get_prompt(error_prefix, cur_axtree_txt, prev_action_str)
|
||||
prompt = get_prompt(error_prefix, cur_url, cur_axtree_txt, prev_action_str)
|
||||
messages.append(Message(role='user', content=[TextContent(text=prompt)]))
|
||||
logger.debug(prompt)
|
||||
|
||||
response = self.llm.completion(
|
||||
messages=[message.model_dump() for message in messages],
|
||||
messages=self.llm.format_messages_for_llm(messages),
|
||||
temperature=0.0,
|
||||
stop=[')```', ')\n```'],
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ class Flags:
|
||||
|
||||
@classmethod
|
||||
def from_dict(self, flags_dict):
|
||||
"""Helper for JSON serializble requirement."""
|
||||
"""Helper for JSON serializable requirement."""
|
||||
if isinstance(flags_dict, Flags):
|
||||
return flags_dict
|
||||
|
||||
@@ -354,7 +354,7 @@ and executed by a program, make sure to follow the formatting instructions.
|
||||
self._prompt += '\n'.join(
|
||||
[
|
||||
f"""\
|
||||
- [{msg['role']}] {msg['message']}"""
|
||||
- [{msg['role']}], {msg['message']}"""
|
||||
for msg in chat_messages
|
||||
]
|
||||
)
|
||||
|
||||
@@ -24,9 +24,9 @@ class BrowsingResponseParser(ResponseParser):
|
||||
if action_str is None:
|
||||
return ''
|
||||
action_str = action_str.strip()
|
||||
if not action_str.endswith('```'):
|
||||
if action_str and not action_str.endswith('```'):
|
||||
action_str = action_str + ')```'
|
||||
logger.info(action_str)
|
||||
logger.debug(action_str)
|
||||
return action_str
|
||||
|
||||
def parse_action(self, action_str: str) -> Action:
|
||||
|
||||
@@ -6,6 +6,7 @@ from openhands.events.action import (
|
||||
AgentDelegateAction,
|
||||
AgentFinishAction,
|
||||
CmdRunAction,
|
||||
FileEditAction,
|
||||
IPythonRunCellAction,
|
||||
MessageAction,
|
||||
)
|
||||
@@ -16,6 +17,7 @@ class CodeActResponseParser(ResponseParser):
|
||||
- CmdRunAction(command) - bash command to run
|
||||
- IPythonRunCellAction(code) - IPython code to run
|
||||
- AgentDelegateAction(agent, inputs) - delegate action for (sub)task
|
||||
- FileEditAction(diff_block) - Search/Replace block to edit.
|
||||
- MessageAction(content) - Message action to run (e.g. ask for clarification)
|
||||
- AgentFinishAction() - end the interaction
|
||||
"""
|
||||
@@ -28,6 +30,7 @@ class CodeActResponseParser(ResponseParser):
|
||||
CodeActActionParserCmdRun(),
|
||||
CodeActActionParserIPythonRunCell(),
|
||||
CodeActActionParserAgentDelegate(),
|
||||
CodeActActionParserFileEdit(),
|
||||
]
|
||||
self.default_parser = CodeActActionParserMessage()
|
||||
|
||||
@@ -39,7 +42,7 @@ class CodeActResponseParser(ResponseParser):
|
||||
action = response.choices[0].message.content
|
||||
if action is None:
|
||||
return ''
|
||||
for lang in ['bash', 'ipython', 'browse']:
|
||||
for lang in ['bash', 'ipython', 'edit', 'browse']:
|
||||
if f'<execute_{lang}>' in action and f'</execute_{lang}>' not in action:
|
||||
action += f'</execute_{lang}>'
|
||||
return action
|
||||
@@ -158,6 +161,33 @@ class CodeActActionParserAgentDelegate(ActionParser):
|
||||
return AgentDelegateAction(agent='BrowsingAgent', inputs={'task': task})
|
||||
|
||||
|
||||
class CodeActActionParserFileEdit(ActionParser):
|
||||
"""Parser action:
|
||||
- FileEditAction(diff_block) - Search/Replace block to edit.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
):
|
||||
self.diff_block = None
|
||||
|
||||
def check_condition(self, action_str: str) -> bool:
|
||||
self.diff_block = re.search(
|
||||
r'<execute_edit>(.*)</execute_edit>', action_str, re.DOTALL
|
||||
)
|
||||
return self.diff_block is not None
|
||||
|
||||
def parse(self, action_str: str) -> Action:
|
||||
assert (
|
||||
self.diff_block is not None
|
||||
), 'self.diff_block should not be None when parse is called'
|
||||
thought = action_str.replace(self.diff_block.group(0), '').strip()
|
||||
return FileEditAction(
|
||||
diff_block=self.diff_block.group(1).strip(),
|
||||
thought=thought,
|
||||
)
|
||||
|
||||
|
||||
class CodeActActionParserMessage(ActionParser):
|
||||
"""Parser action:
|
||||
- MessageAction(content) - Message action to run (e.g. ask for clarification)
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import os
|
||||
from itertools import islice
|
||||
|
||||
from agenthub.codeact_agent.action_parser import CodeActResponseParser
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import AgentConfig
|
||||
from openhands.core.exceptions import OperationCancelled
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.message import ImageContent, Message, TextContent
|
||||
from openhands.events.action import (
|
||||
Action,
|
||||
AgentDelegateAction,
|
||||
AgentFinishAction,
|
||||
CmdRunAction,
|
||||
FileEditAction,
|
||||
IPythonRunCellAction,
|
||||
MessageAction,
|
||||
)
|
||||
from openhands.events.observation import (
|
||||
AgentDelegateObservation,
|
||||
CmdOutputObservation,
|
||||
FileEditObservation,
|
||||
IPythonRunCellObservation,
|
||||
UserRejectObservation,
|
||||
)
|
||||
@@ -33,7 +38,7 @@ from openhands.utils.prompt import PromptManager
|
||||
|
||||
|
||||
class CodeActAgent(Agent):
|
||||
VERSION = '1.9'
|
||||
VERSION = '1.10'
|
||||
"""
|
||||
The Code Act Agent is a minimalist agent.
|
||||
The agent works by passing the model a list of action-observation pairs and prompting the model to take the next step.
|
||||
@@ -101,6 +106,8 @@ class CodeActAgent(Agent):
|
||||
return f'{action.thought}\n<execute_ipython>\n{action.code}\n</execute_ipython>'
|
||||
elif isinstance(action, AgentDelegateAction):
|
||||
return f'{action.thought}\n<execute_browse>\n{action.inputs["task"]}\n</execute_browse>'
|
||||
elif isinstance(action, FileEditAction):
|
||||
return f'{action.thought}\n<execute_edit>\n{action.diff_block}\n</execute_edit>'
|
||||
elif isinstance(action, MessageAction):
|
||||
return action.content
|
||||
elif isinstance(action, AgentFinishAction) and action.source == 'agent':
|
||||
@@ -110,6 +117,7 @@ class CodeActAgent(Agent):
|
||||
def get_action_message(self, action: Action) -> Message | None:
|
||||
if (
|
||||
isinstance(action, AgentDelegateAction)
|
||||
or isinstance(action, FileEditAction)
|
||||
or isinstance(action, CmdRunAction)
|
||||
or isinstance(action, IPythonRunCellAction)
|
||||
or isinstance(action, MessageAction)
|
||||
@@ -117,7 +125,11 @@ class CodeActAgent(Agent):
|
||||
):
|
||||
content = [TextContent(text=self.action_to_str(action))]
|
||||
|
||||
if isinstance(action, MessageAction) and action.images_urls:
|
||||
if (
|
||||
self.llm.vision_is_active()
|
||||
and isinstance(action, MessageAction)
|
||||
and action.images_urls
|
||||
):
|
||||
content.append(ImageContent(image_urls=action.images_urls))
|
||||
|
||||
return Message(
|
||||
@@ -127,14 +139,15 @@ class CodeActAgent(Agent):
|
||||
|
||||
def get_observation_message(self, obs: Observation) -> Message | None:
|
||||
max_message_chars = self.llm.config.max_message_chars
|
||||
obs_prefix = 'OBSERVATION:\n'
|
||||
if isinstance(obs, CmdOutputObservation):
|
||||
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
|
||||
text = obs_prefix + truncate_content(obs.content, max_message_chars)
|
||||
text += (
|
||||
f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]'
|
||||
)
|
||||
return Message(role='user', content=[TextContent(text=text)])
|
||||
elif isinstance(obs, IPythonRunCellObservation):
|
||||
text = 'OBSERVATION:\n' + obs.content
|
||||
text = obs_prefix + obs.content
|
||||
# replace base64 images with a placeholder
|
||||
splitted = text.split('\n')
|
||||
for i, line in enumerate(splitted):
|
||||
@@ -145,17 +158,21 @@ class CodeActAgent(Agent):
|
||||
text = '\n'.join(splitted)
|
||||
text = truncate_content(text, max_message_chars)
|
||||
return Message(role='user', content=[TextContent(text=text)])
|
||||
elif isinstance(obs, FileEditObservation):
|
||||
text = obs_prefix + truncate_content(obs.content, max_message_chars)
|
||||
return Message(role='user', content=[TextContent(text=text)])
|
||||
elif isinstance(obs, AgentDelegateObservation):
|
||||
text = 'OBSERVATION:\n' + truncate_content(
|
||||
str(obs.outputs), max_message_chars
|
||||
text = obs_prefix + truncate_content(
|
||||
obs.outputs['content'] if 'content' in obs.outputs else '',
|
||||
max_message_chars,
|
||||
)
|
||||
return Message(role='user', content=[TextContent(text=text)])
|
||||
elif isinstance(obs, ErrorObservation):
|
||||
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
|
||||
text = obs_prefix + truncate_content(obs.content, max_message_chars)
|
||||
text += '\n[Error occurred in processing last action]'
|
||||
return Message(role='user', content=[TextContent(text=text)])
|
||||
elif isinstance(obs, UserRejectObservation):
|
||||
text = 'OBSERVATION:\n' + truncate_content(obs.content, max_message_chars)
|
||||
text = obs_prefix + truncate_content(obs.content, max_message_chars)
|
||||
text += '\n[Last action has been rejected by the user]'
|
||||
return Message(role='user', content=[TextContent(text=text)])
|
||||
else:
|
||||
@@ -178,6 +195,7 @@ class CodeActAgent(Agent):
|
||||
- CmdRunAction(command) - bash command to run
|
||||
- IPythonRunCellAction(code) - IPython code to run
|
||||
- AgentDelegateAction(agent, inputs) - delegate action for (sub)task
|
||||
- FileEditAction(diff_block) - Search/Replace block to edit.
|
||||
- MessageAction(content) - Message action to run (e.g. ask for clarification)
|
||||
- AgentFinishAction() - end the interaction
|
||||
"""
|
||||
@@ -188,26 +206,31 @@ class CodeActAgent(Agent):
|
||||
|
||||
# prepare what we want to send to the LLM
|
||||
messages = self._get_messages(state)
|
||||
|
||||
params = {
|
||||
'messages': [message.model_dump() for message in messages],
|
||||
'messages': self.llm.format_messages_for_llm(messages),
|
||||
'stop': [
|
||||
'</execute_ipython>',
|
||||
'</execute_bash>',
|
||||
'</execute_browse>',
|
||||
'</execute_edit>',
|
||||
],
|
||||
'temperature': 0.0,
|
||||
}
|
||||
|
||||
if self.llm.supports_prompt_caching:
|
||||
if self.llm.is_caching_prompt_active():
|
||||
params['extra_headers'] = {
|
||||
'anthropic-beta': 'prompt-caching-2024-07-31',
|
||||
}
|
||||
|
||||
# TODO: move exception handling to agent_controller
|
||||
try:
|
||||
response = self.llm.completion(**params)
|
||||
except Exception:
|
||||
except OperationCancelled as e:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f'{e}')
|
||||
error_message = '{}: {}'.format(type(e).__name__, str(e).split('\n')[0])
|
||||
return AgentFinishAction(
|
||||
thought='Agent encountered an error while processing the last action. Please try again.'
|
||||
thought=f'Agent encountered an error while processing the last action.\nError: {error_message}\nPlease try again.'
|
||||
)
|
||||
|
||||
return self.action_parser.parse(response)
|
||||
@@ -219,7 +242,7 @@ class CodeActAgent(Agent):
|
||||
content=[
|
||||
TextContent(
|
||||
text=self.prompt_manager.system_message,
|
||||
cache_prompt=self.llm.supports_prompt_caching, # Cache system prompt
|
||||
cache_prompt=self.llm.is_caching_prompt_active(), # Cache system prompt
|
||||
)
|
||||
],
|
||||
),
|
||||
@@ -228,7 +251,7 @@ class CodeActAgent(Agent):
|
||||
content=[
|
||||
TextContent(
|
||||
text=self.prompt_manager.initial_user_message,
|
||||
cache_prompt=self.llm.supports_prompt_caching, # if the user asks the same query,
|
||||
cache_prompt=self.llm.is_caching_prompt_active(), # if the user asks the same query,
|
||||
)
|
||||
],
|
||||
),
|
||||
@@ -247,14 +270,14 @@ class CodeActAgent(Agent):
|
||||
if message:
|
||||
# handle error if the message is the SAME role as the previous message
|
||||
# litellm.exceptions.BadRequestError: litellm.BadRequestError: OpenAIException - Error code: 400 - {'detail': 'Only supports u/a/u/a/u...'}
|
||||
# there should not have two consecutive messages from the same role
|
||||
# there shouldn't be two consecutive messages from the same role
|
||||
if messages and messages[-1].role == message.role:
|
||||
messages[-1].content.extend(message.content)
|
||||
else:
|
||||
messages.append(message)
|
||||
|
||||
# Add caching to the last 2 user messages
|
||||
if self.llm.supports_prompt_caching:
|
||||
if self.llm.is_caching_prompt_active():
|
||||
user_turns_processed = 0
|
||||
for message in reversed(messages):
|
||||
if message.role == 'user' and user_turns_processed < 2:
|
||||
@@ -263,14 +286,17 @@ class CodeActAgent(Agent):
|
||||
].cache_prompt = True # Last item inside the message content
|
||||
user_turns_processed += 1
|
||||
|
||||
# the latest user message is important:
|
||||
# The latest user message is important:
|
||||
# we want to remind the agent of the environment constraints
|
||||
latest_user_message = next(
|
||||
(
|
||||
m
|
||||
for m in reversed(messages)
|
||||
if m.role == 'user'
|
||||
and any(isinstance(c, TextContent) for c in m.content)
|
||||
islice(
|
||||
(
|
||||
m
|
||||
for m in reversed(messages)
|
||||
if m.role == 'user'
|
||||
and any(isinstance(c, TextContent) for c in m.content)
|
||||
),
|
||||
1,
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
@@ -5,8 +5,13 @@ The assistant can use a Python environment with <execute_ipython>, e.g.:
|
||||
print("Hello World!")
|
||||
</execute_ipython>
|
||||
The assistant can execute bash commands wrapped with <execute_bash>, e.g. <execute_bash> ls </execute_bash>.
|
||||
The assistant is not allowed to run interactive commands. For commands that may run indefinitely,
|
||||
the output should be redirected to a file and the command run in the background, e.g. <execute_bash> python3 app.py > server.log 2>&1 & </execute_bash>
|
||||
If a bash command returns exit code `-1`, this means the process is not yet finished.
|
||||
The assistant must then send a second <execute_bash>. The second <execute_bash> can be empty
|
||||
(which will retrieve any additional logs), or it can contain text to be sent to STDIN of the running process,
|
||||
or it can contain the text `ctrl+c` to interrupt the process.
|
||||
|
||||
For commands that may run indefinitely, the output should be redirected to a file and the command run
|
||||
in the background, e.g. <execute_bash> python3 app.py > server.log 2>&1 & </execute_bash>
|
||||
If a command execution result says "Command timed out. Sending SIGINT to the process",
|
||||
the assistant should retry running the command in the background.
|
||||
{% endset %}
|
||||
@@ -14,22 +19,44 @@ the assistant should retry running the command in the background.
|
||||
The assistant can browse the Internet with <execute_browse> and </execute_browse>.
|
||||
For example, <execute_browse> Tell me the usa's president using google search </execute_browse>.
|
||||
Or <execute_browse> Tell me what is in http://example.com </execute_browse>.
|
||||
{% endset %}
|
||||
{% set EDIT_DIFF_PREFIX %}
|
||||
The assistant can edit files with <execute_edit> and </execute_edit>. Each change must be described with a SEARCH/REPLACE block.
|
||||
Every SEARCH section must EXACTLY MATCH the existing file content, character for character, including all comments, docstrings, etc. SEARCH/REPLACE blocks will replace all matching occurrences. Include enough lines to make the SEARCH blocks uniquely match the lines to change.
|
||||
Keep SEARCH/REPLACE blocks as concise as possible. Break large SEARCH/REPLACE blocks into a series of smaller blocks that each change a small portion of the file.
|
||||
To move code within a file, use 2 SEARCH/REPLACE blocks: 1 to delete it from its current location, 1 to insert it in the new location.
|
||||
If you want to put code in a new file, use a SEARCH/REPLACE block with: a new file path, an empty `SEARCH` section and the new file's contents in the `REPLACE` section.
|
||||
|
||||
Every SEARCH/REPLACE block must use this format:
|
||||
1. The FULL file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
||||
2. The start of search block: <<<<<<< SEARCH
|
||||
3. A contiguous chunk of lines to search for in the existing source code
|
||||
4. The dividing line: =======
|
||||
5. The lines to replace into the source code
|
||||
6. The end of the replace block: >>>>>>> REPLACE
|
||||
|
||||
For example,
|
||||
<execute_edit>
|
||||
demo.py
|
||||
<<<<<<< SEARCH
|
||||
print("hello")
|
||||
=======
|
||||
print("goodbye")
|
||||
>>>>>>> REPLACE
|
||||
</execute_edit>
|
||||
|
||||
{% endset %}
|
||||
{% set PIP_INSTALL_PREFIX %}
|
||||
The assistant can install Python packages using the %pip magic command in an IPython environment by using the following syntax: <execute_ipython> %pip install [package needed] </execute_ipython> and should always import packages and define variables before starting to use them.
|
||||
{% endset %}
|
||||
{% set SYSTEM_PREFIX = MINIMAL_SYSTEM_PREFIX + BROWSING_PREFIX + PIP_INSTALL_PREFIX %}
|
||||
{% set SYSTEM_PREFIX = MINIMAL_SYSTEM_PREFIX + BROWSING_PREFIX + EDIT_DIFF_PREFIX + PIP_INSTALL_PREFIX %}
|
||||
{% set COMMAND_DOCS %}
|
||||
Apart from the standard Python library, the assistant can also use the following functions (already imported) in <execute_ipython> environment:
|
||||
{{ agent_skills_docs }}
|
||||
IMPORTANT:
|
||||
- `open_file` only returns the first 100 lines of the file by default! The assistant MUST use `scroll_down` repeatedly to read the full file BEFORE making edits!
|
||||
- The assistant shall adhere to THE `edit_file_by_replace`, `append_file` and `insert_content_at_line` FUNCTIONS REQUIRING PROPER INDENTATION. If the assistant would like to add the line ' print(x)', it must fully write the line out, with all leading spaces before the code!
|
||||
- Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
|
||||
- Any code issued should be less than 50 lines to avoid context being cut off!
|
||||
- After EVERY `create_file` the method `append_file` shall be used to write the FIRST content!
|
||||
- For `edit_file_by_replace` NEVER provide empty parameters!
|
||||
- For `edit_file_by_replace` the file must be read fully before any replacements!
|
||||
{% endset %}
|
||||
{% set SYSTEM_SUFFIX %}
|
||||
Responses should be concise.
|
||||
|
||||
@@ -4,23 +4,12 @@
|
||||
USER: Create a list of numbers from 1 to 10, and display them in a web page at port 5000.
|
||||
|
||||
ASSISTANT:
|
||||
Sure! Let me create a Python file `app.py`:
|
||||
<execute_ipython>
|
||||
create_file('app.py')
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
OBSERVATION:
|
||||
[File: /workspace/app.py (1 lines total)]
|
||||
(this is the beginning of the file)
|
||||
1|
|
||||
(this is the end of the file)
|
||||
[File app.py created.]
|
||||
|
||||
ASSISTANT:
|
||||
Now I will write the Python code for starting a web server and save it to the file `app.py`:
|
||||
<execute_ipython>
|
||||
EDITED_CODE="""from flask import Flask
|
||||
Sure! Let me write the Python code for starting a web server and save it to a new Python file `app.py`:
|
||||
<execute_edit>
|
||||
app.py
|
||||
<<<<<<< SEARCH
|
||||
=======
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
@@ -29,30 +18,17 @@ def index():
|
||||
return str(numbers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=5000)"""
|
||||
app.run(port=5000)
|
||||
>>>>>>> REPLACE
|
||||
</execute_edit>
|
||||
|
||||
insert_content_at_line(
|
||||
'app.py',
|
||||
1,
|
||||
EDITED_CODE,
|
||||
)
|
||||
</execute_ipython>
|
||||
|
||||
USER:
|
||||
OBSERVATION:
|
||||
(this is the beginning of the file)
|
||||
1|from flask import Flask
|
||||
2|app = Flask(__name__)
|
||||
3|
|
||||
4|@app.route('/')
|
||||
5|def index():
|
||||
6| numbers = list(range(1, 11))
|
||||
7| return str(numbers)
|
||||
8|
|
||||
9|if __name__ == '__main__':
|
||||
10| app.run(port=5000)
|
||||
(this is the end of the file)
|
||||
[File updated (edited at line 1). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
|
||||
I updated the file app.py by
|
||||
appending:
|
||||
from flask import Flask
|
||||
app = Flask(__name__)
|
||||
|
||||
ASSISTANT:
|
||||
I have created a Python file `app.py` that will display a list of numbers from 1 to 10 when you run it. Let me run the Python file for you:
|
||||
@@ -177,30 +153,22 @@ USER:
|
||||
|
||||
ASSISTANT:
|
||||
I should edit the file to display the numbers in a table format. I should include correct indentation. Let me update the file:
|
||||
<execute_ipython>
|
||||
edit_file_by_replace(
|
||||
'app.py',
|
||||
to_replace=" return str(numbers)",
|
||||
new_content=" return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'",
|
||||
)
|
||||
</execute_ipython>
|
||||
<execute_edit>
|
||||
app.py
|
||||
<<<<<<< SEARCH
|
||||
return str(numbers)
|
||||
=======
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
>>>>>>> REPLACE
|
||||
</execute_edit>
|
||||
|
||||
USER:
|
||||
Observation:
|
||||
[File: /workspace/app.py (10 lines total after edit)]
|
||||
(this is the beginning of the file)
|
||||
1|from flask import Flask
|
||||
2|app = Flask(__name__)
|
||||
3|
|
||||
4|@app.route('/')
|
||||
5|def index():
|
||||
6| numbers = list(range(1, 11))
|
||||
7| return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
8|
|
||||
9|if __name__ == '__main__':
|
||||
10| app.run(port=5000)
|
||||
(this is the end of the file)
|
||||
[File updated (edited at line 7). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
|
||||
I updated the file app.py by
|
||||
replacing:
|
||||
return str(numbers)
|
||||
with:
|
||||
return '<table>' + ''.join([f'<tr><td>{i}</td></tr>' for i in numbers]) + '</table>'
|
||||
|
||||
ASSISTANT:
|
||||
Running the updated file:
|
||||
|
||||
@@ -94,7 +94,11 @@ class CodeActSWEAgent(Agent):
|
||||
):
|
||||
content = [TextContent(text=self.action_to_str(action))]
|
||||
|
||||
if isinstance(action, MessageAction) and action.images_urls:
|
||||
if (
|
||||
self.llm.vision_is_active()
|
||||
and isinstance(action, MessageAction)
|
||||
and action.images_urls
|
||||
):
|
||||
content.append(ImageContent(image_urls=action.images_urls))
|
||||
|
||||
return Message(
|
||||
@@ -156,9 +160,8 @@ class CodeActSWEAgent(Agent):
|
||||
|
||||
# prepare what we want to send to the LLM
|
||||
messages: list[Message] = self._get_messages(state)
|
||||
|
||||
response = self.llm.completion(
|
||||
messages=[message.model_dump() for message in messages],
|
||||
messages=self.llm.format_messages_for_llm(messages),
|
||||
stop=[
|
||||
'</execute_ipython>',
|
||||
'</execute_bash>',
|
||||
|
||||
@@ -73,10 +73,13 @@ class MicroAgent(Agent):
|
||||
latest_user_message=last_user_message,
|
||||
)
|
||||
content = [TextContent(text=prompt)]
|
||||
if last_image_urls:
|
||||
if self.llm.vision_is_active() and last_image_urls:
|
||||
content.append(ImageContent(image_urls=last_image_urls))
|
||||
message = Message(role='user', content=content)
|
||||
resp = self.llm.completion(messages=[message.model_dump()])
|
||||
resp = self.llm.completion(
|
||||
messages=self.llm.format_messages_for_llm(message),
|
||||
temperature=0.0,
|
||||
)
|
||||
action_resp = resp['choices'][0]['message']['content']
|
||||
action = parse_response(action_resp)
|
||||
return action
|
||||
|
||||
@@ -46,8 +46,8 @@ class PlannerAgent(Agent):
|
||||
state, self.llm.config.max_message_chars
|
||||
)
|
||||
content = [TextContent(text=prompt)]
|
||||
if image_urls:
|
||||
if self.llm.vision_is_active() and image_urls:
|
||||
content.append(ImageContent(image_urls=image_urls))
|
||||
message = Message(role='user', content=content)
|
||||
resp = self.llm.completion(messages=[message.model_dump()])
|
||||
resp = self.llm.completion(messages=self.llm.format_messages_for_llm(message))
|
||||
return self.response_parser.parse(resp)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
#
|
||||
services:
|
||||
openhands:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./containers/app/Dockerfile
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.9-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${WORKSPACE_BASE:-$PWD/workspace}:/opt/workspace_base
|
||||
pull_policy: build
|
||||
stdin_open: true
|
||||
tty: true
|
||||
@@ -126,21 +126,29 @@ embedding_model = ""
|
||||
# Model to use
|
||||
model = "gpt-4o"
|
||||
|
||||
# Number of retries to attempt
|
||||
#num_retries = 5
|
||||
# Number of retries to attempt when an operation fails with the LLM.
|
||||
# Increase this value to allow more attempts before giving up
|
||||
#num_retries = 8
|
||||
|
||||
# Retry maximum wait time
|
||||
#retry_max_wait = 60
|
||||
# Maximum wait time (in seconds) between retry attempts
|
||||
# This caps the exponential backoff to prevent excessively long
|
||||
#retry_max_wait = 120
|
||||
|
||||
# Retry minimum wait time
|
||||
#retry_min_wait = 3
|
||||
# Minimum wait time (in seconds) between retry attempts
|
||||
# This sets the initial delay before the first retry
|
||||
#retry_min_wait = 15
|
||||
|
||||
# Retry multiplier for exponential backoff
|
||||
# Multiplier for exponential backoff calculation
|
||||
# The wait time increases by this factor after each failed attempt
|
||||
# A value of 2.0 means each retry waits twice as long as the previous one
|
||||
#retry_multiplier = 2.0
|
||||
|
||||
# Drop any unmapped (unsupported) params without causing an exception
|
||||
#drop_params = false
|
||||
|
||||
# Using the prompt caching feature provided by the LLM
|
||||
#caching_prompt = false
|
||||
|
||||
# Base URL for the OLLAMA API
|
||||
#ollama_base_url = ""
|
||||
|
||||
@@ -151,14 +159,17 @@ model = "gpt-4o"
|
||||
#timeout = 0
|
||||
|
||||
# Top p for the API
|
||||
#top_p = 0.5
|
||||
#top_p = 1.0
|
||||
|
||||
[llm.gpt3]
|
||||
# If model is vision capable, this option allows to disable image processing (useful for cost reduction).
|
||||
#disable_vision = true
|
||||
|
||||
[llm.gpt4o-mini]
|
||||
# API key to use
|
||||
api_key = "your-api-key"
|
||||
|
||||
# Model to use
|
||||
model = "gpt-3.5"
|
||||
model = "gpt-4o-mini"
|
||||
|
||||
#################################### Agent ###################################
|
||||
# Configuration for agents (group name starts with 'agent')
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
###
|
||||
FROM ubuntu:22.04 AS dind
|
||||
|
||||
# https://docs.docker.com/engine/install/ubuntu/
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
&& install -m 0755 -d /etc/apt/keyrings \
|
||||
&& curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc \
|
||||
&& chmod a+r /etc/apt/keyrings/docker.asc \
|
||||
&& echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
docker-ce \
|
||||
docker-ce-cli \
|
||||
containerd.io \
|
||||
docker-buildx-plugin \
|
||||
docker-compose-plugin \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get clean \
|
||||
&& apt-get autoremove -y
|
||||
|
||||
###
|
||||
FROM dind AS openhands
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
#
|
||||
RUN apt-get update && apt-get install -y \
|
||||
bash \
|
||||
build-essential \
|
||||
curl \
|
||||
git \
|
||||
git-lfs \
|
||||
software-properties-common \
|
||||
make \
|
||||
netcat \
|
||||
sudo \
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get clean \
|
||||
&& apt-get autoremove -y
|
||||
|
||||
# https://github.com/cli/cli/blob/trunk/docs/install_linux.md
|
||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
||||
&& apt-get update && apt-get -y install \
|
||||
gh \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get clean \
|
||||
&& apt-get autoremove -y
|
||||
|
||||
# Python 3.11
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y python3.11 python3.11-venv python3.11-dev python3-pip \
|
||||
&& ln -s /usr/bin/python3.11 /usr/bin/python
|
||||
|
||||
# NodeJS >= 18.17.1
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||
&& apt-get install -y nodejs
|
||||
|
||||
# Poetry >= 1.8
|
||||
RUN curl -fsSL https://install.python-poetry.org | python3.11 - \
|
||||
&& ln -s ~/.local/bin/poetry /usr/local/bin/poetry
|
||||
|
||||
#
|
||||
RUN <<EOF
|
||||
#!/bin/bash
|
||||
printf "#!/bin/bash
|
||||
set +x
|
||||
uname -a
|
||||
docker --version
|
||||
gh --version | head -n 1
|
||||
git --version
|
||||
#
|
||||
python --version
|
||||
echo node `node --version`
|
||||
echo npm `npm --version`
|
||||
poetry --version
|
||||
netcat -h 2>&1 | head -n 1
|
||||
" > /version.sh
|
||||
chmod a+x /version.sh
|
||||
EOF
|
||||
|
||||
###
|
||||
FROM openhands AS dev
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
dnsutils \
|
||||
file \
|
||||
iproute2 \
|
||||
jq \
|
||||
lsof \
|
||||
ripgrep \
|
||||
silversearcher-ag \
|
||||
vim \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& apt-get clean \
|
||||
&& apt-get autoremove -y
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# cache build dependencies
|
||||
RUN \
|
||||
--mount=type=bind,source=./,target=/app/ \
|
||||
<<EOF
|
||||
#!/bin/bash
|
||||
make -s clean
|
||||
make -s check-dependencies
|
||||
make -s install-python-dependencies
|
||||
|
||||
# NOTE
|
||||
# node_modules are .dockerignore-d therefore not mountable
|
||||
# make -s install-frontend-dependencies
|
||||
EOF
|
||||
|
||||
#
|
||||
CMD ["bash"]
|
||||
@@ -0,0 +1,54 @@
|
||||
# Develop in Docker
|
||||
|
||||
Install [Docker](https://docs.docker.com/engine/install/) on your host machine and run:
|
||||
|
||||
```bash
|
||||
make docker-dev
|
||||
# same as:
|
||||
cd ./containers/dev
|
||||
./dev.sh
|
||||
```
|
||||
|
||||
It could take some time if you are running for the first time as Docker will pull all the tools required for building OpenHands. The next time you run again, it should be instant.
|
||||
|
||||
## Build and run
|
||||
|
||||
If everything goes well, you should be inside a container after Docker finishes building the `openhands:dev` image similar to the following:
|
||||
|
||||
```bash
|
||||
Build and run in Docker ...
|
||||
root@93fc0005fcd2:/app#
|
||||
```
|
||||
|
||||
You may now proceed with the normal [build and run](../../Development.md) workflow as if you were on the host.
|
||||
|
||||
## Make changes
|
||||
|
||||
The source code on the host is mounted as `/app` inside docker. You may edit the files as usual either inside the Docker container or on your host with your favorite IDE/editors.
|
||||
|
||||
The following are also mapped as readonly from your host:
|
||||
|
||||
```yaml
|
||||
# host credentials
|
||||
- $HOME/.git-credentials:/root/.git-credentials:ro
|
||||
- $HOME/.gitconfig:/root/.gitconfig:ro
|
||||
- $HOME/.npmrc:/root/.npmrc:ro
|
||||
```
|
||||
|
||||
## VSCode
|
||||
|
||||
Alternatively, if you use VSCode, you could also [attach to the running container](https://code.visualstudio.com/docs/devcontainers/attach-container).
|
||||
|
||||
See details for [developing in docker](https://code.visualstudio.com/docs/devcontainers/containers) or simply ask `OpenHands` ;-)
|
||||
|
||||
## Rebuild dev image
|
||||
|
||||
You could optionally pass additional options to the build script.
|
||||
|
||||
```bash
|
||||
make docker-dev OPTIONS="--build"
|
||||
# or
|
||||
./containers/dev/dev.sh --build
|
||||
```
|
||||
|
||||
See [docker compose run](https://docs.docker.com/reference/cli/docker/compose/run/) for more options.
|
||||
@@ -0,0 +1,38 @@
|
||||
#
|
||||
services:
|
||||
dev:
|
||||
privileged: true
|
||||
build:
|
||||
context: ${OPENHANDS_WORKSPACE:-../../}
|
||||
dockerfile: ./containers/dev/Dockerfile
|
||||
image: openhands:dev
|
||||
container_name: openhands-dev
|
||||
environment:
|
||||
- BACKEND_HOST=${BACKEND_HOST:-"0.0.0.0"}
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.9-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${WORKSPACE_BASE:-$PWD/workspace}:/opt/workspace_base
|
||||
# source code
|
||||
- ${OPENHANDS_WORKSPACE:-../../}:/app
|
||||
# host credentials
|
||||
- $HOME/.git-credentials:/root/.git-credentials:ro
|
||||
- $HOME/.gitconfig:/root/.gitconfig:ro
|
||||
- $HOME/.npmrc:/root/.npmrc:ro
|
||||
# cache
|
||||
- cache-data:/root/.cache
|
||||
pull_policy: never
|
||||
stdin_open: true
|
||||
tty: true
|
||||
|
||||
##
|
||||
volumes:
|
||||
cache-data:
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
set -o pipefail
|
||||
|
||||
function get_docker() {
|
||||
echo "Docker is required to build and run OpenHands."
|
||||
echo "https://docs.docker.com/get-started/get-docker/"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function check_tools() {
|
||||
command -v docker &>/dev/null || get_docker
|
||||
}
|
||||
|
||||
function exit_if_indocker() {
|
||||
if [ -f /.dockerenv ]; then
|
||||
echo "Running inside a Docker container. Exiting..."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
exit_if_indocker
|
||||
|
||||
check_tools
|
||||
|
||||
##
|
||||
OPENHANDS_WORKSPACE=$(git rev-parse --show-toplevel)
|
||||
|
||||
cd "$OPENHANDS_WORKSPACE/containers/dev/" || exit 1
|
||||
|
||||
##
|
||||
export BACKEND_HOST="0.0.0.0"
|
||||
#
|
||||
export SANDBOX_USER_ID=$(id -u)
|
||||
export WORKSPACE_BASE=${WORKSPACE_BASE:-$OPENHANDS_WORKSPACE/workspace}
|
||||
|
||||
docker compose run --rm --service-ports "$@" dev
|
||||
|
||||
##
|
||||
@@ -1,11 +1,12 @@
|
||||
# Dynamic constructed Dockerfile
|
||||
# Dynamically constructed Dockerfile
|
||||
|
||||
This folder builds runtime image (sandbox), which will use a `Dockerfile` that is dynamically generated depends on the `base_image` AND a [Python source distribution](https://docs.python.org/3.10/distutils/sourcedist.html) that's based on the current commit of `openhands`.
|
||||
This folder builds a runtime image (sandbox), which will use a dynamically generated `Dockerfile`
|
||||
that depends on the `base_image` **AND** a [Python source distribution](https://docs.python.org/3.10/distutils/sourcedist.html) that is based on the current commit of `openhands`.
|
||||
|
||||
The following command will generate Dockerfile for `ubuntu:22.04` and the source distribution `.tar` into `containers/runtime`.
|
||||
The following command will generate a `Dockerfile` file for `nikolaik/python-nodejs:python3.11-nodejs22` (the default base image), an updated `config.sh` and the runtime source distribution files/folders into `containers/runtime`:
|
||||
|
||||
```bash
|
||||
poetry run python3 openhands/runtime/utils/runtime_build.py \
|
||||
--base_image ubuntu:22.04 \
|
||||
--base_image nikolaik/python-nodejs:python3.11-nodejs22 \
|
||||
--build_folder containers/runtime
|
||||
```
|
||||
|
||||
@@ -4,8 +4,8 @@ import { themes as prismThemes } from "prism-react-renderer";
|
||||
|
||||
const config: Config = {
|
||||
title: "OpenHands",
|
||||
tagline: "An Open Platform for AI Software Developers as Generalist Agents",
|
||||
favicon: "img/logo.png",
|
||||
tagline: "Code Less, Make More",
|
||||
favicon: "img/logo-square.png",
|
||||
|
||||
// Set the production url of your site here
|
||||
url: "https://docs.all-hands.dev",
|
||||
@@ -73,23 +73,28 @@ const config: Config = {
|
||||
type: "docSidebar",
|
||||
sidebarId: "docsSidebar",
|
||||
position: "left",
|
||||
label: "Docs",
|
||||
label: "User Guides",
|
||||
},
|
||||
{
|
||||
type: "docSidebar",
|
||||
sidebarId: "apiSidebar",
|
||||
position: "left",
|
||||
label: "Codebase",
|
||||
label: "Python API",
|
||||
},
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
href: "https://all-hands.dev",
|
||||
label: "Company",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
href: "https://github.com/All-Hands-AI/OpenHands",
|
||||
label: "GitHub",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
type: 'localeDropdown',
|
||||
position: 'left',
|
||||
},
|
||||
],
|
||||
},
|
||||
prism: {
|
||||
|
||||
@@ -59,10 +59,6 @@ Félicitations !
|
||||
|
||||
## Explication technique
|
||||
|
||||
Le code pertinent est défini dans [ssh_box.py](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/docker/ssh_box.py) et [image_agnostic_util.py](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/docker/image_agnostic_util.py).
|
||||
|
||||
En particulier, ssh_box.py vérifie l'objet config pour ```config.sandbox.base_container_image``` et ensuite tente de récupérer l'image à l'aide de [get_od_sandbox_image](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/docker/image_agnostic_util.py#L72), qui est défini dans image_agnostic_util.py.
|
||||
|
||||
Lorsqu'une image personnalisée est utilisée pour la première fois, elle ne sera pas trouvée et donc elle sera construite (à l'exécution ultérieure, l'image construite sera trouvée et renvoyée).
|
||||
|
||||
L'image personnalisée est construite avec [_build_sandbox_image()](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/runtime/docker/image_agnostic_util.py#L29), qui crée un fichier docker en utilisant votre image personnalisée comme base et configure ensuite l'environnement pour OpenHands, comme ceci:
|
||||
|
||||
@@ -41,4 +41,4 @@ ne peut être aussi puissant que les modèles qui le pilotent -- heureusement, l
|
||||
|
||||
Certains LLM ont des limites de taux et peuvent nécessiter des réessais. OpenHands réessaiera automatiquement les demandes s'il reçoit une erreur 429 ou une erreur de connexion API.
|
||||
Vous pouvez définir les variables d'environnement `LLM_NUM_RETRIES`, `LLM_RETRY_MIN_WAIT`, `LLM_RETRY_MAX_WAIT` pour contrôler le nombre de réessais et le temps entre les réessais.
|
||||
Par défaut, `LLM_NUM_RETRIES` est 5 et `LLM_RETRY_MIN_WAIT`, `LLM_RETRY_MAX_WAIT` sont respectivement de 3 secondes et 60 secondes.
|
||||
Par défaut, `LLM_NUM_RETRIES` est 8 et `LLM_RETRY_MIN_WAIT`, `LLM_RETRY_MAX_WAIT` sont respectivement de 15 secondes et 120 secondes.
|
||||
|
||||
@@ -43,4 +43,4 @@ OpenHands 将向你配置的 LLM 发出许多提示。大多数这些 LLM 都是
|
||||
|
||||
一些 LLM 有速率限制,可能需要重试操作。OpenHands 会在收到 429 错误或 API 连接错误时自动重试请求。
|
||||
你可以设置 `LLM_NUM_RETRIES`,`LLM_RETRY_MIN_WAIT`,`LLM_RETRY_MAX_WAIT` 环境变量来控制重试次数和重试之间的时间。
|
||||
默认情况下,`LLM_NUM_RETRIES` 为 5,`LLM_RETRY_MIN_WAIT` 和 `LLM_RETRY_MAX_WAIT` 分别为 3 秒和 60 秒。
|
||||
默认情况下,`LLM_NUM_RETRIES` 为 8,`LLM_RETRY_MIN_WAIT` 和 `LLM_RETRY_MAX_WAIT` 分别为 15 秒和 120 秒。
|
||||
|
||||
@@ -74,7 +74,7 @@ WORKSPACE_DIR="$(pwd)/workspace"
|
||||
|
||||
如有需要,可以替换您选择的 `LLM_MODEL`。
|
||||
|
||||
完成!现在您可以通过 `make run` 启动 Devin 而无需 Docker。现在您应该可以连接到 `http://localhost:3000/`
|
||||
完成!现在您可以通过 `make run` 启动 OpenHands 而无需 Docker。现在您应该可以连接到 `http://localhost:3000/`
|
||||
|
||||
## 选择您的模型
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ graph TD
|
||||
```
|
||||
|
||||
1. User Input: The user provides a custom base Docker image
|
||||
2. Image Building: OpenHands builds a new Docker image (the "OD 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 OD runtime 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
|
||||
6. Action Execution: The runtime client receives actions from the backend, executes them in the sandboxed environment, and sends back observations
|
||||
@@ -62,7 +62,7 @@ The role of the client:
|
||||
- It formats and returns observations to the backend, ensuring a consistent interface for processing results
|
||||
|
||||
|
||||
## How OpenHands builds and maintains OD Runtime images
|
||||
## How OpenHands builds and maintains OH Runtime images
|
||||
|
||||
OpenHands' approach to building and managing runtime images ensures efficiency, consistency, and flexibility in creating and maintaining Docker images for both production and development environments.
|
||||
|
||||
@@ -80,9 +80,9 @@ OpenHands uses a dual-tagging system for its runtime images to balance reproduci
|
||||
- This ensures reproducibility; the same hash always means the same image contents
|
||||
|
||||
2. Generic tag: `{target_image_repo}:{target_image_tag}`.
|
||||
Example: `runtime:od_v0.8.3_ubuntu_tag_22.04`
|
||||
Example: `runtime:oh_v0.9.3_ubuntu_tag_22.04`
|
||||
|
||||
- This tag follows the format: `runtime:od_v{OD_VERSION}_{BASE_IMAGE_NAME}_tag_{BASE_IMAGE_TAG}`
|
||||
- This tag follows the format: `runtime:oh_v{OH_VERSION}_{BASE_IMAGE_NAME}_tag_{BASE_IMAGE_TAG}`
|
||||
- It represents the latest build for a particular base image and OpenHands version combination
|
||||
- This tag is updated whenever a new image is built from the same base image, even if the source code changes
|
||||
|
||||
@@ -94,11 +94,11 @@ The hash-based tag ensures reproducibility, while the generic tag provides a sta
|
||||
- Hash-based tag: `{target_image_repo}:{target_image_hash_tag}`.
|
||||
Example: `runtime:abc123def456`
|
||||
- Generic tag: `{target_image_repo}:{target_image_tag}`.
|
||||
Example: `runtime:od_v0.8.3_ubuntu_tag_22.04`
|
||||
Example: `runtime:oh_v0.9.3_ubuntu_tag_22.04`
|
||||
|
||||
2. Build Process:
|
||||
- a. Convert the base image name to an OD runtime image name
|
||||
Example: `ubuntu:22.04` -> `runtime:od_v0.8.3_ubuntu_tag_22.04`
|
||||
- a. Convert the base image name to an OH runtime image name
|
||||
Example: `ubuntu:22.04` -> `runtime:oh_v0.9.3_ubuntu_tag_22.04`
|
||||
- b. Generate a build context (Dockerfile and OpenHands source code) and calculate its hash
|
||||
- c. Check for an existing image with the calculated hash
|
||||
- d. If not found, check for a recent compatible image to use as a base
|
||||
@@ -108,7 +108,7 @@ The hash-based tag ensures reproducibility, while the generic tag provides a sta
|
||||
3. Image Reuse and Rebuilding Logic:
|
||||
The system follows these steps to determine whether to build a new image or use an existing one from a user-provided (base) image (e.g., `ubuntu:22.04`):
|
||||
- a. If an image exists with the same hash (e.g., `runtime:abc123def456`), it will be reused as is
|
||||
- b. If the exact hash is not found, the system will try to rebuild using the latest generic image (e.g., `runtime:od_v0.8.3_ubuntu_tag_22.04`) as a base. This saves time by leveraging existing dependencies
|
||||
- b. If the exact hash is not found, the system will try to rebuild using the latest generic image (e.g., `runtime:oh_v0.9.3_ubuntu_tag_22.04`) as a base. This saves time by leveraging existing dependencies
|
||||
- c. If neither the hash-tagged nor the generic-tagged image is found, the system will build the image completely from scratch
|
||||
|
||||
4. Caching and Efficiency:
|
||||
@@ -121,10 +121,10 @@ Here's a flowchart illustrating the build process:
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Start] --> B{Convert base image name}
|
||||
B --> |ubuntu:22.04 -> runtime:od_v0.8.3_ubuntu_tag_22.04| C[Generate build context and hash]
|
||||
B --> |ubuntu:22.04 -> runtime:oh_v0.9.3_ubuntu_tag_22.04| C[Generate build context and hash]
|
||||
C --> D{Check for existing image with hash}
|
||||
D -->|Found runtime:abc123def456| E[Use existing image]
|
||||
D -->|Not found| F{Check for runtime:od_v0.8.3_ubuntu_tag_22.04}
|
||||
D -->|Not found| F{Check for runtime:oh_v0.9.3_ubuntu_tag_22.04}
|
||||
F -->|Found| G[Rebuild based on recent image]
|
||||
F -->|Not found| H[Build from scratch]
|
||||
G --> I[Tag with hash and generic tags]
|
||||
@@ -137,13 +137,13 @@ This approach ensures that:
|
||||
|
||||
1. Identical source code and Dockerfile always produce the same image (via hash-based tags)
|
||||
2. The system can quickly rebuild images when minor changes occur (by leveraging recent compatible images)
|
||||
3. The generic tag (e.g., `runtime:od_v0.8.3_ubuntu_tag_22.04`) always points to the latest build for a particular base image and OpenHands version combination
|
||||
3. The generic tag (e.g., `runtime:oh_v0.9.3_ubuntu_tag_22.04`) always points to the latest build for a particular base image and OpenHands version combination
|
||||
|
||||
## Runtime Plugin System
|
||||
|
||||
The OpenHands Runtime supports a plugin system that allows for extending functionality and customizing the runtime environment. Plugins are initialized when the runtime client starts up.
|
||||
|
||||
Check [an example of Jupyter plugin here](https://github.com/All-Hands-AI/OpenHands/blob/9c44d94cef32e6426ebd8deeeb52963153b2348a/openhands/runtime/plugins/jupyter/__init__.py#L30-L63) if you want to implement your own plugin.
|
||||
Check [an example of Jupyter plugin here](https://github.com/All-Hands-AI/OpenHands/blob/ecf4aed28b0cf7c18d4d8ff554883ba182fc6bdd/openhands/runtime/plugins/jupyter/__init__.py#L21-L55) if you want to implement your own plugin.
|
||||
|
||||
*More details about the Plugin system are still under construction - contributions are welcomed!*
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
## System Requirements
|
||||
|
||||
* Docker version 26.0.0+ or Docker Desktop 4.31.0+
|
||||
* You must be using Linux or Mac OS
|
||||
* If you are on Windows, you must use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install)
|
||||
|
||||
## Installation
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
export WORKSPACE_BASE=$(pwd)/workspace
|
||||
|
||||
docker run -it --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.9-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-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.9
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
## Setup
|
||||
|
||||
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.
|
||||
|
||||
If the required `LLM Model` does not exist in the list, you can toggle `Advanced Options` and manually enter it with the correct prefix
|
||||
in the `Custom Model` text box.
|
||||
The `Advanced Options` also allow you to specify a `Base URL` if required.
|
||||
|
||||
<div style={{ display: 'flex', justifyContent: 'center', gap: '20px' }}>
|
||||
<img src="/img/settings-screenshot.png" alt="settings-modal" width="340" />
|
||||
<img src="/img/settings-advanced.png" alt="settings-modal" width="335" />
|
||||
</div>
|
||||
|
||||
## Versions
|
||||
|
||||
The command above pulls the `0.9` tag, which represents 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.
|
||||
- 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.
|
||||
|
||||
You can choose the tag that best suits your needs based on stability requirements and desired features.
|
||||
|
||||
For the development workflow, see [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
|
||||
Are you having trouble? Check out our [Troubleshooting Guide](https://docs.all-hands.dev/modules/usage/troubleshooting).
|
||||
@@ -0,0 +1,108 @@
|
||||
# CLI Mode
|
||||
|
||||
OpenHands can be run in an interactive CLI mode, which allows users to start an interactive session via the command line.
|
||||
|
||||
This mode is different from the [headless mode](headless-mode), which is non-interactive and better for scripting.
|
||||
|
||||
## With Python
|
||||
|
||||
To start an interactive OpenHands session via the command line, follow these steps:
|
||||
|
||||
1. Ensure you have followed the [Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md)
|
||||
|
||||
2. Run the following command:
|
||||
|
||||
```bash
|
||||
poetry run python -m openhands.core.cli
|
||||
```
|
||||
|
||||
This command will start an interactive session where you can input tasks and receive responses from OpenHands.
|
||||
|
||||
You'll need to be sure to set your model, API key, and other settings via environment variables
|
||||
[or the `config.toml` file](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml).
|
||||
|
||||
|
||||
## With Docker
|
||||
|
||||
To run OpenHands in CLI mode with Docker, follow these steps:
|
||||
|
||||
1. Set `WORKSPACE_BASE` to the directory you want OpenHands to edit:
|
||||
|
||||
```bash
|
||||
WORKSPACE_BASE=$(pwd)/workspace
|
||||
```
|
||||
|
||||
2. Set `LLM_MODEL` to the model you want to use:
|
||||
|
||||
```bash
|
||||
LLM_MODEL="anthropic/claude-3-5-sonnet-20240620"
|
||||
```
|
||||
|
||||
3. Set `LLM_API_KEY` to your API key:
|
||||
|
||||
```bash
|
||||
LLM_API_KEY="sk_test_12345"
|
||||
```
|
||||
|
||||
4. Run the following Docker command:
|
||||
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
-e LLM_MODEL=$LLM_MODEL \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-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.9 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
This command will start an interactive session in Docker where you can input tasks and receive responses from OpenHands.
|
||||
|
||||
## Examples of CLI Commands and Expected Outputs
|
||||
|
||||
Here are some examples of CLI commands and their expected outputs:
|
||||
|
||||
### Example 1: Simple Task
|
||||
|
||||
```bash
|
||||
How can I help? >> Write a Python script that prints "Hello, World!"
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
|
||||
```bash
|
||||
🤖 Sure! Here is a Python script that prints "Hello, World!":
|
||||
|
||||
❯ print("Hello, World!")
|
||||
```
|
||||
|
||||
### Example 2: Bash Command
|
||||
|
||||
```bash
|
||||
How can I help? >> Create a directory named "test_dir"
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
|
||||
```bash
|
||||
🤖 Creating a directory named "test_dir":
|
||||
|
||||
❯ mkdir test_dir
|
||||
```
|
||||
|
||||
### Example 3: Error Handling
|
||||
|
||||
```bash
|
||||
How can I help? >> Delete a non-existent file
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
|
||||
```bash
|
||||
🤖 An error occurred. Please try again.
|
||||
```
|
||||
@@ -1,26 +1,19 @@
|
||||
# Create and Use a Custom Docker Sandbox
|
||||
# Custom Sandbox
|
||||
|
||||
The default OpenHands sandbox comes with a [minimal ubuntu configuration](https://github.com/All-Hands-AI/OpenHands/blob/main/containers/sandbox/Dockerfile).
|
||||
Your use case may need additional software installed by default.
|
||||
The sandbox is where the agent does its work. Instead of running commands directly on your computer
|
||||
(which could be dangerous), the agent runs them inside of a Docker container.
|
||||
|
||||
The default OpenHands sandbox (`python-nodejs:python3.11-nodejs22`
|
||||
from [nikolaik/python-nodejs](https://hub.docker.com/r/nikolaik/python-nodejs)) comes with some packages installed such
|
||||
as python and Node.js but your use case may need additional software installed by default.
|
||||
|
||||
There are two ways you can do so:
|
||||
|
||||
1. Use an existing image from docker hub. For instance, if you want to have `nodejs` installed, you can do so by using the `node:20` image
|
||||
1. Use an existing image from docker hub
|
||||
2. Creating your own custom docker image and using it
|
||||
|
||||
If you want to take the first approach, you can skip the `Create Your Docker Image` section.
|
||||
|
||||
For a more feature-rich environment, you might consider using pre-built images like **[nikolaik/python-nodejs](https://hub.docker.com/r/nikolaik/python-nodejs)**, which comes with both Python and Node.js pre-installed, along with many other useful tools and libraries, like:
|
||||
|
||||
- Node.js: 22.x
|
||||
- npm: 10.x
|
||||
- yarn: stable
|
||||
- Python: latest
|
||||
- pip: latest
|
||||
- pipenv: latest
|
||||
- poetry: latest
|
||||
- uv: latest
|
||||
|
||||
## Setup
|
||||
|
||||
Make sure you are able to run OpenHands using the [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md) first.
|
||||
@@ -79,7 +72,7 @@ Run OpenHands by running ```make run``` in the top level directory.
|
||||
|
||||
Navigate to ```localhost:3001``` and check if your desired dependencies are available.
|
||||
|
||||
In the case of the example above, running ```node -v``` in the terminal produces ```v20.15.0```
|
||||
In the case of the example above, running ```node -v``` in the terminal produces ```v20.15.0```.
|
||||
|
||||
Congratulations!
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Contribute to OpenHands Evaluation Harness
|
||||
# Evaluation
|
||||
|
||||
This guide provides an overview of how to integrate your own evaluation benchmark into the OpenHands framework.
|
||||
|
||||
@@ -12,7 +12,7 @@ Here's an example configuration file you can use to define and use multiple LLMs
|
||||
```toml
|
||||
[llm]
|
||||
# IMPORTANT: add your API key here, and set the model to the one you want to evaluate
|
||||
model = "gpt-4o-2024-05-13"
|
||||
model = "claude-3-5-sonnet-20240620"
|
||||
api_key = "sk-XXX"
|
||||
|
||||
[llm.eval_gpt4_1106_preview_llm]
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# Running in Headless / CLI Mode
|
||||
# Headless Mode
|
||||
|
||||
You can run OpenHands via a CLI, without starting the web application. This makes it easy
|
||||
to automate tasks with OpenHands. There are 2 main modes of operation:
|
||||
You can run OpenHands with a single command, without starting the web application.
|
||||
This makes it easy to write scripts and automate tasks with OpenHands.
|
||||
|
||||
* **Headless** : Designed for use with scripts
|
||||
* **CLI** : Designed for interactive use via a console
|
||||
|
||||
As with other modes, the environment is configurable via environment variables or by saving values into [config.toml](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml)
|
||||
This is different from [CLI Mode](cli-mode), which is interactive, and better for active development.
|
||||
|
||||
## With Python
|
||||
|
||||
@@ -14,34 +11,36 @@ To run OpenHands in headless mode with Python,
|
||||
[follow the Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md),
|
||||
and then run:
|
||||
|
||||
### Headless with Python
|
||||
|
||||
```bash
|
||||
poetry run python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
### CLI with Python
|
||||
You'll need to be sure to set your model, API key, and other settings via environment variables
|
||||
[or the `config.toml` file](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml).
|
||||
|
||||
## With Docker
|
||||
|
||||
1. Set `WORKSPACE_BASE` to the directory you want OpenHands to edit:
|
||||
|
||||
```bash
|
||||
poetry run python -m openhands.core.cli
|
||||
|
||||
How can I help? >> write a bash script that prints hi
|
||||
WORKSPACE_BASE=$(pwd)/workspace
|
||||
```
|
||||
|
||||
## Headless With Docker
|
||||
|
||||
To run OpenHands in headless mode with Docker, run:
|
||||
2. Set `LLM_MODEL` to the model you want to use:
|
||||
|
||||
```bash
|
||||
# Set WORKSPACE_BASE to the directory you want OpenHands to edit
|
||||
WORKSPACE_BASE=$(pwd)/workspace
|
||||
LLM_MODEL="anthropic/claude-3-5-sonnet-20240620"
|
||||
```
|
||||
|
||||
# Set LLM_API_KEY to an API key, e.g. for OpenAI or Anthropic
|
||||
LLM_API_KEY="abcde"
|
||||
3. Set `LLM_API_KEY` to your API key:
|
||||
|
||||
# Set LLM_MODEL to the model you want to use
|
||||
LLM_MODEL="gpt-4o"
|
||||
```bash
|
||||
LLM_API_KEY="sk_test_12345"
|
||||
```
|
||||
|
||||
4. Run the following Docker command:
|
||||
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
@@ -53,34 +52,5 @@ docker run -it \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
ghcr.io/all-hands-ai/openhands:0.9 \
|
||||
poetry run python -m openhands.core.main \
|
||||
-t "Write a bash script that prints Hello World"
|
||||
```
|
||||
|
||||
## CLI With Docker
|
||||
|
||||
To run OpenHands in cli mode with Docker, run:
|
||||
|
||||
```bash
|
||||
# Set WORKSPACE_BASE to the directory you want OpenHands to edit
|
||||
WORKSPACE_BASE=$(pwd)/workspace
|
||||
|
||||
# Set LLM_API_KEY to an API key, e.g. for OpenAI or Anthropic
|
||||
LLM_API_KEY="abcde"
|
||||
|
||||
# Set LLM_MODEL to the model you want to use
|
||||
LLM_MODEL="gpt-4o"
|
||||
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
-e LLM_MODEL=$LLM_MODEL \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-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.9 \
|
||||
poetry run python -m openhands.core.cli
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
sidebar_position: 6
|
||||
---
|
||||
|
||||
# 🔎 How To Section
|
||||
@@ -1,6 +1,6 @@
|
||||
# Use OpenHands in OpenShift/K8S
|
||||
# Kubernetes
|
||||
|
||||
There are different ways this can be accomplished. This guide goes through one possible way:
|
||||
There are different ways you might run OpenHands on Kubernetes or OpenShift. This guide goes through one possible way:
|
||||
1. Create a PV "as a cluster admin" to map workspace_base data and docker directory to the pod through the worker node
|
||||
2. Create a PVC to be able to mount those PVs to the pod
|
||||
3. Create a pod which contains two containers; the OpenHands and Sandbox containers
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 💻 OpenHands
|
||||
|
||||
OpenHands is an **autonomous AI software engineer** capable of executing complex engineering tasks and collaborating actively with users on software development projects.
|
||||
This project is fully open-source, so you can use and modify it however you like.
|
||||
|
||||
:::tip
|
||||
Explore the codebase of OpenHands on [GitHub](https://github.com/All-Hands-AI/OpenHands) or join one of our communities!
|
||||
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/graphs/contributors">
|
||||
<img
|
||||
src="https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge"
|
||||
alt="Contributors"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/network/members">
|
||||
<img
|
||||
src="https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge"
|
||||
alt="Forks"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers">
|
||||
<img
|
||||
src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge"
|
||||
alt="Stargazers"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/issues">
|
||||
<img
|
||||
src="https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge"
|
||||
alt="Issues"
|
||||
/>
|
||||
</a>
|
||||
<br></br>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE">
|
||||
<img
|
||||
src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge"
|
||||
alt="MIT License"
|
||||
/>
|
||||
</a>
|
||||
<br></br>
|
||||
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge"
|
||||
alt="Join our Slack community"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://discord.gg/ESHStjSjD4">
|
||||
<img
|
||||
src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge"
|
||||
alt="Join our Discord community"
|
||||
/>
|
||||
</a>
|
||||
:::
|
||||
|
||||
## 🛠️ Getting Started
|
||||
|
||||
[Check out the getting started guide on Github](https://github.com/All-Hands-AI/OpenHands?tab=readme-ov-file#-getting-started)
|
||||
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[contributors-url]: https://github.com/All-Hands-AI/OpenHands/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[forks-url]: https://github.com/All-Hands-AI/OpenHands/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[stars-url]: https://github.com/All-Hands-AI/OpenHands/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[issues-url]: https://github.com/All-Hands-AI/OpenHands/issues
|
||||
[license-shield]: https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge
|
||||
[license-url]: https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE
|
||||
@@ -1,55 +1,46 @@
|
||||
# Azure OpenAI LLM
|
||||
# Azure
|
||||
|
||||
## Completion
|
||||
OpenHands uses LiteLLM to make calls to Azure's chat models. You can find their documentation on using Azure as a provider [here](https://docs.litellm.ai/docs/providers/azure).
|
||||
|
||||
OpenHands uses LiteLLM for completion calls. You can find their documentation on Azure [here](https://docs.litellm.ai/docs/providers/azure)
|
||||
## Azure OpenAI Configuration
|
||||
|
||||
### Azure openai configs
|
||||
|
||||
When running the OpenHands Docker image, you'll need to set the following environment variables using `-e`:
|
||||
When running OpenHands, you'll need to set the following environment variable using `-e` in the
|
||||
[docker run command](/modules/usage/getting-started#installation):
|
||||
|
||||
```
|
||||
LLM_BASE_URL="<azure-api-base-url>" # e.g. "https://openai-gpt-4-test-v-1.openai.azure.com/"
|
||||
LLM_API_KEY="<azure-api-key>"
|
||||
LLM_MODEL="azure/<your-gpt-deployment-name>"
|
||||
LLM_API_VERSION="<api-version>" # e.g. "2024-02-15-preview"
|
||||
LLM_API_VERSION="<api-version>" # e.g. "2023-05-15"
|
||||
```
|
||||
|
||||
Example:
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_BASE_URL="x.openai.azure.com" \
|
||||
-e LLM_API_VERSION="2024-02-15-preview" \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-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:main
|
||||
docker run -it --pull=always \
|
||||
-e LLM_API_VERSION="2023-05-15"
|
||||
...
|
||||
```
|
||||
|
||||
You can set the LLM_MODEL and LLM_API_KEY in the OpenHands UI itself.
|
||||
Then set the following in the OpenHands UI through the Settings:
|
||||
|
||||
:::note
|
||||
You can find your ChatGPT deployment name on the deployments page in Azure. It could be the same with the chat model name (e.g. 'GPT4-1106-preview'), by default or initially set, but it doesn't have to be the same. Run openhands, and when you load it in the browser, go to Settings and set model as above: "azure/<your-actual-gpt-deployment-name>". If it's not in the list, enter your own text and save it.
|
||||
You will need your ChatGPT deployment name which can be found on the deployments page in Azure. This is referenced as
|
||||
<deployment-name> below.
|
||||
:::
|
||||
|
||||
* Enable `Advanced Options`
|
||||
* `Custom Model` to azure/<deployment-name>
|
||||
* `Base URL` to your Azure API Base URL (e.g. `https://example-endpoint.openai.azure.com`)
|
||||
* `API Key` to your Azure API key
|
||||
|
||||
## Embeddings
|
||||
|
||||
OpenHands uses llama-index for embeddings. You can find their documentation on Azure [here](https://docs.llamaindex.ai/en/stable/api_reference/embeddings/azure_openai/)
|
||||
OpenHands uses llama-index for embeddings. You can find their documentation on Azure [here](https://docs.llamaindex.ai/en/stable/api_reference/embeddings/azure_openai/).
|
||||
|
||||
### Azure openai configs
|
||||
### Azure OpenAI Configuration
|
||||
|
||||
The model used for Azure OpenAI embeddings is "text-embedding-ada-002".
|
||||
You need the correct deployment name for this model in your Azure account.
|
||||
|
||||
When running OpenHands in Docker, set the following environment variables using `-e`:
|
||||
When running OpenHands, set the following environment variables using `-e` in the
|
||||
[docker run command](/modules/usage/getting-started#installation):
|
||||
|
||||
```
|
||||
LLM_EMBEDDING_MODEL="azureopenai"
|
||||
LLM_EMBEDDING_DEPLOYMENT_NAME="<your-embedding-deployment-name>" # e.g. "TextEmbedding...<etc>"
|
||||
LLM_API_VERSION="<api-version>" # e.g. "2024-02-15-preview"
|
||||
LLM_EMBEDDING_DEPLOYMENT_NAME="<your-embedding-deployment-name>" # e.g. "TextEmbedding...<etc>"
|
||||
LLM_API_VERSION="<api-version>" # e.g. "2024-02-15-preview"
|
||||
```
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
# Google Gemini/Vertex LLM
|
||||
# Google Gemini/Vertex
|
||||
|
||||
## Completion
|
||||
|
||||
OpenHands uses LiteLLM for completion calls. The following resources are relevant for using OpenHands with Google's LLMs
|
||||
OpenHands uses LiteLLM to make calls to Google's chat models. You can find their documentation on using Google as a provider:
|
||||
|
||||
- [Gemini - Google AI Studio](https://docs.litellm.ai/docs/providers/gemini)
|
||||
- [VertexAI - Google Cloud Platform](https://docs.litellm.ai/docs/providers/vertex)
|
||||
|
||||
### Gemini - Google AI Studio Configs
|
||||
## Gemini - Google AI Studio Configs
|
||||
|
||||
To use Gemini through Google AI Studio when running the OpenHands Docker image, you'll need to set the following environment variables using `-e`:
|
||||
When running OpenHands, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `Gemini`
|
||||
* `LLM Model` to the model you will be using.
|
||||
If the model is not in the list, toggle `Advanced Options`, and enter it in `Custom Model` (e.g. gemini/<model-name> like `gemini/gemini-1.5-pro`).
|
||||
* `API Key` to your Gemini API key
|
||||
|
||||
```
|
||||
GEMINI_API_KEY="<your-google-api-key>"
|
||||
LLM_MODEL="gemini/gemini-1.5-pro"
|
||||
```
|
||||
## VertexAI - Google Cloud Platform Configs
|
||||
|
||||
### Vertex AI - Google Cloud Platform Configs
|
||||
|
||||
To use Vertex AI through Google Cloud Platform when running the OpenHands Docker image, you'll need to set the following environment variables using `-e`:
|
||||
To use Vertex AI through Google Cloud Platform when running OpenHands, you'll need to set the following environment
|
||||
variables using `-e` in the [docker run command](/modules/usage/getting-started#installation):
|
||||
|
||||
```
|
||||
GOOGLE_APPLICATION_CREDENTIALS="<json-dump-of-gcp-service-account-json>"
|
||||
VERTEXAI_PROJECT="<your-gcp-project-id>"
|
||||
VERTEXAI_LOCATION="<your-gcp-location>"
|
||||
LLM_MODEL="vertex_ai/<desired-llm-model>"
|
||||
```
|
||||
|
||||
Then set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `VertexAI`
|
||||
* `LLM Model` to the model you will be using.
|
||||
If the model is not in the list, toggle `Advanced Options`, and enter it in `Custom Model` (e.g. vertex_ai/<model-name>).
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Groq
|
||||
|
||||
OpenHands uses LiteLLM to make calls to chat models on Groq. You can find their documentation on using Groq as a provider [here](https://docs.litellm.ai/docs/providers/groq).
|
||||
|
||||
## Configuration
|
||||
|
||||
When running OpenHands, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `Groq`
|
||||
* `LLM Model` to the model you will be using. [Visit here to see the list of
|
||||
models that Groq hosts](https://console.groq.com/docs/models). If the model is not in the list, toggle
|
||||
`Advanced Options`, and enter it in `Custom Model` (e.g. groq/<model-name> like `groq/llama3-70b-8192`)
|
||||
* `API key` to your Groq API key. To find or create your Groq API Key, [see here](https://console.groq.com/keys)
|
||||
|
||||
|
||||
|
||||
## Using Groq as an OpenAI-Compatible Endpoint
|
||||
|
||||
The Groq endpoint for chat completion is [mostly OpenAI-compatible](https://console.groq.com/docs/openai). Therefore, you can access Groq models as you
|
||||
would access any OpenAI-compatible endpoint. You can set the following in the OpenHands UI through the Settings:
|
||||
* Enable `Advanced Options`
|
||||
* `Custom Model` to the prefix `openai/` + the model you will be using (e.g. `openai/llama3-70b-8192`)
|
||||
* `Base URL` to `https://api.groq.com/openai/v1`
|
||||
* `API Key` to your Groq API key
|
||||
@@ -1,57 +1,67 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# 🤖 LLM Backends
|
||||
|
||||
OpenHands can connect to any LLM supported by LiteLLM. However, it requires a powerful model to work.
|
||||
The following are verified by the community to work with OpenHands:
|
||||
|
||||
* claude-3-5-sonnet
|
||||
* gemini-1.5-pro / gemini-1.5-flash
|
||||
* gpt-4 / gpt-4o
|
||||
* llama-3.1-405b / hermes-3-llama-3.1-405b
|
||||
* wizardlm-2-8x22b
|
||||
|
||||
:::warning
|
||||
OpenHands will issue many prompts to the LLM you configure. Most of these LLMs cost money, so be sure to set spending
|
||||
limits and monitor usage.
|
||||
:::
|
||||
|
||||
If you have successfully run OpenHands with specific LLMs not in the list, please add them to the verified list. We
|
||||
also encourage you to open a PR to share your setup process to help others using the same provider and LLM!
|
||||
|
||||
For a full list of the providers and models available, please consult the
|
||||
[litellm documentation](https://docs.litellm.ai/docs/providers).
|
||||
|
||||
## Local and Open Source Models
|
||||
:::note
|
||||
Most current local and open source models are not as powerful. When using such models, you may see long
|
||||
wait times between messages, poor responses, or errors about malformed JSON. OpenHands can only be as powerful as the
|
||||
models driving it. However, if you do find ones that work, please add them to the verified list above.
|
||||
|
||||
## LLM Configuration
|
||||
:::warning
|
||||
OpenHands will issue many prompts to the LLM you configure. Most of these LLMs cost money, so be sure to set spending
|
||||
limits and monitor usage.
|
||||
:::
|
||||
|
||||
The `LLM_MODEL` environment variable controls which model is used in programmatic interactions.
|
||||
But when using the OpenHands UI, you'll need to choose your model in the settings window.
|
||||
## LLM Configuration
|
||||
|
||||
The following environment variables might be necessary for some LLMs/providers:
|
||||
The following can be set in the OpenHands UI through the Settings:
|
||||
* `LLM Provider`
|
||||
* `LLM Model`
|
||||
* `API Key`
|
||||
* `Base URL` (through `Advanced Settings`)
|
||||
|
||||
- `LLM_API_KEY`
|
||||
- `LLM_BASE_URL`
|
||||
- `LLM_EMBEDDING_MODEL`
|
||||
- `LLM_EMBEDDING_DEPLOYMENT_NAME`
|
||||
- `LLM_API_VERSION`
|
||||
- `LLM_DROP_PARAMS`
|
||||
There are some settings that may be necessary for some LLMs/providers that cannot be set through the UI. Instead, these
|
||||
can be set through environment variables passed to the [docker run command](/modules/usage/getting-started#installation)
|
||||
using `-e`:
|
||||
|
||||
* `LLM_API_VERSION`
|
||||
* `LLM_EMBEDDING_MODEL`
|
||||
* `LLM_EMBEDDING_DEPLOYMENT_NAME`
|
||||
* `LLM_DROP_PARAMS`
|
||||
* `LLM_DISABLE_VISION`
|
||||
* `LLM_CACHING_PROMPT`
|
||||
|
||||
We have a few guides for running OpenHands with specific model providers:
|
||||
|
||||
- [OpenAI](llms/openai-llms)
|
||||
- [ollama](llms/local-llms)
|
||||
- [Azure](llms/azure-llms)
|
||||
- [Google](llms/google-llms)
|
||||
* [Azure](llms/azure-llms)
|
||||
* [Google](llms/google-llms)
|
||||
* [Groq](llms/groq)
|
||||
* [OpenAI](llms/openai-llms)
|
||||
* [OpenRouter](llms/openrouter)
|
||||
|
||||
### API retries and rate limits
|
||||
|
||||
Some LLMs have rate limits and may require retries. OpenHands will automatically retry requests if it receives a 429 error or API connection error.
|
||||
You can set the following environment variables to control the number of retries and the time between retries:
|
||||
* `LLM_NUM_RETRIES` (Default of 5)
|
||||
* `LLM_RETRY_MIN_WAIT` (Default of 3 seconds)
|
||||
* `LLM_RETRY_MAX_WAIT` (Default of 60 seconds)
|
||||
|
||||
* `LLM_NUM_RETRIES` (Default of 8)
|
||||
* `LLM_RETRY_MIN_WAIT` (Default of 15 seconds)
|
||||
* `LLM_RETRY_MAX_WAIT` (Default of 120 seconds)
|
||||
|
||||
@@ -5,7 +5,7 @@ When using a Local LLM, OpenHands may have limited functionality.
|
||||
:::
|
||||
|
||||
Ensure that you have the Ollama server up and running.
|
||||
For detailed startup instructions, refer to [here](https://github.com/ollama/ollama)
|
||||
For detailed startup instructions, refer to [here](https://github.com/ollama/ollama).
|
||||
|
||||
This guide assumes you've started ollama with `ollama serve`. If you're running ollama differently (e.g. inside docker), the instructions might need to be modified. Please note that if you're running WSL the default ollama configuration blocks requests from docker containers. See [here](#configuring-ollama-service-wsl-en).
|
||||
|
||||
@@ -28,17 +28,14 @@ mistral:7b-instruct-v0.2-q4_K_M eb14864c7427 4.4 GB 2 weeks ago
|
||||
starcoder2:latest f67ae0f64584 1.7 GB 19 hours ago
|
||||
```
|
||||
|
||||
## Start OpenHands
|
||||
## Run OpenHands with Docker
|
||||
|
||||
### Docker
|
||||
|
||||
Use the instructions [here](../intro) to start OpenHands using Docker.
|
||||
### Start OpenHands
|
||||
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_API_KEY="ollama" \
|
||||
-e LLM_BASE_URL="http://host.docker.internal:11434" \
|
||||
-e LLM_OLLAMA_BASE_URL="http://host.docker.internal:11434" \
|
||||
```
|
||||
|
||||
@@ -55,8 +52,6 @@ docker run \
|
||||
--pull=always \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e LLM_API_KEY="ollama" \
|
||||
-e LLM_BASE_URL="http://host.docker.internal:11434" \
|
||||
-e LLM_OLLAMA_BASE_URL="http://host.docker.internal:11434" \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
@@ -67,6 +62,16 @@ docker run \
|
||||
|
||||
You should now be able to connect to `http://localhost:3000/`
|
||||
|
||||
### Configure the Web Application
|
||||
|
||||
When running `openhands`, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
- the model to "ollama/<model-name>"
|
||||
- the base url to `http://host.docker.internal:11434`
|
||||
- the API key is optional, you can use any string, such as `ollama`.
|
||||
|
||||
|
||||
## Run OpenHands in Development Mode
|
||||
|
||||
### Build from Source
|
||||
|
||||
Use the instructions in [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md) to build OpenHands.
|
||||
@@ -77,23 +82,22 @@ Make sure `config.toml` is there by running `make setup-config` which will creat
|
||||
workspace_base="./workspace"
|
||||
|
||||
[llm]
|
||||
model="ollama/codellama:7b"
|
||||
api_key="ollama"
|
||||
embedding_model="local"
|
||||
base_url="http://localhost:11434"
|
||||
ollama_base_url="http://localhost:11434"
|
||||
|
||||
```
|
||||
|
||||
Replace `LLM_MODEL` of your choice if you need to.
|
||||
Done! Now you can start OpenHands by: `make run`. You now should be able to connect to `http://localhost:3000/`
|
||||
|
||||
Done! Now you can start OpenHands by: `make run` without Docker. You now should be able to connect to `http://localhost:3000/`
|
||||
|
||||
## Select your Model
|
||||
### Configure the Web Application
|
||||
|
||||
In the OpenHands UI, click on the Settings wheel in the bottom-left corner.
|
||||
Then in the `Model` input, enter `ollama/codellama:7b`, or the name of the model you pulled earlier.
|
||||
If it doesn’t show up in a dropdown, that’s fine, just type it in. Click Save when you’re done.
|
||||
If it doesn’t show up in the dropdown, enable `Advanced Settings` and type it in. Please note: you need the model name as listed by `ollama list`, with the prefix `ollama/`.
|
||||
|
||||
In the API Key field, enter `ollama` or any value, since you don't need a particular key.
|
||||
|
||||
In the Base URL field, enter `http://localhost:11434`.
|
||||
|
||||
And now you're ready to go!
|
||||
|
||||
@@ -200,7 +204,7 @@ base_url="http://localhost:1234/v1"
|
||||
custom_llm_provider="openai"
|
||||
```
|
||||
|
||||
Done! Now you can start Devin by: `make run` without Docker. You now should be able to connect to `http://localhost:3000/`
|
||||
Done! Now you can start OpenHands by: `make run` without Docker. You now should be able to connect to `http://localhost:3000/`
|
||||
|
||||
# Note
|
||||
|
||||
|
||||
@@ -1,75 +1,24 @@
|
||||
# OpenAI
|
||||
|
||||
OpenHands uses [LiteLLM](https://www.litellm.ai/) to make calls to OpenAI's chat models. You can find their full documentation on OpenAI chat calls [here](https://docs.litellm.ai/docs/providers/openai).
|
||||
OpenHands uses LiteLLM to make calls to OpenAI's chat models. You can find their documentation on using OpenAI as a provider [here](https://docs.litellm.ai/docs/providers/openai).
|
||||
|
||||
## Configuration
|
||||
|
||||
### Manual Configuration
|
||||
|
||||
When running the OpenHands Docker image, you'll need to set the following environment variables:
|
||||
|
||||
```sh
|
||||
LLM_MODEL="openai/<gpt-model-name>" # e.g. "openai/gpt-4o"
|
||||
LLM_API_KEY="<your-openai-project-api-key>"
|
||||
```
|
||||
|
||||
To see a full list of OpenAI models that LiteLLM supports, please visit https://docs.litellm.ai/docs/providers/openai#openai-chat-completion-models.
|
||||
|
||||
To find or create your OpenAI Project API Key, please visit https://platform.openai.com/api-keys.
|
||||
|
||||
**Example**:
|
||||
|
||||
```sh
|
||||
export WORKSPACE_BASE=$(pwd)/workspace
|
||||
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e LLM_MODEL="openai/<gpt-model-name>" \
|
||||
-e LLM_API_KEY="<your-openai-project-api-key>" \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-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/opendevin/opendevin:0.8
|
||||
```
|
||||
|
||||
### UI Configuration
|
||||
|
||||
You can also directly set the `LLM_MODEL` and `LLM_API_KEY` in the OpenHands client itself. Follow this guide to get up and running with the OpenHands client.
|
||||
|
||||
From there, you can set your model and API key in the settings window.
|
||||
When running OpenHands, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `OpenAI`
|
||||
* `LLM Model` to the model you will be using.
|
||||
[Visit here to see a full list of OpenAI models that LiteLLM supports.](https://docs.litellm.ai/docs/providers/openai#openai-chat-completion-models)
|
||||
If the model is not in the list, toggle `Advanced Options`, and enter it in `Custom Model` (e.g. openai/<model-name> like `openai/gpt-4o`).
|
||||
* `API Key` to your OpenAI API key. To find or create your OpenAI Project API Key, [see here](https://platform.openai.com/api-keys).
|
||||
|
||||
## Using OpenAI-Compatible Endpoints
|
||||
|
||||
Just as for OpenAI Chat completions, we use LiteLLM for OpenAI-compatible endpoints. You can find their full documentation on this topic [here](https://docs.litellm.ai/docs/providers/openai_compatible).
|
||||
|
||||
When running the OpenHands Docker image, you'll need to set the following environment variables:
|
||||
## Using an OpenAI Proxy
|
||||
|
||||
```sh
|
||||
LLM_BASE_URL="<api-base-url>" # e.g. "http://0.0.0.0:3000"
|
||||
LLM_MODEL="openai/<model-name>" # e.g. "openai/mistral"
|
||||
LLM_API_KEY="<your-api-key>"
|
||||
```
|
||||
|
||||
**Example**:
|
||||
|
||||
```sh
|
||||
export WORKSPACE_BASE=$(pwd)/workspace
|
||||
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_BASE_URL="<api-base-url>" \
|
||||
-e LLM_MODEL="openai/<model-name>" \
|
||||
-e LLM_API_KEY="<your-api-key>" \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-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/opendevin/opendevin:0.8
|
||||
```
|
||||
If you're using an OpenAI proxy, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* Enable `Advanced Options`
|
||||
* `Custom Model` to openai/<model-name> (e.g. `openai/gpt-4o` or openai/<proxy-prefix>/<model-name>)
|
||||
* `Base URL` to the URL of your OpenAI proxy
|
||||
* `API Key` to your OpenAI API key
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# OpenRouter
|
||||
|
||||
OpenHands uses LiteLLM to make calls to chat models on OpenRouter. You can find their documentation on using OpenRouter as a provider [here](https://docs.litellm.ai/docs/providers/openrouter).
|
||||
|
||||
## Configuration
|
||||
|
||||
When running OpenHands, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `OpenRouter`
|
||||
* `LLM Model` to the model you will be using.
|
||||
[Visit here to see a full list of OpenRouter models](https://openrouter.ai/models).
|
||||
If the model is not in the list, toggle `Advanced Options`, and enter it in `Custom Model` (e.g. openrouter/<model-name> like `openrouter/anthropic/claude-3.5-sonnet`).
|
||||
* `API Key` to your OpenRouter API key.
|
||||
@@ -9,13 +9,14 @@ We'll try to make the install process easier, but for now you can look for your
|
||||
If you find more information or a workaround for one of these issues, please open a *PR* to add details to this file.
|
||||
|
||||
:::tip
|
||||
If you're running on Windows and having trouble, check out our [Notes for Windows and WSL users](troubleshooting/windows).
|
||||
OpenHands only supports Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
Please be sure to run all commands inside your WSL terminal.
|
||||
Check out [Notes for WSL on Windows Users](troubleshooting/windows) for some troubleshooting guides.
|
||||
:::
|
||||
|
||||
## Common Issues
|
||||
|
||||
* [Unable to connect to Docker](#unable-to-connect-to-docker)
|
||||
* [Unable to connect to SSH box](#unable-to-connect-to-ssh-box)
|
||||
* [404 Resource not found](#404-resource-not-found)
|
||||
* [`make build` getting stuck on package installations](#make-build-getting-stuck-on-package-installations)
|
||||
* [Sessions are not restored](#sessions-are-not-restored)
|
||||
@@ -45,58 +46,6 @@ OpenHands uses a Docker container to do its work safely, without potentially bre
|
||||
* If you are on a Mac, check the [permissions requirements](https://docs.docker.com/desktop/mac/permission-requirements/) and in particular consider enabling the `Allow the default Docker socket to be used` under `Settings > Advanced` in Docker Desktop.
|
||||
* In addition, upgrade your Docker to the latest version under `Check for Updates`
|
||||
|
||||
---
|
||||
### Unable to connect to SSH box
|
||||
|
||||
[GitHub Issue](https://github.com/All-Hands-AI/OpenHands/issues/1156)
|
||||
|
||||
**Symptoms**
|
||||
|
||||
```python
|
||||
self.shell = DockerSSHBox(
|
||||
...
|
||||
pexpect.pxssh.ExceptionPxssh: Could not establish connection to host
|
||||
```
|
||||
|
||||
**Details**
|
||||
|
||||
By default, OpenHands connects to a running container using SSH. On some machines,
|
||||
especially Windows, this seems to fail.
|
||||
|
||||
**Workarounds**
|
||||
|
||||
* Restart your computer (sometimes it does work)
|
||||
* Be sure to have the latest versions of WSL and Docker
|
||||
* Check that your distribution in WSL is up to date as well
|
||||
* Try [this reinstallation guide](https://github.com/All-Hands-AI/OpenHands/issues/1156#issuecomment-2064549427)
|
||||
|
||||
---
|
||||
### Unable to connect to LLM
|
||||
|
||||
[GitHub Issue](https://github.com/All-Hands-AI/OpenHands/issues/1208)
|
||||
|
||||
**Symptoms**
|
||||
|
||||
```python
|
||||
File "/app/.venv/lib/python3.12/site-packages/openai/_exceptions.py", line 81, in __init__
|
||||
super().__init__(message, response.request, body=body)
|
||||
^^^^^^^^^^^^^^^^
|
||||
AttributeError: 'NoneType' object has no attribute 'request'
|
||||
```
|
||||
|
||||
**Details**
|
||||
|
||||
[GitHub Issues](https://github.com/All-Hands-AI/OpenHands/issues?q=is%3Aissue+is%3Aopen+404)
|
||||
|
||||
This usually happens with *local* LLM setups, when OpenHands can't connect to the LLM server.
|
||||
See our guide for [local LLMs](llms/local-llms) for more information.
|
||||
|
||||
**Workarounds**
|
||||
|
||||
* Check your `base_url` in your config.toml (if it exists) under the "llm" section
|
||||
* Check that ollama (or whatever LLM you're using) is running OK
|
||||
* Make sure you're using `--add-host host.docker.internal:host-gateway` when running in Docker
|
||||
|
||||
---
|
||||
### `404 Resource not found`
|
||||
|
||||
@@ -138,7 +87,6 @@ the API endpoint you're trying to connect to. Most often this happens for Azure
|
||||
* If you're running inside the UI, be sure to set the `model` in the settings modal
|
||||
* If you're running headless (via main.py) be sure to set `LLM_MODEL` in your env/config
|
||||
* Make sure you've followed any special instructions for your LLM provider
|
||||
* [ollama](/modules/usage/llms/local-llms)
|
||||
* [Azure](/modules/usage/llms/azure-llms)
|
||||
* [Google](/modules/usage/llms/google-llms)
|
||||
* Make sure your API key is correct
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Notes for Windows and WSL Users
|
||||
# Notes for WSL on Windows Users
|
||||
|
||||
OpenHands only supports Windows via [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
Please be sure to run all commands inside your WSL terminal.
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@docusaurus/module-type-aliases": "^3.5.1",
|
||||
"@docusaurus/tsconfig": "^3.5.2",
|
||||
"@docusaurus/types": "^3.5.1",
|
||||
"typescript": "~5.5.4"
|
||||
"typescript": "~5.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
@@ -14853,9 +14853,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
|
||||
"integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@docusaurus/module-type-aliases": "^3.5.1",
|
||||
"@docusaurus/tsconfig": "^3.5.2",
|
||||
"@docusaurus/types": "^3.5.1",
|
||||
"typescript": "~5.5.4"
|
||||
"typescript": "~5.6.2"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -1,8 +1,113 @@
|
||||
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
|
||||
|
||||
const sidebars: SidebarsConfig = {
|
||||
docsSidebar: [{ type: "autogenerated", dirName: "usage" }],
|
||||
apiSidebar: [require("./modules/python/sidebar.json")],
|
||||
docsSidebar: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Getting Started',
|
||||
id: 'usage/getting-started',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'LLMs',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Overview',
|
||||
id: 'usage/llms/llms',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Providers',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Azure',
|
||||
id: 'usage/llms/azure-llms',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Google',
|
||||
id: 'usage/llms/google-llms',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Groq',
|
||||
id: 'usage/llms/groq',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'OpenAI',
|
||||
id: 'usage/llms/openai-llms',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'OpenRouter',
|
||||
id: 'usage/llms/openrouter',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Troubleshooting',
|
||||
id: 'usage/troubleshooting/troubleshooting',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Feedback',
|
||||
id: 'usage/feedback',
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'How-to Guides',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'usage/how-to/cli-mode',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'usage/how-to/headless-mode',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'usage/how-to/custom-sandbox-guide',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'usage/how-to/evaluation-harness',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'usage/how-to/openshift-example',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Architecture',
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Backend',
|
||||
id: 'usage/architecture/backend',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Runtime',
|
||||
id: 'usage/architecture/runtime',
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'About',
|
||||
id: 'usage/about',
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
export default sidebars;
|
||||
|
||||
@@ -7,17 +7,6 @@ function CustomFooter() {
|
||||
return (
|
||||
<footer className="custom-footer">
|
||||
<div className="footer-content">
|
||||
<div className="footer-top">
|
||||
<div className="footer-title">
|
||||
<Translate id="footer.title">OpenHands</Translate>
|
||||
</div>
|
||||
<div className="footer-link">
|
||||
<a href="/modules/usage/intro">
|
||||
<Translate id="footer.docs">Docs</Translate>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="footer-icons">
|
||||
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA" target="_blank" rel="noopener noreferrer">
|
||||
<FaSlack />
|
||||
@@ -32,7 +21,7 @@ function CustomFooter() {
|
||||
<div className="footer-bottom">
|
||||
<p>
|
||||
<Translate id="footer.copyright" values={{ year: new Date().getFullYear() }}>
|
||||
{'Copyright © {year} OpenHands'}
|
||||
{'Copyright © {year} All Hands AI, Inc'}
|
||||
</Translate>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -17,23 +17,19 @@ export function HomepageHeader() {
|
||||
|
||||
<p className="header-subtitle">{siteConfig.tagline}</p>
|
||||
|
||||
<div className="header-links">
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands">
|
||||
<img src="https://img.shields.io/badge/Code-Github-purple?logo=github&logoColor=white&style=for-the-badge" alt="Code" />
|
||||
</a>
|
||||
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA">
|
||||
<img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community" />
|
||||
</a>
|
||||
<a href="https://discord.gg/ESHStjSjD4">
|
||||
<img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community" />
|
||||
</a>
|
||||
|
||||
<a href="https://arxiv.org/abs/2407.16741">
|
||||
<img src="https://img.shields.io/badge/Paper-%20on%20Arxiv-red?logo=arxiv&style=for-the-badge" alt="Paper on Arxiv" />
|
||||
</a>
|
||||
<a href="https://huggingface.co/spaces/OpenDevin/evaluation">
|
||||
<img src="https://img.shields.io/badge/Evaluation-Benchmark%20on%20HF%20Space-green?logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark" />
|
||||
</a>
|
||||
<div align="center" className="header-links">
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/graphs/contributors"><img src="https://img.shields.io/github/contributors/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Contributors" /></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/stargazers"><img src="https://img.shields.io/github/stars/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="Stargazers" /></a>
|
||||
<a href="https://codecov.io/github/All-Hands-AI/OpenHands?branch=main"><img alt="CodeCov" src="https://img.shields.io/codecov/c/github/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" /></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/LICENSE"><img src="https://img.shields.io/github/license/All-Hands-AI/OpenHands?style=for-the-badge&color=blue" alt="MIT License" /></a>
|
||||
<br/>
|
||||
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2oikve2hu-UDxHeo8nsE69y6T7yFX_BA"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community" /></a>
|
||||
<a href="https://discord.gg/ESHStjSjD4"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community" /></a>
|
||||
<a href="https://github.com/All-Hands-AI/OpenHands/blob/main/CREDITS.md"><img src="https://img.shields.io/badge/Project-Credits-blue?style=for-the-badge&color=FFE165&logo=github&logoColor=white" alt="Credits" /></a>
|
||||
<br/>
|
||||
<a href="https://docs.all-hands.dev/modules/usage/getting-started"><img src="https://img.shields.io/badge/Documentation-000?logo=googledocs&logoColor=FFE165&style=for-the-badge" alt="Check out the documentation" /></a>
|
||||
<a href="https://arxiv.org/abs/2407.16741"><img src="https://img.shields.io/badge/Paper%20on%20Arxiv-000?logoColor=FFE165&logo=arxiv&style=for-the-badge" alt="Paper on Arxiv" /></a>
|
||||
<a href="https://huggingface.co/spaces/OpenHands/evaluation"><img src="https://img.shields.io/badge/Benchmark%20score-000?logoColor=FFE165&logo=huggingface&style=for-the-badge" alt="Evaluation Benchmark Score" /></a>
|
||||
</div>
|
||||
|
||||
<Demo />
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
/* You can override the default Infima variables here. */
|
||||
|
||||
:root {
|
||||
--ifm-color-primary: #4465db;
|
||||
--ifm-code-font-size: 95%;
|
||||
--ifm-color-primary: #000;
|
||||
--ifm-background-color: #F1EAE0;
|
||||
--ifm-navbar-background-color: #F1EAE0;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||
--secondary: #171717;
|
||||
--secondary-dark: #0a0a0a;
|
||||
@@ -17,21 +19,15 @@
|
||||
|
||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||
[data-theme="dark"] {
|
||||
--ifm-color-primary: #4465db;
|
||||
--ifm-color-primary: #FFF;
|
||||
--ifm-background-color: #000;
|
||||
--ifm-navbar-background-color: #000;
|
||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
|
||||
--secondary: #737373;
|
||||
--secondary-dark: #171717;
|
||||
--secondary-light: #d4d4d4;
|
||||
--secondary-light: #ccc;
|
||||
}
|
||||
|
||||
.footer--dark {
|
||||
background-image: linear-gradient(
|
||||
140deg,
|
||||
var(--secondary) 20%,
|
||||
var(--secondary-light) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.a {
|
||||
article a, .a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
|
||||
.custom-footer {
|
||||
background-color: dark;
|
||||
color: white;
|
||||
height: 200px;
|
||||
color: #000;
|
||||
height: 100px;
|
||||
/* background: linear-gradient(to bottom, #1a1a1a, #1a1a1a); */
|
||||
background: linear-gradient(to bottom, #1f2937, #000000);
|
||||
background-color: #F1EAE0;
|
||||
|
||||
}
|
||||
|
||||
[data-theme="dark"] .custom-footer {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -47,7 +53,6 @@
|
||||
}
|
||||
|
||||
.footer-community {
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
@@ -65,7 +70,3 @@
|
||||
.footer-icons a:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
.homepage-header {
|
||||
height: 800px;
|
||||
color: white;
|
||||
background: linear-gradient(to top, #64748b, #000000);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
|
||||
@@ -20,8 +20,7 @@ export default function Home(): JSX.Element {
|
||||
title={`${siteConfig.title}`}
|
||||
description={translate({
|
||||
id: 'homepage.description',
|
||||
message: 'An Open Platform for AI Software Developers as Generalist Agents',
|
||||
description: 'The homepage description',
|
||||
message: 'Code Less, Make More',
|
||||
})}
|
||||
>
|
||||
<HomepageHeader />
|
||||
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -9,7 +9,7 @@ To better organize the evaluation folder, we should follow the rules below:
|
||||
- Each subfolder contains a specific benchmark or experiment. For example, `evaluation/swe_bench` should contain
|
||||
all the preprocessing/evaluation/analysis scripts.
|
||||
- Raw data and experimental records should not be stored within this repo.
|
||||
- For model outputs, they should be stored at [this huggingface space](https://huggingface.co/spaces/OpenDevin/evaluation) for visualization.
|
||||
- For model outputs, they should be stored at [this huggingface space](https://huggingface.co/spaces/OpenHands/evaluation) for visualization.
|
||||
- Important data files of manageable size and analysis scripts (e.g., jupyter notebooks) can be directly uploaded to this repo.
|
||||
|
||||
## Supported Benchmarks
|
||||
@@ -69,8 +69,8 @@ temperature = 0.0
|
||||
|
||||
### Result Visualization
|
||||
|
||||
Check [this huggingface space](https://huggingface.co/spaces/OpenDevin/evaluation) for visualization of existing experimental results.
|
||||
Check [this huggingface space](https://huggingface.co/spaces/OpenHands/evaluation) for visualization of existing experimental results.
|
||||
|
||||
### Upload your results
|
||||
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results to our hosted huggingface repo via PR following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results to our hosted huggingface repo via PR following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
|
||||
@@ -256,6 +256,7 @@ def process_instance(
|
||||
if __name__ == '__main__':
|
||||
args = parse_arguments()
|
||||
dataset = load_dataset('RajMaheshwari/Exercism-Python')
|
||||
dataset = dataset.shuffle(seed=42)
|
||||
aider_bench_tests = dataset['train'].to_pandas()
|
||||
|
||||
llm_config = None
|
||||
|
||||
@@ -49,4 +49,4 @@ fact8
|
||||
facts.Smart(Dave, True)
|
||||
facts.Kind(Dave, True)
|
||||
assert
|
||||
facts.Quiet(Dave, True)
|
||||
facts.Quiet(Dave, True)
|
||||
|
||||
@@ -52,7 +52,7 @@ def get_config(
|
||||
base_container_image='xingyaoww/od-eval-logic-reasoning:v1.0',
|
||||
enable_auto_lint=True,
|
||||
use_host_network=False,
|
||||
runtime_extra_deps='$OD_INTERPRETER_PATH -m pip install scitools-pyke',
|
||||
runtime_extra_deps='$OH_INTERPRETER_PATH -m pip install scitools-pyke',
|
||||
),
|
||||
# do not mount workspace
|
||||
workspace_base=None,
|
||||
|
||||
@@ -26,7 +26,7 @@ poetry run python evaluation/miniwob/get_success_rate.py evaluation/evaluation_o
|
||||
|
||||
## Submit your evaluation results
|
||||
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
|
||||
|
||||
## BrowsingAgent V1.0 result
|
||||
|
||||
@@ -105,7 +105,7 @@ def get_config(
|
||||
base_container_image='xingyaoww/od-eval-mint:v1.0',
|
||||
enable_auto_lint=True,
|
||||
use_host_network=False,
|
||||
runtime_extra_deps=f'$OD_INTERPRETER_PATH -m pip install {" ".join(MINT_DEPENDENCIES)}',
|
||||
runtime_extra_deps=f'$OH_INTERPRETER_PATH -m pip install {" ".join(MINT_DEPENDENCIES)}',
|
||||
),
|
||||
# do not mount workspace
|
||||
workspace_base=None,
|
||||
|
||||
@@ -14,9 +14,9 @@ To run the tests for OpenHands project, you can use the provided test runner scr
|
||||
3. Navigate to the root directory of the project.
|
||||
4. Run the test suite using the test runner script with the required arguments:
|
||||
```
|
||||
python evaluation/regression/run_tests.py --OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxx --model=gpt-3.5-turbo
|
||||
python evaluation/regression/run_tests.py --OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxx --model=gpt-4o
|
||||
```
|
||||
Replace `sk-xxxxxxxxxxxxxxxxxxxxxx` with your actual OpenAI API key. The default model is `gpt-3.5-turbo`, but you can specify a different model if needed.
|
||||
Replace `sk-xxxxxxxxxxxxxxxxxxxxxx` with your actual OpenAI API key. The default model is `gpt-4o`, but you can specify a different model if needed.
|
||||
|
||||
The test runner will discover and execute all the test cases in the `cases/` directory, and display the results of the test suite, including the status of each individual test case and the overall summary.
|
||||
|
||||
|
||||
@@ -29,21 +29,27 @@ if __name__ == '__main__':
|
||||
|
||||
if command == 'reverse':
|
||||
from commands.reverse import reverse_string
|
||||
|
||||
print(reverse_string(input_string))
|
||||
elif command == 'uppercase':
|
||||
from commands.uppercase import to_uppercase
|
||||
|
||||
print(to_uppercase(input_string))
|
||||
elif command == 'lowercase':
|
||||
from commands.lowercase import to_lowercase
|
||||
|
||||
print(to_lowercase(input_string))
|
||||
elif command == 'spongebob':
|
||||
from commands.spongebob import spongebob_case
|
||||
|
||||
print(spongebob_case(input_string))
|
||||
elif command == 'length':
|
||||
from commands.length import string_length
|
||||
|
||||
print(string_length(input_string))
|
||||
elif command == 'scramble':
|
||||
from commands.scramble import scramble_string
|
||||
|
||||
print(scramble_string(input_string))
|
||||
else:
|
||||
print('Invalid command!')
|
||||
|
||||
@@ -10,21 +10,27 @@ if __name__ == '__main__':
|
||||
|
||||
if command == 'reverse':
|
||||
from commands.reverse import reverse_string
|
||||
|
||||
print(reverse_string(input_string))
|
||||
elif command == 'uppercase':
|
||||
from commands.uppercase import to_uppercase
|
||||
|
||||
print(to_uppercase(input_string))
|
||||
elif command == 'lowercase':
|
||||
from commands.lowercase import to_lowercase
|
||||
|
||||
print(to_lowercase(input_string))
|
||||
elif command == 'spongebob':
|
||||
from commands.spongebob import spongebob_case
|
||||
|
||||
print(spongebob_case(input_string))
|
||||
elif command == 'length':
|
||||
from commands.length import string_length
|
||||
|
||||
print(string_length(input_string))
|
||||
elif command == 'scramble':
|
||||
from commands.scramble import scramble_string
|
||||
|
||||
print(scramble_string(input_string))
|
||||
else:
|
||||
print('Invalid command!')
|
||||
|
||||
@@ -24,7 +24,7 @@ This is now the default behavior.
|
||||
|
||||
Make sure your Docker daemon is running, and you have ample disk space (at least 200-500GB, depends on the SWE-Bench set you are running on) for the [instance-level docker image](#openhands-swe-bench-instance-level-docker-support).
|
||||
|
||||
When the `run_infer.sh` script is started, it will automatically pull the relavant SWE-Bench images. For example, for instance ID `django_django-11011`, it will try to pull our pre-build docker image `sweb.eval.x86_64.django_s_django-11011` from DockerHub. This image will be used create an OpenHands runtime image where the agent will operate on.
|
||||
When the `run_infer.sh` script is started, it will automatically pull the relevant SWE-Bench images. For example, for instance ID `django_django-11011`, it will try to pull our pre-build docker image `sweb.eval.x86_64.django_s_django-11011` from DockerHub. This image will be used create an OpenHands runtime image where the agent will operate on.
|
||||
|
||||
```bash
|
||||
./evaluation/swe_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
@@ -63,11 +63,22 @@ then your command would be:
|
||||
./evaluation/swe_bench/scripts/run_infer.sh llm.eval_gpt4_1106_preview HEAD CodeActAgent 10
|
||||
```
|
||||
|
||||
**Evaluate on `RemoteRuntime` (alpha)** (contact Xingyao over slack if you want to try this out!)
|
||||
### Run Inference on `RemoteRuntime` (experimental)
|
||||
|
||||
This is in limited beta. Contact Xingyao over slack if you want to try this out!
|
||||
|
||||
```bash
|
||||
SANDBOX_API_KEY="CONTACT-XINGYAO-TO-GET-A-TESTING-API-KEY" RUNTIME=remote EVAL_DOCKER_IMAGE_PREFIX="us-docker.pkg.dev/evaluation-428620/swe-bench-images" ./evaluation/swe_bench/scripts/run_infer.sh llm.eval HEAD CodeActAgent 300
|
||||
# ./evaluation/swe_bench/scripts/run_infer.sh [model_config] [git-version] [agent] [eval_limit] [max_iter] [num_workers] [dataset] [dataset_split]
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" RUNTIME=remote EVAL_DOCKER_IMAGE_PREFIX="us-docker.pkg.dev/evaluation-428620/swe-bench-images" \
|
||||
./evaluation/swe_bench/scripts/run_infer.sh llm.eval HEAD CodeActAgent 300 30 16 "princeton-nlp/SWE-bench_Lite" test
|
||||
# This example runs evaluation on CodeActAgent for 300 instances on "princeton-nlp/SWE-bench_Lite"'s test set, with max 30 iteration per instances, with 16 number of workers running in parallel
|
||||
```
|
||||
|
||||
To clean-up all existing runtime you've already started, run:
|
||||
|
||||
```bash
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" ./evaluation/swe_bench/scripts/cleanup_remote_runtime.sh
|
||||
```
|
||||
Multi-processing is still WIP.
|
||||
|
||||
### Specify a subset of tasks to run infer
|
||||
|
||||
@@ -114,7 +125,7 @@ With `output.jsonl` file, you can run `eval_infer.sh` to evaluate generated patc
|
||||
|
||||
> If you want to evaluate existing results, you should first run this to clone existing outputs
|
||||
>```bash
|
||||
>git clone https://huggingface.co/spaces/OpenDevin/evaluation evaluation/evaluation_outputs
|
||||
>git clone https://huggingface.co/spaces/OpenHands/evaluation evaluation/evaluation_outputs
|
||||
>```
|
||||
|
||||
NOTE, you should have already pulled the instance-level OR env-level docker images following [this section](#openhands-swe-bench-instance-level-docker-support).
|
||||
@@ -146,12 +157,29 @@ The final results will be saved to `evaluation/evaluation_outputs/outputs/swe_be
|
||||
- `report.json`: a JSON file that contains keys like `"resolved_ids"` pointing to instance IDs that are resolved by the agent.
|
||||
- `logs/`: a directory of test logs
|
||||
|
||||
## Visualize Results
|
||||
### Run evaluation with `RemoteRuntime` (experimental)
|
||||
|
||||
First you need to clone `https://huggingface.co/spaces/OpenDevin/evaluation` and add your own running results from openhands into the `outputs` of the cloned repo.
|
||||
This is in limited beta. Contact Xingyao over slack if you want to try this out!
|
||||
|
||||
```bash
|
||||
git clone https://huggingface.co/spaces/OpenDevin/evaluation
|
||||
# ./evaluation/swe_bench/scripts/eval_infer_remote.sh [output.jsonl filepath] [num_workers]
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" RUNTIME=remote EVAL_DOCKER_IMAGE_PREFIX="us-docker.pkg.dev/evaluation-428620/swe-bench-images" evaluation/swe_bench/scripts/eval_infer_remote.sh evaluation/outputs/swe_bench_lite/CodeActAgent/Llama-3.1-70B-Instruct-Turbo_maxiter_30_N_v1.9-no-hint/output.jsonl 16 "princeton-nlp/SWE-bench_Lite" "test"
|
||||
# This example evaluate patches generated by CodeActAgent on Llama-3.1-70B-Instruct-Turbo on "princeton-nlp/SWE-bench_Lite"'s test set, with 16 number of workers running in parallel
|
||||
```
|
||||
|
||||
To clean-up all existing runtimes that you've already started, run:
|
||||
|
||||
```bash
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" ./evaluation/swe_bench/scripts/cleanup_remote_runtime.sh
|
||||
```
|
||||
|
||||
|
||||
## Visualize Results
|
||||
|
||||
First you need to clone `https://huggingface.co/spaces/OpenHands/evaluation` and add your own running results from openhands into the `outputs` of the cloned repo.
|
||||
|
||||
```bash
|
||||
git clone https://huggingface.co/spaces/OpenHands/evaluation
|
||||
```
|
||||
|
||||
**(optional) setup streamlit environment with conda**:
|
||||
@@ -168,11 +196,11 @@ Then, in a separate Python environment with `streamlit` library, you can run the
|
||||
```bash
|
||||
# Make sure you are inside the cloned `evaluation` repo
|
||||
conda activate streamlit # if you follow the optional conda env setup above
|
||||
streamlit run 0_📊_OpenHands_Benchmark.py --server.port 8501 --server.address 0.0.0.0
|
||||
streamlit app.py --server.port 8501 --server.address 0.0.0.0
|
||||
```
|
||||
|
||||
Then you can access the SWE-Bench trajectory visualizer at `localhost:8501`.
|
||||
|
||||
## Submit your evaluation results
|
||||
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
import pandas as pd
|
||||
from swebench.harness.grading import get_eval_report
|
||||
from swebench.harness.run_evaluation import (
|
||||
APPLY_PATCH_FAIL,
|
||||
APPLY_PATCH_PASS,
|
||||
)
|
||||
from swebench.harness.test_spec import SWEbenchInstance, TestSpec, make_test_spec
|
||||
from swebench.harness.utils import load_swebench_dataset
|
||||
|
||||
from evaluation.swe_bench.run_infer import get_instance_docker_image
|
||||
from evaluation.utils.shared import (
|
||||
EvalMetadata,
|
||||
EvalOutput,
|
||||
prepare_dataset,
|
||||
reset_logger_for_multiprocessing,
|
||||
run_evaluation,
|
||||
)
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
SandboxConfig,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime
|
||||
from openhands.events.action import CmdRunAction
|
||||
from openhands.events.observation import CmdOutputObservation
|
||||
|
||||
# TODO: migrate all swe-bench docker to ghcr.io/openhands
|
||||
DOCKER_IMAGE_PREFIX = os.environ.get('EVAL_DOCKER_IMAGE_PREFIX', 'docker.io/xingyaoww/')
|
||||
logger.info(f'Using docker image prefix: {DOCKER_IMAGE_PREFIX}')
|
||||
|
||||
|
||||
def process_git_patch(patch):
|
||||
if not isinstance(patch, str):
|
||||
return ''
|
||||
|
||||
if not patch.strip():
|
||||
# skip empty patches
|
||||
return ''
|
||||
|
||||
patch = patch.replace('\r\n', '\n')
|
||||
# There might be some weird characters at the beginning of the patch
|
||||
# due to some OpenHands inference command outputs
|
||||
|
||||
# FOR EXAMPLE:
|
||||
# git diff --no-color --cached 895f28f9cbed817c00ab68770433170d83132d90
|
||||
# [A[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[K0
|
||||
# diff --git a/django/db/models/sql/.backup.query.py b/django/db/models/sql/.backup.query.py
|
||||
# new file mode 100644
|
||||
# index 0000000000..fc13db5948
|
||||
|
||||
# We "find" the first line that starts with "diff" and then we remove lines before it
|
||||
lines = patch.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('diff --git'):
|
||||
patch = '\n'.join(lines[i:])
|
||||
break
|
||||
|
||||
patch = patch.rstrip() + '\n' # Make sure the last line ends with a newline
|
||||
return patch
|
||||
|
||||
|
||||
def get_config(instance: pd.Series) -> AppConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
base_container_image = get_instance_docker_image(instance['instance_id'])
|
||||
logger.info(
|
||||
f'Using instance container image: {base_container_image}. '
|
||||
f'Please make sure this image exists. '
|
||||
f'Submit an issue on https://github.com/All-Hands-AI/OpenHands if you run into any issues.'
|
||||
)
|
||||
config = AppConfig(
|
||||
run_as_openhands=False,
|
||||
runtime=os.environ.get('RUNTIME', 'eventstream'),
|
||||
sandbox=SandboxConfig(
|
||||
base_container_image=base_container_image,
|
||||
use_host_network=False,
|
||||
# large enough timeout, since some testcases take very long to run
|
||||
timeout=1800,
|
||||
api_key=os.environ.get('ALLHANDS_API_KEY', None),
|
||||
),
|
||||
# do not mount workspace
|
||||
workspace_base=None,
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def process_instance(
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata | None = None,
|
||||
reset_logger: bool = True,
|
||||
) -> EvalOutput:
|
||||
# Setup the logger properly, so you can run multi-processing to parallelize the evaluation
|
||||
if reset_logger:
|
||||
global output_file
|
||||
log_dir = output_file.replace('.jsonl', '.logs')
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
reset_logger_for_multiprocessing(logger, instance.instance_id, log_dir)
|
||||
else:
|
||||
logger.info(f'Starting evaluation for instance {instance.instance_id}.')
|
||||
|
||||
config = get_config(instance)
|
||||
instance_id = instance.instance_id
|
||||
model_patch = instance['model_patch']
|
||||
test_spec: TestSpec = instance['test_spec']
|
||||
logger.info(f'Starting evaluation for instance {instance_id}.')
|
||||
|
||||
if 'test_result' not in instance.keys():
|
||||
instance['test_result'] = {}
|
||||
instance['test_result']['report'] = {
|
||||
'empty_generation': False,
|
||||
'resolved': False,
|
||||
'failed_apply_patch': False,
|
||||
'error_eval': False,
|
||||
'test_timeout': False,
|
||||
}
|
||||
|
||||
if model_patch == '':
|
||||
instance['test_result']['report']['empty_generation'] = True
|
||||
return EvalOutput(
|
||||
instance_id=instance_id,
|
||||
test_result=instance['test_result'],
|
||||
)
|
||||
|
||||
runtime = create_runtime(config, sid=instance_id)
|
||||
|
||||
# Get patch and save it to /tmp/patch.diff
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Patch file
|
||||
patch_file_path = os.path.join(temp_dir, 'patch.diff')
|
||||
with open(patch_file_path, 'w') as f:
|
||||
f.write(model_patch)
|
||||
runtime.copy_to(patch_file_path, '/tmp')
|
||||
# Eval script
|
||||
eval_script_path = os.path.join(temp_dir, 'eval.sh')
|
||||
with open(eval_script_path, 'w') as f:
|
||||
f.write(test_spec.eval_script)
|
||||
runtime.copy_to(eval_script_path, '/tmp')
|
||||
|
||||
# Set +x
|
||||
action = CmdRunAction(command='chmod +x /tmp/eval.sh')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
# Apply patch
|
||||
exec_command = (
|
||||
'cd /testbed && '
|
||||
"(git apply -v /tmp/patch.diff && echo 'APPLY_PATCH_PASS' || "
|
||||
"(echo 'Failed to apply patch with git apply, trying with patch command...' && "
|
||||
"(patch --batch --fuzz=5 -p1 -i /tmp/patch.diff && echo 'APPLY_PATCH_PASS' || "
|
||||
"echo 'APPLY_PATCH_FAIL')))"
|
||||
)
|
||||
action = CmdRunAction(command=exec_command, keep_prompt=False)
|
||||
action.timeout = 600
|
||||
obs = runtime.run_action(action)
|
||||
assert isinstance(obs, CmdOutputObservation)
|
||||
apply_patch_output = obs.content
|
||||
assert isinstance(apply_patch_output, str)
|
||||
instance['test_result']['apply_patch_output'] = apply_patch_output
|
||||
|
||||
try:
|
||||
if 'APPLY_PATCH_FAIL' in apply_patch_output:
|
||||
logger.info(f'[{instance_id}] {APPLY_PATCH_FAIL}:\n{apply_patch_output}')
|
||||
instance['test_result']['report']['failed_apply_patch'] = True
|
||||
|
||||
return EvalOutput(
|
||||
instance_id=instance_id,
|
||||
test_result=instance['test_result'],
|
||||
)
|
||||
elif 'APPLY_PATCH_PASS' in apply_patch_output:
|
||||
logger.info(f'[{instance_id}] {APPLY_PATCH_PASS}:\n{apply_patch_output}')
|
||||
|
||||
# Run eval script in background and save output to log file
|
||||
log_file = '/tmp/eval_output.log'
|
||||
action = CmdRunAction(
|
||||
command=f'/tmp/eval.sh > {log_file} 2>&1 & echo $!', keep_prompt=False
|
||||
)
|
||||
action.timeout = 60 # Short timeout just to get the process ID
|
||||
obs = runtime.run_action(action)
|
||||
|
||||
if isinstance(obs, CmdOutputObservation) and obs.exit_code == 0:
|
||||
pid = obs.content.split()[-1].strip()
|
||||
logger.info(
|
||||
f'[{instance_id}] Evaluation process started with PID: {pid}'
|
||||
)
|
||||
|
||||
# Poll for completion
|
||||
start_time = time.time()
|
||||
timeout = 1800 # 30 minutes
|
||||
while True:
|
||||
seconds_elapsed = time.time() - start_time
|
||||
if seconds_elapsed > timeout:
|
||||
logger.info(
|
||||
f'[{instance_id}] Evaluation timed out after {timeout} seconds'
|
||||
)
|
||||
instance['test_result']['report']['test_timeout'] = True
|
||||
break
|
||||
check_action = CmdRunAction(
|
||||
command=f'ps -p {pid} > /dev/null; echo $?', keep_prompt=False
|
||||
)
|
||||
check_action.timeout = 60
|
||||
check_obs = runtime.run_action(check_action)
|
||||
if (
|
||||
isinstance(check_obs, CmdOutputObservation)
|
||||
and check_obs.content.split()[-1].strip() == '1'
|
||||
):
|
||||
logger.info(
|
||||
f'[{instance_id}] Evaluation process completed after {seconds_elapsed} seconds'
|
||||
)
|
||||
break
|
||||
logger.info(
|
||||
f'[{instance_id}] [{seconds_elapsed:.0f}s] Evaluation still running, waiting...'
|
||||
)
|
||||
time.sleep(30) # Wait for 30 seconds before checking again
|
||||
|
||||
# Read the log file
|
||||
cat_action = CmdRunAction(command=f'cat {log_file}', keep_prompt=False)
|
||||
cat_action.timeout = 300
|
||||
cat_obs = runtime.run_action(cat_action)
|
||||
|
||||
# Grade answer
|
||||
if isinstance(cat_obs, CmdOutputObservation) and cat_obs.exit_code == 0:
|
||||
test_output = cat_obs.content
|
||||
assert isinstance(test_output, str)
|
||||
instance['test_result']['test_output'] = test_output
|
||||
|
||||
# Get report from test output
|
||||
logger.info(f'[{instance_id}] Grading answer...')
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Create a directory structure that matches the expected format
|
||||
# NOTE: this is a hack to make the eval report format consistent
|
||||
# with the original SWE-Bench eval script
|
||||
log_dir = os.path.join(temp_dir, 'logs', instance_id)
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
test_output_path = os.path.join(log_dir, 'test_output.txt')
|
||||
with open(test_output_path, 'w') as f:
|
||||
f.write(test_output)
|
||||
|
||||
_report = get_eval_report(
|
||||
test_spec=test_spec,
|
||||
prediction={
|
||||
'model_patch': model_patch,
|
||||
'instance_id': instance_id,
|
||||
},
|
||||
log_path=test_output_path,
|
||||
include_tests_status=True,
|
||||
)
|
||||
report = _report[instance_id]
|
||||
logger.info(
|
||||
f"[{instance_id}] report: {report}\nResult for {instance_id}: resolved: {report['resolved']}"
|
||||
)
|
||||
instance['test_result']['report']['resolved'] = report[
|
||||
'resolved'
|
||||
]
|
||||
else:
|
||||
logger.info(f'[{instance_id}] Error when starting eval:\n{obs.content}')
|
||||
instance['test_result']['report']['error_eval'] = True
|
||||
|
||||
return EvalOutput(
|
||||
instance_id=instance_id,
|
||||
test_result=instance['test_result'],
|
||||
)
|
||||
else:
|
||||
logger.info(
|
||||
f'[{instance_id}] Unexpected output when applying patch:\n{apply_patch_output}'
|
||||
)
|
||||
raise RuntimeError(
|
||||
instance_id,
|
||||
f'Unexpected output when applying patch:\n{apply_patch_output}',
|
||||
logger,
|
||||
)
|
||||
finally:
|
||||
runtime.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser.add_argument(
|
||||
'--input-file',
|
||||
type=str,
|
||||
help='Path to input predictions file',
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
default='princeton-nlp/SWE-bench',
|
||||
help='data set to evaluate on, either full-test or lite-test',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--split',
|
||||
type=str,
|
||||
default='test',
|
||||
help='split to evaluate on',
|
||||
)
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
# Load SWE-Bench dataset
|
||||
full_dataset: list[SWEbenchInstance] = load_swebench_dataset(
|
||||
args.dataset, args.split
|
||||
)
|
||||
instance_id_to_instance = {
|
||||
instance['instance_id']: instance for instance in full_dataset
|
||||
}
|
||||
logger.info(
|
||||
f'Loaded dataset {args.dataset} with split {args.split} to run inference on.'
|
||||
)
|
||||
|
||||
# Load predictions
|
||||
assert args.input_file.endswith('.jsonl'), 'Input file must be a jsonl file.'
|
||||
predictions = pd.read_json(args.input_file, lines=True)
|
||||
assert (
|
||||
'instance_id' in predictions.columns
|
||||
), 'Input file must contain instance_id column.'
|
||||
|
||||
if 'model_patch' not in predictions.columns and (
|
||||
'test_result' in predictions.columns
|
||||
and 'model_patch' in predictions['test_result'].iloc[0]
|
||||
):
|
||||
raise ValueError(
|
||||
'Input file must contain model_patch column OR test_result column with model_patch field.'
|
||||
)
|
||||
assert len(predictions['instance_id'].unique()) == len(
|
||||
predictions
|
||||
), 'instance_id column must be unique.'
|
||||
|
||||
if 'model_patch' not in predictions.columns:
|
||||
predictions['model_patch'] = predictions['test_result'].apply(
|
||||
lambda x: x['git_patch']
|
||||
)
|
||||
assert {'instance_id', 'model_patch'}.issubset(
|
||||
set(predictions.columns)
|
||||
), 'Input file must contain instance_id and model_patch columns.'
|
||||
|
||||
# Process model_patch
|
||||
predictions['model_patch'] = predictions['model_patch'].apply(process_git_patch)
|
||||
|
||||
# Merge predictions with dataset
|
||||
predictions['instance'] = predictions['instance_id'].apply(
|
||||
lambda x: instance_id_to_instance[x]
|
||||
)
|
||||
predictions['test_spec'] = predictions['instance'].apply(make_test_spec)
|
||||
|
||||
# Prepare dataset
|
||||
output_file = args.input_file.replace('.jsonl', '.swebench_eval.jsonl')
|
||||
instances = prepare_dataset(predictions, output_file, args.eval_n_limit)
|
||||
|
||||
run_evaluation(
|
||||
instances,
|
||||
metadata=None,
|
||||
output_file=output_file,
|
||||
num_workers=args.eval_num_workers,
|
||||
process_instance_func=process_instance,
|
||||
)
|
||||
|
||||
# Load evaluated predictions & print number of resolved predictions
|
||||
evaluated_predictions = pd.read_json(output_file, lines=True)
|
||||
fields = ['resolved', 'failed_apply_patch', 'error_eval', 'empty_generation']
|
||||
|
||||
def count_report_field(row, field):
|
||||
return row['test_result']['report'][field]
|
||||
|
||||
for field in fields:
|
||||
count = evaluated_predictions.apply(
|
||||
count_report_field, args=(field,), axis=1
|
||||
).sum()
|
||||
logger.info(
|
||||
f'# {field}: {count} / {len(evaluated_predictions)}. ({count / len(evaluated_predictions):.2%})'
|
||||
)
|
||||
@@ -2,7 +2,6 @@ import asyncio
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
@@ -26,13 +25,14 @@ from openhands.core.config import (
|
||||
SandboxConfig,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
load_from_env,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action import CmdRunAction
|
||||
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.utils.shutdown_listener import sleep_if_should_continue
|
||||
|
||||
USE_HINT_TEXT = os.environ.get('USE_HINT_TEXT', 'false').lower() == 'true'
|
||||
USE_INSTANCE_IMAGE = os.environ.get('USE_INSTANCE_IMAGE', 'false').lower() == 'true'
|
||||
@@ -123,26 +123,19 @@ def get_config(
|
||||
run_as_openhands=False,
|
||||
max_budget_per_task=4,
|
||||
max_iterations=metadata.max_iterations,
|
||||
runtime=os.environ.get('RUNTIME', 'eventstream'),
|
||||
sandbox=SandboxConfig(
|
||||
base_container_image=base_container_image,
|
||||
enable_auto_lint=True,
|
||||
use_host_network=False,
|
||||
# large enough timeout, since some testcases take very long to run
|
||||
timeout=300,
|
||||
api_key=os.environ.get('ALLHANDS_API_KEY', None),
|
||||
),
|
||||
# do not mount workspace
|
||||
workspace_base=None,
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
selected_env_vars = {'runtime', 'sandbox_api_key'}
|
||||
selected_env_vars = {
|
||||
k: v for k, v in os.environ.items() if k.lower() in selected_env_vars
|
||||
}
|
||||
if selected_env_vars:
|
||||
logger.info(
|
||||
f'Loading config keys from env vars: {list(selected_env_vars.keys())}'
|
||||
)
|
||||
load_from_env(config, selected_env_vars)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
return config
|
||||
|
||||
@@ -165,12 +158,14 @@ def initialize_runtime(
|
||||
action = CmdRunAction(
|
||||
command=f"""echo 'export SWE_INSTANCE_ID={instance['instance_id']}' >> ~/.bashrc && echo 'export PIP_CACHE_DIR=~/.cache/pip' >> ~/.bashrc && echo "alias git='git --no-pager'" >> ~/.bashrc"""
|
||||
)
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(command="""export USER=$(whoami); echo USER=${USER} """)
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -182,6 +177,7 @@ def initialize_runtime(
|
||||
|
||||
# inject the instance info
|
||||
action = CmdRunAction(command='mkdir -p /swe_util/eval_data/instances')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -209,18 +205,21 @@ def initialize_runtime(
|
||||
'/swe_util/',
|
||||
)
|
||||
action = CmdRunAction(command='cat ~/.bashrc')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(command='source ~/.bashrc')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(command='source /swe_util/instance_swe_entry.sh')
|
||||
action.timeout = 3600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -236,12 +235,14 @@ def initialize_runtime(
|
||||
), f'Failed to source /swe_util/swe_entry.sh: {obs.content}'
|
||||
|
||||
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(command='git reset --hard')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -250,6 +251,7 @@ def initialize_runtime(
|
||||
action = CmdRunAction(
|
||||
command='for remote_name in $(git remote); do git remote remove "${remote_name}"; done'
|
||||
)
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -277,18 +279,21 @@ def complete_runtime(
|
||||
workspace_dir_name = _get_swebench_workspace_dir_name(instance)
|
||||
|
||||
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(command='git config --global core.pager ""')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(command='git add -A')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -312,10 +317,10 @@ def complete_runtime(
|
||||
break
|
||||
else:
|
||||
logger.info('Failed to get git diff, retrying...')
|
||||
time.sleep(10)
|
||||
sleep_if_should_continue(10)
|
||||
elif isinstance(obs, ErrorObservation):
|
||||
logger.error(f'Error occurred: {obs.content}. Retrying...')
|
||||
time.sleep(10)
|
||||
sleep_if_should_continue(10)
|
||||
else:
|
||||
raise ValueError(f'Unexpected observation type: {type(obs)}')
|
||||
|
||||
@@ -379,10 +384,7 @@ def process_instance(
|
||||
if state is None:
|
||||
raise ValueError('State should not be None.')
|
||||
|
||||
# history is now available as a stream of events, rather than list of pairs of (Action, Observation)
|
||||
# for compatibility with the existing output format, we can remake the pairs here
|
||||
# remove when it becomes unnecessary
|
||||
histories = state.history.compatibility_for_eval_history_pairs()
|
||||
histories = [event_to_dict(event) for event in state.history.get_events()]
|
||||
metrics = state.metrics.get() if state.metrics else None
|
||||
|
||||
# Save the output
|
||||
@@ -394,6 +396,7 @@ def process_instance(
|
||||
metadata=metadata,
|
||||
history=histories,
|
||||
metrics=metrics,
|
||||
llm_completions=state.extra_data.get('llm_completions', []),
|
||||
error=state.last_error if state and state.last_error else None,
|
||||
)
|
||||
return output
|
||||
@@ -464,6 +467,12 @@ if __name__ == '__main__':
|
||||
output_file = os.path.join(metadata.eval_output_dir, 'output.jsonl')
|
||||
instances = prepare_dataset(swe_bench_tests, output_file, args.eval_n_limit)
|
||||
|
||||
if len(instances) > 0 and not isinstance(
|
||||
instances['PASS_TO_PASS'][instances['PASS_TO_PASS'].index[0]], str
|
||||
):
|
||||
for col in ['PASS_TO_PASS', 'FAIL_TO_PASS']:
|
||||
instances[col] = instances[col].apply(lambda x: str(x))
|
||||
|
||||
run_evaluation(
|
||||
instances, metadata, output_file, args.eval_num_workers, process_instance
|
||||
)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
|
||||
# API base URL
|
||||
BASE_URL="https://api.all-hands.dev/v0"
|
||||
|
||||
# Get the list of runtimes
|
||||
response=$(curl --silent --location --request GET "${BASE_URL}/runtime/list" \
|
||||
--header "X-API-Key: ${ALLHANDS_API_KEY}")
|
||||
|
||||
n_runtimes=$(echo $response | jq -r '.total')
|
||||
echo "Found ${n_runtimes} runtimes. Stopping them..."
|
||||
|
||||
runtime_ids=$(echo $response | jq -r '.runtimes | .[].runtime_id')
|
||||
# Loop through each runtime and stop it
|
||||
counter=1
|
||||
for runtime_id in $runtime_ids; do
|
||||
echo "Stopping runtime ${counter}/${n_runtimes}: ${runtime_id}"
|
||||
curl --silent --location --request POST "${BASE_URL}/runtime/stop" \
|
||||
--header "X-API-Key: ${ALLHANDS_API_KEY}" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data-raw "{\"runtime_id\": \"${runtime_id}\"}"
|
||||
echo
|
||||
((counter++))
|
||||
done
|
||||
|
||||
echo "All runtimes have been stopped."
|
||||
@@ -1,63 +0,0 @@
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('od_output_file', type=str)
|
||||
args = parser.parse_args()
|
||||
output_filepath = args.od_output_file.replace('.jsonl', '.swebench.jsonl')
|
||||
print(f'Converting {args.od_output_file} to {output_filepath}')
|
||||
|
||||
od_format = pd.read_json(args.od_output_file, orient='records', lines=True)
|
||||
# model name is the folder name of od_output_file
|
||||
model_name = os.path.basename(os.path.dirname(args.od_output_file))
|
||||
|
||||
|
||||
def process_git_patch(patch):
|
||||
if not isinstance(patch, str):
|
||||
return ''
|
||||
|
||||
if not patch.strip():
|
||||
# skip empty patches
|
||||
return ''
|
||||
|
||||
patch = patch.replace('\r\n', '\n')
|
||||
# There might be some weird characters at the beginning of the patch
|
||||
# due to some OpenHands inference command outputs
|
||||
|
||||
# FOR EXAMPLE:
|
||||
# git diff --no-color --cached 895f28f9cbed817c00ab68770433170d83132d90
|
||||
# [A[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[C[K0
|
||||
# diff --git a/django/db/models/sql/.backup.query.py b/django/db/models/sql/.backup.query.py
|
||||
# new file mode 100644
|
||||
# index 0000000000..fc13db5948
|
||||
|
||||
# We "find" the first line that starts with "diff" and then we remove lines before it
|
||||
lines = patch.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('diff --git'):
|
||||
patch = '\n'.join(lines[i:])
|
||||
break
|
||||
|
||||
patch = patch.rstrip() + '\n' # Make sure the last line ends with a newline
|
||||
return patch
|
||||
|
||||
|
||||
def convert_row_to_swebench_format(row):
|
||||
if 'git_patch' in row:
|
||||
model_patch = row['git_patch']
|
||||
elif 'test_result' in row and 'git_patch' in row['test_result']:
|
||||
model_patch = row['test_result']['git_patch']
|
||||
else:
|
||||
raise ValueError(f'Row {row} does not have a git_patch')
|
||||
|
||||
return {
|
||||
'instance_id': row['instance_id'],
|
||||
'model_patch': process_git_patch(model_patch),
|
||||
'model_name_or_path': model_name,
|
||||
}
|
||||
|
||||
|
||||
swebench_format = od_format.apply(convert_row_to_swebench_format, axis=1)
|
||||
swebench_format.to_json(output_filepath, lines=True, orient='records')
|
||||
@@ -0,0 +1,35 @@
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from evaluation.swe_bench.eval_infer import process_git_patch
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('oh_output_file', type=str)
|
||||
args = parser.parse_args()
|
||||
output_filepath = args.oh_output_file.replace('.jsonl', '.swebench.jsonl')
|
||||
print(f'Converting {args.oh_output_file} to {output_filepath}')
|
||||
|
||||
oh_format = pd.read_json(args.oh_output_file, orient='records', lines=True)
|
||||
# model name is the folder name of oh_output_file
|
||||
model_name = os.path.basename(os.path.dirname(args.oh_output_file))
|
||||
|
||||
|
||||
def convert_row_to_swebench_format(row):
|
||||
if 'git_patch' in row:
|
||||
model_patch = row['git_patch']
|
||||
elif 'test_result' in row and 'git_patch' in row['test_result']:
|
||||
model_patch = row['test_result']['git_patch']
|
||||
else:
|
||||
raise ValueError(f'Row {row} does not have a git_patch')
|
||||
|
||||
return {
|
||||
'instance_id': row['instance_id'],
|
||||
'model_patch': process_git_patch(model_patch),
|
||||
'model_name_or_path': model_name,
|
||||
}
|
||||
|
||||
|
||||
swebench_format = oh_format.apply(convert_row_to_swebench_format, axis=1)
|
||||
swebench_format.to_json(output_filepath, lines=True, orient='records')
|
||||
@@ -0,0 +1,27 @@
|
||||
import argparse
|
||||
|
||||
import pandas as pd
|
||||
from datasets import load_dataset
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('output_filepath', type=str, help='Path to save the output file')
|
||||
parser.add_argument(
|
||||
'--dataset_name',
|
||||
type=str,
|
||||
help='Name of the dataset to download',
|
||||
default='princeton-nlp/SWE-bench_Lite',
|
||||
)
|
||||
parser.add_argument('--split', type=str, help='Split to download', default='test')
|
||||
args = parser.parse_args()
|
||||
|
||||
dataset = load_dataset(args.dataset_name, split=args.split)
|
||||
output_filepath = args.output_filepath
|
||||
print(
|
||||
f'Downloading gold patches from {args.dataset_name} (split: {args.split}) to {output_filepath}'
|
||||
)
|
||||
patches = [
|
||||
{'instance_id': row['instance_id'], 'model_patch': row['patch']} for row in dataset
|
||||
]
|
||||
print(f'{len(patches)} gold patches loaded')
|
||||
pd.DataFrame(patches).to_json(output_filepath, lines=True, orient='records')
|
||||
print(f'Patches saved to {output_filepath}')
|
||||
@@ -28,9 +28,9 @@ FILE_NAME=$(basename $PROCESS_FILEPATH)
|
||||
echo "Evaluating $FILE_NAME @ $FILE_DIR"
|
||||
|
||||
# ================================================
|
||||
# detect whether PROCESS_FILEPATH is in OD format or in SWE-bench format
|
||||
# detect whether PROCESS_FILEPATH is in OH format or in SWE-bench format
|
||||
echo "=============================================================="
|
||||
echo "Detecting whether PROCESS_FILEPATH is in OD format or in SWE-bench format"
|
||||
echo "Detecting whether PROCESS_FILEPATH is in OH format or in SWE-bench format"
|
||||
echo "=============================================================="
|
||||
# SWE-bench format is a JSONL where every line has three fields: model_name_or_path, instance_id, and model_patch
|
||||
function is_swebench_format() {
|
||||
@@ -56,9 +56,9 @@ if [ $IS_SWEBENCH_FORMAT -eq 0 ]; then
|
||||
else
|
||||
echo "The file IS NOT in SWE-bench format."
|
||||
|
||||
# ==== Convert OD format to SWE-bench format ====
|
||||
# ==== Convert OH format to SWE-bench format ====
|
||||
echo "Merged output file with fine-grained report will be saved to $FILE_DIR"
|
||||
poetry run python3 evaluation/swe_bench/scripts/eval/convert_od_output_to_swe_json.py $PROCESS_FILEPATH
|
||||
poetry run python3 evaluation/swe_bench/scripts/eval/convert_oh_output_to_swe_json.py $PROCESS_FILEPATH
|
||||
# replace .jsonl with .swebench.jsonl in filename
|
||||
SWEBENCH_FORMAT_JSONL=${PROCESS_FILEPATH/.jsonl/.swebench.jsonl}
|
||||
echo "SWEBENCH_FORMAT_JSONL: $SWEBENCH_FORMAT_JSONL"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
INPUT_FILE=$1
|
||||
NUM_WORKERS=$2
|
||||
DATASET=$3
|
||||
SPLIT=$4
|
||||
|
||||
if [ -z "$INPUT_FILE" ]; then
|
||||
echo "INPUT_FILE not specified (should be a path to a jsonl file)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$DATASET" ]; then
|
||||
echo "DATASET not specified, use default princeton-nlp/SWE-bench_Lite"
|
||||
DATASET="princeton-nlp/SWE-bench_Lite"
|
||||
fi
|
||||
|
||||
if [ -z "$SPLIT" ]; then
|
||||
echo "SPLIT not specified, use default test"
|
||||
SPLIT="test"
|
||||
fi
|
||||
|
||||
if [ -z "$NUM_WORKERS" ]; then
|
||||
echo "NUM_WORKERS not specified, use default 1"
|
||||
NUM_WORKERS=1
|
||||
fi
|
||||
|
||||
echo "... Evaluating on $INPUT_FILE ..."
|
||||
|
||||
COMMAND="poetry run python evaluation/swe_bench/eval_infer.py \
|
||||
--eval-num-workers $NUM_WORKERS \
|
||||
--input-file $INPUT_FILE \
|
||||
--dataset $DATASET \
|
||||
--split $SPLIT"
|
||||
|
||||
if [ -n "$EVAL_LIMIT" ]; then
|
||||
echo "EVAL_LIMIT: $EVAL_LIMIT"
|
||||
COMMAND="$COMMAND --eval-n-limit $EVAL_LIMIT"
|
||||
fi
|
||||
|
||||
# Run the command
|
||||
eval $COMMAND
|
||||
@@ -66,6 +66,11 @@ if [ "$USE_HINT_TEXT" = false ]; then
|
||||
EVAL_NOTE="$EVAL_NOTE-no-hint"
|
||||
fi
|
||||
|
||||
if [ -n "$EXP_NAME" ]; then
|
||||
EVAL_NOTE="$EVAL_NOTE-$EXP_NAME"
|
||||
fi
|
||||
echo "EVAL_NOTE: $EVAL_NOTE"
|
||||
|
||||
unset SANDBOX_ENV_GITHUB_TOKEN # prevent the agent from using the github token to push
|
||||
|
||||
COMMAND="poetry run python evaluation/swe_bench/run_infer.py \
|
||||
|
||||
@@ -19,10 +19,10 @@ def extract_modified_files(patch):
|
||||
return modified_files
|
||||
|
||||
|
||||
def process_report(od_output_file):
|
||||
def process_report(oh_output_file):
|
||||
succ = 0
|
||||
fail = 0
|
||||
for line in open(od_output_file):
|
||||
for line in open(oh_output_file):
|
||||
line = json.loads(line)
|
||||
instance_id = line['instance_id']
|
||||
gold_patch = line['swe_instance']['patch']
|
||||
@@ -48,7 +48,7 @@ def process_report(od_output_file):
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--od_output_file', help='Path to the OD output file')
|
||||
parser.add_argument('--oh_output_file', help='Path to the OH output file')
|
||||
args = parser.parse_args()
|
||||
|
||||
process_report(args.od_output_file)
|
||||
process_report(args.oh_output_file)
|
||||
|
||||
@@ -6,15 +6,15 @@ mkdir -p $EVAL_WORKSPACE
|
||||
|
||||
# 1. Prepare REPO
|
||||
echo "==== Prepare SWE-bench repo ===="
|
||||
OD_SWE_BENCH_REPO_PATH="https://github.com/All-Hands-AI/OD-SWE-bench.git"
|
||||
OD_SWE_BENCH_REPO_BRANCH="eval"
|
||||
git clone -b $OD_SWE_BENCH_REPO_BRANCH $OD_SWE_BENCH_REPO_PATH $EVAL_WORKSPACE/OD-SWE-bench
|
||||
OH_SWE_BENCH_REPO_PATH="https://github.com/All-Hands-AI/SWE-bench.git"
|
||||
OH_SWE_BENCH_REPO_BRANCH="eval"
|
||||
git clone -b $OH_SWE_BENCH_REPO_BRANCH $OH_SWE_BENCH_REPO_PATH $EVAL_WORKSPACE/OH-SWE-bench
|
||||
|
||||
# 2. Prepare DATA
|
||||
echo "==== Prepare SWE-bench data ===="
|
||||
EVAL_IMAGE=ghcr.io/all-hands-ai/eval-swe-bench:builder_with_conda
|
||||
EVAL_WORKSPACE=$(realpath $EVAL_WORKSPACE)
|
||||
chmod +x $EVAL_WORKSPACE/OD-SWE-bench/swebench/harness/prepare_data.sh
|
||||
chmod +x $EVAL_WORKSPACE/OH-SWE-bench/swebench/harness/prepare_data.sh
|
||||
if [ -d $EVAL_WORKSPACE/eval_data ]; then
|
||||
rm -r $EVAL_WORKSPACE/eval_data
|
||||
fi
|
||||
@@ -24,4 +24,4 @@ docker run \
|
||||
-u $(id -u):$(id -g) \
|
||||
-e HF_DATASETS_CACHE="/tmp" \
|
||||
--rm -it $EVAL_IMAGE \
|
||||
bash -c "cd OD-SWE-bench/swebench/harness && /swe_util/miniforge3/bin/conda run -n swe-bench-eval ./prepare_data.sh && mv eval_data /workspace/"
|
||||
bash -c "cd OH-SWE-bench/swebench/harness && /swe_util/miniforge3/bin/conda run -n swe-bench-eval ./prepare_data.sh && mv eval_data /workspace/"
|
||||
|
||||
@@ -60,7 +60,7 @@ conda activate swe-bench-eval
|
||||
|
||||
mkdir -p $SWE_TASK_DIR/reset_testbed_temp
|
||||
mkdir -p $SWE_TASK_DIR/reset_testbed_log_dir
|
||||
SWE_BENCH_DIR=/swe_util/OD-SWE-bench
|
||||
SWE_BENCH_DIR=/swe_util/OH-SWE-bench
|
||||
output=$(
|
||||
export PYTHONPATH=$SWE_BENCH_DIR && \
|
||||
cd $SWE_BENCH_DIR && \
|
||||
|
||||
@@ -5,8 +5,8 @@ import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import time
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from typing import Any, Awaitable, Callable
|
||||
import traceback
|
||||
from typing import Any, Awaitable, Callable, TextIO
|
||||
|
||||
import pandas as pd
|
||||
from pydantic import BaseModel
|
||||
@@ -49,15 +49,20 @@ class EvalMetadata(BaseModel):
|
||||
class EvalOutput(BaseModel):
|
||||
# NOTE: User-specified
|
||||
instance_id: str
|
||||
instruction: str
|
||||
# output of the evaluation
|
||||
# store anything that is needed for the score calculation
|
||||
test_result: dict[str, Any]
|
||||
|
||||
instruction: str | None = None
|
||||
|
||||
# Interaction info
|
||||
metadata: EvalMetadata
|
||||
history: list[tuple[dict[str, Any], dict[str, Any]]]
|
||||
metrics: dict[str, Any]
|
||||
metadata: EvalMetadata | None = None
|
||||
# list[tuple[dict[str, Any], dict[str, Any]]] - for compatibility with the old format
|
||||
history: (
|
||||
list[dict[str, Any]] | list[tuple[dict[str, Any], dict[str, Any]]] | None
|
||||
) = None
|
||||
llm_completions: list[dict[str, Any]]
|
||||
metrics: dict[str, Any] | None = None
|
||||
error: str | None = None
|
||||
|
||||
# Optionally save the input test instance
|
||||
@@ -65,15 +70,19 @@ class EvalOutput(BaseModel):
|
||||
|
||||
def model_dump(self, *args, **kwargs):
|
||||
dumped_dict = super().model_dump(*args, **kwargs)
|
||||
# Remove None values
|
||||
dumped_dict = {k: v for k, v in dumped_dict.items() if v is not None}
|
||||
# Apply custom serialization for metadata (to avoid leaking sensitive information)
|
||||
dumped_dict['metadata'] = self.metadata.model_dump()
|
||||
if self.metadata is not None:
|
||||
dumped_dict['metadata'] = self.metadata.model_dump()
|
||||
return dumped_dict
|
||||
|
||||
def model_dump_json(self, *args, **kwargs):
|
||||
dumped = super().model_dump_json(*args, **kwargs)
|
||||
dumped_dict = json.loads(dumped)
|
||||
# Apply custom serialization for metadata (to avoid leaking sensitive information)
|
||||
dumped_dict['metadata'] = json.loads(self.metadata.model_dump_json())
|
||||
if 'metadata' in dumped_dict:
|
||||
dumped_dict['metadata'] = json.loads(self.metadata.model_dump_json())
|
||||
return json.dumps(dumped_dict)
|
||||
|
||||
|
||||
@@ -227,56 +236,120 @@ def prepare_dataset(
|
||||
return pd.DataFrame(new_dataset)
|
||||
|
||||
|
||||
def update_progress(
|
||||
result: EvalOutput,
|
||||
pbar: tqdm,
|
||||
output_fp: TextIO,
|
||||
):
|
||||
"""Update the progress bar and write the result to the output file."""
|
||||
pbar.update(1)
|
||||
pbar.set_description(f'Instance {result.instance_id}')
|
||||
pbar.set_postfix_str(f'Test Result: {result.test_result}')
|
||||
logger.info(
|
||||
f'Finished evaluation for instance {result.instance_id}: {str(result.test_result)[:300]}...\n'
|
||||
)
|
||||
output_fp.write(json.dumps(result.model_dump()) + '\n')
|
||||
output_fp.flush()
|
||||
|
||||
|
||||
def _process_instance_wrapper(
|
||||
process_instance_func: Callable[[pd.Series, EvalMetadata, bool], EvalOutput],
|
||||
instance: pd.Series,
|
||||
metadata: EvalMetadata,
|
||||
use_mp: bool,
|
||||
max_retries: int = 5,
|
||||
) -> EvalOutput:
|
||||
"""Wrap the process_instance_func to handle retries and errors.
|
||||
|
||||
Retry an instance up to max_retries times if it fails (e.g., due to transient network/runtime issues).
|
||||
"""
|
||||
for attempt in range(max_retries + 1):
|
||||
try:
|
||||
result = process_instance_func(instance, metadata, use_mp)
|
||||
return result
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
stacktrace = traceback.format_exc()
|
||||
if attempt == max_retries:
|
||||
logger.exception(e)
|
||||
msg = (
|
||||
'-' * 10
|
||||
+ '\n'
|
||||
+ f'Error in instance [{instance.instance_id}]: {error}. Stacktrace:\n{stacktrace}'
|
||||
+ '\n'
|
||||
+ f'[Encountered after {max_retries} retries. Please check the logs and report the issue.]'
|
||||
+ '-' * 10
|
||||
)
|
||||
# Raise an error after all retries & stop the evaluation
|
||||
logger.exception(e)
|
||||
raise RuntimeError(
|
||||
f'Maximum error retries reached for instance {instance.instance_id}'
|
||||
) from e
|
||||
msg = (
|
||||
'-' * 10
|
||||
+ '\n'
|
||||
+ f'Error in instance [{instance.instance_id}]: {error}. Stacktrace:\n{stacktrace}'
|
||||
+ '\n'
|
||||
+ '-' * 10
|
||||
+ f'[The above error occurred. Retrying... (attempt {attempt + 1} of {max_retries})]'
|
||||
+ '-' * 10
|
||||
+ '\n'
|
||||
)
|
||||
logger.error(msg)
|
||||
if use_mp:
|
||||
print(msg) # use print to directly print to console
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
def _process_instance_wrapper_mp(args):
|
||||
"""Wrapper for multiprocessing, especially for imap_unordered."""
|
||||
return _process_instance_wrapper(*args)
|
||||
|
||||
|
||||
def run_evaluation(
|
||||
dataset: pd.DataFrame,
|
||||
metadata: EvalMetadata,
|
||||
metadata: EvalMetadata | None,
|
||||
output_file: str,
|
||||
num_workers: int,
|
||||
process_instance_func: Callable[
|
||||
[pd.Series, EvalMetadata, bool], Awaitable[EvalOutput]
|
||||
],
|
||||
max_retries: int = 5, # number of retries for each instance
|
||||
):
|
||||
use_multiprocessing = num_workers > 1
|
||||
logger.info(
|
||||
f'Evaluation started with Agent {metadata.agent_class}:\n'
|
||||
f'model {metadata.llm_config.model}, max iterations {metadata.max_iterations}.\n'
|
||||
)
|
||||
pbar = tqdm(total=len(dataset))
|
||||
output_fp = open(output_file, 'a')
|
||||
|
||||
def update_progress(future):
|
||||
pbar.update(1)
|
||||
output: EvalOutput = future.result() if use_multiprocessing else future
|
||||
|
||||
pbar.set_description(f'Instance {output.instance_id}')
|
||||
pbar.set_postfix_str(f'Test Result: {output.test_result}')
|
||||
if metadata is not None:
|
||||
logger.info(
|
||||
f'Finished evaluation for instance {output.instance_id}: {str(output.test_result)[:300]}...\n'
|
||||
f'Evaluation started with Agent {metadata.agent_class}:\n'
|
||||
f'model {metadata.llm_config.model}, max iterations {metadata.max_iterations}.\n'
|
||||
)
|
||||
output_fp.write(json.dumps(output.model_dump()) + '\n')
|
||||
output_fp.flush()
|
||||
else:
|
||||
logger.info(f'Evaluation started with {num_workers} workers.')
|
||||
|
||||
total_instances = len(dataset)
|
||||
pbar = tqdm(total=total_instances, desc='Instances processed')
|
||||
output_fp = open(output_file, 'a')
|
||||
|
||||
try:
|
||||
if use_multiprocessing:
|
||||
with ProcessPoolExecutor(num_workers) as executor:
|
||||
futures = []
|
||||
for _, instance in dataset.iterrows():
|
||||
future = executor.submit(
|
||||
process_instance_func,
|
||||
instance,
|
||||
metadata,
|
||||
bool(num_workers > 1),
|
||||
)
|
||||
future.add_done_callback(update_progress)
|
||||
futures.append(future)
|
||||
for future in futures:
|
||||
future.result()
|
||||
# Use plain for loop for single process for easier debugging
|
||||
with mp.Pool(num_workers) as pool:
|
||||
args_iter = (
|
||||
(process_instance_func, instance, metadata, True, max_retries)
|
||||
for _, instance in dataset.iterrows()
|
||||
)
|
||||
results = pool.imap_unordered(_process_instance_wrapper_mp, args_iter)
|
||||
for result in results:
|
||||
update_progress(result, pbar, output_fp)
|
||||
else:
|
||||
assert num_workers == 1
|
||||
for _, instance in dataset.iterrows():
|
||||
output = process_instance_func(instance, metadata, False)
|
||||
update_progress(output)
|
||||
result = _process_instance_wrapper(
|
||||
process_instance_func=process_instance_func,
|
||||
instance=instance,
|
||||
metadata=metadata,
|
||||
use_mp=False,
|
||||
max_retries=max_retries,
|
||||
)
|
||||
update_progress(result, pbar, output_fp)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print('\nKeyboardInterrupt received. Cleaning up...\n')
|
||||
|
||||
@@ -37,7 +37,7 @@ poetry run python evaluation/webarena/get_success_rate.py evaluation/evaluation_
|
||||
|
||||
## Submit your evaluation results
|
||||
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenDevin/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
|
||||
## BrowsingAgent V1.0 result
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "openhands-frontend",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@nextui-org/react": "^2.4.6",
|
||||
"@nextui-org/react": "^2.4.8",
|
||||
"@react-types/shared": "^3.24.1",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
@@ -16,23 +16,23 @@
|
||||
"@xterm/xterm": "^5.4.0",
|
||||
"clsx": "^2.1.1",
|
||||
"eslint-config-airbnb-typescript": "^18.0.0",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next": "^23.15.1",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"i18next-http-backend": "^2.6.1",
|
||||
"jose": "^5.8.0",
|
||||
"monaco-editor": "^0.51.0",
|
||||
"jose": "^5.9.3",
|
||||
"monaco-editor": "^0.52.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-highlight": "^0.15.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-i18next": "^15.0.1",
|
||||
"react-i18next": "^15.0.2",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"tailwind-merge": "^2.5.2",
|
||||
"vite": "^5.4.2",
|
||||
"vite": "^5.4.7",
|
||||
"web-vitals": "^3.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -64,8 +64,8 @@
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/node": "^22.5.2",
|
||||
"@types/react": "^18.3.5",
|
||||
"@types/node": "^22.6.1",
|
||||
"@types/react": "^18.3.8",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/react-highlight": "^0.12.8",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
@@ -82,13 +82,13 @@
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.35.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"husky": "^9.1.5",
|
||||
"husky": "^9.1.6",
|
||||
"jsdom": "^25.0.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"postcss": "^8.4.44",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"tailwindcss": "^3.4.10",
|
||||
"typescript": "^5.5.4",
|
||||
"tailwindcss": "^3.4.12",
|
||||
"typescript": "^5.6.2",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 754 B After Width: | Height: | Size: 803 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -1,472 +1,32 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="1024.000000pt" height="1024.000000pt" viewBox="0 0 1024.000000 1024.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,1024.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M5195 8749 c-122 -19 -282 -101 -383 -197 -66 -61 -59 -81 52 -160
|
||||
49 -34 99 -65 111 -68 16 -4 44 9 94 41 93 60 169 94 256 112 l70 14 82 -42
|
||||
c46 -23 83 -44 83 -48 -1 -3 -34 -21 -74 -40 -217 -98 -499 -312 -667 -505
|
||||
-138 -157 -309 -430 -309 -493 0 -38 66 -100 149 -140 42 -20 77 -38 79 -39 2
|
||||
-1 -17 -32 -43 -70 -185 -277 -278 -669 -246 -1041 20 -245 61 -392 167 -612
|
||||
35 -74 63 -135 62 -135 -2 -1 -30 -10 -62 -21 -38 -13 -67 -30 -79 -47 -19
|
||||
-26 -19 -29 -3 -71 23 -61 101 -184 166 -262 99 -119 265 -247 425 -327 149
|
||||
-75 397 -152 602 -188 l72 -13 -63 -67 c-139 -147 -205 -389 -139 -503 11 -18
|
||||
40 -43 70 -58 50 -26 51 -28 33 -44 -11 -10 -40 -58 -66 -109 -77 -149 -235
|
||||
-389 -267 -407 -17 -10 -269 -65 -352 -78 -38 -6 -115 -15 -169 -20 -74 -8
|
||||
-107 -15 -128 -31 -21 -16 -47 -21 -110 -24 -61 -3 -121 -15 -223 -46 -77 -23
|
||||
-153 -49 -168 -57 -16 -8 -52 -51 -84 -101 -31 -48 -69 -103 -85 -122 -54 -66
|
||||
-31 -122 68 -159 47 -18 48 -19 18 -25 -49 -10 -352 -44 -694 -76 -314 -30
|
||||
-326 -32 -347 -55 -19 -22 -31 -25 -85 -25 -91 0 -104 -12 -112 -103 -8 -94 3
|
||||
-121 58 -142 23 -8 174 -56 336 -106 162 -50 445 -137 628 -195 183 -57 482
|
||||
-149 665 -205 182 -55 367 -111 410 -125 43 -13 102 -24 130 -24 97 0 1501
|
||||
180 1564 201 46 15 77 70 77 135 0 82 -43 130 -126 141 -24 4 -59 16 -78 29
|
||||
-22 14 -129 46 -280 83 -135 34 -255 64 -268 67 -20 6 -16 10 30 31 29 13 63
|
||||
34 75 45 22 21 24 21 122 5 124 -21 158 -21 217 -1 46 15 48 15 108 -13 64
|
||||
-29 144 -37 231 -21 22 4 29 4 16 0 -24 -7 -23 -8 15 -19 68 -20 155 -35 169
|
||||
-29 8 3 15 0 17 -6 3 -7 10 -8 17 -4 11 7 11 10 0 17 -10 6 -8 9 9 9 14 0 21
|
||||
-4 16 -11 -4 -7 0 -9 14 -5 12 4 128 -13 259 -38 131 -25 362 -67 513 -95 815
|
||||
-146 759 -135 707 -143 -27 -3 -51 -9 -54 -11 -2 -3 38 -12 89 -21 51 -10 125
|
||||
-23 163 -31 98 -19 336 -53 420 -60 68 -5 69 -5 30 8 -22 7 -73 19 -114 27
|
||||
-41 8 -70 15 -64 17 11 4 120 -12 607 -92 154 -25 281 -43 284 -40 8 7 -16 13
|
||||
-153 39 -407 77 -958 186 -963 190 -8 8 15 135 48 268 46 182 103 350 221 643
|
||||
144 360 219 605 288 936 69 337 104 670 99 964 -3 232 -6 241 -89 282 -50 25
|
||||
-234 64 -414 88 -66 9 -134 18 -151 21 l-31 4 57 60 c233 242 426 579 505 884
|
||||
14 55 30 105 35 112 23 27 71 180 78 247 4 40 3 110 -1 157 -11 105 -11 95 -2
|
||||
210 5 73 3 111 -9 164 -31 131 -107 254 -204 328 -26 20 -42 40 -42 54 0 12
|
||||
-27 74 -59 137 -204 399 -520 697 -963 912 -375 181 -795 278 -1268 292 -338
|
||||
10 -691 -35 -972 -123 l-77 -25 -233 109 c-295 138 -320 146 -413 131z m107
|
||||
-89 c20 -6 65 -24 100 -40 l63 -30 -65 6 c-68 6 -173 -10 -260 -41 -52 -18
|
||||
-72 -32 -87 -59 -10 -18 -14 -14 -37 32 l-26 52 47 23 c68 34 154 60 213 66 8
|
||||
0 32 -4 52 -9z m1843 -110 c448 -52 824 -167 1126 -343 307 -179 534 -393 691
|
||||
-652 63 -102 101 -175 96 -181 -2 -2 -26 12 -52 31 -27 19 -64 39 -82 45 -19
|
||||
7 -34 15 -34 18 -1 31 -32 148 -48 178 -32 63 -65 88 -105 80 -29 -5 -35 -3
|
||||
-48 20 -8 15 -44 52 -81 83 -178 150 -544 337 -968 495 -129 48 -139 50 -235
|
||||
50 -130 -1 -217 -23 -378 -96 -86 -40 -93 -59 -44 -126 41 -55 141 -152 157
|
||||
-152 6 0 34 12 63 26 100 51 255 94 338 94 51 0 160 -42 324 -126 173 -88 320
|
||||
-176 394 -236 l61 -49 -35 -30 c-19 -17 -38 -42 -41 -54 -3 -14 -16 -26 -32
|
||||
-30 -63 -16 -56 -59 31 -192 l73 -110 -39 -44 c-53 -61 -138 -191 -180 -274
|
||||
-64 -130 -120 -339 -134 -502 l-6 -72 -71 -11 c-39 -6 -87 -13 -107 -16 l-36
|
||||
-5 -13 73 c-56 317 -249 568 -557 726 -254 130 -574 200 -1108 244 -339 27
|
||||
-1062 15 -1363 -23 -46 -6 -85 -9 -88 -7 -6 7 63 131 124 220 365 540 1013
|
||||
885 1797 958 120 11 484 5 610 -10z m-2169 -15 c20 -30 11 -81 -15 -88 -18 -4
|
||||
-81 28 -81 41 0 6 70 70 77 72 2 0 10 -11 19 -25z m2662 -299 c67 -27 122 -52
|
||||
122 -57 0 -12 -22 -7 -35 7 -5 6 -26 14 -45 17 -19 3 -44 9 -54 13 -14 5 -17
|
||||
4 -12 -4 4 -7 2 -12 -3 -12 -6 0 -11 5 -11 11 0 6 -7 9 -15 6 -8 -4 -17 -2
|
||||
-20 3 -3 5 -12 7 -20 4 -8 -4 -29 -1 -46 5 -24 8 -36 8 -45 0 -11 -9 -14 -8
|
||||
-14 2 0 11 -2 11 -9 0 -6 -10 -30 -16 -68 -17 -32 -2 -58 -4 -58 -4 0 -1 -8
|
||||
-3 -17 -5 -9 -2 -19 4 -22 12 -9 21 -29 4 -22 -19 2 -12 -1 -27 -9 -34 -18
|
||||
-18 -25 -18 -25 1 0 9 -7 30 -16 46 -8 17 -13 34 -10 37 21 20 149 42 236 39
|
||||
87 -2 105 -6 218 -51z m-478 -25 c17 -32 12 -61 -10 -61 -10 0 -23 -8 -29 -17
|
||||
-9 -16 -12 -15 -40 11 -17 17 -31 33 -31 37 0 7 77 47 92 49 4 0 12 -9 18 -19z
|
||||
m710 -76 c10 6 102 -34 112 -49 4 -6 8 -8 8 -4 0 4 13 2 30 -3 16 -6 28 -14
|
||||
25 -19 -2 -4 8 -13 23 -20 22 -11 24 -14 9 -18 -21 -4 -47 14 -47 34 0 14 -73
|
||||
14 -101 0 -8 -4 -5 -1 5 8 17 14 17 16 3 22 -9 3 -15 10 -12 14 3 5 -5 5 -17
|
||||
2 -19 -5 -20 -7 -5 -10 20 -4 22 -17 5 -28 -7 -5 -8 -3 -3 6 6 10 4 12 -8 7
|
||||
-9 -3 -23 1 -32 9 -8 8 -20 12 -26 8 -8 -4 -9 -3 -5 5 4 6 16 9 27 6 13 -4 19
|
||||
-2 19 9 0 9 -5 15 -11 14 -6 -2 -13 3 -14 10 -2 7 -1 8 2 2 2 -5 8 -8 13 -5z
|
||||
m384 -187 c-3 -4 -9 -8 -15 -8 -5 0 -9 4 -9 8 0 5 7 9 15 9 8 0 12 -4 9 -9z
|
||||
m241 -144 c39 -28 83 -65 99 -82 l29 -31 -25 -6 c-40 -10 -61 -2 -79 30 -9 17
|
||||
-37 51 -63 77 -25 26 -46 53 -46 58 0 6 -5 8 -12 4 -7 -4 -8 -3 -4 5 9 13 11
|
||||
12 101 -55z m-61 -152 c7 -12 -101 -68 -111 -59 -11 12 58 66 84 67 12 0 24
|
||||
-4 27 -8z m348 -64 l18 -48 -73 0 c-85 0 -150 -16 -246 -60 -68 -32 -95 -33
|
||||
-84 -5 12 33 204 102 306 112 37 3 64 9 60 14 -10 9 -157 -14 -225 -37 -29 -9
|
||||
-82 -32 -117 -50 -74 -39 -96 -41 -131 -13 l-25 21 36 -5 c25 -4 54 3 95 20
|
||||
126 53 162 65 244 83 116 26 121 24 142 -32z m-24 -113 c-2 -3 -29 -9 -59 -15
|
||||
-30 -6 -97 -31 -149 -56 -93 -45 -94 -45 -66 -15 32 34 111 68 182 80 59 10
|
||||
99 13 92 6z m317 -219 c113 -53 219 -169 265 -292 18 -51 19 -59 6 -85 -8 -17
|
||||
-10 -31 -5 -35 5 -3 13 5 18 18 8 19 9 16 9 -19 0 -22 3 -46 6 -52 4 -6 5 -11
|
||||
3 -11 -2 0 -16 17 -30 39 -20 30 -23 41 -14 51 9 10 9 11 -3 8 -9 -3 -35 14
|
||||
-63 39 -46 43 -156 103 -188 103 -9 0 -19 10 -23 23 -8 32 -60 144 -91 195
|
||||
l-26 42 43 0 c24 0 65 -11 93 -24z m-250 -76 c41 -67 79 -166 70 -181 -4 -5
|
||||
-12 -9 -18 -9 -31 0 -158 -72 -208 -118 -43 -39 -68 -74 -103 -144 -95 -192
|
||||
-101 -405 -16 -586 18 -38 27 -69 22 -74 -16 -16 -88 -11 -134 10 -86 37 -172
|
||||
152 -184 245 -4 26 -11 47 -17 47 -17 0 1 160 28 256 51 176 158 351 286 468
|
||||
62 56 201 146 226 146 6 0 28 -27 48 -60z m-295 2 c0 -5 -34 -43 -75 -86 -41
|
||||
-42 -96 -107 -122 -144 -39 -54 -49 -63 -51 -45 -10 67 166 283 230 283 10 0
|
||||
18 -4 18 -8z m-3625 -38 c-42 -65 -90 -153 -129 -237 -35 -76 -41 -97 -34
|
||||
-122 13 -48 0 -84 -38 -107 -19 -11 -34 -21 -34 -22 0 -1 -9 -42 -19 -91 -36
|
||||
-162 -47 -318 -41 -525 3 -69 5 -55 11 75 11 230 41 363 78 341 5 -3 12 2 15
|
||||
11 6 14 19 15 109 9 91 -6 142 -11 142 -16 0 -1 10 -3 22 -6 18 -3 27 4 44 37
|
||||
11 23 22 39 24 37 2 -2 26 -69 53 -148 28 -80 73 -201 101 -270 62 -148 61
|
||||
-145 67 -240 7 -93 -16 -250 -45 -310 -13 -26 -40 -59 -67 -79 -74 -56 -104
|
||||
-68 -156 -64 -27 2 -48 -1 -48 -6 0 -5 -8 -11 -17 -13 -16 -3 -15 -7 8 -29 14
|
||||
-14 32 -29 40 -32 12 -5 12 -7 -1 -16 -10 -7 -17 -6 -22 2 -6 9 -8 9 -8 -1 0
|
||||
-10 -5 -10 -22 -1 -20 11 -22 11 -10 -3 10 -13 10 -18 1 -24 -8 -4 -9 -3 -5 4
|
||||
6 11 2 13 -19 12 -3 0 -26 11 -50 26 -27 16 -54 24 -66 21 -18 -5 -28 10 -79
|
||||
116 -68 142 -111 268 -142 411 -18 85 -22 134 -22 311 0 218 13 336 54 466 11
|
||||
37 15 75 12 112 -5 67 14 107 52 107 20 0 31 13 62 73 42 81 75 136 112 185
|
||||
18 23 32 32 54 32 l30 0 -17 -26z m1046 16 c27 -15 24 -24 -18 -71 -49 -54
|
||||
-89 -78 -198 -119 -195 -72 -303 -179 -415 -411 -22 -46 -40 -78 -40 -72 0 9
|
||||
-82 51 -115 58 -29 6 -46 19 -41 31 3 7 0 16 -6 20 -7 4 -8 3 -4 -4 7 -12 -6
|
||||
-17 -17 -6 -18 18 122 230 222 336 113 121 235 201 352 232 63 17 251 21 280
|
||||
6z m3039 -130 c0 -5 -8 -10 -17 -10 -15 0 -16 2 -3 10 19 12 20 12 20 0z
|
||||
m-823 -221 c-35 -74 -69 -150 -77 -169 -21 -55 -30 -58 -30 -9 0 100 54 249
|
||||
113 312 25 27 30 28 43 16 13 -13 7 -31 -49 -150z m952 138 c24 -12 59 -90 76
|
||||
-166 l7 -34 -38 31 c-54 44 -114 62 -200 62 l-74 0 0 49 c0 48 1 49 38 59 53
|
||||
14 163 14 191 -1z m-63 -219 c51 -45 74 -99 74 -175 0 -36 -4 -63 -10 -63 -5
|
||||
0 -10 -9 -10 -19 0 -10 -6 -21 -14 -24 -11 -4 -13 -13 -7 -34 8 -28 -5 -73
|
||||
-21 -73 -4 0 -7 -6 -5 -12 1 -7 -3 -12 -9 -11 -5 1 -16 -7 -23 -19 -10 -16
|
||||
-10 -19 1 -12 10 6 10 4 -1 -9 -7 -9 -17 -14 -22 -12 -4 3 -15 -5 -24 -19 -29
|
||||
-44 -108 -65 -185 -50 -70 14 -121 159 -99 280 29 159 148 283 271 284 39 0
|
||||
54 -5 84 -32z m-961 -224 c0 -22 -5 -29 -20 -29 -24 0 -34 43 -13 56 21 14 33
|
||||
4 33 -27z m-1195 -115 c22 -7 40 -16 40 -21 0 -4 6 -8 14 -8 8 0 33 -16 55
|
||||
-34 71 -59 157 -204 180 -301 14 -60 19 -272 8 -335 -13 -71 -13 -72 -36 -125
|
||||
-36 -80 -44 -95 -53 -95 -4 0 -8 -6 -8 -14 0 -8 -5 -18 -12 -22 -7 -5 -9 -2
|
||||
-4 7 4 8 -1 4 -11 -8 -21 -25 -138 -85 -147 -76 -3 4 -6 1 -6 -5 0 -6 -7 -9
|
||||
-15 -6 -8 4 -15 1 -15 -6 0 -6 -4 -9 -9 -6 -5 3 -26 8 -47 11 -137 20 -247 93
|
||||
-274 180 -7 22 -19 53 -27 68 -8 16 -16 36 -18 45 -32 137 -36 169 -36 302 0
|
||||
80 2 152 4 160 12 59 44 140 65 168 34 47 79 92 92 92 6 0 22 8 37 17 47 31
|
||||
142 36 223 12z m1060 -98 c0 -38 9 -121 20 -184 35 -210 110 -369 229 -488
|
||||
145 -144 299 -168 529 -83 116 43 296 135 334 172 46 43 43 18 -7 -70 -216
|
||||
-375 -485 -627 -883 -827 -522 -262 -1255 -395 -1988 -360 -514 24 -883 122
|
||||
-1173 312 -133 88 -278 228 -334 324 -4 6 39 -12 95 -41 275 -139 616 -208
|
||||
953 -191 403 19 1003 133 1286 244 238 93 447 251 567 428 129 191 177 368
|
||||
170 636 l-3 144 90 25 c50 14 96 26 103 27 8 1 12 -19 12 -68z m128 37 c14
|
||||
-14 16 -94 3 -102 -30 -19 -56 28 -45 83 7 32 22 39 42 19z m1114 -185 c-6
|
||||
-26 -14 -57 -18 -68 -6 -16 1 -13 36 14 45 33 45 33 -7 -17 -95 -91 -226 -131
|
||||
-331 -103 l-43 12 22 37 c12 20 24 39 26 41 2 3 36 8 75 12 84 9 159 43 207
|
||||
93 19 20 37 34 39 32 2 -3 -1 -26 -6 -53z m32 -158 c-4 -8 -10 -15 -16 -15 -5
|
||||
0 -6 -6 -2 -12 4 -7 4 -10 -1 -6 -4 4 -16 -3 -26 -15 -16 -19 -18 -19 -12 -3
|
||||
5 15 3 17 -8 11 -19 -11 -2 6 35 33 34 26 37 27 30 7z m-149 -88 c-11 -7 -35
|
||||
-28 -54 -47 l-35 -35 20 34 c10 18 33 40 49 47 37 16 47 17 20 1z m-297 -84
|
||||
c-60 -88 -154 -190 -165 -180 -4 4 14 29 39 55 25 26 71 82 102 125 73 100 93
|
||||
100 24 0z m372 54 c0 -2 -19 -21 -42 -42 l-43 -40 40 43 c36 39 45 47 45 39z
|
||||
m-215 -146 c3 -6 -4 -15 -15 -21 -20 -11 -21 -10 -10 9 12 23 16 25 25 12z
|
||||
m-165 -41 c-11 -16 -26 -33 -33 -37 -12 -7 0 11 32 50 23 28 24 20 1 -13z
|
||||
m-1221 -46 c9 -8 -102 -149 -183 -230 -206 -208 -417 -302 -876 -394 -340 -68
|
||||
-617 -95 -885 -87 -217 6 -371 31 -539 87 -186 63 -464 202 -425 214 8 2 36
|
||||
11 63 20 67 21 74 21 126 -15 120 -81 319 -161 504 -201 90 -20 129 -23 366
|
||||
-22 297 0 442 15 747 79 484 101 758 245 945 496 l50 68 51 -6 c29 -3 54 -7
|
||||
56 -9z m1341 -640 c104 -15 247 -40 285 -51 13 -4 -3 -12 -50 -25 -91 -25 -99
|
||||
-25 -170 5 -33 14 -103 38 -155 52 -176 49 -154 53 90 19z m462 -132 c17 -65
|
||||
0 -429 -33 -692 -57 -461 -173 -905 -341 -1309 -24 -58 -48 -106 -55 -108 -15
|
||||
-5 -80 53 -98 88 -14 26 -12 37 35 170 28 78 49 146 48 151 -2 5 -25 -44 -52
|
||||
-109 -27 -65 -93 -217 -146 -338 -54 -121 -103 -237 -111 -257 -14 -42 -17
|
||||
-44 -43 -25 -17 13 -12 27 82 228 160 343 224 502 316 784 123 378 198 842
|
||||
182 1120 -6 111 -28 229 -46 251 -12 13 17 27 90 43 113 26 166 27 172 3z
|
||||
m-1252 -392 c63 -5 215 -10 336 -10 l222 0 7 -27 c3 -16 9 -131 12 -256 6
|
||||
-245 -6 -401 -35 -449 -21 -36 -69 -58 -126 -58 -40 0 -46 3 -52 25 -3 14 -3
|
||||
36 0 51 4 14 7 123 7 242 1 282 -18 394 -70 421 -10 6 -90 15 -177 21 -150 10
|
||||
-240 27 -274 50 -12 8 -9 9 10 5 14 -2 77 -9 140 -15z m-275 -35 c241 -62 374
|
||||
-242 375 -505 0 -115 -19 -144 -133 -199 -101 -50 -171 -69 -306 -82 -113 -11
|
||||
-271 1 -340 26 -170 60 -231 160 -201 333 33 197 175 352 364 398 39 9 85 23
|
||||
101 30 40 17 69 17 140 -1z m-1874 -81 l36 -6 -14 -41 c-9 -27 -12 -57 -8 -85
|
||||
5 -33 3 -47 -9 -55 -8 -7 -24 -41 -36 -77 -12 -36 -36 -90 -54 -120 -18 -30
|
||||
-41 -76 -51 -102 -16 -42 -21 -48 -47 -48 -45 0 -97 29 -115 63 -29 57 9 210
|
||||
78 315 38 58 150 162 174 162 5 0 26 -3 46 -6z m126 -131 c10 -12 12 -13 8 -3
|
||||
-4 8 1 6 10 -5 9 -11 19 -19 23 -18 4 2 15 0 25 -3 13 -4 15 -9 8 -18 -14 -17
|
||||
-2 -29 15 -15 10 8 11 5 7 -12 -4 -15 -3 -20 5 -15 7 4 12 3 12 -2 0 -5 10
|
||||
-12 22 -15 12 -4 20 -9 17 -12 -9 -8 -88 30 -105 51 -9 10 -13 13 -9 7 10 -22
|
||||
-23 -3 -38 20 -18 28 -28 57 -20 57 3 0 12 -8 20 -17z m180 -74 c21 -8 24 -12
|
||||
14 -19 -17 -10 -38 -3 -45 16 -6 17 -5 17 31 3z m724 -40 c-6 -24 -11 -78 -11
|
||||
-120 l0 -76 -46 -36 c-180 -140 -693 -157 -854 -29 -53 43 -72 87 -66 149 6
|
||||
53 25 106 35 101 3 -2 44 -22 91 -45 114 -57 238 -86 365 -86 160 -1 284 42
|
||||
435 151 30 22 56 39 58 37 1 -1 -2 -22 -7 -46z m-711 -9 c-20 -13 -33 -13 -25
|
||||
0 3 6 14 10 23 10 15 0 15 -2 2 -10z m-309 -254 c73 -120 247 -173 536 -163
|
||||
222 8 391 54 495 137 28 23 38 27 46 17 14 -17 47 -143 63 -241 14 -87 38
|
||||
-131 135 -242 29 -34 50 -64 47 -68 -3 -3 -1 -6 6 -6 6 0 28 -15 47 -32 33
|
||||
-30 36 -37 31 -73 -7 -53 -15 -89 -33 -155 -25 -89 -24 -87 -34 -70 -6 8 -10
|
||||
11 -10 5 0 -5 -12 4 -26 20 -14 17 -22 24 -19 17 4 -7 -31 14 -78 47 -161 116
|
||||
-495 262 -714 312 -109 25 -337 36 -411 20 -75 -16 -140 -49 -159 -82 -10 -16
|
||||
-21 -29 -25 -29 -28 0 -46 210 -29 330 18 118 77 301 98 300 4 0 20 -20 34
|
||||
-44z m2441 -248 c74 -8 112 -8 159 1 33 7 64 10 67 6 16 -16 -161 -611 -192
|
||||
-642 -28 -30 -107 -6 -121 37 -3 11 16 97 43 192 65 224 94 358 78 358 -6 0
|
||||
-42 5 -79 10 -62 9 -93 24 -150 71 -5 3 16 0 45 -7 29 -8 97 -19 150 -26z
|
||||
m-233 -47 c24 -18 23 -18 -120 -24 -86 -3 -202 -15 -289 -30 -156 -28 -310
|
||||
-46 -310 -38 0 3 21 27 47 53 l47 48 91 0 c133 0 262 16 346 43 l74 23 45 -28
|
||||
c25 -15 56 -36 69 -47z m-2433 51 c7 -5 8 -32 3 -82 -10 -104 4 -236 32 -300
|
||||
18 -42 38 -65 96 -111 91 -72 88 -78 -3 -9 -113 87 -224 129 -336 130 l-47 0
|
||||
74 110 c41 60 92 146 112 190 37 78 46 88 69 72z m2448 -162 l59 0 -6 -94
|
||||
c-12 -175 -77 -427 -156 -600 l-31 -69 -28 53 c-63 125 -217 236 -362 262 -59
|
||||
10 -186 0 -235 -18 -10 -4 -8 11 8 58 17 51 21 89 21 200 l1 138 45 5 c25 3
|
||||
126 17 225 32 226 32 320 42 365 37 19 -2 62 -4 94 -4z m-1904 -334 c0 -88 24
|
||||
-218 59 -316 l29 -83 -81 6 c-78 5 -84 7 -110 39 -60 71 -98 153 -104 227 -9
|
||||
112 18 152 122 177 87 21 85 22 85 -50z m225 34 c141 -29 340 -116 488 -213
|
||||
49 -33 73 -54 65 -58 -38 -21 -228 -50 -385 -60 -164 -9 -211 -16 -254 -34
|
||||
-14 -5 -21 6 -37 57 -36 109 -42 141 -48 239 l-7 96 59 -7 c33 -4 86 -13 119
|
||||
-20z m-969 -20 c42 -21 84 -70 103 -123 23 -61 2 -238 -31 -258 -48 -30 -558
|
||||
-89 -558 -65 0 4 5 16 10 27 48 89 14 270 -62 323 -12 9 -20 17 -17 19 2 2 49
|
||||
11 104 21 55 9 150 30 210 46 134 34 185 37 241 10z m-730 -164 c28 -9 63 -25
|
||||
78 -37 24 -19 26 -27 26 -92 0 -40 -5 -89 -10 -109 -30 -106 -187 -190 -249
|
||||
-134 -12 11 -21 25 -21 33 0 7 31 46 69 85 l69 73 -42 2 c-22 1 -65 5 -93 8
|
||||
l-53 6 0 49 c0 68 -24 94 -84 95 -25 0 -46 1 -46 2 0 5 48 27 75 34 46 12 223
|
||||
2 281 -15z m204 -18 c15 -24 20 -49 19 -93 l-2 -60 -7 67 c-3 36 -14 78 -23
|
||||
92 -10 14 -15 26 -12 26 3 0 14 -15 25 -32z m1049 -164 c17 -28 31 -54 31 -57
|
||||
0 -3 -15 -8 -32 -12 -18 -4 -68 -22 -110 -41 -43 -18 -115 -43 -160 -54 -45
|
||||
-11 -92 -29 -103 -40 -12 -11 -31 -20 -43 -20 -24 0 -303 48 -308 53 -2 2 36
|
||||
9 84 15 193 26 356 84 439 156 23 20 61 66 84 102 l43 65 21 -58 c12 -32 36
|
||||
-81 54 -109z m-1776 111 c-12 -8 -51 -57 -87 -108 -87 -122 -95 -132 -102
|
||||
-124 -4 4 11 30 32 59 22 29 55 77 73 106 18 29 45 59 60 67 35 19 51 19 24 0z
|
||||
m245 3 c17 -17 15 -42 -7 -69 -16 -20 -28 -23 -79 -24 -43 0 -65 5 -79 18 -11
|
||||
9 -25 17 -33 17 -7 0 -1 -9 14 -21 14 -11 26 -26 26 -33 0 -7 -14 -41 -31 -75
|
||||
-34 -68 -68 -92 -131 -90 -26 0 -29 2 -14 8 14 5 16 10 7 15 -6 4 -17 3 -24
|
||||
-3 -7 -5 -18 -6 -25 -2 -10 6 5 36 57 113 38 57 77 111 86 118 40 33 208 53
|
||||
233 28z m2962 -43 c66 -23 125 -77 159 -147 26 -52 31 -73 31 -135 0 -104 -26
|
||||
-202 -57 -214 -74 -28 -500 -69 -716 -69 -91 0 -99 2 -92 18 4 9 12 42 17 72
|
||||
17 102 -20 226 -93 305 -48 52 -49 51 71 60 112 8 299 46 435 88 123 39 183
|
||||
44 245 22z m265 -52 c26 -25 36 -60 25 -88 -10 -25 -30 -35 -30 -14 0 7 -13
|
||||
34 -30 61 -16 26 -30 50 -30 53 0 12 50 3 65 -12z m538 -21 c58 -4 59 -5 53
|
||||
-32 -3 -15 -29 -80 -58 -143 l-53 -116 -48 -1 c-27 0 -46 3 -44 8 73 124 127
|
||||
223 127 231 0 6 -18 11 -40 11 -41 0 -48 9 -34 45 4 10 12 14 23 9 9 -4 42 -9
|
||||
74 -12z m-3726 -149 c-9 -28 -7 -38 9 -61 60 -81 178 -81 289 -1 l45 32 117
|
||||
-27 c65 -15 214 -46 330 -69 l213 -42 -61 -49 c-57 -46 -61 -52 -56 -83 5 -28
|
||||
3 -33 -14 -33 -28 0 -299 62 -534 122 -110 28 -294 72 -410 98 l-210 47 60 31
|
||||
c33 16 77 43 98 59 36 27 42 29 86 19 l48 -9 -10 -34z m1868 -9 l116 -18 -49
|
||||
-18 c-80 -30 -87 -43 -88 -172 l-1 -111 -66 -3 -65 -3 10 35 c8 26 8 42 -2 66
|
||||
-18 43 -49 61 -109 62 -45 0 -52 3 -64 26 -9 19 -22 28 -47 32 -26 4 -35 11
|
||||
-40 30 -4 19 -13 26 -38 28 -42 4 -40 18 6 38 88 37 221 40 437 8z m319 -68
|
||||
c22 -80 29 -129 17 -121 -6 3 -11 18 -11 33 0 32 -22 107 -45 153 l-16 34 21
|
||||
-24 c11 -13 26 -46 34 -75z m-839 6 c-6 -5 -28 -14 -51 -20 -63 -18 -104 -51
|
||||
-165 -132 -31 -41 -70 -83 -85 -92 -32 -20 -69 -23 -79 -8 -7 11 173 187 214
|
||||
209 20 12 82 30 171 50 2 1 0 -3 -5 -7z m76 -36 c0 -5 -24 -17 -53 -26 -82
|
||||
-26 -114 -46 -212 -134 -50 -45 -100 -85 -111 -89 -16 -6 -15 -3 5 13 14 11
|
||||
27 20 30 20 3 0 34 36 69 80 67 83 106 109 199 135 52 15 73 15 73 1z m605
|
||||
-27 c14 -16 18 -34 17 -70 -3 -74 -12 -84 -72 -83 -28 0 -53 3 -56 6 -3 3 -6
|
||||
34 -6 69 -1 54 2 66 21 81 30 24 73 23 96 -3z m-2335 -21 l52 -12 -66 -12
|
||||
c-57 -11 -72 -10 -124 6 -47 15 -53 19 -33 23 49 9 119 7 171 -5z m1819 -6 c0
|
||||
-4 -23 -16 -52 -26 -35 -12 -72 -37 -114 -78 -34 -33 -65 -59 -68 -58 -4 1
|
||||
-31 -11 -61 -26 -53 -25 -105 -28 -105 -4 0 16 163 142 216 167 29 13 78 27
|
||||
110 31 59 8 74 7 74 -6z m-2055 -19 l40 -8 -40 -5 c-50 -6 -105 -1 -105 11 0
|
||||
11 50 11 105 2z m-190 -28 c29 -14 26 -15 -48 -11 -43 1 -83 8 -90 15 -17 17
|
||||
101 14 138 -4z m623 3 l47 -12 -55 -11 c-59 -12 -116 -5 -171 19 -34 14 -33
|
||||
14 49 15 46 1 104 -4 130 -11z m-903 -18 c27 -7 27 -8 5 -8 -14 0 -36 4 -50 8
|
||||
-20 6 -21 8 -5 8 11 0 34 -4 50 -8z m2689 -12 c7 -12 7 -26 0 -45 -9 -27 -12
|
||||
-28 -82 -28 -62 0 -72 -2 -72 -17 0 -23 -67 -67 -113 -75 -45 -7 -87 3 -87 21
|
||||
0 19 123 134 162 151 47 21 177 16 192 -7z m-2802 -5 l68 -17 -60 -13 c-55
|
||||
-11 -67 -10 -138 12 l-77 23 45 4 c25 3 56 5 70 6 14 1 55 -6 92 -15z m727 -5
|
||||
c36 -6 62 -13 56 -14 -5 -2 -44 2 -85 10 -41 8 -66 14 -56 15 11 1 49 -4 85
|
||||
-11z m441 -7 l55 -18 -50 -7 c-31 -4 -78 -2 -120 5 l-70 13 50 12 c66 16 71
|
||||
16 135 -5z m-625 -2 c13 -6 0 -7 -40 -3 -33 4 -62 8 -64 10 -9 8 85 2 104 -7z
|
||||
m-242 -6 c-13 -2 -35 -2 -50 0 -16 2 -5 4 22 4 28 0 40 -2 28 -4z m4772 -13
|
||||
c25 -28 8 -45 -31 -30 -20 8 -115 9 -316 4 -384 -9 -384 8 0 28 158 8 296 16
|
||||
308 17 11 0 29 -8 39 -19z m-4229 1 c-3 -5 -15 -7 -26 -4 -28 7 -25 13 6 13
|
||||
14 0 23 -4 20 -9z m47 -8 c-7 -2 -19 -2 -25 0 -7 3 -2 5 12 5 14 0 19 -2 13
|
||||
-5z m452 -9 l40 -7 -41 -18 c-41 -17 -78 -15 -164 7 -22 6 -19 8 25 19 49 13
|
||||
61 12 140 -1z m-640 -3 c14 -6 5 -8 -30 -9 -27 -1 -57 3 -65 8 -19 12 65 13
|
||||
95 1z m232 3 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m-622 -47
|
||||
c122 -32 331 -89 455 -124 l55 -15 -34 -14 c-42 -18 -66 -14 -251 41 -80 24
|
||||
-219 64 -310 89 -222 60 -237 68 -130 62 52 -3 136 -18 215 -39z m160 33 c19
|
||||
-5 6 -6 -35 -3 -36 3 -67 7 -69 9 -8 7 73 2 104 -6z m383 -41 c-13 -9 -88 -3
|
||||
-118 8 -11 4 -8 8 10 12 33 9 126 -9 108 -20z m194 10 c-15 -5 -36 -6 -55 0
|
||||
-29 8 -27 9 23 9 44 1 50 -1 32 -9z m508 -10 l55 -13 -64 -10 c-51 -7 -78 -6
|
||||
-140 10 -73 17 -75 18 -41 25 44 9 118 4 190 -12z m-925 1 c27 -7 28 -8 5 -8
|
||||
-14 0 -45 3 -70 7 l-45 8 40 1 c22 0 54 -4 70 -8z m1742 -15 c-15 -14 -36 -25
|
||||
-45 -25 -10 0 0 11 23 24 52 31 57 31 22 1z m-1098 -25 c21 -13 8 -12 -69 5
|
||||
l-65 15 59 -5 c32 -3 66 -10 75 -15z m-474 -15 c6 -3 -16 -3 -48 -2 -32 2 -62
|
||||
8 -68 14 -10 10 92 -1 116 -12z m175 15 c-12 -8 -12 -10 -1 -10 8 0 12 -2 9
|
||||
-5 -3 -3 -25 -3 -49 1 -34 6 -39 9 -24 15 11 4 34 8 50 8 23 1 26 -2 15 -9z
|
||||
m800 -14 l45 -13 -61 -7 c-62 -8 -184 9 -184 25 0 22 122 19 200 -5z m-722 7
|
||||
c-10 -2 -28 -2 -40 0 -13 2 -5 4 17 4 22 1 32 -1 23 -4z m357 -18 l40 -14 -58
|
||||
3 c-32 1 -63 8 -69 14 -17 17 35 15 87 -3z m-1473 -16 c44 -12 77 -23 75 -26
|
||||
-4 -4 -158 36 -166 43 -9 8 14 4 91 -17z m1263 0 c26 0 27 -2 10 -9 -25 -11
|
||||
-102 -12 -135 -3 -23 7 -21 8 15 14 22 4 49 5 60 3 11 -2 34 -4 50 -5z m1210
|
||||
1 c4 -7 -3 -8 -22 -4 -38 9 -42 14 -10 14 14 0 29 -5 32 -10z m1393 3 c-10 -2
|
||||
-26 -2 -35 0 -10 3 -2 5 17 5 19 0 27 -2 18 -5z m-1841 -20 c52 -9 90 -20 85
|
||||
-25 -20 -20 -113 -21 -198 -2 l-85 19 48 11 c26 6 50 12 52 13 3 0 47 -7 98
|
||||
-16z m-1027 -3 l25 -8 -25 0 c-14 -1 -43 3 -65 7 l-40 9 40 0 c22 0 51 -4 65
|
||||
-8z m745 -21 l40 -13 -42 -9 c-52 -12 -226 22 -182 35 32 10 134 2 184 -13z
|
||||
m-1085 -152 c578 -168 1031 -304 1038 -312 18 -18 -78 6 -332 81 -160 47 -356
|
||||
104 -435 125 -225 63 -901 279 -826 265 11 -2 261 -74 555 -159z m769 144 c25
|
||||
-16 -6 -22 -76 -15 -47 5 -61 10 -48 15 27 10 107 11 124 0z m-259 -11 c19 -6
|
||||
12 -8 -30 -8 -30 0 -64 3 -75 8 -26 11 70 11 105 0z m1148 -28 c97 -34 108
|
||||
-42 56 -42 -30 0 -36 3 -24 11 11 8 -1 9 -44 5 -35 -3 -91 1 -135 10 -104 21
|
||||
-114 27 -61 34 88 13 128 10 208 -18z m2355 -9 c-2 -10 -11 -27 -18 -37 -14
|
||||
-18 -17 -18 -97 -2 -139 27 -154 35 -73 34 39 0 81 4 95 10 14 6 41 11 62 11
|
||||
30 1 35 -2 31 -16z m-2978 -22 c44 -11 113 -25 154 -32 l74 -11 -32 -14 c-19
|
||||
-8 -45 -14 -60 -14 -24 0 -290 69 -351 91 -47 17 132 0 215 -20z m245 22 c73
|
||||
-5 74 -5 46 -20 -23 -12 -39 -13 -99 -3 -133 22 -105 35 53 23z m-815 -31 c64
|
||||
-21 61 -27 -22 -37 -42 -4 -71 0 -140 22 l-88 28 50 6 c69 9 130 4 200 -19z
|
||||
m225 19 l25 -7 -30 -6 c-16 -4 -57 0 -90 7 l-60 13 65 0 c36 0 76 -3 90 -7z
|
||||
m765 -28 c31 -5 35 -8 20 -14 -30 -12 -86 -9 -125 6 l-35 13 50 1 c28 0 68 -2
|
||||
90 -6z m508 -24 c26 -6 47 -14 47 -17 0 -15 -139 -11 -210 7 l-75 18 95 1 c52
|
||||
1 116 -3 143 -9z m107 -19 c-36 -11 -52 -11 -45 0 3 6 21 10 38 9 31 -1 31 -1
|
||||
7 -9z m-359 -14 l34 -14 -28 -7 c-29 -8 -172 1 -217 13 -24 6 -23 7 10 13 65
|
||||
12 167 9 201 -5z m-986 -10 c28 -7 66 -20 85 -28 l35 -15 -83 -6 c-75 -6 -89
|
||||
-4 -160 22 -68 26 -74 29 -47 33 61 10 121 7 170 -6z m1544 -14 l75 -17 -78
|
||||
-14 c-74 -12 -83 -11 -160 11 l-81 23 65 6 c36 3 74 6 85 7 11 1 53 -7 94 -16z
|
||||
m-819 -2 c0 -5 -6 -10 -12 -11 -7 0 -4 -4 7 -9 40 -17 -59 -10 -125 10 l-65
|
||||
19 98 0 c58 1 97 -3 97 -9z m-181 -38 c20 -7 11 -10 -52 -19 -65 -9 -86 -8
|
||||
-144 7 -65 17 -66 18 -33 23 49 7 200 0 229 -11z m743 -1 c11 -12 10 -13 -3
|
||||
-8 -8 3 -38 1 -65 -5 -56 -10 -211 10 -177 24 32 13 231 5 245 -11z m-265 -30
|
||||
c15 -9 13 -10 -12 -11 -17 0 -45 -4 -62 -9 -24 -7 -49 -5 -95 9 l-63 18 40 5
|
||||
c57 7 172 0 192 -12z m-730 -15 l37 -13 -46 -6 c-47 -6 -143 14 -163 35 -13
|
||||
14 123 1 172 -16z m488 -16 c14 -5 -1 -11 -55 -20 -60 -10 -87 -10 -135 0 -63
|
||||
12 -115 34 -60 24 17 -2 57 0 90 5 59 9 121 6 160 -9z m1154 -44 c-2 -2 -13 8
|
||||
-23 22 l-19 27 24 -22 c12 -12 21 -24 18 -27z m-1420 -6 c12 -7 8 -10 -19 -11
|
||||
-53 -3 -80 1 -114 17 -29 13 -27 14 43 9 40 -3 81 -10 90 -15z m1351 17 c0 -2
|
||||
-9 -12 -20 -22 -24 -21 -27 -14 -4 9 15 15 24 20 24 13z m35 -17 c3 -6 -1 -7
|
||||
-9 -4 -18 7 -21 14 -7 14 6 0 13 -4 16 -10z m2322 -26 c-3 -3 -12 -4 -19 -1
|
||||
-8 3 -5 6 6 6 11 1 17 -2 13 -5z m-2383 -57 c7 -6 6 -7 -4 -4 -8 2 -16 14 -17
|
||||
28 -1 19 -1 20 4 4 3 -11 10 -24 17 -28z"/>
|
||||
<path d="M5283 8633 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z"/>
|
||||
<path d="M5218 8623 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z"/>
|
||||
<path d="M5113 8592 c-23 -10 -48 -23 -55 -30 -11 -11 97 33 111 44 10 9 -17
|
||||
2 -56 -14z"/>
|
||||
<path d="M7275 8230 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0
|
||||
-7 -4 -4 -10z"/>
|
||||
<path d="M9280 6916 c0 -2 7 -9 15 -16 9 -7 15 -8 15 -2 0 5 -7 12 -15 16 -8
|
||||
3 -15 4 -15 2z"/>
|
||||
<path d="M8555 7085 c-33 -30 -55 -54 -49 -55 6 0 17 6 23 13 6 8 28 22 49 31
|
||||
29 13 38 23 38 42 0 13 0 24 0 24 -1 0 -28 -25 -61 -55z"/>
|
||||
<path d="M8865 7010 c-16 -7 -17 -9 -3 -9 9 -1 20 4 23 9 7 11 7 11 -20 0z"/>
|
||||
<path d="M8760 6979 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10
|
||||
-5 -10 -11z"/>
|
||||
<path d="M4910 6295 c0 -8 4 -15 8 -15 8 0 32 -43 32 -57 0 -3 -8 1 -17 8 -16
|
||||
13 -17 12 -4 -3 7 -10 17 -15 22 -13 12 8 36 -32 59 -100 22 -64 25 -77 10
|
||||
-55 -8 12 -10 11 -10 -7 0 -15 6 -23 18 -24 10 0 12 -3 5 -6 -9 -3 -13 -31
|
||||
-12 -91 0 -58 3 -80 9 -67 7 15 9 16 9 4 1 -9 -6 -22 -14 -29 -17 -14 -20 -40
|
||||
-5 -40 6 0 6 -9 -1 -26 -5 -15 -9 -45 -7 -66 2 -39 2 -39 18 -17 39 51 55 120
|
||||
55 239 0 93 -5 128 -23 184 -25 75 -67 143 -108 175 -32 25 -44 26 -44 6z"/>
|
||||
<path d="M4622 5910 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z"/>
|
||||
<path d="M4632 5840 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z"/>
|
||||
<path d="M4760 5454 c0 -5 7 -18 15 -28 8 -11 15 -16 15 -10 0 5 -7 18 -15 28
|
||||
-8 11 -15 16 -15 10z"/>
|
||||
<path d="M7960 4356 c24 -26 60 -52 60 -42 0 2 -19 19 -42 37 -40 30 -41 31
|
||||
-18 5z"/>
|
||||
<path d="M8027 4286 c-3 -8 1 -13 11 -12 9 0 16 -5 14 -12 -1 -8 2 -11 8 -7 8
|
||||
5 6 13 -6 26 -20 23 -20 23 -27 5z"/>
|
||||
<path d="M8143 3935 c0 -27 2 -38 4 -22 2 15 2 37 0 50 -2 12 -4 0 -4 -28z"/>
|
||||
<path d="M8131 3834 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/>
|
||||
<path d="M4667 2598 c-22 -14 46 -12 71 1 8 4 -3 7 -23 7 -20 -1 -42 -4 -48
|
||||
-8z"/>
|
||||
<path d="M6554 2467 c-12 -13 4 -87 20 -87 5 0 1 -7 -10 -15 -10 -8 -14 -15
|
||||
-7 -15 36 -1 56 67 33 111 -12 22 -20 23 -36 6z"/>
|
||||
<path d="M6520 2441 c0 -12 5 -21 10 -21 6 0 10 6 10 14 0 8 -4 18 -10 21 -5
|
||||
3 -10 -3 -10 -14z"/>
|
||||
<path d="M6098 2353 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z"/>
|
||||
<path d="M1455 5297 c-20 -17 -28 -32 -27 -55 0 -17 41 -128 91 -247 90 -217
|
||||
90 -217 70 -234 -21 -17 -21 -22 -14 -209 7 -173 10 -195 28 -219 20 -25 23
|
||||
-26 108 -20 62 4 90 2 97 -6 5 -7 71 -156 146 -332 89 -207 133 -321 125 -324
|
||||
-15 -5 -45 -35 -51 -51 -2 -7 -83 -20 -238 -36 -441 -45 -636 -92 -873 -210
|
||||
-204 -103 -362 -274 -412 -448 -69 -241 88 -466 431 -615 119 -51 269 -94 489
|
||||
-138 145 -29 185 -41 206 -60 21 -20 491 -223 643 -279 48 -17 222 -18 600 -3
|
||||
290 11 307 15 327 65 6 18 5 34 -3 52 -14 29 -107 72 -433 202 -88 35 -165 68
|
||||
-172 74 -7 6 -17 41 -23 77 -30 195 76 616 194 763 l23 30 79 -17 c148 -31
|
||||
889 -188 1033 -218 160 -35 178 -33 206 21 20 39 19 42 -95 315 -98 234 -363
|
||||
899 -605 1515 -201 514 -202 516 -233 536 -28 19 -28 19 -1052 63 -256 11
|
||||
-504 23 -551 26 -81 6 -88 5 -114 -18z m792 -83 c-3 -3 -12 -4 -19 -1 -8 3 -5
|
||||
6 6 6 11 1 17 -2 13 -5z m578 -25 c73 -5 19 -6 -160 -3 -148 3 -301 9 -340 14
|
||||
-86 10 341 1 500 -11z m-947 -135 c23 -4 32 -10 32 -24 0 -24 -2 -24 -105 -8
|
||||
-76 11 -94 20 -95 44 0 9 38 7 168 -12z m132 -67 c0 -32 0 -32 -45 -29 -42 4
|
||||
-45 6 -45 33 0 28 2 29 45 29 44 0 45 -1 45 -33z m90 4 c0 -17 -4 -33 -10 -36
|
||||
-6 -4 -10 8 -10 29 0 20 5 36 10 36 6 0 10 -13 10 -29z m190 4 c0 -11 -5 -12
|
||||
-23 -6 -14 6 -30 6 -38 0 -19 -12 -32 -11 -24 1 3 6 -5 10 -19 10 -15 0 -26
|
||||
-7 -29 -17 -4 -14 -5 -12 -6 5 -1 21 3 22 69 22 55 0 70 -3 70 -15z m-421 -1
|
||||
c21 -4 31 -12 31 -25 0 -17 -3 -17 -45 -2 -68 23 -59 41 14 27z m718 -20 c18
|
||||
-5 21 -13 21 -48 l-1 -42 -56 8 c-130 19 -121 14 -121 60 l0 41 68 -7 c37 -4
|
||||
77 -9 89 -12z m-217 -25 c0 -17 -4 -28 -10 -24 -5 3 -10 17 -10 31 0 13 5 24
|
||||
10 24 6 0 10 -14 10 -31z m308 -16 c2 -8 -5 -13 -17 -13 -12 0 -21 6 -21 16 0
|
||||
18 31 15 38 -3z m-816 -9 c53 -6 56 -8 61 -38 2 -17 3 -32 2 -34 -2 -2 -46 5
|
||||
-99 14 -96 17 -96 17 -96 47 0 29 1 29 38 24 20 -4 62 -9 94 -13z m1101 5 c3
|
||||
-2 8 -21 11 -43 l7 -39 -63 7 c-140 16 -138 15 -138 54 l0 34 90 -4 c49 -2 91
|
||||
-6 93 -9z m-1253 -96 c0 -5 -5 -15 -10 -23 -8 -12 -10 -11 -10 8 0 12 5 22 10
|
||||
22 6 0 10 -3 10 -7z m1039 -14 c83 -10 102 -15 106 -30 4 -11 12 -19 19 -19 8
|
||||
0 19 -7 26 -15 16 -19 9 -19 -145 0 -66 8 -123 15 -127 15 -5 0 -8 14 -8 30 0
|
||||
35 -6 34 129 19z m-611 -40 c116 -12 157 -29 52 -21 l-60 4 6 -78 c3 -44 7
|
||||
-92 9 -109 4 -28 4 -29 -10 -11 -31 40 -76 86 -85 86 -15 0 -23 49 -11 64 9
|
||||
10 4 19 -24 40 -40 31 -45 48 -12 42 12 -3 73 -10 135 -17z m850 -30 c4 -53
|
||||
-13 -42 -21 14 -4 27 -3 37 6 34 7 -2 13 -23 15 -48z m-1020 10 c-3 -8 -7 -3
|
||||
-11 10 -4 17 -3 21 5 13 5 -5 8 -16 6 -23z m952 21 c0 -5 -2 -10 -4 -10 -3 0
|
||||
-8 5 -11 10 -3 6 -1 10 4 10 6 0 11 -4 11 -10z m477 -210 c30 -69 79 -183 108
|
||||
-255 29 -71 110 -269 179 -439 69 -170 124 -311 122 -313 -2 -2 -9 11 -16 29
|
||||
-7 18 -47 107 -87 198 -117 260 -414 977 -413 997 1 14 50 -85 107 -217z
|
||||
m-1462 196 c18 -13 18 -14 -8 -9 -15 3 -27 9 -27 14 0 13 13 11 35 -5z m24
|
||||
-24 c54 -7 54 -7 58 -44 4 -38 4 -38 -34 -38 -49 1 -122 15 -133 26 -5 5 -12
|
||||
21 -15 37 -7 26 -6 27 31 26 22 -1 63 -4 93 -7z m567 -12 l61 0 7 -47 c3 -25
|
||||
4 -48 1 -51 -2 -2 -16 -2 -30 2 -21 5 -25 12 -25 42 0 19 -4 33 -9 30 -12 -8
|
||||
-124 14 -136 27 -7 6 4 8 30 3 22 -3 68 -6 101 -6z m-336 -21 c0 -11 -5 -17
|
||||
-10 -14 -6 4 -8 10 -5 15 3 4 -1 11 -7 13 -8 3 -6 6 5 6 11 1 17 -6 17 -20z
|
||||
m53 14 c-7 -2 -21 -2 -30 0 -10 3 -4 5 12 5 17 0 24 -2 18 -5z m497 -18 c10
|
||||
-6 50 -78 50 -90 0 -7 -85 -6 -104 1 -12 5 -16 20 -16 59 l0 54 30 -9 c16 -5
|
||||
34 -11 40 -15z m-1026 -165 l65 -155 -61 -8 c-34 -4 -64 -4 -68 0 -11 12 -22
|
||||
340 -11 328 5 -6 39 -80 75 -165z m713 123 c-3 -10 -5 -2 -5 17 0 19 2 27 5
|
||||
18 2 -10 2 -26 0 -35z m-17 13 c0 -9 -8 -12 -25 -8 -37 7 -41 22 -6 22 20 0
|
||||
31 -5 31 -14z m-535 -46 c3 -5 1 -10 -4 -10 -6 0 -11 5 -11 10 0 6 2 10 4 10
|
||||
3 0 8 -4 11 -10z m82 -22 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1 -19z
|
||||
m51 4 c2 -7 -2 -10 -12 -6 -9 3 -16 11 -16 16 0 13 23 5 28 -10z m-19 -52 c71
|
||||
-18 91 -30 91 -57 0 -16 1 -16 -97 4 -73 15 -83 20 -83 38 0 17 8 36 14 35 1
|
||||
0 34 -9 75 -20z m98 -111 c57 -10 61 -13 65 -40 3 -16 3 -29 0 -29 -25 0 -190
|
||||
35 -199 43 -7 5 -13 18 -13 29 0 22 3 21 147 -3z m661 -33 c3 -33 1 -36 -22
|
||||
-36 -21 0 -26 6 -32 34 -8 44 0 57 29 47 16 -6 23 -18 25 -45z m248 -6 c1 0 4
|
||||
-14 6 -31 l5 -31 -65 6 c-36 4 -73 11 -83 17 -17 9 -26 48 -14 60 3 4 126 -13
|
||||
151 -21z m-770 10 c21 0 24 -5 24 -35 0 -33 -1 -35 -27 -29 -36 8 -40 13 -49
|
||||
48 -6 25 -5 28 10 23 10 -4 29 -7 42 -7z m124 -36 c0 -19 -4 -34 -10 -34 -5 0
|
||||
-10 18 -10 41 0 24 4 38 10 34 6 -3 10 -22 10 -41z m176 6 c20 0 24 -5 24 -29
|
||||
0 -17 5 -33 10 -36 15 -9 12 -35 -4 -35 -8 0 -16 4 -18 9 -1 5 -26 11 -54 15
|
||||
-72 8 -117 27 -50 20 27 -2 60 -7 73 -10 19 -5 23 -2 23 16 0 19 -4 22 -26 17
|
||||
-14 -2 -31 1 -37 7 -7 6 -23 17 -37 24 -23 11 -20 12 24 7 27 -3 59 -5 72 -5z
|
||||
m-599 -6 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6 11 1 17 -2 13 -5z m59 -61 c-2
|
||||
-3 -15 5 -28 17 -16 16 -18 21 -6 17 10 -3 19 1 24 11 6 13 9 11 12 -11 2 -16
|
||||
1 -31 -2 -34z m150 25 c54 -10 64 -15 69 -36 3 -13 4 -26 1 -29 -2 -2 -40 1
|
||||
-85 8 -79 12 -81 13 -81 41 0 32 5 33 96 16z m489 2 c3 -5 -1 -10 -10 -10 -9
|
||||
0 -13 5 -10 10 3 6 8 10 10 10 2 0 7 -4 10 -10z m-183 -75 c4 -16 1 -25 -8
|
||||
-25 -14 0 -23 19 -16 38 7 20 19 14 24 -13z m88 -5 c0 -5 -4 -10 -9 -10 -6 0
|
||||
-13 5 -16 10 -3 6 1 10 9 10 9 0 16 -4 16 -10z m140 -41 c0 -16 -4 -29 -10
|
||||
-29 -5 0 -10 16 -10 36 0 21 4 33 10 29 6 -3 10 -19 10 -36z m-614 23 c6 -4
|
||||
14 -16 17 -27 7 -18 6 -18 -10 2 -12 14 -32 23 -58 26 l-40 4 40 1 c22 1 45
|
||||
-2 51 -6z m222 -27 c6 -2 12 -16 12 -30 0 -28 -12 -31 -70 -16 -19 5 -52 12
|
||||
-72 16 -34 5 -38 9 -38 34 l0 29 78 -15 c42 -7 83 -16 90 -18z m-238 5 c0 -5
|
||||
-4 -10 -10 -10 -5 0 -10 5 -10 10 0 6 5 10 10 10 6 0 10 -4 10 -10z m877 -12
|
||||
c53 -8 62 -13 68 -35 3 -14 3 -28 0 -31 -5 -5 -213 32 -220 40 -2 2 -2 14 2
|
||||
27 6 24 -2 24 150 -1z m-567 -34 c0 -16 -26 -19 -35 -4 -3 6 -4 14 0 20 8 13
|
||||
35 1 35 -16z m173 9 c13 -4 17 -16 17 -49 0 -50 -12 -55 -47 -22 -29 27 -34
|
||||
95 -5 83 9 -4 25 -10 35 -12z m-405 -71 c2 -32 0 -42 -12 -42 -9 0 -18 12 -22
|
||||
28 -3 15 -7 32 -9 39 -5 15 6 24 25 21 10 -2 16 -17 18 -46z m512 8 c0 -19 -5
|
||||
-30 -14 -30 -8 0 -16 13 -18 30 -4 24 -1 30 14 30 13 0 18 -8 18 -30z m-325
|
||||
-13 c57 -11 60 -14 63 -43 l3 -31 -93 18 c-51 10 -94 19 -95 19 -2 0 -3 14 -3
|
||||
30 0 34 -12 34 125 7z m485 10 c50 -11 55 -15 58 -40 3 -27 4 -28 18 -10 12
|
||||
16 15 16 24 3 19 -30 10 -33 -77 -27 -130 10 -148 17 -148 55 -1 30 1 32 35
|
||||
31 19 0 60 -6 90 -12z m-248 -88 c5 -33 6 -33 -59 -18 -46 11 -49 14 -45 39 4
|
||||
26 4 27 52 17 43 -8 49 -13 52 -38z m88 -19 c0 -11 -2 -20 -4 -20 -2 0 -6 9
|
||||
-9 20 -3 11 -1 20 4 20 5 0 9 -9 9 -20z m198 -7 c5 -34 -34 -31 -41 3 -4 21
|
||||
-1 25 17 22 13 -2 22 -12 24 -25z m-144 -8 c3 -8 1 -15 -4 -15 -6 0 -10 7 -10
|
||||
15 0 8 2 15 4 15 2 0 6 -7 10 -15z m-279 -24 c75 -15 80 -18 83 -43 4 -33 10
|
||||
-32 -101 -13 -119 21 -137 29 -137 59 0 28 -1 28 155 -3z m623 1 c8 -11 12
|
||||
-12 12 -3 0 8 11 11 32 9 24 -2 34 -9 37 -23 5 -29 40 -50 37 -22 0 6 3 13 9
|
||||
14 5 2 10 -5 10 -15 0 -13 11 -18 52 -22 45 -5 87 -20 54 -20 -7 0 -9 -4 -6
|
||||
-10 4 -6 -21 -10 -68 -10 -107 0 -137 8 -151 40 -6 15 -16 25 -23 23 -6 -2
|
||||
-29 1 -51 6 -34 10 -39 14 -35 35 5 22 9 24 42 19 20 -3 42 -12 49 -21z m-168
|
||||
-16 c0 -3 -4 -8 -10 -11 -5 -3 -10 -1 -10 4 0 6 5 11 10 11 6 0 10 -2 10 -4z
|
||||
m-632 -37 c1 -8 -4 -23 -12 -34 -15 -19 -15 -18 -16 18 0 27 4 37 13 35 6 -3
|
||||
13 -11 15 -19z m212 -73 c56 -10 65 -15 68 -35 4 -23 2 -23 -35 -17 -21 4 -57
|
||||
10 -80 13 -24 3 -43 9 -43 13 0 5 -3 15 -6 24 -7 19 6 20 96 2z m778 -13 c2
|
||||
-16 4 -37 5 -47 1 -10 -4 -21 -10 -23 -9 -3 -13 5 -13 24 0 15 -3 38 -6 51 -4
|
||||
15 -2 22 8 22 8 0 15 -12 16 -27z m69 21 c-3 -3 -12 -4 -19 -1 -8 3 -5 6 6 6
|
||||
11 1 17 -2 13 -5z m-1080 -46 c-3 -8 -6 -5 -6 6 -1 11 2 17 5 13 3 -3 4 -12 1
|
||||
-19z m93 -8 c0 -5 -7 -10 -15 -10 -8 0 -15 5 -15 10 0 6 7 10 15 10 8 0 15 -4
|
||||
15 -10z m400 -51 c0 -28 -4 -39 -12 -37 -18 6 -19 78 -2 78 10 0 14 -13 14
|
||||
-41z m-221 25 c18 -4 31 -13 31 -21 0 -12 -8 -14 -37 -8 -44 8 -53 13 -53 26
|
||||
0 11 15 11 59 3z m-295 -10 c20 -8 21 -48 2 -63 -12 -10 -17 -8 -25 8 -13 24
|
||||
-14 61 -2 61 5 0 16 -3 25 -6z m416 -19 c0 -23 -3 -25 -27 -19 -38 9 -43 13
|
||||
-43 30 0 9 11 14 35 14 31 0 35 -3 35 -25z m317 -25 c3 -44 -2 -50 -37 -39
|
||||
-24 7 -24 7 -2 8 19 1 22 6 22 43 0 51 13 42 17 -12z m48 10 c3 -5 2 -10 -4
|
||||
-10 -5 0 -13 5 -16 10 -3 6 -2 10 4 10 5 0 13 -4 16 -10z m-665 -20 c7 0 -49
|
||||
-38 -85 -57 -29 -15 -30 -15 -14 3 9 11 19 31 23 46 6 25 8 26 38 17 18 -5 35
|
||||
-9 38 -9z m817 -106 c-14 -14 -27 11 -27 50 0 62 14 66 24 7 5 -28 6 -54 3
|
||||
-57z m298 31 c119 -23 115 -20 115 -70 l0 -43 -72 14 c-122 23 -118 21 -118
|
||||
57 0 18 -5 38 -12 45 -15 15 -9 15 87 -3z m-155 -19 c0 -2 -7 -7 -16 -10 -8
|
||||
-3 -12 -2 -9 4 6 10 25 14 25 6z m312 -68 c-9 -9 -12 -7 -12 12 0 19 3 21 12
|
||||
12 9 -9 9 -15 0 -24z m-1505 -78 c15 -12 41 -20 62 -20 35 0 37 -2 59 -58 12
|
||||
-32 22 -61 22 -64 0 -4 -20 -36 -45 -71 -81 -117 -123 -227 -179 -478 -45
|
||||
-202 -52 -358 -21 -482 6 -23 4 -24 -127 -46 -73 -13 -154 -30 -179 -38 -44
|
||||
-14 -55 -13 -220 21 -381 78 -596 167 -739 307 -82 80 -115 153 -115 250 1
|
||||
110 48 204 159 315 123 123 270 201 506 269 144 41 264 61 555 90 127 12 232
|
||||
23 233 24 2 0 15 -8 29 -19z m311 -216 c2 -22 -25 -85 -104 -242 -58 -117
|
||||
-110 -212 -115 -212 -16 0 -10 94 11 176 24 97 77 203 131 267 47 54 72 58 77
|
||||
11z m26 -181 c-31 -74 -85 -226 -104 -296 -23 -87 -26 -55 -4 50 18 88 44 157
|
||||
86 231 35 61 45 68 22 15z m-268 -940 c65 -31 148 -67 184 -80 52 -19 57 -22
|
||||
25 -17 -46 7 -279 96 -312 119 -20 14 -31 35 -19 35 1 0 57 -25 122 -57z"/>
|
||||
<path d="M7090 1674 c-52 -7 -131 -25 -175 -39 -92 -29 -208 -103 -242 -154
|
||||
-26 -39 -30 -93 -8 -111 11 -9 13 -16 5 -24 -38 -38 23 -52 265 -61 250 -10
|
||||
695 25 783 61 41 17 52 42 26 63 -14 12 -19 29 -19 61 0 93 -49 126 -278 185
|
||||
-152 39 -203 41 -357 19z m270 -74 c11 -7 6 -10 -21 -10 -20 0 -72 -3 -115 -6
|
||||
-43 -4 -76 -3 -72 1 20 21 181 32 208 15z m-427 -166 c56 -36 48 -48 -25 -40
|
||||
-34 3 -66 10 -70 14 -5 5 -4 21 2 36 12 32 31 30 93 -10z"/>
|
||||
</g>
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1365 1365" width="1365" height="1365">
|
||||
<title>safari-pinned-tab-svg</title>
|
||||
<defs>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="cp1">
|
||||
<path d="m655.2 313.1v822.23h-622.69v-822.23z"/>
|
||||
</clipPath>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="cp2">
|
||||
<path d="m1308.84 304v828.5h-617.24v-828.5z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<style>
|
||||
.s0 { fill: none }
|
||||
.s1 { fill: #000000 }
|
||||
</style>
|
||||
<g id="surface1">
|
||||
<path class="s0" d="m1258.6 499.2c-40.8-26.1-68 13.8-64.7 68.1l-0.3 0.4c0.1-56.6-7.3-119.2-31.7-169.8-8.7-17.9-26.2-47.4-61-33.5-15.2 6.1-29 24.4-21.9 71.7 0 0 8 49.7 6.5 112.2v0.8c-9.9-172.2-47.3-224.7-100.7-221.3-17.1 3.1-40.5 10.8-32.6 63.8 0 0 8.5 55.2 11.3 99.2l0.2 2.2h-0.2c-25.2-94.9-59-96.2-83.5-92.5-22.3 3.3-46.6 27.3-34.3 74.7 38.6 148.4 31 327.2 28.2 352.9-7.9-17.6-10.3-31.4-21.3-50.7-43.9-77.1-64.8-82.8-90.4-84-25.5-1.1-53 15.2-51.2 46.3 1.9 31.1 17.1 36.3 38.7 79.6 16.9 33.8 21.7 78 55.7 158.4 28.1 66.5 101.6 139.5 235.6 130.8 108.5-3.7 270.6-43.2 242.4-302.5-7-45-1.7-82.7 1.9-121.4 5.8-60 14.1-159.4-26.7-185.5z"/>
|
||||
<path class="s0" d="m580.9 695.3c-25.7 1.7-46.4 7.7-89 85.7-10.6 19.4-12.7 33.3-20.3 50.9-3.3-25.5-14-204.2 21.9-353.4 11.4-47.5-13.3-71-35.6-73.9-24.6-3.3-58.5-1.3-81.9 94.6h-0.3l0.4-2.8c1.9-44 9.5-99.4 9.5-99.4 6.9-53.1-16.6-60.3-33.7-63.2-53.4-2.3-89.7 50.4-96.7 221.1h-0.2c-2.4-61.8 4.6-111 4.6-111 6.3-47.5-7.9-65.5-23.2-71.3-35-13.3-52 16.6-60.3 34.7-23.6 51-29.9 113.7-28.7 170.3l-0.4-0.4c2.4-54.2-25.6-93.7-65.9-66.9-40.3 26.9-30.1 126.2-23.4 186 4.5 38.6 10.4 76.2 4.1 121.4-23.5 259.7 139.3 296.1 247.8 297.8 134.1 6.1 206.3-68.3 233.3-135.4 32.5-80.9 36.6-125.3 52.8-159.3 20.8-43.8 36-49.2 37.3-80.3 1.3-31.1-26.5-46.9-52-45.3z"/>
|
||||
<g id="Clip-Path" clip-path="url(#cp1)">
|
||||
<g>
|
||||
<path fill-rule="evenodd" class="s1" d="m634.4 695.7c11.9 12 17.8 27.9 17.1 45.7-1 24.6-9.5 37.8-19.4 53.2-5.8 9-12.3 19.2-19.8 34.9-6.6 13.8-11.2 30.4-17 51.5-7.6 27.3-17 61.2-35.3 106.7-14.1 35.1-71.4 146.1-231.3 147.6q-9.8 0.1-20-0.3c-93.6-1.5-164.2-28.5-209.3-80.6-46.9-54-65.8-134.2-56.4-238.4l0.1-0.9c5.2-37.6 1.5-69.5-2.6-103.3-0.5-4.4-1-8.7-1.5-13.1-9.4-82.5-15.4-173.1 31.8-204.6 27.9-18.5 49.3-11.1 59.6-4.9 1 0.6 1.9 1.2 2.9 1.9 4.5-32.3 12.6-63.9 25.6-92.2 11.2-24.2 37.1-62.2 83.8-44.5 7.6 3 17.3 8.8 24.8 20.2 6.7-14.7 14.5-26.6 23.5-35.8 16.6-17.2 37.4-25.4 61.6-24.3l2.1 0.2c39.2 6.5 55.9 35 49.4 84.9 0 0 0 0.3 0 0.6 20-17 40.4-16.9 56.1-14.8 17.2 2.2 32.8 12.1 42.8 27.2 8.6 13 17.1 35.8 8.7 70.7-22.3 92.9-26.1 198.5-25.2 268.8 36.3-61.5 59.9-74.1 93.2-76.2 20.8-1.3 41.3 6 54.8 19.8zm-316.9-329.4c-35.1 36.2-49.7 148.5-43.4 333.7 19.5-3 39.5-5.2 59.6-6.5 4.7-91.2 13.3-153.7 23.7-197q0-0.5 0-0.9c2-44.4 9.3-98.9 9.7-101.2 4.7-36.3-5.7-39.2-17-41.1-13.2-0.3-23.5 3.8-32.5 13zm-124.6 49.5c-50 108.3-16.2 277.2-8.5 303.8 16.1-4.8 33.8-9.2 52.4-13-2.1-57.7-2.3-108.1-0.6-151.9-2.3-62.3 4.4-111.4 4.7-113.5 1.8-13 4.2-44.5-11.1-50.3-10.9-4.1-22.8-5.7-36.9 24.8zm407.9 357.3c8.9-13.8 12.6-19.5 13.2-33.3 0.2-6.7-1.6-12-5.9-16.3-5.9-6.1-16-9.4-26.1-8.8-17.3 1.2-33.6 2.2-73.8 75.9-5.6 10.4-8.5 18.9-11.8 28.6-2.1 6.3-4.4 13.2-7.7 20.8-0.1 0.3-0.2 0.6-0.4 0.8-1.4 3.3-3 6.8-4.8 10.4-17.6 34.2-46.2 53.4-76.5 51.6-10.4-0.7-18.2-9.9-17.6-20.6 0.6-10.7 9.6-18.7 19.8-18.2 18 1.1 33.1-15.3 41.2-31.1 0.7-1.4 1.3-2.8 2-4.2-4-41.1-11.8-210.7 22.9-354.8 3.9-16.5 2.8-30.1-3.3-39.3-5.6-8.4-13.4-10.3-16.5-10.7-11.2-1.5-19.3-0.9-27.8 6.4-20.5 17.8-46.8 77.8-56.4 261.8 5.8 0 11.6 0 17.3 0.2 10.3 0.3 18.5 9.2 18.2 19.9-0.3 10.7-8.8 19.1-19.2 18.9-95.8-2.8-204.4 22.8-250.2 47.9-2.8 1.5-5.7 2.3-8.6 2.3-6.8 0.1-13.4-3.7-16.8-10.3-4.8-9.5-1.4-21.2 7.8-26.3 8.1-4.4 17.9-8.9 28.9-13.1-6.7-22.4-18.6-82-20.1-150.6-0.3-1.5-0.5-3-0.4-4.6 1.2-29.4-7.6-48.4-16.4-53.6-5.2-3.1-12.1-1.8-20.6 3.9-31.6 21-19.4 127.4-14.9 167.4 0.5 4.3 1 8.6 1.5 12.9 4.1 34.7 8.4 70.6 2.6 113-8.3 92.7 7.5 162.9 47 208.5 38.4 44.2 98 66.3 182.4 67.6 151.5 7.1 203.3-92.6 215.7-123.3 17.4-43.4 26.5-76.2 33.8-102.5 6.4-22.9 11.4-41 19.5-58 8.5-17.9 16.1-29.7 22.2-39.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Clip-Path" clip-path="url(#cp2)">
|
||||
<g>
|
||||
<path fill-rule="evenodd" class="s1" d="m1301.9 802.9l0.1 0.9c11.3 104-6.3 184.6-52.2 239.5-44.1 52.8-114.2 81.3-208.3 84.5q-10.2 0.7-19.9 0.8c-159.5 1.6-218.8-108.4-233.5-143.2-19.1-45.1-29.1-78.9-37.2-106-6.2-21-11.1-37.5-18-51.2-7.7-15.5-14.4-25.6-20.4-34.5-10.1-15.1-18.8-28.2-20.3-52.8-1.1-17.7 4.5-33.6 16.2-45.9 13.3-14 33.8-21.8 54.5-20.9 33.3 1.5 57.1 13.7 94.5 74.5-0.3-70.4-6-175.9-30-268.4-9-34.8-0.9-57.7 7.5-70.8 9.7-15.3 25.1-25.5 42.2-28.1 15.7-2.3 36.2-2.9 56.4 13.8 0-0.2 0-0.4 0-0.4-7.4-49.9 8.8-78.8 47.9-86l2.1-0.3c24.1-1.6 45 6.3 62 23.2 9.1 9 17.1 20.7 24.1 35.3 7.4-11.6 16.9-17.6 24.5-20.6 46.4-18.7 72.9 18.9 84.5 42.9 13.5 28 22.1 59.5 27.3 91.6 0.9-0.6 1.8-1.3 2.8-1.9 10.2-6.3 31.5-14.3 59.7 3.8 47.8 30.6 43.4 121.3 35.5 203.9-0.4 4.4-0.8 8.7-1.3 13.1-3.4 33.9-6.6 65.9-0.8 103.3zm-197.6-256.5c2.5 43.7 3.2 94.2 2.1 151.9 18.7 3.5 36.4 7.5 52.7 12 7.2-26.7 37.9-196.3-14-303.7-14.6-30.1-26.5-28.4-37.4-24.1-15.1 6.1-12.2 37.5-10.2 50.7 0.4 1.9 8 50.9 6.8 113.2zm-117.5-199.2c-11.4 2.2-21.6 5.2-16.3 41.6 0.4 2.1 8.8 56.4 11.5 100.8q0 0.5 0 0.9c11.2 43.2 21 105.4 27.2 196.6 20.2 0.9 40.2 2.7 59.7 5.3 3-185.3-13.5-297.2-49.3-332.8-9.1-9.1-19.5-13-32.7-12.4zm278.5 348.5c0.5-4.3 0.9-8.6 1.3-13 3.9-40.1 14.1-146.6-17.9-167-8.6-5.5-15.5-6.7-20.6-3.5-8.7 5.4-17.2 24.5-15.4 53.9 0.1 1.5 0 3.1-0.3 4.6-0.4 68.5-11.2 128.4-17.5 150.9 11.1 4.1 20.9 8.3 29.1 12.6 9.3 4.9 13 16.6 8.3 26.2-3.3 6.7-9.8 10.6-16.6 10.6-2.9 0-5.9-0.6-8.6-2.1-46.2-24.3-155.4-47.7-251-43.2-10.5 0.3-19.2-7.7-19.6-18.5-0.5-10.7 7.5-19.8 17.9-20.2 5.6-0.3 11.4-0.5 17.2-0.6-12.8-183.8-40.2-243.3-61-260.7-8.6-7.1-16.8-7.6-27.9-5.9-3.2 0.5-10.9 2.5-16.4 11.1-5.9 9.3-6.8 22.9-2.5 39.3 37.3 143.5 32.4 313.2 29.2 354.3 0.7 1.4 1.3 2.7 2.1 4.2 8.4 15.6 23.7 31.7 41.7 30.3 10.3-0.7 19.3 7.2 20.1 17.9 0.8 10.6-6.9 20-17.2 20.8-30.3 2.5-59.3-16.3-77.4-50.1-2-3.6-3.6-7-5.1-10.3-0.1-0.2-0.2-0.5-0.3-0.7-3.5-7.5-5.9-14.5-8.2-20.8-3.5-9.6-6.4-18-12.3-28.3-41.6-72.9-57.9-73.7-75.1-74.5-10.2-0.5-20.2 3.1-26.1 9.3-4.1 4.3-5.9 9.7-5.5 16.4 0.8 13.8 4.6 19.4 13.7 33.1 6.3 9.4 14.1 21 23 38.7 8.3 16.9 13.7 34.8 20.5 57.7 7.7 26.2 17.4 58.7 35.6 101.8 12.9 30.5 66.7 129.1 217.3 119.2 84.9-2.9 144.2-26.2 181.7-71.1 38.7-46.3 53.2-116.8 43.3-209.4-6.6-42.3-3-78.2 0.5-113z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="s1" d="m739.8 434.2c-3 0-6.1-0.6-9-2-10.5-5-15.2-18-10.3-28.9 15.7-35.6 38.8-68.4 66.6-94.9 8.5-8.1 21.9-7.6 29.7 1.3 7.9 8.9 7.4 22.7-1.2 30.8-23.7 22.7-43.4 50.6-56.8 81-3.6 7.9-11.1 12.6-19 12.7z"/>
|
||||
<path class="s1" d="m668.8 421.6c-10.9 0.1-20.3-8.5-21.2-20-4-51.4-4.2-103.5-0.4-154.8 0.9-12 11.1-21 22.6-20.1 11.6 0.9 20.3 11.3 19.4 23.3-3.6 49.1-3.4 98.9 0.4 148 1 12-7.7 22.5-19.3 23.5-0.5 0-1 0-1.5 0z"/>
|
||||
<path class="s1" d="m596.2 435.1c-9.4 0.1-18.2-6.5-20.6-16.4-8.9-36.3-25.9-70.8-48.9-99.7-7.4-9.3-6.1-23 2.8-30.7 9-7.6 22.3-6.3 29.7 3 26.9 33.8 46.7 74.2 57.2 116.7 2.9 11.6-4 23.5-15.2 26.5-1.8 0.4-3.4 0.6-5.1 0.7z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -18,6 +18,7 @@ enum IndicatorColor {
|
||||
function AgentStatusBar() {
|
||||
const { t } = useTranslation();
|
||||
const { curAgentState } = useSelector((state: RootState) => state.agent);
|
||||
const { curStatusMessage } = useSelector((state: RootState) => state.status);
|
||||
|
||||
const AgentStatusMap: {
|
||||
[k: string]: { message: string; indicator: IndicatorColor };
|
||||
@@ -90,14 +91,25 @@ function AgentStatusBar() {
|
||||
}
|
||||
}, [curAgentState]);
|
||||
|
||||
const [statusMessage, setStatusMessage] = React.useState<string>("");
|
||||
|
||||
React.useEffect(() => {
|
||||
const trimmedCustomMessage = curStatusMessage.message.trim();
|
||||
if (trimmedCustomMessage) {
|
||||
setStatusMessage(t(trimmedCustomMessage));
|
||||
} else {
|
||||
setStatusMessage(AgentStatusMap[curAgentState].message);
|
||||
}
|
||||
}, [curAgentState, curStatusMessage.message]);
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`w-3 h-3 mr-2 rounded-full animate-pulse ${AgentStatusMap[curAgentState].indicator}`}
|
||||
/>
|
||||
<span className="text-sm text-stone-400">
|
||||
{AgentStatusMap[curAgentState].message}
|
||||
</span>
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`w-3 h-3 mr-2 rounded-full animate-pulse ${AgentStatusMap[curAgentState].indicator}`}
|
||||
/>
|
||||
<span className="text-sm text-stone-400">{statusMessage}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,16 +5,25 @@ import { renderWithProviders } from "test-utils";
|
||||
import Chat from "./Chat";
|
||||
|
||||
const MESSAGES: Message[] = [
|
||||
{ sender: "assistant", content: "Hello!", imageUrls: [] },
|
||||
{ sender: "user", content: "Hi!", imageUrls: [] },
|
||||
{ sender: "assistant", content: "How can I help you today?", imageUrls: [] },
|
||||
{
|
||||
sender: "assistant",
|
||||
content: "Hello!",
|
||||
imageUrls: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
sender: "user",
|
||||
content: "Hi!",
|
||||
imageUrls: [],
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
];
|
||||
|
||||
describe("Chat", () => {
|
||||
it("should render chat messages", () => {
|
||||
renderWithProviders(<Chat messages={MESSAGES} />);
|
||||
|
||||
const messages = screen.getAllByTestId("message");
|
||||
const messages = screen.getAllByTestId("article");
|
||||
expect(messages).toHaveLength(MESSAGES.length);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,17 +9,62 @@ import ActionType from "#/types/ActionType";
|
||||
import { addAssistantMessage } from "#/state/chatSlice";
|
||||
import AgentState from "#/types/AgentState";
|
||||
|
||||
/// <reference types="vitest" />
|
||||
|
||||
interface CustomMatchers<R = unknown> {
|
||||
toMatchMessageEvent(expected: string): R;
|
||||
}
|
||||
|
||||
declare module "vitest" {
|
||||
interface Assertion<T> extends CustomMatchers<T> {}
|
||||
interface AsymmetricMatchersContaining extends CustomMatchers {}
|
||||
}
|
||||
|
||||
// This is for the scrollview ref in Chat.tsx
|
||||
// TODO: Move this into test setup
|
||||
HTMLElement.prototype.scrollTo = vi.fn().mockImplementation(() => {});
|
||||
const TEST_TIMESTAMP = new Date().toISOString();
|
||||
|
||||
describe("ChatInterface", () => {
|
||||
const sessionSendSpy = vi.spyOn(Session, "send");
|
||||
vi.spyOn(Session, "isConnected").mockReturnValue(true);
|
||||
|
||||
// TODO: replace below with e.g. fake timers
|
||||
// https://vitest.dev/guide/mocking#timers
|
||||
// https://vitest.dev/api/vi.html#vi-usefaketimers
|
||||
// Custom matcher for testing message events
|
||||
expect.extend({
|
||||
toMatchMessageEvent(received, expected) {
|
||||
const receivedObj = JSON.parse(received);
|
||||
const expectedObj = JSON.parse(expected);
|
||||
|
||||
// Compare everything except the timestamp
|
||||
const { timestamp: receivedTimestamp, ...receivedRest } =
|
||||
receivedObj.args;
|
||||
const { timestamp: expectedTimestamp, ...expectedRest } =
|
||||
expectedObj.args;
|
||||
|
||||
const pass =
|
||||
this.equals(receivedRest, expectedRest) &&
|
||||
typeof receivedTimestamp === "string";
|
||||
|
||||
return {
|
||||
pass,
|
||||
message: () =>
|
||||
pass
|
||||
? `expected ${received} not to match the structure of ${expected} (ignoring exact timestamp)`
|
||||
: `expected ${received} to match the structure of ${expected} (ignoring exact timestamp)`,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const userMessageEvent = {
|
||||
action: ActionType.MESSAGE,
|
||||
args: { content: "my message", images_urls: [] },
|
||||
args: {
|
||||
content: "my message",
|
||||
images_urls: [],
|
||||
timestamp: TEST_TIMESTAMP,
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
@@ -28,19 +73,26 @@ describe("ChatInterface", () => {
|
||||
|
||||
it("should render empty message list and input", () => {
|
||||
renderWithProviders(<ChatInterface />);
|
||||
expect(screen.queryAllByTestId("message")).toHaveLength(0);
|
||||
expect(screen.queryAllByTestId("article")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("should render user and assistant messages", () => {
|
||||
const { store } = renderWithProviders(<ChatInterface />, {
|
||||
preloadedState: {
|
||||
chat: {
|
||||
messages: [{ sender: "user", content: "Hello", imageUrls: [] }],
|
||||
messages: [
|
||||
{
|
||||
sender: "user",
|
||||
content: "Hello",
|
||||
imageUrls: [],
|
||||
timestamp: TEST_TIMESTAMP,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(screen.getAllByTestId("message")).toHaveLength(1);
|
||||
expect(screen.getAllByTestId("article")).toHaveLength(1);
|
||||
expect(screen.getByText("Hello")).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
@@ -48,7 +100,7 @@ describe("ChatInterface", () => {
|
||||
store.dispatch(addAssistantMessage("Hello to you!"));
|
||||
});
|
||||
|
||||
expect(screen.getAllByTestId("message")).toHaveLength(2);
|
||||
expect(screen.getAllByTestId("article")).toHaveLength(2);
|
||||
expect(screen.getByText("Hello to you!")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -67,7 +119,7 @@ describe("ChatInterface", () => {
|
||||
await user.keyboard("{Enter}");
|
||||
|
||||
expect(sessionSendSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify(userMessageEvent),
|
||||
expect.toMatchMessageEvent(JSON.stringify(userMessageEvent)),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -86,7 +138,7 @@ describe("ChatInterface", () => {
|
||||
await user.keyboard("{Enter}");
|
||||
|
||||
expect(sessionSendSpy).toHaveBeenCalledWith(
|
||||
JSON.stringify(userMessageEvent),
|
||||
expect.toMatchMessageEvent(JSON.stringify(userMessageEvent)),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||