Compare commits
205 Commits
fix/git-ch
...
pr-9907
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
731b56cf2e | ||
|
|
63051b0bcb | ||
|
|
3cd9e4c23a | ||
|
|
38b1cb3f7b | ||
|
|
655d5730db | ||
|
|
e40681ca61 | ||
|
|
228e50df9c | ||
|
|
fd805eb835 | ||
|
|
426350224b | ||
|
|
3c3be2bded | ||
|
|
3e36911038 | ||
|
|
4c3ba62665 | ||
|
|
f5e7c602dc | ||
|
|
2f32064778 | ||
|
|
5e85986f32 | ||
|
|
4f436922ca | ||
|
|
d256348a46 | ||
|
|
6bdc5563cf | ||
|
|
c2f46200c0 | ||
|
|
e39bf80239 | ||
|
|
368a0248e3 | ||
|
|
db9ceb380a | ||
|
|
c64971d0c4 | ||
|
|
69fa580899 | ||
|
|
e3411f743d | ||
|
|
2b65b8aff2 | ||
|
|
11f364c5e4 | ||
|
|
4e3a862571 | ||
|
|
50aa014876 | ||
|
|
500ab46918 | ||
|
|
e311f3e70f | ||
|
|
f68ad3695c | ||
|
|
ed711318e4 | ||
|
|
57a3d8f17d | ||
|
|
e1559651b8 | ||
|
|
19a6b6b618 | ||
|
|
2b7e44819f | ||
|
|
0699a0ce7c | ||
|
|
1d0d88d491 | ||
|
|
6f21b6700a | ||
|
|
af49b615b1 | ||
|
|
4651edd5b3 | ||
|
|
d7f72fec9c | ||
|
|
09011c91f8 | ||
|
|
e56fabfc5e | ||
|
|
56f752557c | ||
|
|
5f2ad7fbb0 | ||
|
|
758e30c9a8 | ||
|
|
28017f232e | ||
|
|
3302c31c60 | ||
|
|
116ba199d1 | ||
|
|
803bdced9c | ||
|
|
3eecac2003 | ||
|
|
c02e09fc2d | ||
|
|
18f8661770 | ||
|
|
04ff4a025b | ||
|
|
81ef363658 | ||
|
|
1474c5bc1c | ||
|
|
9b0a5da839 | ||
|
|
7ab2ad2c1b | ||
|
|
8416a019cb | ||
|
|
73a7c7786d | ||
|
|
11d12c5a01 | ||
|
|
c4f303a07b | ||
|
|
3a629cdf08 | ||
|
|
6ea33b657d | ||
|
|
a526f53181 | ||
|
|
0d28113df1 | ||
|
|
029a19ca05 | ||
|
|
d525c5ad93 | ||
|
|
881729b49c | ||
|
|
42ed36e5cc | ||
|
|
2b4e9137e3 | ||
|
|
37cebc1f8f | ||
|
|
59ecf5515e | ||
|
|
3f327a940f | ||
|
|
9c83a5623f | ||
|
|
efa3c2187d | ||
|
|
12bc965964 | ||
|
|
256bad9f5a | ||
|
|
e9700ecc3d | ||
|
|
eba4294b08 | ||
|
|
dbba60356e | ||
|
|
dceff1fae4 | ||
|
|
5a35fa571a | ||
|
|
ff2cfb7bce | ||
|
|
1c66347803 | ||
|
|
238ae611f6 | ||
|
|
cda29107f1 | ||
|
|
97bfa96a15 | ||
|
|
0e2f2f4173 | ||
|
|
5554b7b418 | ||
|
|
d30f77c60a | ||
|
|
a36d1673fa | ||
|
|
d233e89873 | ||
|
|
402b6224a6 | ||
|
|
4e5e2a7095 | ||
|
|
a0adbd741a | ||
|
|
d5cdecea21 | ||
|
|
fef287fcb0 | ||
|
|
6fc1a63eb8 | ||
|
|
5364e2638b | ||
|
|
d3983b00bd | ||
|
|
39fff41dd4 | ||
|
|
d0a8c896c2 | ||
|
|
4f24bcaec9 | ||
|
|
d3209f8c28 | ||
|
|
287c34b3f3 | ||
|
|
1cdc38eafb | ||
|
|
ae045251f2 | ||
|
|
9b374cd6b8 | ||
|
|
4759a78c12 | ||
|
|
d88e68eb49 | ||
|
|
b9abdf10ce | ||
|
|
5b5a9718c2 | ||
|
|
86dac5efe4 | ||
|
|
dfeab9f767 | ||
|
|
4b13658401 | ||
|
|
844b00a380 | ||
|
|
29fe911828 | ||
|
|
5282770a4c | ||
|
|
953902dcce | ||
|
|
b28e0533e0 | ||
|
|
43555fa13b | ||
|
|
10ae481b91 | ||
|
|
c2e860fe92 | ||
|
|
c2fc84e6ea | ||
|
|
6f44b7352e | ||
|
|
16106e6262 | ||
|
|
6cea73b6da | ||
|
|
fdf9a49e28 | ||
|
|
e348634dbd | ||
|
|
b67db15f8a | ||
|
|
a32a623078 | ||
|
|
03c8312f5f | ||
|
|
b75a61bce9 | ||
|
|
2c36e2447c | ||
|
|
f87c827fe6 | ||
|
|
3f395e3cee | ||
|
|
7a45ebf0f4 | ||
|
|
5b13cfc2a0 | ||
|
|
5553584056 | ||
|
|
e951612ff4 | ||
|
|
426e16b17d | ||
|
|
d9a595c9b1 | ||
|
|
8fb3728391 | ||
|
|
d4c94dce83 | ||
|
|
74d6633e9b | ||
|
|
eecad803b1 | ||
|
|
da7a31a6fa | ||
|
|
c677f7284e | ||
|
|
60e8e55311 | ||
|
|
18557e8654 | ||
|
|
39c67e2b92 | ||
|
|
b5146e3188 | ||
|
|
a59a6f3041 | ||
|
|
056d3e4933 | ||
|
|
2b4a5a73a4 | ||
|
|
46504ab0da | ||
|
|
412f6ce58d | ||
|
|
c8f9e6b9fc | ||
|
|
979b4c01ab | ||
|
|
00dc756c53 | ||
|
|
588e838dc4 | ||
|
|
2550c08749 | ||
|
|
265e3d91d6 | ||
|
|
0651c51901 | ||
|
|
a74db40eb7 | ||
|
|
f00d9b181f | ||
|
|
bc2b9cc419 | ||
|
|
3ce19993bc | ||
|
|
26a9abbe82 | ||
|
|
21d2d545d0 | ||
|
|
7c44cb9266 | ||
|
|
3a48c97f4b | ||
|
|
240017add1 | ||
|
|
b5958b069e | ||
|
|
59b8009d7a | ||
|
|
b8b4f58a79 | ||
|
|
fcb190281c | ||
|
|
9fcf900a23 | ||
|
|
06ad5e30c9 | ||
|
|
739044087b | ||
|
|
fa041537c3 | ||
|
|
079f423a4b | ||
|
|
f6060f9c53 | ||
|
|
b7f234641c | ||
|
|
4ac0af699f | ||
|
|
fb9a941722 | ||
|
|
c05339cb2d | ||
|
|
2ef518f063 | ||
|
|
fbd9280239 | ||
|
|
45ac6b839c | ||
|
|
8b59143174 | ||
|
|
c7b8f5d0d1 | ||
|
|
09533d3cb9 | ||
|
|
00582a487c | ||
|
|
4b745a957e | ||
|
|
d0fe60cadf | ||
|
|
ec60f0be52 | ||
|
|
df34c453f8 | ||
|
|
942f3d3a24 | ||
|
|
2ae7d24e79 | ||
|
|
7ffdd0dc6c | ||
|
|
f394c75de1 |
2
.github/CODEOWNERS
vendored
@@ -2,7 +2,7 @@
|
||||
# See https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
# Frontend code owners
|
||||
/frontend/ @rbren @amanape
|
||||
/frontend/ @amanape
|
||||
/openhands-ui/ @amanape
|
||||
|
||||
# Evaluation code owners
|
||||
|
||||
71
.github/scripts/update_pr_description.sh
vendored
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
|
||||
# This script updates the PR description with commands to run the PR locally
|
||||
# It adds both Docker and uvx commands
|
||||
|
||||
# Get the branch name for the PR
|
||||
BRANCH_NAME=$(gh pr view "$PR_NUMBER" --json headRefName --jq .headRefName)
|
||||
|
||||
# Define the Docker command
|
||||
DOCKER_RUN_COMMAND="docker run -it --rm \
|
||||
-p 3000:3000 \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:${SHORT_SHA}-nikolaik \
|
||||
--name openhands-app-${SHORT_SHA} \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:${SHORT_SHA}"
|
||||
|
||||
# Define the uvx command
|
||||
UVX_RUN_COMMAND="uvx --python 3.12 --from git+https://github.com/All-Hands-AI/OpenHands@${BRANCH_NAME} openhands"
|
||||
|
||||
# Get the current PR body
|
||||
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq .body)
|
||||
|
||||
# Prepare the new PR body with both commands
|
||||
if echo "$PR_BODY" | grep -q "To run this PR locally, use the following command:"; then
|
||||
# For existing PR descriptions, use a more robust approach
|
||||
# Split the PR body at the "To run this PR locally" section and replace everything after it
|
||||
BEFORE_SECTION=$(echo "$PR_BODY" | sed '/To run this PR locally, use the following command:/,$d')
|
||||
NEW_PR_BODY=$(cat <<EOF
|
||||
${BEFORE_SECTION}
|
||||
|
||||
To run this PR locally, use the following command:
|
||||
|
||||
GUI with Docker:
|
||||
\`\`\`
|
||||
${DOCKER_RUN_COMMAND}
|
||||
\`\`\`
|
||||
|
||||
CLI with uvx:
|
||||
\`\`\`
|
||||
${UVX_RUN_COMMAND}
|
||||
\`\`\`
|
||||
EOF
|
||||
)
|
||||
else
|
||||
# For new PR descriptions: use heredoc safely without indentation
|
||||
NEW_PR_BODY=$(cat <<EOF
|
||||
$PR_BODY
|
||||
|
||||
---
|
||||
|
||||
To run this PR locally, use the following command:
|
||||
|
||||
GUI with Docker:
|
||||
\`\`\`
|
||||
${DOCKER_RUN_COMMAND}
|
||||
\`\`\`
|
||||
|
||||
CLI with uvx:
|
||||
\`\`\`
|
||||
${UVX_RUN_COMMAND}
|
||||
\`\`\`
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
# Update the PR description
|
||||
echo "Updating PR description with Docker and uvx commands"
|
||||
gh pr edit "$PR_NUMBER" --body "$NEW_PR_BODY"
|
||||
223
.github/workflows/e2e-tests.yml
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
name: End-to-End Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, labeled]
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
e2e-tests:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'end-to-end') || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
env:
|
||||
GITHUB_REPO_NAME: ${{ github.repository }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install poetry via pipx
|
||||
uses: abatilo/actions-poetry@v3
|
||||
with:
|
||||
poetry-version: 2.1.3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'poetry'
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 xauth xvfb libgbm1 libasound2t64 netcat-openbsd
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: 'frontend/package-lock.json'
|
||||
|
||||
- name: Setup environment for end-to-end tests
|
||||
run: |
|
||||
# Create test results directory
|
||||
mkdir -p test-results
|
||||
|
||||
# Create downloads directory for OpenHands (use a directory in the home folder)
|
||||
mkdir -p $HOME/downloads
|
||||
sudo chown -R $USER:$USER $HOME/downloads
|
||||
sudo chmod -R 755 $HOME/downloads
|
||||
|
||||
- name: Build OpenHands
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
LLM_MODEL: ${{ secrets.LLM_MODEL || 'gpt-4o' }}
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY || 'test-key' }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
INSTALL_DOCKER: 1
|
||||
RUNTIME: docker
|
||||
FRONTEND_PORT: 12000
|
||||
FRONTEND_HOST: 0.0.0.0
|
||||
BACKEND_HOST: 0.0.0.0
|
||||
BACKEND_PORT: 3000
|
||||
ENABLE_BROWSER: true
|
||||
INSTALL_PLAYWRIGHT: 1
|
||||
run: |
|
||||
# Fix poetry.lock file if needed
|
||||
echo "Fixing poetry.lock file if needed..."
|
||||
poetry lock
|
||||
|
||||
# Build OpenHands using make build
|
||||
echo "Running make build..."
|
||||
make build
|
||||
|
||||
# Install Chromium Headless Shell for Playwright (needed for pytest-playwright)
|
||||
echo "Installing Chromium Headless Shell for Playwright..."
|
||||
poetry run playwright install chromium-headless-shell
|
||||
|
||||
# Verify Playwright browsers are installed (for e2e tests only)
|
||||
echo "Verifying Playwright browsers installation for e2e tests..."
|
||||
BROWSER_CHECK=$(poetry run python tests/e2e/check_playwright.py 2>/dev/null)
|
||||
|
||||
if [ "$BROWSER_CHECK" != "chromium_found" ]; then
|
||||
echo "ERROR: Chromium browser not found or not working for e2e tests"
|
||||
echo "$BROWSER_CHECK"
|
||||
exit 1
|
||||
else
|
||||
echo "Playwright browsers are properly installed for e2e tests."
|
||||
fi
|
||||
|
||||
# Docker runtime will handle workspace directory creation
|
||||
|
||||
# Start the application using make run with custom parameters and reduced logging
|
||||
echo "Starting OpenHands using make run..."
|
||||
# Set environment variables to reduce logging verbosity
|
||||
export PYTHONUNBUFFERED=1
|
||||
export LOG_LEVEL=WARNING
|
||||
export UVICORN_LOG_LEVEL=warning
|
||||
export OPENHANDS_LOG_LEVEL=WARNING
|
||||
FRONTEND_PORT=12000 FRONTEND_HOST=0.0.0.0 BACKEND_HOST=0.0.0.0 make run > /tmp/openhands-e2e-test.log 2>&1 &
|
||||
|
||||
# Store the PID of the make run process
|
||||
MAKE_PID=$!
|
||||
echo "OpenHands started with PID: $MAKE_PID"
|
||||
|
||||
# Wait for the application to start
|
||||
echo "Waiting for OpenHands to start..."
|
||||
max_attempts=15
|
||||
attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
echo "Checking if OpenHands is running (attempt $attempt of $max_attempts)..."
|
||||
|
||||
# Check if the process is still running
|
||||
if ! ps -p $MAKE_PID > /dev/null; then
|
||||
echo "ERROR: OpenHands process has terminated unexpectedly"
|
||||
echo "Last 50 lines of the log:"
|
||||
tail -n 50 /tmp/openhands-e2e-test.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if frontend port is open
|
||||
if nc -z localhost 12000; then
|
||||
# Verify we can get HTML content
|
||||
if curl -s http://localhost:12000 | grep -q "<html"; then
|
||||
echo "SUCCESS: OpenHands is running and serving HTML content on port 12000"
|
||||
break
|
||||
else
|
||||
echo "Port 12000 is open but not serving HTML content yet"
|
||||
fi
|
||||
else
|
||||
echo "Frontend port 12000 is not open yet"
|
||||
fi
|
||||
|
||||
# Show log output on each attempt
|
||||
echo "Recent log output:"
|
||||
tail -n 20 /tmp/openhands-e2e-test.log
|
||||
|
||||
# Wait before next attempt
|
||||
echo "Waiting 10 seconds before next check..."
|
||||
sleep 10
|
||||
attempt=$((attempt + 1))
|
||||
|
||||
# Exit if we've reached the maximum number of attempts
|
||||
if [ $attempt -gt $max_attempts ]; then
|
||||
echo "ERROR: OpenHands failed to start after $max_attempts attempts"
|
||||
echo "Last 50 lines of the log:"
|
||||
tail -n 50 /tmp/openhands-e2e-test.log
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Final verification that the app is running
|
||||
if ! nc -z localhost 12000 || ! curl -s http://localhost:12000 | grep -q "<html"; then
|
||||
echo "ERROR: OpenHands is not running properly on port 12000"
|
||||
echo "Last 50 lines of the log:"
|
||||
tail -n 50 /tmp/openhands-e2e-test.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Print success message
|
||||
echo "OpenHands is running successfully on port 12000"
|
||||
|
||||
- name: Run end-to-end tests
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.E2E_TEST_GITHUB_TOKEN }}
|
||||
LLM_MODEL: ${{ secrets.LLM_MODEL || 'gpt-4o' }}
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY || 'test-key' }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
run: |
|
||||
# Check if the application is running
|
||||
if ! nc -z localhost 12000; then
|
||||
echo "ERROR: OpenHands is not running on port 12000"
|
||||
echo "Last 50 lines of the log:"
|
||||
tail -n 50 /tmp/openhands-e2e-test.log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run the tests with detailed output
|
||||
cd tests/e2e
|
||||
poetry run python -m pytest test_e2e_workflow.py::test_github_token_configuration test_e2e_workflow.py::test_conversation_start -v --no-header --capture=no --timeout=600
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: tests/e2e/test-results/
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload OpenHands logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openhands-logs
|
||||
path: |
|
||||
/tmp/openhands-e2e-test.log
|
||||
/tmp/openhands-e2e-build.log
|
||||
/tmp/openhands-backend.log
|
||||
/tmp/openhands-frontend.log
|
||||
/tmp/backend-health-check.log
|
||||
/tmp/frontend-check.log
|
||||
/tmp/vite-config.log
|
||||
/tmp/makefile-contents.log
|
||||
retention-days: 30
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
run: |
|
||||
# Stop OpenHands processes
|
||||
echo "Stopping OpenHands processes..."
|
||||
pkill -f "python -m openhands.server" || true
|
||||
pkill -f "npm run dev" || true
|
||||
pkill -f "make run" || true
|
||||
|
||||
# Print process status for debugging
|
||||
echo "Checking if any OpenHands processes are still running:"
|
||||
ps aux | grep -E "openhands|npm run dev" || true
|
||||
2
.github/workflows/fe-unit-tests.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: 22
|
||||
node-version: [22]
|
||||
fail-fast: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
28
.github/workflows/ghcr-build.yml
vendored
@@ -332,29 +332,5 @@ jobs:
|
||||
SHORT_SHA: ${{ steps.short_sha.outputs.SHORT_SHA }}
|
||||
shell: bash
|
||||
run: |
|
||||
echo "updating PR description"
|
||||
DOCKER_RUN_COMMAND="docker run -it --rm \
|
||||
-p 3000:3000 \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:$SHORT_SHA-nikolaik \
|
||||
--name openhands-app-$SHORT_SHA \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:$SHORT_SHA"
|
||||
|
||||
PR_BODY=$(gh pr view $PR_NUMBER --json body --jq .body)
|
||||
|
||||
if echo "$PR_BODY" | grep -q "To run this PR locally, use the following command:"; then
|
||||
UPDATED_PR_BODY=$(echo "${PR_BODY}" | sed -E "s|docker run -it --rm.*|$DOCKER_RUN_COMMAND|")
|
||||
else
|
||||
UPDATED_PR_BODY="${PR_BODY}
|
||||
|
||||
---
|
||||
|
||||
To run this PR locally, use the following command:
|
||||
\`\`\`
|
||||
$DOCKER_RUN_COMMAND
|
||||
\`\`\`"
|
||||
fi
|
||||
|
||||
echo "updated body: $UPDATED_PR_BODY"
|
||||
gh pr edit $PR_NUMBER --body "$UPDATED_PR_BODY"
|
||||
echo "Updating PR description with Docker and uvx commands"
|
||||
bash ${GITHUB_WORKSPACE}/.github/scripts/update_pr_description.sh
|
||||
|
||||
8
.github/workflows/py-tests.yml
vendored
@@ -48,11 +48,9 @@ jobs:
|
||||
- name: Build Environment
|
||||
run: make build
|
||||
- name: Run Unit Tests
|
||||
run: poetry run pytest --forked -n auto -svv ./tests/unit
|
||||
run: PYTHONPATH=".:$PYTHONPATH" poetry run pytest --forked -n auto -svv ./tests/unit
|
||||
- name: Run Runtime Tests with CLIRuntime
|
||||
run: TEST_RUNTIME=cli poetry run pytest -svv tests/runtime/test_bash.py
|
||||
- name: Run E2E Tests
|
||||
run: poetry run pytest -svv tests/e2e
|
||||
run: PYTHONPATH=".:$PYTHONPATH" TEST_RUNTIME=cli poetry run pytest -svv tests/runtime/test_bash.py
|
||||
|
||||
# Run specific Windows python tests
|
||||
test-on-windows:
|
||||
@@ -77,9 +75,11 @@ jobs:
|
||||
- name: Run Windows unit tests
|
||||
run: poetry run pytest -svv tests/unit/test_windows_bash.py
|
||||
env:
|
||||
PYTHONPATH: ".;$env:PYTHONPATH"
|
||||
DEBUG: "1"
|
||||
- name: Run Windows runtime tests with LocalRuntime
|
||||
run: $env:TEST_RUNTIME="local"; poetry run pytest -svv tests/runtime/test_bash.py
|
||||
env:
|
||||
PYTHONPATH: ".;$env:PYTHONPATH"
|
||||
TEST_RUNTIME: local
|
||||
DEBUG: "1"
|
||||
|
||||
12
.github/workflows/stale.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
days-before-stale: 30
|
||||
stale-issue-message: 'This issue is stale because it has been open for 40 days with no activity. Remove the stale label or leave a comment, otherwise it will be closed in 10 days.'
|
||||
stale-pr-message: 'This PR is stale because it has been open for 40 days with no activity. Remove the stale label or leave a comment, otherwise it will be closed in 10 days.'
|
||||
days-before-stale: 40
|
||||
exempt-issue-labels: 'roadmap'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for over 30 days with no activity.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for over 30 days with no activity.'
|
||||
days-before-close: 7
|
||||
close-issue-message: 'This issue was automatically closed due to 50 days of inactivity. We do this to help keep the issues somewhat manageable and focus on active issues.'
|
||||
close-pr-message: 'This PR was closed because it had no activity for 50 days. If you feel this was closed in error, and you would like to continue the PR, please resubmit or let us know.'
|
||||
days-before-close: 10
|
||||
operations-per-run: 150
|
||||
|
||||
3
.gitignore
vendored
@@ -254,3 +254,6 @@ containers/runtime/Dockerfile
|
||||
containers/runtime/project.tar.gz
|
||||
containers/runtime/code
|
||||
**/node_modules/
|
||||
|
||||
# test results
|
||||
test-results
|
||||
|
||||
@@ -15,8 +15,6 @@ make build && make run FRONTEND_PORT=12000 FRONTEND_HOST=0.0.0.0 BACKEND_HOST=0.
|
||||
|
||||
IMPORTANT: Before making any changes to the codebase, ALWAYS run `make install-pre-commit-hooks` to ensure pre-commit hooks are properly installed.
|
||||
|
||||
|
||||
|
||||
Before pushing any changes, you MUST ensure that any lint errors or simple test errors have been fixed.
|
||||
|
||||
* If you've made changes to the backend, you should run `pre-commit run --config ./dev_config/python/.pre-commit-config.yaml` (this will run on staged files).
|
||||
@@ -32,6 +30,12 @@ then re-run the command to ensure it passes. Common issues include:
|
||||
- Trailing whitespace
|
||||
- Missing newlines at end of files
|
||||
|
||||
## Git Best Practices
|
||||
|
||||
- Prefer specific `git add <filename>` instead of `git add .` to avoid accidentally staging unintended files
|
||||
- Be especially careful with `git reset --hard` after staging files, as it will remove accidentally staged files
|
||||
- When remote has new changes, use `git fetch upstream && git rebase upstream/<branch>` on the same branch
|
||||
|
||||
## Repository Structure
|
||||
Backend:
|
||||
- Located in the `openhands` directory
|
||||
|
||||
@@ -1,59 +1,158 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Running OpenHands pre-commit hook..."
|
||||
echo "This hook runs 'make lint' to ensure code quality before committing."
|
||||
echo "This hook runs selective linting based on changed files."
|
||||
|
||||
# Store the exit code to return at the end
|
||||
# This allows us to be additive to existing pre-commit hooks
|
||||
EXIT_CODE=0
|
||||
|
||||
# Run make lint to check both frontend and backend code
|
||||
echo "Running linting checks with 'make lint'..."
|
||||
make lint
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Linting failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "Linting checks passed!"
|
||||
fi
|
||||
# Get the list of staged files
|
||||
STAGED_FILES=$(git diff --cached --name-only)
|
||||
|
||||
# Check if frontend directory has changed
|
||||
frontend_changes=$(git diff --cached --name-only | grep "^frontend/")
|
||||
if [ -n "$frontend_changes" ]; then
|
||||
echo "Frontend changes detected. Running additional frontend checks..."
|
||||
# Check if any files match specific patterns
|
||||
has_frontend_changes=false
|
||||
has_backend_changes=false
|
||||
has_vscode_changes=false
|
||||
|
||||
# Check if frontend directory exists
|
||||
if [ -d "frontend" ]; then
|
||||
# Change to frontend directory
|
||||
cd frontend || exit 1
|
||||
|
||||
# Run build
|
||||
echo "Running npm build..."
|
||||
npm run build
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend build failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
# Check each file individually to avoid issues with grep
|
||||
for file in $STAGED_FILES; do
|
||||
if [[ $file == frontend/* ]]; then
|
||||
has_frontend_changes=true
|
||||
elif [[ $file == openhands/* || $file == evaluation/* || $file == tests/* ]]; then
|
||||
has_backend_changes=true
|
||||
# Check for VSCode extension changes (subset of backend changes)
|
||||
if [[ $file == openhands/integrations/vscode/* ]]; then
|
||||
has_vscode_changes=true
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Run tests
|
||||
echo "Running npm test..."
|
||||
npm test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend tests failed. Please fix the failing tests before committing."
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
echo "Analyzing changes..."
|
||||
echo "- Frontend changes: $has_frontend_changes"
|
||||
echo "- Backend changes: $has_backend_changes"
|
||||
echo "- VSCode extension changes: $has_vscode_changes"
|
||||
|
||||
# Return to the original directory
|
||||
cd ..
|
||||
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo "Frontend checks passed!"
|
||||
fi
|
||||
# Run frontend linting if needed
|
||||
if [ "$has_frontend_changes" = true ]; then
|
||||
# Check if we're in a CI environment or if frontend dependencies are missing
|
||||
if [ -n "$CI" ] || ! command -v react-router &> /dev/null || ! command -v vitest &> /dev/null; then
|
||||
echo "Skipping frontend checks (CI environment or missing dependencies detected)."
|
||||
echo "WARNING: Frontend files have changed but frontend checks are being skipped."
|
||||
echo "Please run 'make lint-frontend' manually before submitting your PR."
|
||||
else
|
||||
echo "Frontend directory not found. Skipping frontend checks."
|
||||
echo "Running frontend linting..."
|
||||
make lint-frontend
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend linting failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "Frontend linting checks passed!"
|
||||
fi
|
||||
|
||||
# Run additional frontend checks
|
||||
if [ -d "frontend" ]; then
|
||||
echo "Running additional frontend checks..."
|
||||
cd frontend || exit 1
|
||||
|
||||
# Run build
|
||||
echo "Running npm build..."
|
||||
npm run build
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend build failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
|
||||
# Run tests
|
||||
echo "Running npm test..."
|
||||
npm test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend tests failed. Please fix the failing tests before committing."
|
||||
EXIT_CODE=1
|
||||
fi
|
||||
|
||||
cd ..
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "No frontend changes detected. Skipping additional frontend checks."
|
||||
echo "Skipping frontend checks (no frontend changes detected)."
|
||||
fi
|
||||
|
||||
# Run backend linting if needed
|
||||
if [ "$has_backend_changes" = true ]; then
|
||||
echo "Running backend linting..."
|
||||
make lint-backend
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Backend linting failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "Backend linting checks passed!"
|
||||
fi
|
||||
else
|
||||
echo "Skipping backend checks (no backend changes detected)."
|
||||
fi
|
||||
|
||||
# Run VSCode extension checks if needed
|
||||
if [ "$has_vscode_changes" = true ]; then
|
||||
# Check if we're in a CI environment
|
||||
if [ -n "$CI" ]; then
|
||||
echo "Skipping VSCode extension checks (CI environment detected)."
|
||||
echo "WARNING: VSCode extension files have changed but checks are being skipped."
|
||||
echo "Please run VSCode extension checks manually before submitting your PR."
|
||||
else
|
||||
echo "Running VSCode extension checks..."
|
||||
if [ -d "openhands/integrations/vscode" ]; then
|
||||
cd openhands/integrations/vscode || exit 1
|
||||
|
||||
echo "Running npm lint:fix..."
|
||||
npm run lint:fix
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "VSCode extension linting failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "VSCode extension linting passed!"
|
||||
fi
|
||||
|
||||
echo "Running npm typecheck..."
|
||||
npm run typecheck
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "VSCode extension type checking failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "VSCode extension type checking passed!"
|
||||
fi
|
||||
|
||||
echo "Running npm compile..."
|
||||
npm run compile
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "VSCode extension compilation failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "VSCode extension compilation passed!"
|
||||
fi
|
||||
|
||||
cd ../../..
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Skipping VSCode extension checks (no VSCode extension changes detected)."
|
||||
fi
|
||||
|
||||
# If no specific code changes detected, run basic checks
|
||||
if [ "$has_frontend_changes" = false ] && [ "$has_backend_changes" = false ]; then
|
||||
echo "No specific code changes detected. Running basic checks..."
|
||||
if [ -n "$STAGED_FILES" ]; then
|
||||
# Run only basic pre-commit hooks for non-code files
|
||||
poetry run pre-commit run --files $(echo "$STAGED_FILES" | tr '\n' ' ') --hook-stage commit --config ./dev_config/python/.pre-commit-config.yaml
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Basic checks failed. Please fix the issues before committing."
|
||||
EXIT_CODE=1
|
||||
else
|
||||
echo "Basic checks passed!"
|
||||
fi
|
||||
else
|
||||
echo "No files changed. Skipping basic checks."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run any existing pre-commit hooks that might have been installed by the user
|
||||
|
||||
@@ -34,7 +34,7 @@ _Dev Container: Reopen in Container_ command from the Command Palette
|
||||
|
||||
#### Develop without sudo access
|
||||
|
||||
If you want to develop without system admin/sudo access to upgrade/install `Python` and/or `NodeJs`, you can use
|
||||
If you want to develop without system admin/sudo access to upgrade/install `Python` and/or `NodeJS`, you can use
|
||||
`conda` or `mamba` to manage the packages for you:
|
||||
|
||||
```bash
|
||||
@@ -71,7 +71,7 @@ This command will prompt you to enter the LLM API key, model name, and other var
|
||||
tailored to your specific needs. Note that the model name will apply only when you run headless. If you use the UI,
|
||||
please set the model in the UI.
|
||||
|
||||
Note: If you have previously run OpenHands using the docker command, you may have already set some environmental
|
||||
Note: If you have previously run OpenHands using the docker command, you may have already set some environment
|
||||
variables in your terminal. The final configurations are set from highest to lowest priority:
|
||||
Environment variables > config.toml variables > default variables
|
||||
|
||||
@@ -154,12 +154,12 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
1. Add your dependency in `pyproject.toml` or use `poetry add xxx`.
|
||||
2. Update the poetry.lock file via `poetry lock --no-update`.
|
||||
|
||||
### 9. Use existing Docker image
|
||||
### 10. Use existing Docker image
|
||||
|
||||
To reduce build time (e.g., if no changes were made to the client-runtime component), you can use an existing Docker
|
||||
container image by setting the SANDBOX_RUNTIME_CONTAINER_IMAGE environment variable to the desired Docker image.
|
||||
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.49-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.53-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
|
||||
12
Makefile
@@ -3,10 +3,10 @@ SHELL=/usr/bin/env bash
|
||||
|
||||
# Variables
|
||||
BACKEND_HOST ?= "127.0.0.1"
|
||||
BACKEND_PORT = 3000
|
||||
BACKEND_PORT ?= 3000
|
||||
BACKEND_HOST_PORT = "$(BACKEND_HOST):$(BACKEND_PORT)"
|
||||
FRONTEND_HOST ?= "127.0.0.1"
|
||||
FRONTEND_PORT = 3001
|
||||
FRONTEND_PORT ?= 3001
|
||||
DEFAULT_WORKSPACE_DIR = "./workspace"
|
||||
DEFAULT_MODEL = "gpt-4o"
|
||||
CONFIG_FILE = config.toml
|
||||
@@ -174,7 +174,7 @@ install-python-dependencies:
|
||||
fi
|
||||
@echo "$(GREEN)Python dependencies installed successfully.$(RESET)"
|
||||
|
||||
install-frontend-dependencies:
|
||||
install-frontend-dependencies: check-npm check-nodejs
|
||||
@echo "$(YELLOW)Setting up frontend environment...$(RESET)"
|
||||
@echo "$(YELLOW)Detect Node.js version...$(RESET)"
|
||||
@cd frontend && node ./scripts/detect-node-version.js
|
||||
@@ -182,17 +182,17 @@ install-frontend-dependencies:
|
||||
@cd frontend && npm install
|
||||
@echo "$(GREEN)Frontend dependencies installed successfully.$(RESET)"
|
||||
|
||||
install-pre-commit-hooks:
|
||||
install-pre-commit-hooks: check-python check-poetry install-python-dependencies
|
||||
@echo "$(YELLOW)Installing pre-commit hooks...$(RESET)"
|
||||
@git config --unset-all core.hooksPath || true
|
||||
@poetry run pre-commit install --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
@echo "$(GREEN)Pre-commit hooks installed successfully.$(RESET)"
|
||||
|
||||
lint-backend:
|
||||
lint-backend: install-pre-commit-hooks
|
||||
@echo "$(YELLOW)Running linters...$(RESET)"
|
||||
@poetry run pre-commit run --all-files --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
|
||||
|
||||
lint-frontend:
|
||||
lint-frontend: install-frontend-dependencies
|
||||
@echo "$(YELLOW)Running linters for frontend...$(RESET)"
|
||||
@cd frontend && npm run lint
|
||||
|
||||
|
||||
48
README.md
@@ -52,37 +52,63 @@ which comes with $20 in free credits for new users.
|
||||
|
||||
## 💻 Running OpenHands Locally
|
||||
|
||||
OpenHands can also run on your local system using Docker.
|
||||
See the [Running OpenHands](https://docs.all-hands.dev/usage/installation) guide for
|
||||
system requirements and more information.
|
||||
### Option 1: CLI Launcher (Recommended)
|
||||
|
||||
> [!WARNING]
|
||||
> On a public network? See our [Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)
|
||||
> to secure your deployment by restricting network binding and implementing additional security measures.
|
||||
The easiest way to run OpenHands locally is using the CLI launcher with [uv](https://docs.astral.sh/uv/). This provides better isolation from your current project's virtual environment and is required for OpenHands' default MCP servers.
|
||||
|
||||
**Install uv** (if you haven't already):
|
||||
|
||||
See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for the latest installation instructions for your platform.
|
||||
|
||||
**Launch OpenHands**:
|
||||
```bash
|
||||
# Launch the GUI server
|
||||
uvx --python 3.12 --from openhands-ai openhands serve
|
||||
|
||||
# Or launch the CLI
|
||||
uvx --python 3.12 --from openhands-ai openhands
|
||||
```
|
||||
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000) (for GUI mode)!
|
||||
|
||||
### Option 2: Docker
|
||||
|
||||
<details>
|
||||
<summary>Click to expand Docker command</summary>
|
||||
|
||||
You can also run OpenHands directly with Docker:
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
|
||||
> [!WARNING]
|
||||
> On a public network? See our [Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)
|
||||
> to secure your deployment by restricting network binding and implementing additional security measures.
|
||||
|
||||
### Getting Started
|
||||
|
||||
When you open the application, you'll be asked to choose an LLM provider and add an API key.
|
||||
[Anthropic's Claude Sonnet 4](https://www.anthropic.com/api) (`anthropic/claude-sonnet-4-20250514`)
|
||||
works best, but you have [many options](https://docs.all-hands.dev/usage/llms).
|
||||
|
||||
See the [Running OpenHands](https://docs.all-hands.dev/usage/installation) guide for
|
||||
system requirements and more information.
|
||||
|
||||
## 💡 Other ways to run OpenHands
|
||||
|
||||
> [!WARNING]
|
||||
@@ -93,8 +119,8 @@ works best, but you have [many options](https://docs.all-hands.dev/usage/llms).
|
||||
> [OpenHands Cloud Helm Chart](https://github.com/all-Hands-AI/OpenHands-cloud)
|
||||
|
||||
You can [connect OpenHands to your local filesystem](https://docs.all-hands.dev/usage/runtimes/docker#connecting-to-your-filesystem),
|
||||
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/usage/how-to/headless-mode),
|
||||
interact with it via a [friendly CLI](https://docs.all-hands.dev/usage/how-to/cli-mode),
|
||||
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/usage/how-to/headless-mode),
|
||||
or run it on tagged issues with [a github action](https://docs.all-hands.dev/usage/how-to/github-action).
|
||||
|
||||
Visit [Running OpenHands](https://docs.all-hands.dev/usage/installation) for more information and setup instructions.
|
||||
|
||||
@@ -51,17 +51,17 @@ OpenHands也可以使用Docker在本地系统上运行。
|
||||
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
||||
```
|
||||
|
||||
> **注意**: 如果您在0.44版本之前使用过OpenHands,您可能需要运行 `mv ~/.openhands-state ~/.openhands` 来将对话历史迁移到新位置。
|
||||
|
||||
@@ -42,17 +42,17 @@ OpenHandsはDockerを利用してローカル環境でも実行できます。
|
||||
> 公共ネットワークで実行していますか?[Hardened Docker Installation Guide](https://docs.all-hands.dev/usage/runtimes/docker#hardened-docker-installation)を参照して、ネットワークバインディングの制限や追加のセキュリティ対策を実施してください。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
||||
```
|
||||
|
||||
**注**: バージョン0.44以前のOpenHandsを使用していた場合は、会話履歴を移行するために `mv ~/.openhands-state ~/.openhands` を実行してください。
|
||||
|
||||
180
REFACTORING_PLAN.md
Normal file
@@ -0,0 +1,180 @@
|
||||
# Tool Decoupling Refactoring Plan
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
**Where we are:**
|
||||
- New `openhands/tools/` module with unified Tool architecture (✅ committed)
|
||||
- Existing tools scattered in `openhands/agenthub/codeact_agent/tools/` (old approach)
|
||||
- Function calling logic hardcoded in `function_calling.py` with manual validation
|
||||
- Multiple agents (codeact, loc, readonly) each have their own function_calling.py
|
||||
- Tool schemas defined as dictionaries in individual tool files
|
||||
|
||||
**Key Integration Points:**
|
||||
1. `openhands/agenthub/codeact_agent/function_calling.py` - main function call processor
|
||||
2. `openhands/agenthub/codeact_agent/codeact_agent.py` - imports tools for schema generation
|
||||
3. `openhands/agenthub/loc_agent/function_calling.py` - similar pattern
|
||||
4. `openhands/agenthub/readonly_agent/function_calling.py` - similar pattern
|
||||
|
||||
## Target State
|
||||
|
||||
**Where we need to get to:**
|
||||
- All agents use the new Tool classes for consistent behavior
|
||||
- Function calling delegates to `Tool.validate_function_call()` for parameter validation
|
||||
- Tool schemas come from `Tool.get_schema()`
|
||||
- Action creation remains in function_calling.py (simple, no over-abstraction)
|
||||
- Remove duplicated tool logic across agents
|
||||
- **No registry needed** - agents directly import and use the tools they need
|
||||
|
||||
## Minimal Refactoring Strategy
|
||||
|
||||
### Phase 1: Create Bridge Layer (Non-breaking)
|
||||
**Goal:** Make new tools work alongside existing system without breaking anything
|
||||
|
||||
1. **Create tool adapter in function_calling.py**
|
||||
- Add import for new `openhands.tools` (BashTool, FileEditorTool, etc.)
|
||||
- Create helper function `validate_with_new_tools()` that attempts new tool validation
|
||||
- Fall back to existing hardcoded logic if tool not found
|
||||
- This allows gradual migration without breaking existing functionality
|
||||
|
||||
2. **Update tool imports in codeact_agent.py**
|
||||
- Import new Tool classes alongside existing tool imports
|
||||
- Modify `get_tools()` method to include schemas from both old and new tools
|
||||
- Ensure no duplicate tool names
|
||||
|
||||
### Phase 2: Migrate Core Tools (One by one)
|
||||
**Goal:** Replace existing tools with new implementations
|
||||
|
||||
1. **Start with bash tool (lowest risk)**
|
||||
- Update function_calling.py to use BashTool for execute_bash calls
|
||||
- Remove old bash tool logic once confirmed working
|
||||
- Keep old bash.py file temporarily for reference
|
||||
|
||||
2. **Migrate str_replace_editor tool**
|
||||
- Update function_calling.py to use FileEditorTool
|
||||
- Remove complex str_replace_editor logic from function_calling.py
|
||||
- Keep old str_replace_editor.py temporarily
|
||||
|
||||
3. **Migrate remaining tools one by one**
|
||||
- finish, browser, think, ipython, condensation_request
|
||||
- Each migration should be a separate commit for easy rollback
|
||||
|
||||
### Phase 3: Clean Up (Remove old code)
|
||||
**Goal:** Remove duplicate/obsolete code
|
||||
|
||||
1. **Remove old tool files**
|
||||
- Delete `openhands/agenthub/codeact_agent/tools/` directory
|
||||
- Update imports in codeact_agent.py
|
||||
|
||||
2. **Simplify function_calling.py**
|
||||
- Remove all hardcoded tool logic
|
||||
- Replace with simple registry lookup and delegation
|
||||
- Should be ~50 lines instead of ~250 lines
|
||||
|
||||
### Phase 4: Extend to Other Agents (Optional)
|
||||
**Goal:** Apply same pattern to loc_agent and readonly_agent
|
||||
|
||||
1. **Update loc_agent and readonly_agent**
|
||||
- Replace their function_calling.py with registry-based approach
|
||||
- Reuse same tool implementations
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Bridge Function (Phase 1)
|
||||
```python
|
||||
def validate_with_new_tools(tool_call):
|
||||
"""Try new tool classes for validation, fall back to old logic"""
|
||||
from openhands.tools import BashTool, FileEditorTool
|
||||
|
||||
# Map tool names to tool instances
|
||||
tools = {
|
||||
'execute_bash': BashTool(),
|
||||
'str_replace_editor': FileEditorTool(),
|
||||
}
|
||||
|
||||
tool = tools.get(tool_call.function.name)
|
||||
if tool:
|
||||
try:
|
||||
return tool.validate_function_call(tool_call.function)
|
||||
except ToolValidationError as e:
|
||||
raise FunctionCallValidationError(str(e))
|
||||
|
||||
# Fall back to existing hardcoded validation
|
||||
return None # Signal to use old logic
|
||||
```
|
||||
|
||||
### Simplified function_calling.py (Phase 3)
|
||||
```python
|
||||
def response_to_actions(response: ModelResponse, mcp_tool_names: list[str] | None = None) -> list[Action]:
|
||||
"""Convert LLM response to OpenHands actions using new tool classes"""
|
||||
from openhands.tools import BashTool, FileEditorTool
|
||||
|
||||
# Create tool instances (could be module-level for efficiency)
|
||||
tools = {
|
||||
'execute_bash': BashTool(),
|
||||
'str_replace_editor': FileEditorTool(),
|
||||
}
|
||||
|
||||
actions = []
|
||||
# ... existing response parsing logic ...
|
||||
|
||||
for tool_call in assistant_msg.tool_calls:
|
||||
tool = tools.get(tool_call.function.name)
|
||||
if tool:
|
||||
# Validate parameters using tool
|
||||
try:
|
||||
validated_params = tool.validate_function_call(tool_call.function)
|
||||
except ToolValidationError as e:
|
||||
raise FunctionCallValidationError(str(e))
|
||||
|
||||
# Create action based on tool type (simple logic remains here)
|
||||
if tool_call.function.name == 'execute_bash':
|
||||
action = CmdRunAction(command=validated_params['command'], ...)
|
||||
elif tool_call.function.name == 'str_replace_editor':
|
||||
action = FileEditAction(path=validated_params['path'], ...)
|
||||
# ... etc for other tools
|
||||
|
||||
actions.append(action)
|
||||
elif mcp_tool_names and tool_call.function.name in mcp_tool_names:
|
||||
# Handle MCP tools
|
||||
actions.append(MCPAction(...))
|
||||
else:
|
||||
raise FunctionCallNotExistsError(f'Tool {tool_call.function.name} not found')
|
||||
|
||||
return actions
|
||||
```
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
1. **Incremental approach** - Each phase can be tested independently
|
||||
2. **Backward compatibility** - Bridge layer ensures nothing breaks during transition
|
||||
3. **Easy rollback** - Each tool migration is a separate commit
|
||||
4. **Minimal changes** - Don't touch agent logic, only function calling layer
|
||||
5. **Keep it simple** - Don't over-engineer, just replace existing functionality
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] All existing tests pass
|
||||
- [ ] Function calling behavior unchanged from user perspective
|
||||
- [ ] Tool logic consolidated in single location
|
||||
- [ ] Easy to add new tools by extending Tool base class
|
||||
- [ ] Reduced code duplication across agents
|
||||
- [ ] Cleaner, more maintainable codebase
|
||||
|
||||
## Files to Modify
|
||||
|
||||
**Phase 1:**
|
||||
- `openhands/agenthub/codeact_agent/function_calling.py` (add bridge)
|
||||
- `openhands/agenthub/codeact_agent/codeact_agent.py` (import registry)
|
||||
|
||||
**Phase 2:**
|
||||
- `openhands/agenthub/codeact_agent/function_calling.py` (migrate tools one by one)
|
||||
|
||||
**Phase 3:**
|
||||
- `openhands/agenthub/codeact_agent/function_calling.py` (simplify)
|
||||
- Remove `openhands/agenthub/codeact_agent/tools/` directory
|
||||
|
||||
**Phase 4 (Optional):**
|
||||
- `openhands/agenthub/loc_agent/function_calling.py`
|
||||
- `openhands/agenthub/readonly_agent/function_calling.py`
|
||||
|
||||
This plan prioritizes **working incrementally** while **maintaining stability** throughout the refactoring process.
|
||||
219
TOOL_DECOUPLING_PLAN.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# OpenHands Tool Decoupling - Complete Implementation Plan
|
||||
|
||||
## 🎯 Goal
|
||||
Decouple AI agent tools into their own classes to encapsulate tool definitions, error validation, and response interpretation separate from regular agent LLM response processing.
|
||||
|
||||
## 📊 Current Status: CRITICAL MILESTONE ACHIEVED ✅
|
||||
|
||||
**function_calling.py Migration Complete**: Successfully migrated CodeActAgent to use unified tool validation for all 4 core tools!
|
||||
|
||||
### 🏗️ Architecture Summary
|
||||
- **CodeActAgent**: 4 base tools (BashTool, FileEditorTool, BrowserTool, FinishTool)
|
||||
- **ReadOnlyAgent**: Inherits FinishTool + adds 3 safe tools (ViewTool, GrepTool, GlobTool)
|
||||
- **LocAgent**: Inherits all CodeAct tools + adds 3 search tools (SearchEntityTool, SearchRepoTool, ExploreStructureTool)
|
||||
|
||||
### 🚀 Migration Achievement: function_calling.py Complete
|
||||
- ✅ **Fixed legacy tool import conflicts** with proper aliasing (LegacyBrowserTool, LegacyFinishTool)
|
||||
- ✅ **Updated BrowserTool interface** to match legacy (code parameter instead of action)
|
||||
- ✅ **All 4 core tools using unified validation**:
|
||||
- BashTool: `validate_parameters()` with proper error handling
|
||||
- FinishTool: `validate_parameters()` with parameter mapping (summary/outputs)
|
||||
- FileEditorTool: `validate_parameters()` with command handling (view/edit)
|
||||
- BrowserTool: `validate_parameters()` with code parameter validation
|
||||
- ✅ **Fixed tool name constant references** throughout function_calling.py
|
||||
- ✅ **Created comprehensive integration tests** verifying tool validation works
|
||||
- ✅ **Maintained backward compatibility** with legacy fallback paths
|
||||
|
||||
### 🧪 Testing Status
|
||||
- **192 total tests** (all passing)
|
||||
- **Integration tests passing** for all 4 core tools
|
||||
- **163 original tests**: Base Tool class, validation, error handling, inheritance patterns
|
||||
- **29 new LocAgent tests**: Complete coverage of search tools and inheritance
|
||||
|
||||
### 🔧 Implementation Status
|
||||
- ✅ **Tool base class** with abstract methods and validation framework
|
||||
- ✅ **CodeAct tools** with full parameter validation and schema generation
|
||||
- ✅ **ReadOnly tools** with inheritance pattern and safety validation
|
||||
- ✅ **LocAgent tools** with complex parameter validation and search capabilities
|
||||
- ✅ **Comprehensive test suite** covering all tools and edge cases
|
||||
- ✅ **CodeActAgent function_calling.py migration** with unified tool validation
|
||||
|
||||
## Architecture Decision: Agent-Specific Tool Organization
|
||||
|
||||
After exploring the codebase, we discovered that **agent-specific tool organization** is the correct approach because:
|
||||
|
||||
1. **CodeActAgent** is the base agent with comprehensive tools (bash, file editing, browsing, etc.)
|
||||
2. **ReadOnlyAgent** and **LocAgent** inherit from CodeActAgent but completely override `_get_tools()`
|
||||
3. Each agent has its own `tools/` directory and `function_calling.py` module
|
||||
4. Child agents can selectively inherit parent tools and add their own
|
||||
|
||||
## Current Architecture
|
||||
|
||||
```
|
||||
openhands/agenthub/codeact_agent/tools/unified/
|
||||
├── __init__.py # Exports all CodeAct tools
|
||||
├── base.py # Tool base class with validation
|
||||
├── bash_tool.py # Full bash access
|
||||
├── file_editor_tool.py # File editing capabilities
|
||||
├── browser_tool.py # Web browsing
|
||||
└── finish_tool.py # Task completion
|
||||
|
||||
openhands/agenthub/readonly_agent/tools/unified/
|
||||
├── __init__.py # Imports FinishTool from CodeAct + own tools
|
||||
├── view_tool.py # Safe file/directory viewing
|
||||
├── grep_tool.py # Safe text search
|
||||
└── glob_tool.py # Safe file pattern matching
|
||||
|
||||
openhands/agenthub/loc_agent/tools/unified/
|
||||
└── [TODO] Inherit from CodeAct + add search tools
|
||||
```
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ✅ COMPLETED (Phase 1: Tool Architecture)
|
||||
- [x] Base Tool class with schema definition and parameter validation
|
||||
- [x] CodeAct unified tools (BashTool, FileEditorTool, BrowserTool, FinishTool)
|
||||
- [x] ReadOnly unified tools (ViewTool, GrepTool, GlobTool)
|
||||
- [x] Inheritance pattern: ReadOnly imports FinishTool from CodeAct parent
|
||||
- [x] Parameter validation with comprehensive error handling
|
||||
- [x] Schema generation compatible with LiteLLM function calling
|
||||
|
||||
### ✅ COMPLETED (Phase 2: Tool Architecture & Testing)
|
||||
- [x] **Comprehensive unit tests** (192 tests, all passing)
|
||||
- [x] **LocAgent tool organization** (inherit from CodeAct + add search tools)
|
||||
- [x] All agent-specific tool architectures complete
|
||||
|
||||
### 🔄 IN PROGRESS (Phase 3: Integration & Migration)
|
||||
- ✅ **CodeActAgent function_calling.py migration** (COMPLETED!)
|
||||
- [ ] ReadOnlyAgent function_calling.py migration (NEXT)
|
||||
- [ ] LocAgent function_calling.py migration (NEXT)
|
||||
|
||||
### 📋 TODO (Phase 3: Full Migration)
|
||||
- [ ] Remove old tool definitions after migration complete
|
||||
- [ ] Documentation and cleanup
|
||||
- [ ] Performance testing and optimization
|
||||
|
||||
## Detailed Implementation Plan
|
||||
|
||||
### Phase 2: Testing & Integration (CURRENT)
|
||||
|
||||
#### 2.1 Comprehensive Unit Tests (IMMEDIATE)
|
||||
Create `tests/unit/tools/` with complete test coverage:
|
||||
|
||||
**Base Infrastructure Tests:**
|
||||
- `test_base_tool.py` - Tool base class, validation, error handling
|
||||
- `test_tool_inheritance.py` - Agent inheritance patterns
|
||||
|
||||
**CodeAct Tool Tests:**
|
||||
- `test_bash_tool.py` - BashTool schema and validation
|
||||
- `test_file_editor_tool.py` - FileEditorTool schema and validation
|
||||
- `test_browser_tool.py` - BrowserTool schema and validation
|
||||
- `test_finish_tool.py` - FinishTool schema and validation
|
||||
|
||||
**ReadOnly Tool Tests:**
|
||||
- `test_view_tool.py` - ViewTool schema and validation
|
||||
- `test_grep_tool.py` - GrepTool schema and validation
|
||||
- `test_glob_tool.py` - GlobTool schema and validation
|
||||
|
||||
**Integration Tests:**
|
||||
- `test_agent_tool_integration.py` - Agent-specific tool loading
|
||||
- `test_function_call_validation.py` - End-to-end function call processing
|
||||
|
||||
#### 2.2 Bridge Layer Implementation
|
||||
- Create adapter functions in each agent's function_calling.py
|
||||
- Gradual migration: new tools alongside existing ones
|
||||
- Validation layer that uses new Tool classes
|
||||
|
||||
#### 2.3 Integration Points
|
||||
- Update `openhands/agenthub/codeact_agent/function_calling.py`
|
||||
- Update `openhands/agenthub/readonly_agent/function_calling.py`
|
||||
- Ensure backward compatibility during transition
|
||||
|
||||
### Phase 3: Full Migration
|
||||
|
||||
#### 3.1 LocAgent Tool Organization ✅
|
||||
```
|
||||
openhands/agenthub/loc_agent/tools/unified/
|
||||
├── __init__.py # Inherit from CodeAct + add search tools
|
||||
├── search_entity_tool.py # SearchEntityTool for entity retrieval
|
||||
├── search_repo_tool.py # SearchRepoTool for code snippet search
|
||||
└── explore_structure_tool.py # ExploreStructureTool for dependency analysis
|
||||
```
|
||||
|
||||
#### 3.2 Complete Migration
|
||||
- Replace all old tool definitions with new unified classes
|
||||
- Update all function_calling.py modules
|
||||
- Remove legacy tool code
|
||||
- Update agent `_get_tools()` methods to use new architecture
|
||||
|
||||
#### 3.3 Cleanup & Documentation
|
||||
- Remove unused tool files
|
||||
- Update documentation
|
||||
- Add migration guide for future tool additions
|
||||
|
||||
## Key Benefits of This Architecture
|
||||
|
||||
1. **Encapsulation**: Tool logic separated from agent processing
|
||||
2. **Inheritance**: Child agents can reuse parent tools selectively
|
||||
3. **Validation**: Centralized parameter validation with clear error messages
|
||||
4. **Extensibility**: Easy to add new tools or modify existing ones
|
||||
5. **Type Safety**: Proper typing and schema validation
|
||||
6. **Testing**: Each tool can be unit tested independently
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Test Coverage Requirements
|
||||
- **Schema Generation**: Verify correct LiteLLM-compatible schemas
|
||||
- **Parameter Validation**: Test all validation rules and edge cases
|
||||
- **Error Handling**: Test all error conditions and messages
|
||||
- **Inheritance**: Verify child agents can inherit and extend parent tools
|
||||
- **Integration**: Test function call processing end-to-end
|
||||
|
||||
### Test Categories
|
||||
1. **Positive Tests**: Valid inputs produce expected outputs
|
||||
2. **Negative Tests**: Invalid inputs produce appropriate errors
|
||||
3. **Edge Cases**: Boundary conditions, empty values, type mismatches
|
||||
4. **Integration Tests**: Agent-tool interaction, function calling flow
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
1. **Parallel Implementation**: New tools alongside existing ones
|
||||
2. **Gradual Adoption**: Migrate one agent at a time
|
||||
3. **Backward Compatibility**: Maintain existing functionality during transition
|
||||
4. **Validation**: Comprehensive testing at each step
|
||||
5. **Cleanup**: Remove old code only after full migration
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] All agents use unified tool architecture
|
||||
- [ ] 100% test coverage for tool functionality
|
||||
- [ ] No regression in existing functionality
|
||||
- [ ] Clear separation of concerns between tools and agents
|
||||
- [ ] Easy to add new tools or modify existing ones
|
||||
- [ ] Comprehensive error handling and validation
|
||||
|
||||
## Current State Summary
|
||||
|
||||
**MAJOR MILESTONE ACHIEVED**: ReadOnlyAgent function_calling.py migration complete!
|
||||
|
||||
### Phase 2 Complete: Agent-Specific Tool Implementation ✅
|
||||
- **CodeActAgent tools**: 4 unified tools (BashTool, FileEditorTool, BrowserTool, FinishTool)
|
||||
- **ReadOnlyAgent tools**: 4 unified tools (ViewTool, GrepTool, GlobTool, FinishTool inherited)
|
||||
- **LocAgent tools**: 3 specialized tools + all CodeAct tools inherited
|
||||
- **All 192 tests passing** (163 original + 29 LocAgent tests)
|
||||
|
||||
### Phase 3 In Progress: function_calling.py Migration 🔄
|
||||
- **CodeActAgent function_calling.py**: ✅ COMPLETE (unified validation for all 4 tools)
|
||||
- **ReadOnlyAgent function_calling.py**: ✅ COMPLETE (unified validation for all 4 tools)
|
||||
- **LocAgent function_calling.py**: ⏳ PENDING (next step)
|
||||
|
||||
### Architecture Summary
|
||||
- **Tool Classes**: Encapsulate schema definition and parameter validation
|
||||
- **Inheritance Pattern**: Child agents import parent tools + add their own
|
||||
- **Validation Strategy**: Unified validation with legacy fallbacks
|
||||
- **Error Handling**: Comprehensive ToolValidationError system
|
||||
- **Testing**: 192 comprehensive unit tests covering all scenarios
|
||||
|
||||
**CURRENT**: LocAgent function_calling.py migration
|
||||
**NEXT**: Final integration testing and cleanup
|
||||
**GOAL**: Complete tool decoupling with zero regression
|
||||
@@ -93,8 +93,7 @@ def build_vscode_extension():
|
||||
|
||||
|
||||
def build(setup_kwargs):
|
||||
"""
|
||||
This function is called by Poetry during the build process.
|
||||
"""This function is called by Poetry during the build process.
|
||||
`setup_kwargs` is a dictionary that will be passed to `setuptools.setup()`.
|
||||
"""
|
||||
print('--- Running custom Poetry build script (build_vscode.py) ---')
|
||||
|
||||
@@ -58,8 +58,8 @@ RUN sed -i 's/^UID_MIN.*/UID_MIN 499/' /etc/login.defs
|
||||
# Default is 60000, but we've seen up to 200000
|
||||
RUN sed -i 's/^UID_MAX.*/UID_MAX 1000000/' /etc/login.defs
|
||||
|
||||
RUN groupadd app
|
||||
RUN useradd -l -m -u $OPENHANDS_USER_ID -s /bin/bash openhands && \
|
||||
RUN groupadd --gid $OPENHANDS_USER_ID app
|
||||
RUN useradd -l -m -u $OPENHANDS_USER_ID --gid $OPENHANDS_USER_ID -s /bin/bash openhands && \
|
||||
usermod -aG app openhands && \
|
||||
usermod -aG sudo openhands && \
|
||||
echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
|
||||
|
||||
@@ -23,6 +23,18 @@ if [ -z "$WORKSPACE_MOUNT_PATH" ]; then
|
||||
unset WORKSPACE_BASE
|
||||
fi
|
||||
|
||||
if [[ "$INSTALL_THIRD_PARTY_RUNTIMES" == "true" ]]; then
|
||||
echo "Downloading and installing third_party_runtimes..."
|
||||
echo "Warning: Third-party runtimes are provided as-is, not actively supported and may be removed in future releases."
|
||||
|
||||
if pip install 'openhands-ai[third_party_runtimes]' -qqq 2> >(tee /dev/stderr); then
|
||||
echo "third_party_runtimes installed successfully."
|
||||
else
|
||||
echo "Failed to install third_party_runtimes." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$SANDBOX_USER_ID" -eq 0 ]]; then
|
||||
echo "Running OpenHands as root"
|
||||
export RUN_AS_OPENHANDS=false
|
||||
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
- DOCKER_HOST_ADDR=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.49-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.53-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -40,7 +40,7 @@ repos:
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies:
|
||||
[types-requests, types-setuptools, types-pyyaml, types-toml, types-docker, pydantic, lxml]
|
||||
[types-requests, types-setuptools, types-pyyaml, types-toml, types-docker, types-Markdown, pydantic, lxml]
|
||||
# To see gaps add `--html-report mypy-report/`
|
||||
entry: mypy --config-file dev_config/python/mypy.ini openhands/
|
||||
always_run: true
|
||||
|
||||
@@ -7,7 +7,7 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik}
|
||||
#- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234} # enable this only if you want a specific non-root sandbox user but you will have to manually adjust permissions of ~/.openhands for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -37,7 +37,16 @@
|
||||
"usage/cloud/bitbucket-installation",
|
||||
"usage/cloud/github-installation",
|
||||
"usage/cloud/gitlab-installation",
|
||||
"usage/cloud/slack-installation"
|
||||
"usage/cloud/slack-installation",
|
||||
{
|
||||
"group": "Project Management Tools",
|
||||
"pages": [
|
||||
"usage/cloud/project-management/overview",
|
||||
"usage/cloud/project-management/jira-integration",
|
||||
"usage/cloud/project-management/jira-dc-integration",
|
||||
"usage/cloud/project-management/linear-integration"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"usage/cloud/cloud-ui",
|
||||
@@ -62,6 +71,7 @@
|
||||
{
|
||||
"group": "Providers",
|
||||
"pages": [
|
||||
"usage/llms/openhands-llms",
|
||||
"usage/llms/azure-llms",
|
||||
"usage/llms/google-llms",
|
||||
"usage/llms/groq",
|
||||
@@ -69,7 +79,6 @@
|
||||
"usage/llms/litellm-proxy",
|
||||
"usage/llms/moonshot",
|
||||
"usage/llms/openai-llms",
|
||||
"usage/llms/openhands-llms",
|
||||
"usage/llms/openrouter"
|
||||
]
|
||||
}
|
||||
|
||||
BIN
docs/static/img/jira-admin-configure.png
vendored
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/static/img/jira-admin-edit.png
vendored
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/static/img/jira-dc-admin-configure.png
vendored
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
docs/static/img/jira-dc-admin-edit.png
vendored
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
docs/static/img/jira-dc-user-link.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/static/img/jira-dc-user-unlink.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/static/img/jira-user-link.png
vendored
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/static/img/jira-user-unlink.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/static/img/linear-admin-configure.png
vendored
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
docs/static/img/linear-admin-edit.png
vendored
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
docs/static/img/linear-user-link.png
vendored
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
docs/static/img/linear-user-unlink.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
@@ -8,6 +8,29 @@ description: This guide walks you through the process of installing OpenHands Cl
|
||||
|
||||
- Signed in to [OpenHands Cloud](https://app.all-hands.dev) with [a Bitbucket account](/usage/cloud/openhands-cloud).
|
||||
|
||||
## IP Whitelisting
|
||||
|
||||
If your Bitbucket Cloud instance has IP restrictions, you'll need to whitelist the following IP addresses to allow OpenHands to access your repositories:
|
||||
|
||||
### Core App IP
|
||||
```
|
||||
34.68.58.200
|
||||
```
|
||||
|
||||
### Runtime IPs
|
||||
```
|
||||
34.10.175.217
|
||||
34.136.162.246
|
||||
34.45.0.142
|
||||
34.28.69.126
|
||||
35.224.240.213
|
||||
34.70.174.52
|
||||
34.42.4.87
|
||||
35.222.133.153
|
||||
34.29.175.97
|
||||
34.60.55.59
|
||||
```
|
||||
|
||||
## Adding Bitbucket Repository Access
|
||||
|
||||
Upon signing into OpenHands Cloud with a Bitbucket account, OpenHands will have access to your repositories.
|
||||
|
||||
126
docs/usage/cloud/project-management/jira-dc-integration.mdx
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: Jira Data Center Integration (Beta)
|
||||
description: Complete guide for setting up Jira Data Center integration with OpenHands Cloud, including service account creation, personal access token generation, webhook configuration, and workspace integration setup.
|
||||
---
|
||||
|
||||
# Jira Data Center Integration
|
||||
|
||||
## Platform Configuration
|
||||
|
||||
### Step 1: Create Service Account
|
||||
|
||||
1. **Access User Management**
|
||||
- Log in to Jira Data Center as administrator
|
||||
- Go to **Administration** > **User Management**
|
||||
|
||||
2. **Create User**
|
||||
- Click **Create User**
|
||||
- Username: `openhands-agent`
|
||||
- Full Name: `OpenHands Agent`
|
||||
- Email: `openhands@yourcompany.com` (replace with your preferred service account email)
|
||||
- Password: Set a secure password
|
||||
- Click **Create**
|
||||
|
||||
3. **Assign Permissions**
|
||||
- Add user to appropriate groups
|
||||
- Ensure access to relevant projects
|
||||
- Grant necessary project permissions
|
||||
|
||||
### Step 2: Generate API Token
|
||||
|
||||
1. **Personal Access Tokens**
|
||||
- Log in as the service account
|
||||
- Go to **Profile** > **Personal Access Tokens**
|
||||
- Click **Create token**
|
||||
- Name: `OpenHands Cloud Integration`
|
||||
- Expiry: Set appropriate expiration (recommend 1 year)
|
||||
- Click **Create**
|
||||
- **Important**: Copy and store the token securely
|
||||
|
||||
### Step 3: Configure Webhook
|
||||
|
||||
1. **Create Webhook**
|
||||
- Go to **Administration** > **System** > **WebHooks**
|
||||
- Click **Create a WebHook**
|
||||
- **Name**: `OpenHands Cloud Integration`
|
||||
- **URL**: `https://app.all-hands.dev/integration/jira-dc/events`
|
||||
- Set a suitable webhook secret
|
||||
- **Issue related events**: Select the following:
|
||||
- Issue updated
|
||||
- Comment created
|
||||
- **JQL Filter**: Leave empty (or customize as needed)
|
||||
- Click **Create**
|
||||
- **Important**: Copy and store the webhook secret securely (you'll need this for workspace integration)
|
||||
|
||||
---
|
||||
|
||||
## Workspace Integration
|
||||
|
||||
### Step 1: Log in to OpenHands Cloud
|
||||
|
||||
1. **Navigate and Authenticate**
|
||||
- Go to [OpenHands Cloud](https://app.all-hands.dev/)
|
||||
- Sign in with your Git provider (GitHub, GitLab, or BitBucket)
|
||||
- **Important:** Make sure you're signing in with the same Git provider account that contains the repositories you want the OpenHands agent to work on.
|
||||
|
||||
### Step 2: Configure Jira Data Center Integration
|
||||
|
||||
1. **Access Integration Settings**
|
||||
- Navigate to **Settings** > **Integrations**
|
||||
- Locate **Jira Data Center** section
|
||||
|
||||
2. **Configure Workspace**
|
||||
- Click **Configure** button
|
||||
- Enter your workspace name and click **Connect**
|
||||
- If no integration exists, you'll be prompted to enter additional credentials required for the workspace integration:
|
||||
- **Webhook Secret**: The webhook secret from Step 3 above
|
||||
- **Service Account Email**: The service account email from Step 1 above
|
||||
- **Service Account API Key**: The personal access token from Step 2 above
|
||||
- Ensure **Active** toggle is enabled
|
||||
|
||||
<Note>
|
||||
Workspace name is the host name of your Jira Data Center instance.
|
||||
|
||||
Eg: http://jira.all-hands.dev/projects/OH/issues/OH-77
|
||||
|
||||
Here the workspace name is **jira.all-hands.dev**.
|
||||
</Note>
|
||||
|
||||
3. **Complete OAuth Flow**
|
||||
- You'll be redirected to Jira Data Center to complete OAuth verification
|
||||
- Grant the necessary permissions to verify your workspace access. If you have access to multiple workspaces, select the correct one that you initially provided
|
||||
- If successful, you will be redirected back to the **Integrations** settings in the OpenHands Cloud UI
|
||||
|
||||
### Managing Your Integration
|
||||
|
||||
**Edit Configuration:**
|
||||
- Click the **Edit** button next to your configured platform
|
||||
- Update any necessary credentials or settings
|
||||
- Click **Update** to apply changes
|
||||
- You will need to repeat the OAuth flow as before
|
||||
- **Important:** Only the original user who created the integration can see the edit view
|
||||
|
||||
**Unlink Workspace:**
|
||||
- In the edit view, click **Unlink** next to the workspace name
|
||||
- This will deactivate your workspace link
|
||||
- **Important:** If the original user who configured the integration chooses to unlink their integration, any users currently linked to that integration will also be unlinked, and the workspace integration will be deactivated. The integration can only be reactivated by the original user.
|
||||
|
||||
### Screenshots
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Workspace link flow">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Workspace Configure flow">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Edit view as a user">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Edit view as the workspace creator">
|
||||

|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
130
docs/usage/cloud/project-management/jira-integration.mdx
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Jira Cloud Integration
|
||||
description: Complete guide for setting up Jira Cloud integration with OpenHands Cloud, including service account creation, API token generation, webhook configuration, and workspace integration setup.
|
||||
---
|
||||
|
||||
# Jira Cloud Integration
|
||||
|
||||
## Platform Configuration
|
||||
|
||||
### Step 1: Create Service Account
|
||||
|
||||
1. **Navigate to User Management**
|
||||
- Go to [Atlassian Admin](https://admin.atlassian.com/)
|
||||
- Select your organization
|
||||
- Go to **Directory** > **Users**
|
||||
|
||||
2. **Create OpenHands Service Account**
|
||||
- Click **Service accounts**
|
||||
- Click **Create a service account**
|
||||
- Name: `OpenHands Agent`
|
||||
- Click **Next**
|
||||
- Select **User** role for Jira app
|
||||
- Click **Create**
|
||||
|
||||
### Step 2: Generate API Token
|
||||
|
||||
1. **Access Service Account Configuration**
|
||||
- Locate the created service account from above step and click on it
|
||||
- Click **Create API token**
|
||||
- Set the expiry to 365 days (maximum allowed value)
|
||||
- Click **Next**
|
||||
- In **Select token scopes** screen, filter by following values
|
||||
- App: Jira
|
||||
- Scope type: Classic
|
||||
- Scope actions: Write, Read
|
||||
- Select `read:jira-work` and `write:jira-work` scopes
|
||||
- Click **Next**
|
||||
- Review and create API token
|
||||
- **Important**: Copy and securely store the token immediately
|
||||
|
||||
### Step 3: Configure Webhook
|
||||
|
||||
1. **Navigate to Webhook Settings**
|
||||
- Go to **Jira Settings** > **System** > **WebHooks**
|
||||
- Click **Create a WebHook**
|
||||
|
||||
2. **Configure Webhook**
|
||||
- **Name**: `OpenHands Cloud Integration`
|
||||
- **Status**: Enabled
|
||||
- **URL**: `https://app.all-hands.dev/integration/jira/events`
|
||||
- **Issue related events**: Select the following:
|
||||
- Issue updated
|
||||
- Comment created
|
||||
- **JQL Filter**: Leave empty (or customize as needed)
|
||||
- Click **Create**
|
||||
- **Important**: Copy and store the webhook secret securely (you'll need this for workspace integration)
|
||||
|
||||
---
|
||||
|
||||
## Workspace Integration
|
||||
|
||||
### Step 1: Log in to OpenHands Cloud
|
||||
|
||||
1. **Navigate and Authenticate**
|
||||
- Go to [OpenHands Cloud](https://app.all-hands.dev/)
|
||||
- Sign in with your Git provider (GitHub, GitLab, or BitBucket)
|
||||
- **Important:** Make sure you're signing in with the same Git provider account that contains the repositories you want the OpenHands agent to work on.
|
||||
|
||||
### Step 2: Configure Jira Integration
|
||||
|
||||
1. **Access Integration Settings**
|
||||
- Navigate to **Settings** > **Integrations**
|
||||
- Locate **Jira Cloud** section
|
||||
|
||||
2. **Configure Workspace**
|
||||
- Click **Configure** button
|
||||
- Enter your workspace name and click **Connect**
|
||||
- **Important:** Make sure you enter the full workspace name, eg: **yourcompany.atlassian.net**
|
||||
- If no integration exists, you'll be prompted to enter additional credentials required for the workspace integration:
|
||||
- **Webhook Secret**: The webhook secret from Step 3 above
|
||||
- **Service Account Email**: The service account email from Step 1 above
|
||||
- **Service Account API Key**: The API token from Step 2 above
|
||||
- Ensure **Active** toggle is enabled
|
||||
|
||||
<Note>
|
||||
Workspace name is the host name when accessing a resource in Jira Cloud.
|
||||
|
||||
Eg: https://all-hands.atlassian.net/browse/OH-55
|
||||
|
||||
Here the workspace name is **all-hands**.
|
||||
</Note>
|
||||
|
||||
3. **Complete OAuth Flow**
|
||||
- You'll be redirected to Jira Cloud to complete OAuth verification
|
||||
- Grant the necessary permissions to verify your workspace access.
|
||||
- If successful, you will be redirected back to the **Integrations** settings in the OpenHands Cloud UI
|
||||
|
||||
### Managing Your Integration
|
||||
|
||||
**Edit Configuration:**
|
||||
- Click the **Edit** button next to your configured platform
|
||||
- Update any necessary credentials or settings
|
||||
- Click **Update** to apply changes
|
||||
- You will need to repeat the OAuth flow as before
|
||||
- **Important:** Only the original user who created the integration can see the edit view
|
||||
|
||||
**Unlink Workspace:**
|
||||
- In the edit view, click **Unlink** next to the workspace name
|
||||
- This will deactivate your workspace link
|
||||
- **Important:** If the original user who configured the integration chooses to unlink their integration, any users currently linked to that workspace integration will also be unlinked, and the workspace integration will be deactivated. The integration can only be reactivated by the original user.
|
||||
|
||||
### Screenshots
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Workspace link flow">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Workspace Configure flow">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Edit view as a user">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Edit view as the workspace creator">
|
||||

|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
130
docs/usage/cloud/project-management/linear-integration.mdx
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
title: Linear Integration
|
||||
description: Complete guide for setting up Linear integration with OpenHands Cloud, including service account creation, API key generation, webhook configuration, and workspace integration setup.
|
||||
---
|
||||
|
||||
# Linear Integration
|
||||
|
||||
## Platform Configuration
|
||||
|
||||
### Step 1: Create Service Account
|
||||
|
||||
1. **Access Team Settings**
|
||||
- Log in to Linear as a team admin
|
||||
- Go to **Settings** > **Members**
|
||||
|
||||
2. **Invite Service Account**
|
||||
- Click **Invite members**
|
||||
- Email: `openhands@yourcompany.com` (replace with your preferred service account email)
|
||||
- Role: **Member** (with appropriate team access)
|
||||
- Send invitation
|
||||
|
||||
3. **Complete Setup**
|
||||
- Accept invitation from the service account email
|
||||
- Complete profile setup
|
||||
- Ensure access to relevant teams/workspaces
|
||||
|
||||
### Step 2: Generate API Key
|
||||
|
||||
1. **Access API Settings**
|
||||
- Log in as the service account
|
||||
- Go to **Settings** > **Security & access**
|
||||
|
||||
2. **Create Personal API Key**
|
||||
- Click **Create new key**
|
||||
- Name: `OpenHands Cloud Integration`
|
||||
- Scopes: Select the following:
|
||||
- `Read` - Read access to issues and comments
|
||||
- `Create comments` - Ability to create or update comments
|
||||
- Select the teams you want to provide access to, or allow access for all teams you have permissions for
|
||||
- Click **Create**
|
||||
- **Important**: Copy and store the API key securely
|
||||
|
||||
### Step 3: Configure Webhook
|
||||
|
||||
1. **Access Webhook Settings**
|
||||
- Go to **Settings** > **API** > **Webhooks**
|
||||
- Click **New webhook**
|
||||
|
||||
2. **Configure Webhook**
|
||||
- **Label**: `OpenHands Cloud Integration`
|
||||
- **URL**: `https://app.all-hands.dev/integration/linear/events`
|
||||
- **Resource types**: Select:
|
||||
- `Comment` - For comment events
|
||||
- `Issue` - For issue updates (label changes)
|
||||
- Select the teams you want to provide access to, or allow access for all public teams
|
||||
- Click **Create webhook**
|
||||
- **Important**: Copy and store the webhook secret securely (you'll need this for workspace integration)
|
||||
|
||||
---
|
||||
|
||||
## Workspace Integration
|
||||
|
||||
### Step 1: Log in to OpenHands Cloud
|
||||
|
||||
1. **Navigate and Authenticate**
|
||||
- Go to [OpenHands Cloud](https://app.all-hands.dev/)
|
||||
- Sign in with your Git provider (GitHub, GitLab, or BitBucket)
|
||||
- **Important:** Make sure you're signing in with the same Git provider account that contains the repositories you want the OpenHands agent to work on.
|
||||
|
||||
### Step 2: Configure Linear Integration
|
||||
|
||||
1. **Access Integration Settings**
|
||||
- Navigate to **Settings** > **Integrations**
|
||||
- Locate **Linear** section
|
||||
|
||||
2. **Configure Workspace**
|
||||
- Click **Configure** button
|
||||
- Enter your workspace name and click **Connect**
|
||||
- If no integration exists, you'll be prompted to enter additional credentials required for the workspace integration:
|
||||
- **Webhook Secret**: The webhook secret from Step 3 above
|
||||
- **Service Account Email**: The service account email from Step 1 above
|
||||
- **Service Account API Key**: The API key from Step 2 above
|
||||
- Ensure **Active** toggle is enabled
|
||||
|
||||
<Note>
|
||||
Workspace name is the identifier after the host name when accessing a resource in Linear.
|
||||
|
||||
Eg: https://linear.app/allhands/issue/OH-37
|
||||
|
||||
Here the workspace name is **allhands**.
|
||||
</Note>
|
||||
|
||||
3. **Complete OAuth Flow**
|
||||
- You'll be redirected to Linear to complete OAuth verification
|
||||
- Grant the necessary permissions to verify your workspace access. If you have access to multiple workspaces, select the correct one that you initially provided
|
||||
- If successful, you will be redirected back to the **Integrations** settings in the OpenHands Cloud UI
|
||||
|
||||
### Managing Your Integration
|
||||
|
||||
**Edit Configuration:**
|
||||
- Click the **Edit** button next to your configured platform
|
||||
- Update any necessary credentials or settings
|
||||
- Click **Update** to apply changes
|
||||
- You will need to repeat the OAuth flow as before
|
||||
- **Important:** Only the original user who created the integration can see the edit view
|
||||
|
||||
**Unlink Workspace:**
|
||||
- In the edit view, click **Unlink** next to the workspace name
|
||||
- This will deactivate your workspace link
|
||||
- **Important:** If the original user who configured the integration chooses to unlink their integration, any users currently linked to that integration will also be unlinked, and the workspace integration will be deactivated. The integration can only be reactivated by the original user.
|
||||
|
||||
### Screenshots
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Workspace link flow">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Workspace Configure flow">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Edit view as a user">
|
||||

|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Edit view as the workspace creator">
|
||||

|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
80
docs/usage/cloud/project-management/overview.mdx
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Project Management Tool Integrations
|
||||
description: Overview of OpenHands Cloud integrations with project management platforms including Jira Cloud, Jira Data Center, and Linear. Learn about setup requirements, usage methods, and troubleshooting.
|
||||
---
|
||||
|
||||
# Project Management Tool Integrations
|
||||
|
||||
## Overview
|
||||
|
||||
OpenHands Cloud integrates with project management platforms (Jira Cloud, Jira Data Center, and Linear) to enable AI-powered task delegation. Users can invoke the OpenHands agent by:
|
||||
- Adding `@openhands` in ticket comments
|
||||
- Adding the `openhands` label to tickets
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Integration requires two levels of setup:
|
||||
1. **Platform Configuration** - Administrative setup of service accounts and webhooks on your project management platform (see individual platform documentation below)
|
||||
2. **Workspace Integration** - Self-service configuration through the OpenHands Cloud UI to link your OpenHands account to the target workspace
|
||||
|
||||
### Platform-Specific Setup Guides:
|
||||
- [Jira Cloud Integration](./jira-integration.md)
|
||||
- [Jira Data Center Integration](./jira-dc-integration.md)
|
||||
- [Linear Integration](./linear-integration.md)
|
||||
|
||||
## Usage
|
||||
|
||||
Once both the platform configuration and workspace integration are completed, users can trigger the OpenHands agent within their project management platforms using two methods:
|
||||
|
||||
### Method 1: Comment Mention
|
||||
Add a comment to any issue with `@openhands` followed by your task description:
|
||||
```
|
||||
@openhands Please implement the user authentication feature described in this ticket
|
||||
```
|
||||
|
||||
### Method 2: Label-based Delegation
|
||||
Add the label `openhands` to any issue. The OpenHands agent will automatically process the issue based on its description and requirements.
|
||||
|
||||
### Git Repository Detection
|
||||
|
||||
The OpenHands agent needs to identify which Git repository to work with when processing your issues. Here's how to ensure proper repository detection:
|
||||
|
||||
#### Specifying the Target Repository
|
||||
|
||||
**Required:** Include the target Git repository in your issue description or comment to ensure the agent works with the correct codebase.
|
||||
|
||||
**Supported Repository Formats:**
|
||||
- Full HTTPS URL: `https://github.com/owner/repository.git`
|
||||
- GitHub URL without .git: `https://github.com/owner/repository`
|
||||
- Owner/repository format: `owner/repository`
|
||||
|
||||
#### Platform-Specific Behavior
|
||||
|
||||
**Linear Integration:** When GitHub integration is enabled for your Linear workspace with issue sync activated, the target repository is automatically detected from the linked GitHub issue. Manual specification is not required in this configuration.
|
||||
|
||||
**Jira Integrations:** Always include the repository information in your issue description or `@openhands` comment to ensure proper repository detection.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Platform Configuration Issues
|
||||
- **Webhook not triggering**: Verify the webhook URL is correct and the proper event types are selected (Comment, Issue updated)
|
||||
- **API authentication failing**: Check API key/token validity and ensure required scopes are granted. If your current API token is expired, make sure to update it in the respective integration settings
|
||||
- **Permission errors**: Ensure the service account has access to relevant projects/teams and appropriate permissions
|
||||
|
||||
### Workspace Integration Issues
|
||||
- **Workspace linking requests credentials**: If there are no active workspace integrations for the workspace you specified, you need to configure it first. Contact your platform administrator that you want to integrate with (eg: Jira, Linear)
|
||||
- **Integration not found**: Verify the workspace name matches exactly and that platform configuration was completed first
|
||||
- **OAuth flow fails**: Make sure that you're authorizing with the correct account with proper workspace access
|
||||
|
||||
### General Issues
|
||||
- **Agent not responding**: Check webhook logs in your platform settings and verify service account status
|
||||
- **Authentication errors**: Verify Git provider permissions and OpenHands Cloud access
|
||||
- **Agent fails to identify git repo**: Ensure you're signing in with the same Git provider account that contains the repositories you want OpenHands to work on
|
||||
- **Partial functionality**: Ensure both platform configuration and workspace integration are properly completed
|
||||
|
||||
### Getting Help
|
||||
For additional support, contact OpenHands Cloud support with:
|
||||
- Your integration platform (Linear, Jira Cloud, or Jira Data Center)
|
||||
- Workspace name
|
||||
- Error logs from webhook/integration attempts
|
||||
- Screenshots of configuration settings (without sensitive credentials)
|
||||
@@ -12,6 +12,10 @@ description: This guide walks you through installing the OpenHands Slack app.
|
||||
allowFullScreen>
|
||||
</iframe>
|
||||
|
||||
<Info>
|
||||
OpenHands utilizes a large language model (LLM), which may generate responses that are inaccurate or incomplete. While we strive for accuracy, OpenHands' outputs are not guaranteed to be correct, and we encourage users to validate critical information independently.
|
||||
</Info>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Access to OpenHands Cloud.
|
||||
@@ -24,7 +28,7 @@ description: This guide walks you through installing the OpenHands Slack app.
|
||||
**This step is for Slack admins/owners**
|
||||
|
||||
1. Make sure you have permissions to install Apps to your workspace.
|
||||
2. Click the button below to install OpenHands Slack App <a target="_blank" href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,chat:write,users:read,channels:history,groups:history,mpim:history,im:history&user_scope=channels:history,groups:history,im:history,mpim:history"><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
|
||||
2. Click the button below to install OpenHands Slack App <a target="_blank" href="https://slack.com/oauth/v2/authorize?client_id=7477886716822.8729519890534&scope=app_mentions:read,channels:history,chat:write,groups:history,im:history,mpim:history,users:read&user_scope="><img alt="Add to Slack" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcSet="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/add_to_slack@2x.png 2x" /></a>
|
||||
3. In the top right corner, select the workspace to install the OpenHands Slack app.
|
||||
4. Review permissions and click allow.
|
||||
|
||||
|
||||
@@ -20,27 +20,42 @@ for scripting.
|
||||
|
||||
### Running with Python
|
||||
|
||||
**Note** - OpenHands requires Python version 3.12 or higher (Python 3.14 is not currently supported)
|
||||
**Note** - OpenHands requires Python version 3.12 or higher (Python 3.14 is not currently supported) and `uv` for the default `fetch` MCP server (more details below).
|
||||
|
||||
1. Install OpenHands using pip:
|
||||
```bash
|
||||
pip install openhands-ai
|
||||
```
|
||||
#### Recommended: Using uv
|
||||
|
||||
Or if you prefer not to manage your own Python environment, you can use `uvx`:
|
||||
We recommend using [uv](https://docs.astral.sh/uv/) for the best OpenHands experience. uv provides better isolation from your current project's virtual environment and is required for OpenHands' default MCP servers.
|
||||
|
||||
1. **Install uv** (if you haven't already):
|
||||
|
||||
See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for the latest installation instructions for your platform.
|
||||
|
||||
2. **Launch OpenHands CLI**:
|
||||
```bash
|
||||
uvx --python 3.12 --from openhands-ai openhands
|
||||
```
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
<Accordion title="Alternative: Traditional pip installation">
|
||||
|
||||
If you prefer to use pip:
|
||||
|
||||
```bash
|
||||
# Install OpenHands
|
||||
pip install openhands-ai
|
||||
```
|
||||
|
||||
Note that you'll still need `uv` installed for the default MCP servers to work properly.
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Create shell aliases for easy access across environments">
|
||||
|
||||
Add the following to your shell configuration file (`.bashrc`, `.zshrc`, etc.):
|
||||
|
||||
```bash
|
||||
# Add OpenHands aliases
|
||||
# Add OpenHands aliases (recommended)
|
||||
alias openhands="uvx --python 3.12 --from openhands-ai openhands"
|
||||
alias oh="uvx --python 3.12 --from openhands-ai openhands"
|
||||
```
|
||||
@@ -72,18 +87,19 @@ source ~/.bashrc # or source ~/.zshrc
|
||||
|
||||
</AccordionGroup>
|
||||
|
||||
2. Launch an interactive OpenHands conversation from the command line:
|
||||
3. Launch an interactive OpenHands conversation from the command line:
|
||||
```bash
|
||||
openhands
|
||||
# If using uvx (recommended)
|
||||
uvx --python 3.12 --from openhands-ai openhands
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you have cloned the repository, you can also run the CLI directly using Poetry:
|
||||
|
||||
poetry run python -m openhands.cli.main
|
||||
poetry run openhands
|
||||
</Note>
|
||||
|
||||
3. Set your model, API key, and other preferences using the UI (or alternatively environment variables, below).
|
||||
4. Set your model, API key, and other preferences using the UI (or alternatively environment variables, below).
|
||||
|
||||
This command opens an interactive prompt where you can type tasks or commands and get responses from OpenHands.
|
||||
The first time you run the CLI, it will take you through configuring the required LLM
|
||||
@@ -103,7 +119,7 @@ The conversation history will be saved in `~/.openhands/sessions`.
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -112,7 +128,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.53 \
|
||||
python -m openhands.cli.main --override-cli-mode true
|
||||
```
|
||||
|
||||
@@ -153,6 +169,7 @@ You can use the following commands whenever the prompt (`>`) is displayed:
|
||||
| `/new` | Start a new conversation |
|
||||
| `/settings` | View and modify current LLM/agent settings |
|
||||
| `/resume` | Resume the agent if paused |
|
||||
| `/mcp` | Manage MCP server configuration and view connection errors |
|
||||
|
||||
#### Settings and Configuration
|
||||
|
||||
@@ -162,7 +179,7 @@ follow the prompts:
|
||||
- **Basic settings**: Choose a model/provider and enter your API key.
|
||||
- **Advanced settings**: Set custom endpoints, enable or disable confirmation mode, and configure memory condensation.
|
||||
|
||||
Settings can also be managed via the `config.toml` file.
|
||||
Settings can also be managed via the `config.toml` file in the current directory or `~/.openhands/config.toml`.
|
||||
|
||||
#### Repository Initialization
|
||||
|
||||
@@ -174,6 +191,41 @@ project details and structure. Use this when onboarding the agent to a new codeb
|
||||
You can pause the agent while it is running by pressing `Ctrl-P`. To continue the conversation after pausing, simply
|
||||
type `/resume` at the prompt.
|
||||
|
||||
#### MCP Server Management
|
||||
|
||||
To configure Model Context Protocol (MCP) servers, you can refer to the documentation on [MCP servers](../mcp) and use the `/mcp` command in the CLI. This command provides an interactive interface for managing Model Context Protocol (MCP) servers:
|
||||
|
||||
- **List configured servers**: View all currently configured MCP servers (SSE, Stdio, and SHTTP)
|
||||
- **Add new server**: Interactively add a new MCP server with guided prompts
|
||||
- **Remove server**: Remove an existing MCP server from your configuration
|
||||
- **View errors**: Display any connection errors that occurred during MCP server startup
|
||||
|
||||
This command modifies your `~/.openhands/config.toml` file and will prompt you to restart OpenHands for changes to take effect.
|
||||
|
||||
By default, the [Fetch MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch) will be automatically configured for OpenHands. You can also [enable search engine](../search-engine-setup) via the [Tavily MCP server](https://github.com/tavily-ai/tavily-mcp) by setting the `search_api_key` under the `[core]` section in the `~/.openhands/config.toml` file.
|
||||
|
||||
##### Example of the `config.toml` file with MCP server configuration:
|
||||
|
||||
```toml
|
||||
[core]
|
||||
search_api_key = "tvly-your-api-key-here"
|
||||
|
||||
[mcp]
|
||||
stdio_servers = [
|
||||
{name="fetch", command="uvx", args=["mcp-server-fetch"]},
|
||||
]
|
||||
|
||||
sse_servers = [
|
||||
# Basic SSE server with just a URL
|
||||
"http://example.com:8080/sse",
|
||||
]
|
||||
|
||||
shttp_servers = [
|
||||
# Streamable HTTP server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
]
|
||||
```
|
||||
|
||||
## Tips and Troubleshooting
|
||||
|
||||
- Use `/help` at any time to see the list of available commands.
|
||||
|
||||
@@ -7,6 +7,67 @@ description: High level overview of the Graphical User Interface (GUI) in OpenHa
|
||||
|
||||
- [OpenHands is running](/usage/local-setup)
|
||||
|
||||
## Launching the GUI Server
|
||||
|
||||
### Using the CLI Command
|
||||
|
||||
You can launch the OpenHands GUI server directly from the command line using the `serve` command:
|
||||
|
||||
<Callout type="info">
|
||||
**Prerequisites**: You need to have the [OpenHands CLI installed](/usage/how-to/cli-mode) first, OR have `uv` installed and run `uvx --python 3.12 --from openhands-ai openhands serve`. Otherwise, you'll need to use Docker directly (see the [Docker section](#using-docker-directly) below).
|
||||
</Callout>
|
||||
|
||||
```bash
|
||||
openhands serve
|
||||
```
|
||||
|
||||
This command will:
|
||||
- Check that Docker is installed and running
|
||||
- Pull the required Docker images
|
||||
- Launch the OpenHands GUI server at http://localhost:3000
|
||||
- Use the same configuration directory (`~/.openhands`) as the CLI mode
|
||||
|
||||
#### Mounting Your Current Directory
|
||||
|
||||
To mount your current working directory into the GUI server container, use the `--mount-cwd` flag:
|
||||
|
||||
```bash
|
||||
openhands serve --mount-cwd
|
||||
```
|
||||
|
||||
This is useful when you want to work on files in your current directory through the GUI. The directory will be mounted at `/workspace` inside the container.
|
||||
|
||||
#### Using GPU Support
|
||||
|
||||
If you have NVIDIA GPUs and want to make them available to the OpenHands container, use the `--gpu` flag:
|
||||
|
||||
```bash
|
||||
openhands serve --gpu
|
||||
```
|
||||
|
||||
This will enable GPU support via nvidia-docker, mounting all available GPUs into the container. You can combine this with other flags:
|
||||
|
||||
```bash
|
||||
openhands serve --gpu --mount-cwd
|
||||
```
|
||||
|
||||
**Prerequisites for GPU support:**
|
||||
- NVIDIA GPU drivers must be installed on your host system
|
||||
- [NVIDIA Container Toolkit (nvidia-docker2)](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) must be installed and configured
|
||||
|
||||
#### Requirements
|
||||
|
||||
Before using the `openhands serve` command, ensure that:
|
||||
- Docker is installed and running on your system
|
||||
- You have internet access to pull the required Docker images
|
||||
- Port 3000 is available on your system
|
||||
|
||||
The CLI will automatically check these requirements and provide helpful error messages if anything is missing.
|
||||
|
||||
### Using Docker Directly
|
||||
|
||||
Alternatively, you can run the GUI server using Docker directly. See the [local setup guide](/usage/local-setup) for detailed Docker instructions.
|
||||
|
||||
## Overview
|
||||
|
||||
### Initial Setup
|
||||
|
||||
@@ -61,7 +61,7 @@ export GITHUB_TOKEN="your-token" # Required for repository operations
|
||||
# Run OpenHands
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e SANDBOX_VOLUMES=$SANDBOX_VOLUMES \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -73,7 +73,7 @@ docker run -it \
|
||||
-v ~/.openhands:/.openhands \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.53 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ Based on these findings and community feedback, these are the latest models that
|
||||
### Cloud / API-Based Models
|
||||
|
||||
- [anthropic/claude-sonnet-4-20250514](https://www.anthropic.com/api) (recommended)
|
||||
- [openai/o4-mini](https://openai.com/index/introducing-o3-and-o4-mini/)
|
||||
- [openai/gpt-5-2025-08-07](https://openai.com/api/) (recommended)
|
||||
- [gemini/gemini-2.5-pro](https://blog.google/technology/google-deepmind/gemini-model-thinking-updates-march-2025/)
|
||||
- [deepseek/deepseek-chat](https://api-docs.deepseek.com/)
|
||||
- [moonshot/kimi-k2-0711-preview](https://platform.moonshot.ai/docs/pricing/chat#generation-model-kimi-k2)
|
||||
@@ -39,6 +39,12 @@ limits and monitor usage.
|
||||
- [mistralai/devstral-small](https://www.all-hands.dev/blog/devstral-a-new-state-of-the-art-open-model-for-coding-agents) (20 May 2025) -- also available through [OpenRouter](https://openrouter.ai/mistralai/devstral-small:free)
|
||||
- [all-hands/openhands-lm-32b-v0.1](https://www.all-hands.dev/blog/introducing-openhands-lm-32b----a-strong-open-coding-agent-model) (31 March 2025) -- also available through [OpenRouter](https://openrouter.ai/all-hands/openhands-lm-32b-v0.1)
|
||||
|
||||
### Known Issues
|
||||
|
||||
<Warning>
|
||||
As of July 2025, there are known issues with Gemini 2.5 Pro conversations taking longer than normal with OpenHands. We are continuing to investigate.
|
||||
</Warning>
|
||||
|
||||
<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
|
||||
|
||||
@@ -68,23 +68,23 @@ Download and install the LM Studio desktop app from [lmstudio.ai](https://lmstud
|
||||
1. Check [the installation guide](/usage/local-setup) and ensure all prerequisites are met before running OpenHands, then run:
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
||||
```
|
||||
|
||||
2. Wait until the server is running (see log below):
|
||||
```
|
||||
Digest: sha256:e72f9baecb458aedb9afc2cd5bc935118d1868719e55d50da73190d3a85c674f
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
Status: Image is up to date for docker.all-hands.dev/all-hands-ai/openhands:0.53
|
||||
Starting OpenHands...
|
||||
Running OpenHands as root
|
||||
14:22:13 - openhands:INFO: server_config.py:50 - Using config class None
|
||||
|
||||
@@ -30,5 +30,6 @@ When running OpenHands, you'll need to set the following in the OpenHands UI thr
|
||||
|
||||
## Pricing
|
||||
|
||||
Pricing follows official API provider rates.
|
||||
[You can view model prices here.](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json)
|
||||
Pricing follows official API provider rates. [You can view model prices here.](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json)
|
||||
|
||||
For `qwen3-coder-480b`, we charge the cheapest FP8 rate available on openrouter: \$0.4 per million input tokens and \$1.6 per million output tokens.
|
||||
|
||||
@@ -66,20 +66,64 @@ A system with a modern processor and a minimum of **4GB RAM** is recommended to
|
||||
|
||||
### Start the App
|
||||
|
||||
#### Option 1: Using the CLI Launcher with uv (Recommended)
|
||||
|
||||
We recommend using [uv](https://docs.astral.sh/uv/) for the best OpenHands experience. uv provides better isolation from your current project's virtual environment and is required for OpenHands' default MCP servers (like the [fetch MCP server](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch)).
|
||||
|
||||
**Install uv** (if you haven't already):
|
||||
|
||||
See the [uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for the latest installation instructions for your platform.
|
||||
|
||||
**Launch OpenHands**:
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik
|
||||
# Launch the GUI server
|
||||
uvx --python 3.12 --from openhands-ai openhands serve
|
||||
|
||||
# Or with GPU support (requires nvidia-docker)
|
||||
uvx --python 3.12 --from openhands-ai openhands serve --gpu
|
||||
|
||||
# Or with current directory mounted
|
||||
uvx --python 3.12 --from openhands-ai openhands serve --mount-cwd
|
||||
```
|
||||
|
||||
This will automatically handle Docker requirements checking, image pulling, and launching the GUI server. The `--gpu` flag enables GPU support via nvidia-docker, and `--mount-cwd` mounts your current directory into the container.
|
||||
|
||||
<Accordion title="Alternative: Traditional pip installation">
|
||||
|
||||
If you prefer to use pip and have Python 3.12+ installed:
|
||||
|
||||
```bash
|
||||
# Install OpenHands
|
||||
pip install openhands-ai
|
||||
|
||||
# Launch the GUI server
|
||||
openhands serve
|
||||
```
|
||||
|
||||
Note that you'll still need `uv` installed for the default MCP servers to work properly.
|
||||
|
||||
</Accordion>
|
||||
|
||||
#### Option 2: Using Docker Directly
|
||||
|
||||
<Accordion title="Docker Command (Click to expand)">
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.49-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.53-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands:/.openhands \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.49
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.53
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
|
||||
> **Note**: If you used OpenHands before version 0.44, you may want to run `mv ~/.openhands-state ~/.openhands` to migrate your conversation history to the new location.
|
||||
|
||||
You'll find OpenHands running at http://localhost:3000!
|
||||
@@ -100,6 +144,16 @@ OpenHands requires an API key to access most language models. Here's how to get
|
||||
|
||||
<AccordionGroup>
|
||||
|
||||
<Accordion title="OpenHands (Recommended)">
|
||||
|
||||
1. [Log in to OpenHands Cloud](https://app.all-hands.dev).
|
||||
2. Go to the Settings page and navigate to the `API Keys` tab.
|
||||
3. Copy your `LLM API Key`.
|
||||
|
||||
OpenHands provides access to state-of-the-art agentic coding models with competitive pricing. [Learn more about OpenHands LLM provider](/usage/llms/openhands-llms).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Anthropic (Claude)">
|
||||
|
||||
1. [Create an Anthropic account](https://console.anthropic.com/).
|
||||
|
||||
@@ -10,47 +10,83 @@ Model Context Protocol (MCP) is a mechanism that allows OpenHands to communicate
|
||||
servers can provide additional functionality to the agent, such as specialized data processing, external API access,
|
||||
or custom tools. MCP is based on the open standard defined at [modelcontextprotocol.io](https://modelcontextprotocol.io).
|
||||
|
||||
|
||||
<Note>
|
||||
MCP is currently not available on OpenHands Cloud. This feature is only available when running OpenHands locally.
|
||||
</Note>
|
||||
|
||||
### How MCP Works
|
||||
|
||||
When OpenHands starts, it:
|
||||
|
||||
1. Reads the MCP configuration.
|
||||
2. Connects to any configured SSE and SHTTP servers.
|
||||
3. Starts any configured stdio servers.
|
||||
4. Registers the tools provided by these servers with the agent.
|
||||
|
||||
The agent can then use these tools just like any built-in tool. When the agent calls an MCP tool:
|
||||
|
||||
1. OpenHands routes the call to the appropriate MCP server.
|
||||
2. The server processes the request and returns a response.
|
||||
3. OpenHands converts the response to an observation and presents it to the agent.
|
||||
|
||||
## Configuration
|
||||
|
||||
MCP configuration can be defined in:
|
||||
* The OpenHands UI through the Settings under the `MCP` tab.
|
||||
* The `config.toml` file under the `[mcp]` section if not using the UI.
|
||||
|
||||
### Configuration Example via config.toml
|
||||
### Configuration Examples
|
||||
|
||||
#### Recommended: Using Proxy Servers (SSE/HTTP)
|
||||
|
||||
For stdio-based MCP servers, we recommend using MCP proxy tools like [`supergateway`](https://github.com/supercorp-ai/supergateway) instead of direct stdio connections.
|
||||
[SuperGateway](https://github.com/supercorp-ai/supergateway) is a popular MCP proxy that converts stdio MCP servers to HTTP/SSE endpoints:
|
||||
|
||||
Start the proxy servers separately:
|
||||
```bash
|
||||
# Terminal 1: Filesystem server proxy
|
||||
supergateway --stdio "npx @modelcontextprotocol/server-filesystem /" --port 8080
|
||||
|
||||
# Terminal 2: Fetch server proxy
|
||||
supergateway --stdio "uvx mcp-server-fetch" --port 8081
|
||||
```
|
||||
|
||||
Then configure OpenHands to use the HTTP endpoint:
|
||||
|
||||
```toml
|
||||
[mcp]
|
||||
# SSE Servers - External servers that communicate via Server-Sent Events
|
||||
# SSE Servers - Recommended approach using proxy tools
|
||||
sse_servers = [
|
||||
# Basic SSE server with just a URL
|
||||
"http://example.com:8080/mcp",
|
||||
|
||||
# SSE server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
# SuperGateway proxy for fetch server
|
||||
"http://localhost:8081/sse",
|
||||
|
||||
# External MCP service with authentication
|
||||
{url="https://api.example.com/mcp/sse", api_key="your-api-key"}
|
||||
]
|
||||
```
|
||||
|
||||
# SHTTP Servers - External servers that communicate via Streamable HTTP
|
||||
shttp_servers = [
|
||||
# Basic SHTTP server with just a URL
|
||||
"http://example.com:8080/mcp",
|
||||
|
||||
# SHTTP server with API key authentication
|
||||
{url="https://secure-example.com/mcp", api_key="your-api-key"}
|
||||
]
|
||||
|
||||
# Stdio Servers - Local processes that communicate via standard input/output
|
||||
#### Alternative: Direct Stdio Servers (Not Recommended for Production)
|
||||
|
||||
```toml
|
||||
[mcp]
|
||||
# Direct stdio servers - use only for development/testing
|
||||
stdio_servers = [
|
||||
# Basic stdio server
|
||||
{name="fetch", command="uvx", args=["mcp-server-fetch"]},
|
||||
|
||||
# Stdio server with environment variables
|
||||
{
|
||||
name="data-processor",
|
||||
command="python",
|
||||
args=["-m", "my_mcp_server"],
|
||||
name="filesystem",
|
||||
command="npx",
|
||||
args=["@modelcontextprotocol/server-filesystem", "/"],
|
||||
env={
|
||||
"DEBUG": "true",
|
||||
"PORT": "8080"
|
||||
"DEBUG": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -84,6 +120,8 @@ SHTTP (Streamable HTTP) servers are configured using either a string URL or an o
|
||||
|
||||
### Stdio Servers
|
||||
|
||||
**Note**: While stdio servers are supported, we recommend using MCP proxies (see above) for better reliability and performance.
|
||||
|
||||
Stdio servers are configured using an object with the following properties:
|
||||
|
||||
- `name` (required)
|
||||
@@ -104,20 +142,38 @@ Stdio servers are configured using an object with the following properties:
|
||||
- Default: `{}`
|
||||
- Description: Environment variables to set for the server process
|
||||
|
||||
## How MCP Works
|
||||
|
||||
When OpenHands starts, it:
|
||||
#### When to Use Direct Stdio
|
||||
|
||||
1. Reads the MCP configuration.
|
||||
2. Connects to any configured SSE and SHTTP servers.
|
||||
3. Starts any configured stdio servers.
|
||||
4. Registers the tools provided by these servers with the agent.
|
||||
Direct stdio connections may still be appropriate in these scenarios:
|
||||
- **Development and testing**: Quick prototyping of MCP servers
|
||||
- **Simple, single-use tools**: Tools that don't require high reliability or concurrent access
|
||||
- **Local-only environments**: When you don't want to manage additional proxy processes
|
||||
|
||||
The agent can then use these tools just like any built-in tool. When the agent calls an MCP tool:
|
||||
For production use, we recommend using proxy tools like SuperGateway.
|
||||
|
||||
1. OpenHands routes the call to the appropriate MCP server.
|
||||
2. The server processes the request and returns a response.
|
||||
3. OpenHands converts the response to an observation and presents it to the agent.
|
||||
### Other Proxy Tools
|
||||
|
||||
Other options include:
|
||||
|
||||
- **Custom FastAPI/Express servers**: Build your own HTTP wrapper around stdio MCP servers
|
||||
- **Docker-based proxies**: Containerized solutions for better isolation
|
||||
- **Cloud-hosted MCP services**: Third-party services that provide MCP endpoints
|
||||
|
||||
### Troubleshooting MCP Connections
|
||||
|
||||
#### Common Issues with Stdio Servers
|
||||
- **Process crashes**: Stdio processes may crash without proper error handling
|
||||
- **Deadlocks**: Stdio communication can deadlock under high load
|
||||
- **Resource leaks**: Zombie processes if not properly managed
|
||||
- **Debugging difficulty**: Hard to inspect stdio communication
|
||||
|
||||
#### Benefits of Using Proxies
|
||||
- **HTTP status codes**: Clear error reporting via standard HTTP responses
|
||||
- **Request logging**: Easy to log and monitor HTTP requests
|
||||
- **Load balancing**: Can distribute requests across multiple server instances
|
||||
- **Health checks**: HTTP endpoints can provide health status
|
||||
- **CORS support**: Better integration with web-based tools
|
||||
|
||||
## Transport Protocols
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -172,7 +172,7 @@ def process_instance(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--answerer_model', '-a', default='gpt-3.5-turbo', help='answerer model'
|
||||
)
|
||||
|
||||
@@ -26,8 +26,8 @@ from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -506,7 +506,6 @@ def commit0_setup(dataset: pd.DataFrame, repo_split: str) -> pd.DataFrame:
|
||||
Returns:
|
||||
Filtered dataset based on split type
|
||||
"""
|
||||
|
||||
filtered_dataset = pd.concat(
|
||||
[
|
||||
dataset[dataset['repo'].str.split('/').str[1] == repo]
|
||||
@@ -525,7 +524,7 @@ def commit0_setup(dataset: pd.DataFrame, repo_split: str) -> pd.DataFrame:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -89,8 +89,7 @@ def get_config(
|
||||
def get_dv_query_for_real(
|
||||
datasets, question, domain_knowledge=None, workflow_tags=None
|
||||
):
|
||||
"""
|
||||
Prepare a structured query for the agent to execute on the specified datasets.
|
||||
"""Prepare a structured query for the agent to execute on the specified datasets.
|
||||
|
||||
This function constructs a query by compiling metadata from the provided datasets, along with any relevant domain knowledge and workflow tags.
|
||||
|
||||
@@ -104,7 +103,6 @@ def get_dv_query_for_real(
|
||||
query_to_dv: Query to be run on the dataset
|
||||
dataset_meta: Metadata of the dataset
|
||||
"""
|
||||
|
||||
dataset_meta = ''
|
||||
for dataset_metadata in datasets:
|
||||
dataset_meta += 'Dataset name: ' + dataset_metadata['name']
|
||||
@@ -140,8 +138,7 @@ def get_dv_query_for_real(
|
||||
|
||||
|
||||
def initialize_runtime(runtime: Runtime, data_files: list[str]):
|
||||
"""
|
||||
Initialize the runtime for the agent.
|
||||
"""Initialize the runtime for the agent.
|
||||
|
||||
This function is called before the runtime is used to run the agent.
|
||||
"""
|
||||
@@ -231,8 +228,7 @@ def process_instance(
|
||||
metadata: EvalMetadata,
|
||||
reset_logger: bool = True,
|
||||
):
|
||||
"""
|
||||
Process and evaluate a single instance of the dataset.
|
||||
"""Process and evaluate a single instance of the dataset.
|
||||
|
||||
This function executes the OpenHands agent
|
||||
for a specific instance of the dataset. It retrieves
|
||||
@@ -247,7 +243,6 @@ def process_instance(
|
||||
Returns:
|
||||
output: EvalOutput object
|
||||
"""
|
||||
|
||||
config = get_config(metadata)
|
||||
|
||||
# Setup the logger properly, so you can run
|
||||
@@ -356,8 +351,7 @@ def list_csv_files(list_of_datasets):
|
||||
|
||||
|
||||
def create_dataset(repo_location: str, split: str = 'test'):
|
||||
"""
|
||||
Create a dataset from the discoverybench repository
|
||||
"""Create a dataset from the discoverybench repository
|
||||
by walking through the repository and extracting metadata
|
||||
from the metadata_{}.json files
|
||||
|
||||
@@ -368,7 +362,6 @@ def create_dataset(repo_location: str, split: str = 'test'):
|
||||
Returns:
|
||||
df: DataFrame containing the dataset instances
|
||||
"""
|
||||
|
||||
data_dict = {}
|
||||
|
||||
data_location = os.path.join(repo_location, 'discoverybench', 'real', split)
|
||||
|
||||
@@ -10,7 +10,6 @@ import huggingface_hub
|
||||
import pandas as pd
|
||||
from datasets import load_dataset
|
||||
from PIL import Image
|
||||
from pydantic import SecretStr
|
||||
|
||||
from evaluation.benchmarks.gaia.scorer import question_scorer
|
||||
from evaluation.benchmarks.gaia.utils import (
|
||||
@@ -31,8 +30,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
load_from_toml,
|
||||
)
|
||||
from openhands.core.config.utils import get_agent_config_arg
|
||||
@@ -80,8 +79,7 @@ def get_config(
|
||||
|
||||
config_copy = copy.deepcopy(config)
|
||||
load_from_toml(config_copy)
|
||||
if config_copy.search_api_key:
|
||||
config.search_api_key = SecretStr(config_copy.search_api_key)
|
||||
config.search_api_key = config_copy.search_api_key
|
||||
return config
|
||||
|
||||
|
||||
@@ -294,7 +292,7 @@ Here is the task:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--level',
|
||||
type=str,
|
||||
|
||||
@@ -20,8 +20,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -134,7 +134,7 @@ def process_instance(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--hubs',
|
||||
type=str,
|
||||
|
||||
@@ -38,8 +38,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -312,7 +312,7 @@ Ok now its time to start solving the question. Good luck!
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
# data split must be one of 'gpqa_main', 'gqpa_diamond', 'gpqa_experts', 'gpqa_extended'
|
||||
parser.add_argument(
|
||||
'--data-split',
|
||||
|
||||
@@ -21,7 +21,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.core.config import (
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_parser,
|
||||
get_evaluation_parser,
|
||||
load_openhands_config,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -167,7 +167,7 @@ def process_predictions(predictions_path: str):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'-s',
|
||||
'--eval-split',
|
||||
|
||||
@@ -30,8 +30,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
load_openhands_config,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -358,7 +358,7 @@ Be thorough in your exploration, testing, and reasoning. It's fine if your think
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'-s',
|
||||
'--eval-split',
|
||||
|
||||
@@ -18,8 +18,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -267,7 +267,7 @@ def process_instance(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -23,8 +23,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -229,7 +229,7 @@ def process_instance(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
|
||||
SUBSETS = [
|
||||
# Eurus subset: https://arxiv.org/abs/2404.02078
|
||||
|
||||
@@ -4,7 +4,11 @@ import pprint
|
||||
|
||||
import tqdm
|
||||
|
||||
from openhands.core.config import get_llm_config_arg, get_parser, load_openhands_config
|
||||
from openhands.core.config import (
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
load_openhands_config,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.llm.llm import LLM
|
||||
|
||||
@@ -111,7 +115,7 @@ def classify_error(llm: LLM, failed_case: dict) -> str:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--json_file_path',
|
||||
type=str,
|
||||
|
||||
@@ -34,8 +34,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
load_openhands_config,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -273,7 +273,7 @@ def process_instance(instance: Any, metadata: EvalMetadata, reset_logger: bool =
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'-s',
|
||||
'--eval-split',
|
||||
|
||||
@@ -30,7 +30,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.core.config import (
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_parser,
|
||||
get_evaluation_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime
|
||||
@@ -105,8 +105,7 @@ def process_instance(
|
||||
log_dir: str | None = None,
|
||||
runtime_failure_count: int = 0,
|
||||
) -> EvalOutput:
|
||||
"""
|
||||
Evaluate agent performance on a SWE-bench problem instance.
|
||||
"""Evaluate agent performance on a SWE-bench problem instance.
|
||||
|
||||
Note that this signature differs from the expected input to `run_evaluation`. Use
|
||||
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
||||
@@ -323,7 +322,7 @@ def process_instance(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--input-file',
|
||||
type=str,
|
||||
|
||||
@@ -32,8 +32,8 @@ from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -772,7 +772,7 @@ def filter_dataset(dataset: pd.DataFrame, filter_column: str) -> pd.DataFrame:
|
||||
|
||||
if __name__ == '__main__':
|
||||
# pdb.set_trace()
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -21,8 +21,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -239,7 +239,7 @@ If the program uses some packages that are incompatible, please figure out alter
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--use-knowledge',
|
||||
type=str,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This folder contains the evaluation harness that we built on top of the original [SWE-Bench benchmark](https://www.swebench.com/) ([paper](https://arxiv.org/abs/2310.06770)).
|
||||
|
||||
**UPDATE (8/12/2025): We now support running SWE-rebench evaluation (see the paper [here](https://arxiv.org/abs/2505.20411))! For how to run it, checkout [this README](./SWE-rebench.md).**
|
||||
|
||||
**UPDATE (6/15/2025): We now support running SWE-bench-Live evaluation (see the paper [here](https://arxiv.org/abs/2505.23419))! For how to run it, checkout [this README](./SWE-bench-Live.md).**
|
||||
|
||||
**UPDATE (5/26/2025): We now support running interactive SWE-Bench evaluation (see the paper [here](https://arxiv.org/abs/2502.13069))! For how to run it, checkout [this README](./SWE-Interact.md).**
|
||||
@@ -183,24 +185,7 @@ 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
|
||||
|
||||
### Run evaluation with `RemoteRuntime`
|
||||
|
||||
OpenHands Remote Runtime is currently in beta (read [here](https://runtime.all-hands.dev/) for more details), it allows you to run rollout in parallel in the cloud, so you don't need a powerful machine to run evaluation.
|
||||
Fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSckVz_JFwg2_mOxNZjCtr7aoBFI2Mwdan3f75J_TrdMS1JV2g/viewform) to apply if you want to try this out!
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/eval_infer_remote.sh [output.jsonl filepath] [num_workers]
|
||||
|
||||
# Example - This evaluates 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
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" RUNTIME=remote SANDBOX_REMOTE_RUNTIME_API_URL="https://runtime.eval.all-hands.dev" EVAL_DOCKER_IMAGE_PREFIX="us-central1-docker.pkg.dev/evaluation-092424/swe-bench-images" \
|
||||
evaluation/benchmarks/swe_bench/scripts/eval_infer_remote.sh evaluation/evaluation_outputs/outputs/swe-bench-lite/CodeActAgent/Llama-3.1-70B-Instruct-Turbo_maxiter_100_N_v1.9-no-hint/output.jsonl 16 "princeton-nlp/SWE-bench_Lite" "test"
|
||||
```
|
||||
|
||||
To clean-up all existing runtimes that you've already started, run:
|
||||
|
||||
```bash
|
||||
ALLHANDS_API_KEY="YOUR-API-KEY" ./evaluation/utils/scripts/cleanup_remote_runtime.sh
|
||||
```
|
||||
|
||||
## SWT-Bench Evaluation
|
||||
|
||||
|
||||
84
evaluation/benchmarks/swe_bench/SWE-rebench.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# SWE-rebench
|
||||
|
||||
<p align="center">
|
||||
<a href="https://arxiv.org/abs/2505.20411">📃 Paper</a>
|
||||
•
|
||||
<a href="https://huggingface.co/datasets/nebius/SWE-rebench">🤗 HuggingFace</a>
|
||||
•
|
||||
<a href="https://swe-rebench.com/leaderboard">📊 Leaderboard</a>
|
||||
</p>
|
||||
|
||||
SWE-rebench is a large-scale dataset for verifiable software engineering tasks.
|
||||
It comes in **two datasets**:
|
||||
|
||||
* **[`nebius/SWE-rebench-leaderboard`](https://huggingface.co/datasets/nebius/SWE-rebench-leaderboard)** – updatable benchmark used for [leaderboard evaluation](https://swe-rebench.com/leaderboard).
|
||||
* **[`nebius/SWE-rebench`](https://huggingface.co/datasets/nebius/SWE-rebench)** – full dataset with **21,302 tasks**, suitable for training or large-scale offline evaluation.
|
||||
|
||||
This document explains how to run OpenHands on SWE-rebench, using the leaderboard split as the main example.
|
||||
To run on the full dataset, simply replace the dataset name.
|
||||
|
||||
|
||||
## Setting Up
|
||||
|
||||
Set up your development environment and configure your LLM provider by following the [SWE-bench README](README.md) in this directory.
|
||||
|
||||
|
||||
## Running Inference
|
||||
|
||||
Use the existing SWE-bench inference script, changing the dataset to `nebius/SWE-rebench-leaderboard` and selecting the split (`test` for leaderboard submission):
|
||||
|
||||
```bash
|
||||
./evaluation/benchmarks/swe_bench/scripts/run_infer.sh \
|
||||
llm.your_llm HEAD CodeActAgent 30 50 1 nebius/SWE-rebench-leaderboard test
|
||||
```
|
||||
|
||||
Arguments:
|
||||
|
||||
* `llm.your_llm` – your model configuration key
|
||||
* `HEAD` – commit reference for reproducibility
|
||||
* `CodeActAgent` – agent type
|
||||
* `10` – number of examples to evaluate
|
||||
* `50` – maximum iterations per task (increase if needed)
|
||||
* `1` – number of workers
|
||||
* `nebius/SWE-rebench-leaderboard` – Hugging Face dataset name
|
||||
* `test` – dataset split
|
||||
|
||||
**Tip:** To run on the **full 21k dataset**, replace `nebius/SWE-rebench-leaderboard` with `nebius/SWE-rebench`.
|
||||
|
||||
|
||||
## Evaluating Results
|
||||
|
||||
After inference completes, evaluate using the [SWE-bench-fork evaluation harness](https://github.com/SWE-rebench/SWE-bench-fork).
|
||||
|
||||
1. Convert the OpenHands output to SWE-bench evaluation format:
|
||||
|
||||
```bash
|
||||
python evaluation/benchmarks/swe_bench/scripts/live/convert.py \
|
||||
--output_jsonl path/to/evaluation/output.jsonl > preds.jsonl
|
||||
```
|
||||
|
||||
2. Clone the SWE-bench-fork repo (https://github.com/SWE-rebench/SWE-bench-fork) and follow its README to install dependencies.
|
||||
|
||||
|
||||
3. Run the evaluation using the fork:
|
||||
|
||||
```bash
|
||||
python -m swebench.harness.run_evaluation \
|
||||
--dataset_name nebius/SWE-rebench-leaderboard \
|
||||
--split test \
|
||||
--predictions_path preds.jsonl \
|
||||
--max_workers 10 \
|
||||
--run_id openhands
|
||||
```
|
||||
|
||||
|
||||
## Citation
|
||||
|
||||
```bibtex
|
||||
@article{badertdinov2025swerebench,
|
||||
title={SWE-rebench: An Automated Pipeline for Task Collection and Decontaminated Evaluation of Software Engineering Agents},
|
||||
author={Badertdinov, Ibragim and Golubev, Alexander and Nekrashevich, Maksim and Shevtsov, Anton and Karasik, Simon and Andriushchenko, Andrei and Trofimova, Maria and Litvintseva, Daria and Yangel, Boris},
|
||||
journal={arXiv preprint arXiv:2505.20411},
|
||||
year={2025}
|
||||
}
|
||||
```
|
||||
@@ -1,11 +1,8 @@
|
||||
"""
|
||||
Utilities for handling binary files and patch generation in SWE-bench evaluation.
|
||||
"""
|
||||
"""Utilities for handling binary files and patch generation in SWE-bench evaluation."""
|
||||
|
||||
|
||||
def remove_binary_diffs(patch_text):
|
||||
"""
|
||||
Remove binary file diffs from a git patch.
|
||||
"""Remove binary file diffs from a git patch.
|
||||
|
||||
Args:
|
||||
patch_text (str): The git patch text
|
||||
@@ -36,8 +33,7 @@ def remove_binary_diffs(patch_text):
|
||||
|
||||
|
||||
def remove_binary_files_from_git():
|
||||
"""
|
||||
Generate a bash command to remove binary files from git staging.
|
||||
"""Generate a bash command to remove binary files from git staging.
|
||||
|
||||
Returns:
|
||||
str: A bash command that removes binary files from git staging
|
||||
|
||||
@@ -26,7 +26,7 @@ from evaluation.utils.shared import (
|
||||
from openhands.core.config import (
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_parser,
|
||||
get_evaluation_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime
|
||||
@@ -111,8 +111,7 @@ def process_instance(
|
||||
runtime_failure_count: int = 0,
|
||||
conditional_imports: ConditionalImports | None = None,
|
||||
) -> EvalOutput:
|
||||
"""
|
||||
Evaluate agent performance on a SWE-bench problem instance.
|
||||
"""Evaluate agent performance on a SWE-bench problem instance.
|
||||
|
||||
Note that this signature differs from the expected input to `run_evaluation`. Use
|
||||
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
||||
@@ -353,7 +352,7 @@ def process_instance(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--input-file',
|
||||
type=str,
|
||||
|
||||
@@ -16,8 +16,7 @@ from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
class LocEvaluator:
|
||||
def __init__(self, args):
|
||||
"""
|
||||
Localization evaluation.
|
||||
"""Localization evaluation.
|
||||
|
||||
Args:
|
||||
args: all main arguments
|
||||
@@ -76,8 +75,7 @@ class LocEvaluator:
|
||||
self.task_resolved = False
|
||||
|
||||
def _init_dir(self, directory_path):
|
||||
"""
|
||||
Check if a directory exists and create it if it doesn't.
|
||||
"""Check if a directory exists and create it if it doesn't.
|
||||
|
||||
Args:
|
||||
directory_path (str): Path to the directory to check/create
|
||||
@@ -207,8 +205,7 @@ class LocEvaluator:
|
||||
self._compute_avg_over_all()
|
||||
|
||||
def _write_to_json(self, data, file_name):
|
||||
"""
|
||||
Writes the current object data to a JSON file.
|
||||
"""Writes the current object data to a JSON file.
|
||||
|
||||
Returns:
|
||||
bool: True if writing was successful, False otherwise.
|
||||
@@ -225,8 +222,7 @@ class LocEvaluator:
|
||||
return False
|
||||
|
||||
def read_from_json(self, file_path):
|
||||
"""
|
||||
Reads data from a JSON file and loads it into the current object.
|
||||
"""Reads data from a JSON file and loads it into the current object.
|
||||
|
||||
Returns:
|
||||
dict: The loaded JSON data, or an empty dict if the file doesn't exist
|
||||
@@ -253,8 +249,7 @@ class LocEvaluator:
|
||||
return {}
|
||||
|
||||
def read_from_jsonl(self, file_path):
|
||||
"""
|
||||
Reads data from a JSON file and loads it into the current object.
|
||||
"""Reads data from a JSON file and loads it into the current object.
|
||||
|
||||
Returns:
|
||||
dict: The loaded JSON data, or an empty dict if the file doesn't exist
|
||||
@@ -294,8 +289,7 @@ class LocEvaluator:
|
||||
history_idx += 1
|
||||
|
||||
def _parse_string_to_dict(self, dict_string) -> dict:
|
||||
"""
|
||||
Convert a string representation of a dictionary to an actual dictionary.
|
||||
"""Convert a string representation of a dictionary to an actual dictionary.
|
||||
|
||||
Args:
|
||||
dict_string (str): String representation of a dictionary
|
||||
@@ -328,8 +322,7 @@ class LocEvaluator:
|
||||
return None
|
||||
|
||||
def _parse_value_from_args(self, argument_str: str, key: str) -> str:
|
||||
"""
|
||||
Parse a specific key's value from argument string.
|
||||
"""Parse a specific key's value from argument string.
|
||||
|
||||
Args:
|
||||
argument_str (str): The argument string containing key-value pairs
|
||||
@@ -407,8 +400,7 @@ class LocEvaluator:
|
||||
return ''
|
||||
|
||||
def _parse_path_from_args(self, argument_str: str) -> str:
|
||||
"""
|
||||
Parse path from argument string.
|
||||
"""Parse path from argument string.
|
||||
|
||||
Args:
|
||||
argument_str (str): The argument string containing path information
|
||||
@@ -419,8 +411,7 @@ class LocEvaluator:
|
||||
return self._parse_value_from_args(argument_str, 'path')
|
||||
|
||||
def _parse_func_names_from_str(self, code_patch) -> list:
|
||||
"""
|
||||
Parse function names from the new_str code patch.
|
||||
"""Parse function names from the new_str code patch.
|
||||
|
||||
Args:
|
||||
code_patch: Either a string (argument string) or already extracted new_str code
|
||||
@@ -801,8 +792,7 @@ class LocEvaluator:
|
||||
|
||||
|
||||
def swe_data_loader(args):
|
||||
"""
|
||||
Loading SWE-Bench data.
|
||||
"""Loading SWE-Bench data.
|
||||
|
||||
Args:
|
||||
args: Main arguments.
|
||||
@@ -834,8 +824,7 @@ def swe_data_loader(args):
|
||||
|
||||
|
||||
def infer_data_loader(args):
|
||||
"""
|
||||
Load instance IDs.
|
||||
"""Load instance IDs.
|
||||
|
||||
Args:
|
||||
args: Main arguments.
|
||||
@@ -868,8 +857,7 @@ def infer_data_loader(args):
|
||||
|
||||
|
||||
def infer_cost_calculator(args):
|
||||
"""
|
||||
Calculate total and average costs from metric JSON files with detailed output.
|
||||
"""Calculate total and average costs from metric JSON files with detailed output.
|
||||
|
||||
Args:
|
||||
args: Main arguments.
|
||||
|
||||
@@ -28,8 +28,7 @@ class LocalizationInfo:
|
||||
hunks_per_file: dict[str, int] # File -> number of hunks
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""
|
||||
Convert LocalizationInfo to a dictionary for JSON serialization.
|
||||
"""Convert LocalizationInfo to a dictionary for JSON serialization.
|
||||
|
||||
Returns:
|
||||
Dictionary representation of the localization information
|
||||
@@ -58,8 +57,7 @@ class LocalizationInfo:
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any]) -> 'LocalizationInfo':
|
||||
"""
|
||||
Create LocalizationInfo from a dictionary (for loading from JSON).
|
||||
"""Create LocalizationInfo from a dictionary (for loading from JSON).
|
||||
|
||||
Args:
|
||||
data: Dictionary containing localization information
|
||||
@@ -91,8 +89,7 @@ class LocalizationInfo:
|
||||
|
||||
|
||||
class LocMeta:
|
||||
"""
|
||||
SWE-Bench dataset loader and ground-truth localization parser.
|
||||
"""SWE-Bench dataset loader and ground-truth localization parser.
|
||||
|
||||
This class handles loading SWE-Bench datasets and extracting ground-truth
|
||||
localization information from patches for code localization evaluation.
|
||||
@@ -104,8 +101,7 @@ class LocMeta:
|
||||
dataset_name: str = 'princeton-nlp/SWE-bench_Verified',
|
||||
split: str = 'test',
|
||||
):
|
||||
"""
|
||||
Initialize LocMeta with a SWE-Bench dataset.
|
||||
"""Initialize LocMeta with a SWE-Bench dataset.
|
||||
|
||||
Args:
|
||||
dataset_name: HuggingFace dataset name (e.g., "princeton-nlp/SWE-bench_Verified")
|
||||
@@ -124,8 +120,7 @@ class LocMeta:
|
||||
self._init_swe_dataset()
|
||||
|
||||
def _init_swe_dataset(self) -> None:
|
||||
"""
|
||||
Load and initialize the SWE-Bench dataset from HuggingFace.
|
||||
"""Load and initialize the SWE-Bench dataset from HuggingFace.
|
||||
Converts to pandas DataFrame for easy manipulation.
|
||||
"""
|
||||
try:
|
||||
@@ -150,8 +145,7 @@ class LocMeta:
|
||||
raise
|
||||
|
||||
def get_instance_by_id(self, instance_id: str) -> pd.Series:
|
||||
"""
|
||||
Retrieve a specific instance by its ID.
|
||||
"""Retrieve a specific instance by its ID.
|
||||
|
||||
Args:
|
||||
instance_id: The instance identifier
|
||||
@@ -169,8 +163,7 @@ class LocMeta:
|
||||
return self.df.iloc[idx]
|
||||
|
||||
def parse_instance_loc(self, instance: Union[pd.Series, str]) -> LocalizationInfo:
|
||||
"""
|
||||
Parse ground-truth localization information from a SWE-Bench instance.
|
||||
"""Parse ground-truth localization information from a SWE-Bench instance.
|
||||
|
||||
Args:
|
||||
instance: Either a pandas Series with instance data or an instance_id string
|
||||
@@ -218,8 +211,7 @@ class LocMeta:
|
||||
def _parse_file_patch_lines(
|
||||
self, file_patch: str
|
||||
) -> tuple[list[tuple[int, int]], int, int]:
|
||||
"""
|
||||
Parse line ranges and count changes from a single file patch.
|
||||
"""Parse line ranges and count changes from a single file patch.
|
||||
|
||||
Args:
|
||||
file_patch: Patch content for a single file
|
||||
@@ -253,8 +245,7 @@ class LocMeta:
|
||||
def _parse_code_structures_from_patch(
|
||||
self, file_patch: str, file_path: str
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""
|
||||
Extract function and class names from patch context (fallback method).
|
||||
"""Extract function and class names from patch context (fallback method).
|
||||
|
||||
Args:
|
||||
file_patch: Patch content for a single file
|
||||
@@ -311,8 +302,7 @@ class LocMeta:
|
||||
def _parse_patch_localization(
|
||||
self, patch_content: str, instance_id: str
|
||||
) -> LocalizationInfo:
|
||||
"""
|
||||
Parse localization information from a git patch (improved method).
|
||||
"""Parse localization information from a git patch (improved method).
|
||||
|
||||
Args:
|
||||
patch_content: The git patch content
|
||||
@@ -390,8 +380,7 @@ class LocMeta:
|
||||
def _extract_code_structures_from_patch(
|
||||
self, file_patch: str, file_path: str
|
||||
) -> tuple[list[str], list[str]]:
|
||||
"""
|
||||
Extract function and class names from patch context and content.
|
||||
"""Extract function and class names from patch context and content.
|
||||
|
||||
Args:
|
||||
file_patch: Patch content for a single file
|
||||
@@ -519,8 +508,7 @@ class LocMeta:
|
||||
def _parse_patch_localization_with_runtime(
|
||||
self, patch_content: str, instance_id: str, runtime: Runtime
|
||||
) -> LocalizationInfo:
|
||||
"""
|
||||
Parse localization information from a git patch using OpenHands runtime.
|
||||
"""Parse localization information from a git patch using OpenHands runtime.
|
||||
This is the superior method when runtime is available.
|
||||
|
||||
Args:
|
||||
@@ -596,8 +584,7 @@ class LocMeta:
|
||||
def parse_instance_loc_with_runtime(
|
||||
self, instance: Union[pd.Series, str], runtime: Runtime = None
|
||||
) -> LocalizationInfo:
|
||||
"""
|
||||
Parse ground-truth localization information using OpenHands runtime.
|
||||
"""Parse ground-truth localization information using OpenHands runtime.
|
||||
|
||||
Args:
|
||||
instance: Either a pandas Series with instance data or an instance_id string
|
||||
@@ -634,8 +621,7 @@ class LocMeta:
|
||||
def _analyze_source_code_with_runtime(
|
||||
self, runtime: Runtime, file_path: str, affected_lines: list[int]
|
||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||
"""
|
||||
Analyze source code using OpenHands runtime to find functions and classes.
|
||||
"""Analyze source code using OpenHands runtime to find functions and classes.
|
||||
|
||||
Args:
|
||||
runtime: OpenHands runtime object
|
||||
@@ -695,8 +681,7 @@ class LocMeta:
|
||||
def _parse_cython_content_with_line_mapping(
|
||||
self, content: str, affected_lines: list[int]
|
||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||
"""
|
||||
Parse Cython content to extract functions and classes with line mapping.
|
||||
"""Parse Cython content to extract functions and classes with line mapping.
|
||||
Since Cython files can't be parsed with Python's AST, we use regex-based parsing.
|
||||
|
||||
Args:
|
||||
@@ -828,8 +813,7 @@ class LocMeta:
|
||||
def _parse_python_content_with_line_mapping(
|
||||
self, content: str, affected_lines: list[int]
|
||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||
"""
|
||||
Parse Python content to extract functions and classes with accurate line mapping.
|
||||
"""Parse Python content to extract functions and classes with accurate line mapping.
|
||||
|
||||
Args:
|
||||
content: Python source code content
|
||||
@@ -914,8 +898,7 @@ class LocMeta:
|
||||
def _parse_python_content(
|
||||
self, content: str, affected_lines: list[int]
|
||||
) -> tuple[list[str], list[str], dict[int, str], dict[int, str]]:
|
||||
"""
|
||||
Parse Python content to extract functions and classes.
|
||||
"""Parse Python content to extract functions and classes.
|
||||
|
||||
Args:
|
||||
content: Python source code content
|
||||
@@ -989,8 +972,7 @@ class LocMeta:
|
||||
return [], [], {}, {}
|
||||
|
||||
def _split_patch_by_files(self, patch_content: str) -> dict[str, str]:
|
||||
"""
|
||||
Split a multi-file patch into individual file patches.
|
||||
"""Split a multi-file patch into individual file patches.
|
||||
|
||||
Args:
|
||||
patch_content: Complete patch content
|
||||
@@ -1049,8 +1031,7 @@ class LocMeta:
|
||||
def _empty_localization_info(
|
||||
self, instance_id: str = 'unknown'
|
||||
) -> LocalizationInfo:
|
||||
"""
|
||||
Return an empty LocalizationInfo object.
|
||||
"""Return an empty LocalizationInfo object.
|
||||
|
||||
Args:
|
||||
instance_id: Instance identifier
|
||||
@@ -1072,8 +1053,7 @@ class LocMeta:
|
||||
)
|
||||
|
||||
def get_dataset_statistics(self) -> dict[str, Any]:
|
||||
"""
|
||||
Get statistics about the loaded dataset.
|
||||
"""Get statistics about the loaded dataset.
|
||||
|
||||
Returns:
|
||||
Dictionary containing dataset statistics
|
||||
@@ -1095,8 +1075,7 @@ class LocMeta:
|
||||
return stats
|
||||
|
||||
def get_instances_by_repo(self, repo_name: str) -> pd.DataFrame:
|
||||
"""
|
||||
Get all instances for a specific repository.
|
||||
"""Get all instances for a specific repository.
|
||||
|
||||
Args:
|
||||
repo_name: Repository name (e.g., "django/django")
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<uploaded_files>
|
||||
/workspace/{{ workspace_dir_name }}
|
||||
</uploaded_files>
|
||||
|
||||
I've uploaded a python code repository in the directory {{ workspace_dir_name }}. Consider the following issue description:
|
||||
|
||||
<issue_description>
|
||||
{{ instance.problem_statement }}
|
||||
</issue_description>
|
||||
|
||||
Can you help me implement the necessary changes to the repository so that the requirements specified in the <issue_description> are met?
|
||||
I've already taken care of all changes to any of the test files described in the <issue_description>. This means you DON'T have to modify the testing logic or any of the tests in any way!
|
||||
Also the development Python environment is already set up for you (i.e., all dependencies already installed), so you don't need to install other packages.
|
||||
Your task is to make the minimal changes to non-test files in the /workspace/{{ workspace_dir_name }} directory to ensure the <issue_description> is satisfied.
|
||||
|
||||
Follow these phases to resolve the issue:
|
||||
|
||||
Phase 1. READING: read the problem and reword it in clearer terms
|
||||
1.1 If there are code or config snippets. Express in words any best practices or conventions in them.
|
||||
1.2 Hightlight message errors, method names, variables, file names, stack traces, and technical details.
|
||||
1.3 Explain the problem in clear terms.
|
||||
1.4 Enumerate the steps to reproduce the problem.
|
||||
1.5 Hightlight any best practices to take into account when testing and fixing the issue
|
||||
|
||||
Phase 2. RUNNING: install and run the tests on the repository
|
||||
2.1 Follow the readme
|
||||
2.2 Install the environment and anything needed
|
||||
2.2 Iterate and figure out how to run the tests
|
||||
|
||||
Phase 3. EXPLORATION: find the files that are related to the problem and possible solutions
|
||||
3.1 Use `grep` to search for relevant methods, classes, keywords and error messages.
|
||||
3.2 Identify all files related to the problem statement.
|
||||
3.3 Propose the methods and files to fix the issue and explain why.
|
||||
3.4 From the possible file locations, select the most likely location to fix the issue.
|
||||
|
||||
Phase 4. TEST CREATION: before implementing any fix, create a script to reproduce and verify the issue.
|
||||
4.1 Look at existing test files in the repository to understand the test format/structure.
|
||||
4.2 Create a minimal reproduction script that reproduces the located issue.
|
||||
4.3 Run the reproduction script to confirm you are reproducing the issue.
|
||||
4.4 Adjust the reproduction script as necessary.
|
||||
|
||||
Phase 5. FIX ANALYSIS: state clearly the problem and how to fix it
|
||||
5.1 State clearly what the problem is.
|
||||
5.2 State clearly where the problem is located.
|
||||
5.3 State clearly how the test reproduces the issue.
|
||||
5.4 State clearly the best practices to take into account in the fix.
|
||||
5.5 State clearly how to fix the problem.
|
||||
|
||||
Phase 6. FIX IMPLEMENTATION: Edit the source code to implement your chosen solution.
|
||||
6.1 Make minimal, focused changes to fix the issue.
|
||||
|
||||
Phase 7. VERIFICATION: Test your implementation thoroughly.
|
||||
7.1 Run your reproduction script to verify the fix works.
|
||||
7.2 Add edge cases to your test script to ensure comprehensive coverage.
|
||||
7.3 Run existing tests related to the modified code to ensure you haven't broken anything.
|
||||
|
||||
8. FINAL REVIEW: Carefully re-read the problem description and compare your changes with the base commit {{ instance.base_commit }}.
|
||||
8.1 Ensure you've fully addressed all requirements.
|
||||
8.2 Run any tests in the repository related to:
|
||||
8.2.1 The issue you are fixing
|
||||
8.2.2 The files you modified
|
||||
8.2.3 The functions you changed
|
||||
8.3 If any tests fail, revise your implementation until all tests pass
|
||||
|
||||
Be thorough in your exploration, testing, and reasoning. It's fine if your thinking process is lengthy - quality and completeness are more important than brevity.
|
||||
@@ -1,45 +0,0 @@
|
||||
# Task: Fix Issue in Python Repository
|
||||
|
||||
## Repository Context
|
||||
You are provided with a Python code repository that contains an issue requiring your attention. The repository is located in a sandboxed environment, and you have access to the codebase to implement the necessary changes.
|
||||
The code repository is located at: `/workspace/{{ workspace_dir_name }}`
|
||||
(This path is provided for context; use file system tools to confirm paths before access).
|
||||
|
||||
## Goal
|
||||
Your goal is to fix the issue described in the **Issue Description** section below. Implement the necessary changes to **non-test files only** within the repository, ensuring that **all relevant tests pass** after your changes.
|
||||
|
||||
## Key Requirements & Constraints
|
||||
|
||||
1. **Understand the problem** very well: it is a bug report, and you know humans don't always write good descriptions. Explore the codebase to understand the related code and the problem in depth. It is possible that the solution needs to be a bit more extensive than just the stated text. Don't exagerate though: don't do unrelated refactoring, but also don't interpret the description too strictly.
|
||||
2. **Focus on the issues:** Implement the fix focusing on non-test files related to the issue.
|
||||
2. **Environment Ready:** The Python environment is pre-configured with all dependencies. Do not install packages.
|
||||
3. **Mandatory Testing Procedure:**
|
||||
* **Create Test to Reproduce the Issue:** *Before* implementing any fix, you MUST create a *new test* (separate from existing tests) that specifically reproduces the issue.
|
||||
* Take existing tests as example to understand the testing format/structure.
|
||||
* Enhance this test with edge cases.
|
||||
* Run this test to confirm reproduction.
|
||||
* **Verify Fix:** After implementing the fix, run your test again to verify the issue is resolved.
|
||||
* **Identify ALL Relevant Tests:** You MUST perform a **dedicated search and analysis** to identify **all** existing unit tests potentially affected by your changes. This includes:
|
||||
* Tests in the same module/directory as the changed files (e.g., `tests/` subdirectories).
|
||||
* Tests explicitly importing or using the modified code/classes/functions.
|
||||
* Tests mentioned in the issue description or related documentation.
|
||||
* Tests covering functionalities that *depend on* the modified code (analyze callers/dependencies if necessary).
|
||||
**If you cannot confidently identify a specific subset, you MUST identify and plan to run the entire test suite for the modified application or module(s). State your identified test scope clearly.**
|
||||
* **Run Identified Relevant Tests:** You MUST execute the **complete set** of relevant existing unit tests you identified in the previous step. Ensure you are running the *correct and comprehensive set* of tests. You MUST NOT modify these existing tests.
|
||||
* **Final Check & Verification:** Before finishing, ensure **all** identified relevant existing tests pass. **Explicitly confirm that you have considered potential omissions in your test selection and believe the executed tests comprehensively cover the impact of your changes.** Failing to identify and run the *complete* relevant set constitutes a failure. If any identified tests fail, revise your fix. Passing all relevant tests is the primary measure of success.
|
||||
4. **Defensive Programming:** Actively practice defensive programming: anticipate and handle potential edge cases, unexpected inputs, and different ways the affected code might be called **to ensure the fix works reliably and allows relevant tests to pass.** Analyze the potential impact on other parts of the codebase.
|
||||
5. **Final Review:** Compare your solution against the original issue and the base commit ({{ instance.base_commit }}) to ensure completeness and test passage.
|
||||
|
||||
## General Workflow Guidance
|
||||
|
||||
* Prioritize understanding the problem, exploring the code, planning your fix, implementing it carefully using the required diff format, and **thoroughly testing** according to the **Mandatory Testing Procedure**.
|
||||
* Consider trade-offs between different solutions. The goal is a **robust change that makes the relevant tests pass.** Quality, correctness, and reliability are key.
|
||||
* Actively practice defensive programming: anticipate and handle potential edge cases, unexpected inputs, and different ways the affected code might be called **to ensure the fix works reliably and allows relevant tests to pass.** Analyze the potential impact on other parts of the codebase.
|
||||
|
||||
* IMPORTANT: Your solution will be tested by additional hidden tests, so do not assume the task is complete just because visible tests pass! Refine the solution until you are confident that it is robust and comprehensive according to the **Defensive Programming** requirement.
|
||||
|
||||
## Final Note
|
||||
Be thorough in your exploration, testing, and reasoning. It's fine if your thinking process is lengthy - quality and completeness are more important than brevity.
|
||||
|
||||
## Issue Description
|
||||
{{ instance.problem_statement }}
|
||||
@@ -43,8 +43,8 @@ from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.config.condenser_config import NoOpCondenserConfig
|
||||
from openhands.core.config.utils import get_condenser_config_arg
|
||||
@@ -80,6 +80,8 @@ def set_dataset_type(dataset_name: str) -> str:
|
||||
DATASET_TYPE = 'SWE-Gym'
|
||||
elif 'swe-bench-live' in name_lower:
|
||||
DATASET_TYPE = 'SWE-bench-Live'
|
||||
elif 'swe-rebench' in name_lower:
|
||||
DATASET_TYPE = 'SWE-rebench'
|
||||
elif 'multimodal' in name_lower:
|
||||
DATASET_TYPE = 'Multimodal'
|
||||
else:
|
||||
@@ -109,9 +111,7 @@ def get_instruction(instance: pd.Series, metadata: EvalMetadata) -> MessageActio
|
||||
if mode.startswith('swt'):
|
||||
template_name = 'swt.j2'
|
||||
elif mode == 'swe':
|
||||
if 'claude' in llm_model:
|
||||
template_name = 'swe_default.j2'
|
||||
elif 'gpt-4.1' in llm_model:
|
||||
if 'gpt-4.1' in llm_model:
|
||||
template_name = 'swe_gpt4.j2'
|
||||
else:
|
||||
template_name = (
|
||||
@@ -180,6 +180,8 @@ def get_instance_docker_image(
|
||||
docker_image_prefix = 'docker.io/starryzhang/'
|
||||
elif DATASET_TYPE == 'SWE-bench':
|
||||
docker_image_prefix = 'docker.io/swebench/'
|
||||
elif DATASET_TYPE == 'SWE-rebench':
|
||||
docker_image_prefix = 'docker.io/swerebench/'
|
||||
repo, name = instance_id.split('__')
|
||||
image_name = f'{docker_image_prefix.rstrip("/")}/sweb.eval.x86_64.{repo}_1776_{name}:latest'.lower()
|
||||
logger.debug(f'Using official SWE-Bench image: {image_name}')
|
||||
@@ -320,6 +322,8 @@ def initialize_runtime(
|
||||
# inject the instance swe entry
|
||||
if DATASET_TYPE == 'SWE-bench-Live':
|
||||
entry_script_path = 'instance_swe_entry_live.sh'
|
||||
elif DATASET_TYPE == 'SWE-rebench':
|
||||
entry_script_path = 'instance_swe_entry_rebench.sh'
|
||||
else:
|
||||
entry_script_path = 'instance_swe_entry.sh'
|
||||
runtime.copy_to(
|
||||
@@ -732,7 +736,7 @@ def filter_dataset(dataset: pd.DataFrame, filter_column: str) -> pd.DataFrame:
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -28,8 +28,8 @@ from evaluation.utils.shared import (
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.config.condenser_config import NoOpCondenserConfig
|
||||
from openhands.core.config.utils import get_condenser_config_arg
|
||||
@@ -201,7 +201,7 @@ def process_instance(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -31,8 +31,8 @@ from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AgentConfig,
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -644,7 +644,7 @@ SWEGYM_EXCLUDE_IDS = [
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -6,8 +6,7 @@ from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
|
||||
def verify_instance_costs(row: pd.Series) -> float:
|
||||
"""
|
||||
Verifies that the accumulated_cost matches the sum of individual costs in metrics.
|
||||
"""Verifies that the accumulated_cost matches the sum of individual costs in metrics.
|
||||
Also checks for duplicate consecutive costs which might indicate buggy counting.
|
||||
If the consecutive costs are identical, the file is affected by this bug:
|
||||
https://github.com/All-Hands-AI/OpenHands/issues/5383
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env 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/benchmarks/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
|
||||
|
||||
# update the output with evaluation results
|
||||
poetry run python evaluation/benchmarks/swe_bench/scripts/eval/update_output_with_eval.py $INPUT_FILE
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
source ~/.bashrc
|
||||
SWEUTIL_DIR=/swe_util
|
||||
|
||||
# FIXME: Cannot read SWE_INSTANCE_ID from the environment variable
|
||||
# SWE_INSTANCE_ID=django__django-11099
|
||||
if [ -z "$SWE_INSTANCE_ID" ]; then
|
||||
echo "Error: SWE_INSTANCE_ID is not set." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read the swe-bench-test-lite.json file and extract the required item based on instance_id
|
||||
item=$(jq --arg INSTANCE_ID "$SWE_INSTANCE_ID" '.[] | select(.instance_id == $INSTANCE_ID)' $SWEUTIL_DIR/eval_data/instances/swe-bench-instance.json)
|
||||
|
||||
if [[ -z "$item" ]]; then
|
||||
echo "No item found for the provided instance ID."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
WORKSPACE_NAME=$(echo "$item" | jq -r '(.repo | tostring) + "__" + (.version | tostring) | gsub("/"; "__")')
|
||||
|
||||
echo "WORKSPACE_NAME: $WORKSPACE_NAME"
|
||||
|
||||
# Clear the workspace
|
||||
if [ -d /workspace ]; then
|
||||
rm -rf /workspace/*
|
||||
else
|
||||
mkdir /workspace
|
||||
fi
|
||||
# Copy repo to workspace
|
||||
if [ -d /workspace/$WORKSPACE_NAME ]; then
|
||||
rm -rf /workspace/$WORKSPACE_NAME
|
||||
fi
|
||||
mkdir -p /workspace
|
||||
cp -r /testbed /workspace/$WORKSPACE_NAME
|
||||
|
||||
# Activate instance-specific environment
|
||||
if [ -d /opt/miniconda3 ]; then
|
||||
. /opt/miniconda3/etc/profile.d/conda.sh
|
||||
conda activate testbed
|
||||
fi
|
||||
|
||||
export PATH=/opt/conda/envs/testbed/bin:$PATH
|
||||
@@ -5,8 +5,7 @@ pynguin_ids = ['pydata__xarray-6548-16541', 'pydata__xarray-7003-16557', 'pydata
|
||||
ids = ['pydata__xarray-3114-16452', 'pydata__xarray-3151-16453', 'pydata__xarray-3156-16454', 'pydata__xarray-3239-16456', 'pydata__xarray-3239-16457', 'pydata__xarray-3239-16458', 'pydata__xarray-3302-16459', 'pydata__xarray-3364-16461', 'pydata__xarray-3677-16471', 'pydata__xarray-3905-16478', 'pydata__xarray-4182-16484', 'pydata__xarray-4248-16486', 'pydata__xarray-4339-16487', 'pydata__xarray-4419-16488', 'pydata__xarray-4629-16492', 'pydata__xarray-4750-16496', 'pydata__xarray-4802-16505', 'pydata__xarray-4966-16515', 'pydata__xarray-4994-16516', 'pydata__xarray-5033-16517', 'pydata__xarray-5126-16518', 'pydata__xarray-5126-16519', 'pydata__xarray-5131-16520', 'pydata__xarray-5365-16529', 'pydata__xarray-5455-16530', 'pydata__xarray-5662-16532', 'pydata__xarray-5731-16534', 'pydata__xarray-6135-16535', 'pydata__xarray-6135-16536', 'pydata__xarray-6386-16537', 'pydata__xarray-6394-16538', 'pydata__xarray-6400-16539', 'pydata__xarray-6461-16540', 'pydata__xarray-6548-16541', 'pydata__xarray-6599-16543', 'pydata__xarray-6601-16544', 'pydata__xarray-6882-16548', 'pydata__xarray-6889-16549', 'pydata__xarray-7003-16557', 'pydata__xarray-7147-16571', 'pydata__xarray-7150-16572', 'pydata__xarray-7203-16577', 'pydata__xarray-7229-16578', 'pydata__xarray-7393-16581', 'pydata__xarray-7400-16582']
|
||||
|
||||
|
||||
Command eval (our approach):
|
||||
poetry run ./evaluation/benchmarks/testgeneval/scripts/eval_infer_remote.sh evaluation/evaluation_outputs/outputs/kjain14__testgeneval-test/CodeActAgent/gpt-4o_maxiter_25_N_v0.20.0-no-hint-run_1/output.jsonl 10 kjain14/testgeneval test true
|
||||
|
||||
|
||||
Command run (our approach):
|
||||
./evaluation/benchmarks/testgeneval/scripts/run_infer.sh llm.eval_gpt HEAD CodeActAgent -1 25 10 kjain14/testgeneval test 1 ../TestGenEval/results/testgeneval/preds/gpt-4o-2024-08-06__testgeneval__0.2__test.jsonl
|
||||
|
||||
@@ -181,9 +181,7 @@ def distinct_methods_stats(tree, num_lines):
|
||||
|
||||
|
||||
def loops_stats(tree, num_lines):
|
||||
"""
|
||||
Calculate the average number of loops.
|
||||
"""
|
||||
"""Calculate the average number of loops."""
|
||||
total_loops = 0
|
||||
|
||||
def traverse(node):
|
||||
@@ -199,9 +197,7 @@ def loops_stats(tree, num_lines):
|
||||
|
||||
|
||||
def branches_stats(tree, num_lines):
|
||||
"""
|
||||
Calculate the average number of branches (conditional statements).
|
||||
"""
|
||||
"""Calculate the average number of branches (conditional statements)."""
|
||||
total_branches = 0
|
||||
|
||||
def traverse(node):
|
||||
|
||||
@@ -41,7 +41,7 @@ from evaluation.utils.shared import (
|
||||
reset_logger_for_multiprocessing,
|
||||
run_evaluation,
|
||||
)
|
||||
from openhands.core.config import OpenHandsConfig, SandboxConfig, get_parser
|
||||
from openhands.core.config import OpenHandsConfig, SandboxConfig, get_evaluation_parser
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime
|
||||
from openhands.events.action import CmdRunAction
|
||||
@@ -192,8 +192,7 @@ def run_mutation_testing(
|
||||
def grade_test_output(
|
||||
test_suite: str, instance: pd.Series, test_output: str, test_spec: TestSpec, runtime
|
||||
):
|
||||
"""
|
||||
Two-pass test grading with short-circuiting:
|
||||
"""Two-pass test grading with short-circuiting:
|
||||
1. Run all tests to identify passing/failing tests
|
||||
2. If no failing tests, evaluate coverage immediately
|
||||
3. Otherwise, run only passing tests for coverage analysis
|
||||
@@ -280,8 +279,7 @@ def process_instance(
|
||||
reset_logger: bool = True,
|
||||
log_dir: str | None = None,
|
||||
) -> EvalOutput:
|
||||
"""
|
||||
Evaluate agent performance on a TestGenEval problem instance.
|
||||
"""Evaluate agent performance on a TestGenEval problem instance.
|
||||
|
||||
Note that this signature differs from the expected input to `run_evaluation`. Use
|
||||
`functools.partial` to provide optional arguments before passing to the evaluation harness.
|
||||
@@ -453,8 +451,7 @@ def process_instance(
|
||||
|
||||
|
||||
def count_and_log_fields(evaluated_predictions, fields, key):
|
||||
"""
|
||||
Count and log the sum of specified fields in the evaluated predictions,
|
||||
"""Count and log the sum of specified fields in the evaluated predictions,
|
||||
ignoring fields with a value of -1. If all values for a field are -1,
|
||||
return -1.
|
||||
|
||||
@@ -484,7 +481,7 @@ def count_and_log_fields(evaluated_predictions, fields, key):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--input-file', type=str, required=True, help='Path to input predictions file'
|
||||
)
|
||||
|
||||
@@ -4,8 +4,7 @@ from evaluation.benchmarks.testgeneval.constants import TestStatus
|
||||
|
||||
|
||||
def parse_log_pytest(log: str) -> dict[str, str]:
|
||||
"""
|
||||
Parser for test logs generated with PyTest framework
|
||||
"""Parser for test logs generated with PyTest framework
|
||||
|
||||
Args:
|
||||
log (str): log content
|
||||
@@ -26,8 +25,7 @@ def parse_log_pytest(log: str) -> dict[str, str]:
|
||||
|
||||
|
||||
def parse_log_pytest_options(log: str) -> dict[str, str]:
|
||||
"""
|
||||
Parser for test logs generated with PyTest framework with options
|
||||
"""Parser for test logs generated with PyTest framework with options
|
||||
|
||||
Args:
|
||||
log (str): log content
|
||||
@@ -61,8 +59,7 @@ def parse_log_pytest_options(log: str) -> dict[str, str]:
|
||||
|
||||
|
||||
def parse_log_django(log: str) -> dict[str, str]:
|
||||
"""
|
||||
Parser for test logs generated with Django tester framework
|
||||
"""Parser for test logs generated with Django tester framework
|
||||
|
||||
Args:
|
||||
log (str): log content
|
||||
@@ -141,8 +138,7 @@ def parse_log_django(log: str) -> dict[str, str]:
|
||||
|
||||
|
||||
def parse_log_pytest_v2(log: str) -> dict[str, str]:
|
||||
"""
|
||||
Parser for test logs generated with PyTest framework (Later Version)
|
||||
"""Parser for test logs generated with PyTest framework (Later Version)
|
||||
|
||||
Args:
|
||||
log (str): log content
|
||||
@@ -170,8 +166,7 @@ def parse_log_pytest_v2(log: str) -> dict[str, str]:
|
||||
|
||||
|
||||
def parse_log_seaborn(log: str) -> dict[str, str]:
|
||||
"""
|
||||
Parser for test logs generated with seaborn testing framework
|
||||
"""Parser for test logs generated with seaborn testing framework
|
||||
|
||||
Args:
|
||||
log (str): log content
|
||||
@@ -196,8 +191,7 @@ def parse_log_seaborn(log: str) -> dict[str, str]:
|
||||
|
||||
|
||||
def parse_log_sympy(log: str) -> dict[str, str]:
|
||||
"""
|
||||
Parser for test logs generated with Sympy framework
|
||||
"""Parser for test logs generated with Sympy framework
|
||||
|
||||
Args:
|
||||
log (str): log content
|
||||
@@ -229,8 +223,7 @@ def parse_log_sympy(log: str) -> dict[str, str]:
|
||||
|
||||
|
||||
def parse_log_matplotlib(log: str) -> dict[str, str]:
|
||||
"""
|
||||
Parser for test logs generated with PyTest framework
|
||||
"""Parser for test logs generated with PyTest framework
|
||||
|
||||
Args:
|
||||
log (str): log content
|
||||
|
||||
@@ -12,8 +12,7 @@ if sys.getrecursionlimit() < 10_000:
|
||||
|
||||
|
||||
def bleu(gold: list[str], pred: list[str]) -> float:
|
||||
"""
|
||||
Calculate BLEU score, using smoothing method 2 with auto reweighting, in the range of 0~100.
|
||||
"""Calculate BLEU score, using smoothing method 2 with auto reweighting, in the range of 0~100.
|
||||
|
||||
:param gold: list of gold tokens
|
||||
:param pred: list of predicted tokens
|
||||
@@ -30,8 +29,7 @@ def bleu(gold: list[str], pred: list[str]) -> float:
|
||||
|
||||
|
||||
def batch_bleu(golds: list[list[str]], preds: list[list[str]]) -> list[float]:
|
||||
"""
|
||||
Calculate BLEU score for a batch of sentences.
|
||||
"""Calculate BLEU score for a batch of sentences.
|
||||
|
||||
:param golds: list of gold sentences
|
||||
:param preds: list of predicted sentences
|
||||
@@ -43,8 +41,7 @@ def batch_bleu(golds: list[list[str]], preds: list[list[str]]) -> list[float]:
|
||||
|
||||
|
||||
def corpus_bleu(golds: list[list[str]], preds: list[list[str]]) -> float:
|
||||
"""
|
||||
Calculate corpus-level BLEU score for a batch of sentences.
|
||||
"""Calculate corpus-level BLEU score for a batch of sentences.
|
||||
|
||||
:param golds: list of gold sentences
|
||||
:param preds: list of predicted sentences
|
||||
@@ -63,8 +60,7 @@ def corpus_bleu(golds: list[list[str]], preds: list[list[str]]) -> float:
|
||||
def edit_sim(
|
||||
gold: Union[str, list[str]], pred: Union[str, list[str]], sep: str = ' '
|
||||
) -> float:
|
||||
"""
|
||||
Calculate char-level edit similarity, in the range of 0~100.
|
||||
"""Calculate char-level edit similarity, in the range of 0~100.
|
||||
|
||||
:param gold: gold sentence or list of gold tokens
|
||||
:param pred: predicted sentence or list of predicted tokens
|
||||
@@ -85,8 +81,7 @@ def batch_edit_sim(
|
||||
preds: list[Union[str, list[str]]],
|
||||
sep: str = ' ',
|
||||
) -> list[float]:
|
||||
"""
|
||||
Calculate char-level edit similarity for a batch of sentences.
|
||||
"""Calculate char-level edit similarity for a batch of sentences.
|
||||
|
||||
:param golds: list of gold sentences
|
||||
:param preds: list of predicted sentences
|
||||
@@ -102,8 +97,7 @@ T = TypeVar('T')
|
||||
|
||||
|
||||
def exact_match(gold: T, pred: T) -> float:
|
||||
"""
|
||||
Calculate exact match accuracy, in the range of {0, 100}.
|
||||
"""Calculate exact match accuracy, in the range of {0, 100}.
|
||||
|
||||
:param gold: gold sentence or list of gold tokens
|
||||
:param pred: predicted sentence or list of predicted tokens
|
||||
@@ -115,8 +109,7 @@ def exact_match(gold: T, pred: T) -> float:
|
||||
|
||||
|
||||
def batch_exact_match(golds: list[T], preds: list[T]) -> list[float]:
|
||||
"""
|
||||
Calculate exact match accuracy for a batch of sentences.
|
||||
"""Calculate exact match accuracy for a batch of sentences.
|
||||
|
||||
:param golds: list of gold sentences
|
||||
:param preds: list of predicted sentences
|
||||
@@ -130,8 +123,7 @@ def batch_exact_match(golds: list[T], preds: list[T]) -> list[float]:
|
||||
def rouge_l(
|
||||
gold: Union[str, list[str]], pred: Union[str, list[str]], sep: str = ' '
|
||||
) -> dict[str, float]:
|
||||
"""
|
||||
Calculate ROUGE-L F1, precision, and recall scores, in the range of 0~100.
|
||||
"""Calculate ROUGE-L F1, precision, and recall scores, in the range of 0~100.
|
||||
|
||||
:param gold: gold sentence or list of gold tokens
|
||||
:param pred: predicted sentence or list of predicted tokens
|
||||
@@ -156,8 +148,7 @@ def batch_rouge_l(
|
||||
preds: list[Union[str, list[str]]],
|
||||
sep: str = ' ',
|
||||
) -> dict[str, list[float]]:
|
||||
"""
|
||||
Calculate ROUGE-L F1, precision, and recall scores for a batch of sentences.
|
||||
"""Calculate ROUGE-L F1, precision, and recall scores for a batch of sentences.
|
||||
|
||||
:param golds: list of gold sentences
|
||||
:param preds: list of predicted sentences
|
||||
@@ -175,8 +166,7 @@ def accuracy(
|
||||
pred: list[str],
|
||||
ignore: Optional[Sequence[str]] = None,
|
||||
) -> float:
|
||||
"""
|
||||
Calculate token-level accuracy, in the range of 0~100.
|
||||
"""Calculate token-level accuracy, in the range of 0~100.
|
||||
If gold and pred are not the same length, the longer one would be truncated.
|
||||
|
||||
:param gold: list of gold tokens
|
||||
@@ -210,8 +200,7 @@ def batch_accuracy(
|
||||
preds: list[list[str]],
|
||||
ignore: Optional[Sequence[str]] = None,
|
||||
) -> list[float]:
|
||||
"""
|
||||
Calculate token-level accuracy for a batch of sentences.
|
||||
"""Calculate token-level accuracy for a batch of sentences.
|
||||
|
||||
:param golds: list of gold sentences
|
||||
:param preds: list of predicted sentences
|
||||
@@ -226,8 +215,7 @@ def batch_accuracy(
|
||||
def first_match_to_topk(
|
||||
first_match_list: list[int], k_values: list[int]
|
||||
) -> dict[int, list[float]]:
|
||||
"""
|
||||
Calculate top-k accuracy with the first match ranks (1-indexed).
|
||||
"""Calculate top-k accuracy with the first match ranks (1-indexed).
|
||||
|
||||
:param first_match: first match ranks (1-indexed)
|
||||
:param k_values: k values to consider
|
||||
@@ -237,8 +225,7 @@ def first_match_to_topk(
|
||||
|
||||
|
||||
def pass_at_k(n: int, c: int, k: int) -> float:
|
||||
"""
|
||||
Sample pass@k metric according to the Codex paper, but in the scale of 0~100.
|
||||
"""Sample pass@k metric according to the Codex paper, but in the scale of 0~100.
|
||||
:param n: total number of samples
|
||||
:param c: number of correct samples
|
||||
:param k: k in pass@$k$
|
||||
@@ -251,8 +238,7 @@ def pass_at_k(n: int, c: int, k: int) -> float:
|
||||
|
||||
|
||||
def self_bleu(samples: list[list[str]]) -> float:
|
||||
"""
|
||||
Calculate self-BLEU among the samples.
|
||||
"""Calculate self-BLEU among the samples.
|
||||
:param samples: the chosen m samples
|
||||
:return: self-BLEU
|
||||
"""
|
||||
@@ -274,8 +260,7 @@ def self_bleu(samples: list[list[str]]) -> float:
|
||||
|
||||
|
||||
def self_edit_distance(samples: list[Union[str, list[str]]], sep=' ') -> float:
|
||||
"""
|
||||
Calculate self-edit-distance among the samples.
|
||||
"""Calculate self-edit-distance among the samples.
|
||||
:param samples: the chosen m samples
|
||||
:param sep: the separator between tokens
|
||||
:return: self-edit-distance
|
||||
|
||||
@@ -30,8 +30,7 @@ def check_mutation(mutation_output):
|
||||
|
||||
|
||||
def count_methods(code_str):
|
||||
"""
|
||||
Counts the number of methods/functions in a given string of code.
|
||||
"""Counts the number of methods/functions in a given string of code.
|
||||
|
||||
Args:
|
||||
code_str (str): A string containing code.
|
||||
@@ -46,8 +45,7 @@ def count_methods(code_str):
|
||||
|
||||
|
||||
def get_lines_of_code(code_str):
|
||||
"""
|
||||
Extracts lines of code from a given string.
|
||||
"""Extracts lines of code from a given string.
|
||||
|
||||
Args:
|
||||
code_str (str): A string containing code.
|
||||
|
||||
@@ -37,8 +37,8 @@ from openhands.core.config import (
|
||||
AgentConfig,
|
||||
OpenHandsConfig,
|
||||
SandboxConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -491,7 +491,7 @@ def prepare_dataset_pre(dataset: pd.DataFrame, filter_column: str) -> pd.DataFra
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -7,8 +7,7 @@ import traceback
|
||||
|
||||
|
||||
def insert_line_in_string(input_string, new_str, insert_line):
|
||||
"""
|
||||
Inserts a new line into a string at the specified line number.
|
||||
"""Inserts a new line into a string at the specified line number.
|
||||
|
||||
:param input_string: The original string.
|
||||
:param new_str: The string to insert.
|
||||
@@ -29,8 +28,7 @@ def insert_line_in_string(input_string, new_str, insert_line):
|
||||
|
||||
|
||||
def print_string_diff(original, modified):
|
||||
"""
|
||||
Prints the differences between two strings line by line.
|
||||
"""Prints the differences between two strings line by line.
|
||||
|
||||
:param original: The original string.
|
||||
:param modified: The modified string.
|
||||
|
||||
@@ -37,8 +37,7 @@ def extract_preamble_classes_and_functions(code):
|
||||
current_position = 0
|
||||
|
||||
def extract_class_body(code: str, start_index: int) -> tuple[str, int]:
|
||||
"""
|
||||
Extracts the body of a class from the given code starting from the specified index.
|
||||
"""Extracts the body of a class from the given code starting from the specified index.
|
||||
Returns the class body and the end index of the class body.
|
||||
"""
|
||||
if not code or start_index < 0 or start_index >= len(code):
|
||||
@@ -168,8 +167,8 @@ def extract_preamble_classes_and_functions(code):
|
||||
def filter_passing_tests(
|
||||
test_content: str, test_output: str, repo: str
|
||||
) -> tuple[str, list[str], list[str]]:
|
||||
"""
|
||||
Filter tests based on their execution results.
|
||||
"""Filter tests based on their execution results.
|
||||
|
||||
Returns:
|
||||
Tuple containing:
|
||||
- Modified test content with only passing tests
|
||||
@@ -246,8 +245,7 @@ def filter_passing_tests(
|
||||
def filter_tests(
|
||||
test_content: str, test_output: str, repo: str
|
||||
) -> tuple[str, list[str], list[str]]:
|
||||
"""
|
||||
Filter tests using AST parsing to remove failing test functions from the test file.
|
||||
"""Filter tests using AST parsing to remove failing test functions from the test file.
|
||||
Non-test functions (e.g. setup or helper methods) and classes (even if all test methods are failing)
|
||||
are preserved.
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ DIFF_MODIFIED_FILE_REGEX = r'--- a/(.*)'
|
||||
|
||||
@dataclass
|
||||
class TestSpec:
|
||||
"""
|
||||
A dataclass that represents a test specification for a single instance of SWE-bench.
|
||||
"""
|
||||
"""A dataclass that represents a test specification for a single instance of SWE-bench."""
|
||||
|
||||
instance_id: str
|
||||
id: str
|
||||
@@ -86,10 +84,7 @@ def make_test_setup(specs, env_name, repo_directory, includes_tox=False):
|
||||
|
||||
|
||||
def make_test_script_list(test_cmd, specs, env_name, repo_directory):
|
||||
"""
|
||||
Runs the tests.
|
||||
"""
|
||||
|
||||
"""Runs the tests."""
|
||||
includes_tox = 'tox' in test_cmd
|
||||
eval_commands = make_test_setup(specs, env_name, repo_directory, includes_tox)
|
||||
eval_commands += [
|
||||
@@ -104,10 +99,7 @@ def make_test_script_list(test_cmd, specs, env_name, repo_directory):
|
||||
|
||||
|
||||
def make_mutation_script_list(specs, env_name, repo_directory, mutation_timeout):
|
||||
"""
|
||||
Runs the tests.
|
||||
"""
|
||||
|
||||
"""Runs the tests."""
|
||||
eval_commands = make_test_setup(specs, env_name, repo_directory)
|
||||
eval_commands += [
|
||||
'cosmic-ray init mutation.toml mutation.sqlite',
|
||||
|
||||
@@ -11,8 +11,7 @@ from evaluation.benchmarks.testgeneval.constants import (
|
||||
|
||||
|
||||
def get_test_directives(instance: TestGenEvalInstance) -> list:
|
||||
"""
|
||||
Get test directives from the test_patch of a task instance
|
||||
"""Get test directives from the test_patch of a task instance
|
||||
|
||||
Args:
|
||||
instance (dict): task instance
|
||||
@@ -43,9 +42,7 @@ def get_test_directives(instance: TestGenEvalInstance) -> list:
|
||||
def load_testgeneval_dataset(
|
||||
name='kjain14/testgeneval', split='test', ids=None
|
||||
) -> list[TestGenEvalInstance]:
|
||||
"""
|
||||
Load SWE-bench dataset from Hugging Face Datasets or local .json/.jsonl file
|
||||
"""
|
||||
"""Load SWE-bench dataset from Hugging Face Datasets or local .json/.jsonl file"""
|
||||
# check that all instance IDs are in the dataset
|
||||
if ids:
|
||||
ids = set(ids)
|
||||
|
||||
@@ -24,9 +24,7 @@ class ActionType(Enum):
|
||||
|
||||
@dataclass
|
||||
class Selector:
|
||||
"""
|
||||
Represents either a direct anchor ID or a descriptive selector
|
||||
"""
|
||||
"""Represents either a direct anchor ID or a descriptive selector"""
|
||||
|
||||
value: str
|
||||
is_anchor: bool = False
|
||||
@@ -149,8 +147,7 @@ def find_matching_anchor(content: str, selector: str) -> str | None:
|
||||
|
||||
|
||||
def resolve_action(action: BrowserAction, content: str) -> BrowserAction:
|
||||
"""
|
||||
Resolve any descriptive selectors in the action to anchor IDs based on the content.
|
||||
"""Resolve any descriptive selectors in the action to anchor IDs based on the content.
|
||||
Returns a new action with resolved selectors.
|
||||
"""
|
||||
if isinstance(action, (InputAction, ClickAction)):
|
||||
@@ -174,8 +171,7 @@ def pre_login(
|
||||
save_screenshots=True,
|
||||
screenshots_dir='screenshots',
|
||||
):
|
||||
"""
|
||||
Logs in to all the websites that are needed for the evaluation.
|
||||
"""Logs in to all the websites that are needed for the evaluation.
|
||||
Once logged in, the sessions would be cached in the browser, so OpenHands
|
||||
agent doesn't need to log in to these websites again.
|
||||
"""
|
||||
|
||||
@@ -18,8 +18,8 @@ from openhands.core.config import (
|
||||
LLMConfig,
|
||||
OpenHandsConfig,
|
||||
get_agent_config_arg,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.config.agent_config import AgentConfig
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
@@ -68,8 +68,7 @@ def get_config(
|
||||
|
||||
|
||||
def load_dependencies(runtime: Runtime) -> list[str]:
|
||||
"""
|
||||
Every task has a dependencies.yml file, which lists all the services that the
|
||||
"""Every task has a dependencies.yml file, which lists all the services that the
|
||||
task depends on. This function loads the file and returns all dependent service names.
|
||||
"""
|
||||
command = 'cat /utils/dependencies.yml'
|
||||
@@ -197,7 +196,7 @@ def run_evaluator(
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--task-image-name',
|
||||
type=str,
|
||||
|
||||
@@ -11,9 +11,7 @@ import sys
|
||||
|
||||
|
||||
def calculate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> float:
|
||||
"""
|
||||
Calculate the cost of the model call.
|
||||
"""
|
||||
"""Calculate the cost of the model call."""
|
||||
if 'claude-3-5-sonnet' in model.lower():
|
||||
# https://www.anthropic.com/pricing#anthropic-api, accessed 12/11/2024
|
||||
return 0.000003 * prompt_tokens + 0.000015 * completion_tokens
|
||||
@@ -60,8 +58,7 @@ def calculate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> fl
|
||||
|
||||
|
||||
def analyze_eval_json_file(filepath: str) -> tuple[int, int]:
|
||||
"""
|
||||
Analyze a single eval JSON file and extract the total and result from final_score.
|
||||
"""Analyze a single eval JSON file and extract the total and result from final_score.
|
||||
|
||||
Args:
|
||||
filepath: Path to the JSON file
|
||||
@@ -84,8 +81,7 @@ def analyze_eval_json_file(filepath: str) -> tuple[int, int]:
|
||||
|
||||
|
||||
def analyze_traj_json_file(filepath: str) -> tuple[int, float]:
|
||||
"""
|
||||
Analyze a single trajectory JSON file and extract the steps and tokens
|
||||
"""Analyze a single trajectory JSON file and extract the steps and tokens
|
||||
for each step. Then estimate the cost based on the tokens and the model type.
|
||||
Note: this is assuming there's no prompt caching at all.
|
||||
"""
|
||||
@@ -115,8 +111,7 @@ def analyze_traj_json_file(filepath: str) -> tuple[int, float]:
|
||||
def analyze_folder(
|
||||
folder_path: str,
|
||||
) -> tuple[dict[str, tuple[int, int]], dict[str, tuple[int, float]]]:
|
||||
"""
|
||||
Analyze all eval_*.json & traj_*.json files in the specified folder.
|
||||
"""Analyze all eval_*.json & traj_*.json files in the specified folder.
|
||||
|
||||
Args:
|
||||
folder_path: Path to the folder containing JSON files
|
||||
@@ -148,9 +143,7 @@ def analyze_folder(
|
||||
|
||||
|
||||
def get_task_nature_category(task_name: str) -> str:
|
||||
"""
|
||||
Get the nature category of the task.
|
||||
"""
|
||||
"""Get the nature category of the task."""
|
||||
task_nature = task_name.split('-')[0]
|
||||
if task_nature.lower() in ['sde', 'pm', 'ds', 'admin', 'hr', 'finance']:
|
||||
return task_nature
|
||||
@@ -159,8 +152,7 @@ def get_task_nature_category(task_name: str) -> str:
|
||||
|
||||
|
||||
def calculate_score(total: int, result: int) -> float:
|
||||
"""
|
||||
Calculate the score as a number between 0 and 1.
|
||||
"""Calculate the score as a number between 0 and 1.
|
||||
|
||||
Formula: score = (result / total) * 0.5 + (result // total) * 0.5
|
||||
Explanation:
|
||||
@@ -178,8 +170,7 @@ def calculate_score(total: int, result: int) -> float:
|
||||
|
||||
|
||||
def is_perfect_completion(total: int, result: int) -> bool:
|
||||
"""
|
||||
Check if the task achieved perfect completion.
|
||||
"""Check if the task achieved perfect completion.
|
||||
|
||||
Args:
|
||||
total: Total possible points
|
||||
|
||||
@@ -19,8 +19,8 @@ from evaluation.utils.shared import (
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
OpenHandsConfig,
|
||||
get_evaluation_parser,
|
||||
get_llm_config_arg,
|
||||
get_parser,
|
||||
)
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
@@ -157,7 +157,7 @@ def process_instance(instance: Any, metadata: EvalMetadata, reset_logger: bool =
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
parser = get_evaluation_parser()
|
||||
parser.add_argument(
|
||||
'--dataset',
|
||||
type=str,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
GPT performs line level generation prediction and truncates overly long tokens
|
||||
"""
|
||||
"""GPT performs line level generation prediction and truncates overly long tokens"""
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -56,8 +54,7 @@ def predict(content, model_name):
|
||||
|
||||
|
||||
def bulid_prompt(description, old_version, old_code, new_version) -> str:
|
||||
"""
|
||||
build prompt
|
||||
"""Build prompt
|
||||
:param version:
|
||||
:param description:
|
||||
:param masked_code:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
GPT performs line level generation prediction and truncates overly long tokens
|
||||
"""
|
||||
"""GPT performs line level generation prediction and truncates overly long tokens"""
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -56,8 +54,7 @@ def predict(content, model_name):
|
||||
|
||||
|
||||
def bulid_prompt(version, description) -> str:
|
||||
"""
|
||||
build prompt
|
||||
"""Build prompt
|
||||
:param version:
|
||||
:param description:
|
||||
:param masked_code:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
block completion
|
||||
"""
|
||||
"""block completion"""
|
||||
|
||||
import copy
|
||||
import gc
|
||||
@@ -79,8 +77,7 @@ def run_inference(model_name, origin_data_list):
|
||||
|
||||
|
||||
def bulid_prompt(version, description) -> str:
|
||||
"""
|
||||
build prompt
|
||||
"""Build prompt
|
||||
:param version:
|
||||
:param description:
|
||||
:param masked_code:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
code migration
|
||||
"""
|
||||
"""code migration"""
|
||||
|
||||
import copy
|
||||
import gc
|
||||
@@ -81,8 +79,7 @@ def run_inference(model_name, origin_data_list):
|
||||
|
||||
|
||||
def bulid_prompt(description, old_version, old_code, new_version) -> str:
|
||||
"""
|
||||
build prompt
|
||||
"""Build prompt
|
||||
:param version:
|
||||
:param description:
|
||||
:param masked_code:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""
|
||||
评测block的预测能力
|
||||
"""评测block的预测能力
|
||||
1、判断是否包含正确的函数名
|
||||
2、判断是否合法
|
||||
3、计算ISM,和PM
|
||||
@@ -22,8 +21,7 @@ def is_code_valid(code):
|
||||
|
||||
|
||||
def longest_common_prefix_between_lists_with_elements(list1, list2):
|
||||
"""
|
||||
计算两个字符串列表中元素的最长前缀匹配长度
|
||||
"""计算两个字符串列表中元素的最长前缀匹配长度
|
||||
:param list1:
|
||||
:param list2:
|
||||
:return:
|
||||
@@ -46,8 +44,7 @@ def longest_common_prefix_between_lists_with_elements(list1, list2):
|
||||
|
||||
|
||||
def get_token(ans_code: str, output_code: str):
|
||||
"""
|
||||
对代码进行词法分析,分解成标识符,返回两个标识符列表
|
||||
"""对代码进行词法分析,分解成标识符,返回两个标识符列表
|
||||
:param ans_code:
|
||||
:param output_code:
|
||||
:return:
|
||||
@@ -94,8 +91,7 @@ def get_token(ans_code: str, output_code: str):
|
||||
|
||||
|
||||
def get_token_per_line(code: str):
|
||||
"""
|
||||
对每一行代码进行词法分析,记录每一行的标识符
|
||||
"""对每一行代码进行词法分析,记录每一行的标识符
|
||||
:param code: 代码字符串
|
||||
:return: 每一行的标识符列表组成的列表
|
||||
"""
|
||||
@@ -117,8 +113,7 @@ def get_token_per_line(code: str):
|
||||
|
||||
|
||||
def get_ISM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
||||
"""
|
||||
计算ISM,返回一个有序的得分列表
|
||||
"""计算ISM,返回一个有序的得分列表
|
||||
:return:
|
||||
"""
|
||||
score_list = []
|
||||
@@ -157,8 +152,7 @@ def get_ISM(answer_code: str, model_output_list: list, answer_name: str) -> list
|
||||
def get_ISM_without_verification(
|
||||
answer_code: str, model_output_list: list, answer_name: str
|
||||
) -> list:
|
||||
"""
|
||||
计算ISM,返回一个有序的得分列表
|
||||
"""计算ISM,返回一个有序的得分列表
|
||||
:return:
|
||||
"""
|
||||
score_list = []
|
||||
@@ -190,8 +184,7 @@ def get_ISM_without_verification(
|
||||
|
||||
|
||||
def longest_common_prefix_with_lengths(list1, list2):
|
||||
"""
|
||||
计算两个二维列表中每个子列表的最长前缀匹配长度,并记录拥有最长前缀匹配长度的两个子列表的长度
|
||||
"""计算两个二维列表中每个子列表的最长前缀匹配长度,并记录拥有最长前缀匹配长度的两个子列表的长度
|
||||
:param list1: 第一个二维列表
|
||||
:param list2: 第二个二维列表
|
||||
:return: 最长前缀匹配长度以及拥有最长前缀匹配长度的两个子列表的长度
|
||||
@@ -216,8 +209,7 @@ def longest_common_prefix_with_lengths(list1, list2):
|
||||
|
||||
|
||||
def get_PM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
||||
"""
|
||||
计算PM,返回一个有序的得分列表
|
||||
"""计算PM,返回一个有序的得分列表
|
||||
:return:
|
||||
"""
|
||||
score_list = []
|
||||
@@ -254,8 +246,7 @@ def get_PM(answer_code: str, model_output_list: list, answer_name: str) -> list:
|
||||
|
||||
|
||||
def get_score(score_list: list, k):
|
||||
"""
|
||||
计算score@n,k
|
||||
"""计算score@n,k
|
||||
:param score_list:
|
||||
:param k:
|
||||
:return:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
"""
|
||||
Calculate the cdc score for migration
|
||||
"""
|
||||
"""Calculate the cdc score for migration"""
|
||||
|
||||
import json
|
||||
import math
|
||||
@@ -11,8 +9,7 @@ import re
|
||||
|
||||
|
||||
def is_correct_parameter_count(function_name, correct_code, test_code):
|
||||
"""
|
||||
判断参数数量是否一致
|
||||
"""判断参数数量是否一致
|
||||
:param function_name:
|
||||
:param correct_code:
|
||||
:param test_code:
|
||||
@@ -43,8 +40,7 @@ def is_correct_parameter_count(function_name, correct_code, test_code):
|
||||
|
||||
|
||||
def check_keyword_parameters(function_name, correct_code, test_code):
|
||||
"""
|
||||
判断关键词参数赋值是否正确使用
|
||||
"""判断关键词参数赋值是否正确使用
|
||||
:param function_name:
|
||||
:param correct_code:
|
||||
:param test_code:
|
||||
@@ -82,8 +78,7 @@ def check_keyword_parameters(function_name, correct_code, test_code):
|
||||
|
||||
|
||||
def with_correct(answer_code: str, model_output: str) -> bool:
|
||||
"""
|
||||
当answer是with结构时,判断模型生成的是不是with结构
|
||||
"""当answer是with结构时,判断模型生成的是不是with结构
|
||||
:param answer_code:
|
||||
:param model_output:
|
||||
:return:
|
||||
@@ -105,9 +100,7 @@ def compute_block_score_k(
|
||||
core_line_in_core_block,
|
||||
core_line_in_output_clear,
|
||||
):
|
||||
"""
|
||||
cdc需要满足五个条件,em只需要满足第一个条件
|
||||
"""
|
||||
"""cdc需要满足五个条件,em只需要满足第一个条件"""
|
||||
c = 0
|
||||
n = len(model_output)
|
||||
for index, code in enumerate(model_output):
|
||||
|
||||