mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
247 Commits
redis-poll
...
eval/visua
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
532a284d5c | ||
|
|
43f6104967 | ||
|
|
e619929909 | ||
|
|
93753ac2e0 | ||
|
|
38e19d214d | ||
|
|
19a4f1c3ec | ||
|
|
45a048f9e3 | ||
|
|
358d9cb3f4 | ||
|
|
e6a2fd3fd4 | ||
|
|
c2f308f397 | ||
|
|
a1f1c802d9 | ||
|
|
ad2237d7dd | ||
|
|
aa0cd51967 | ||
|
|
081a1305f0 | ||
|
|
9912e28576 | ||
|
|
b19a33ccad | ||
|
|
21e912d6fb | ||
|
|
0dd9b95dbe | ||
|
|
aebb583779 | ||
|
|
2ff9ba1229 | ||
|
|
a7e6068ba8 | ||
|
|
24adcee9e3 | ||
|
|
21d4ba0bbd | ||
|
|
5ba9a6d321 | ||
|
|
aa223734d4 | ||
|
|
053723a4d4 | ||
|
|
5a6dbac5a3 | ||
|
|
93d74e9b41 | ||
|
|
1337d03816 | ||
|
|
04e36df4d7 | ||
|
|
f9ba16b648 | ||
|
|
f0dbb02ee1 | ||
|
|
318c811817 | ||
|
|
b468150f2a | ||
|
|
b9a3f1c753 | ||
|
|
09e8a1eeba | ||
|
|
ff3880c76d | ||
|
|
8bd7613724 | ||
|
|
5b7fcfbe1a | ||
|
|
8ae36481df | ||
|
|
25fdb0c3bf | ||
|
|
7f57dbebda | ||
|
|
54589d7e83 | ||
|
|
b7f34c3f8d | ||
|
|
210eeee94a | ||
|
|
509892cf0e | ||
|
|
89963e93d8 | ||
|
|
b6804f9e1e | ||
|
|
d30211da18 | ||
|
|
06121bf20f | ||
|
|
541a445dfc | ||
|
|
03e496fb60 | ||
|
|
1b6e444ecb | ||
|
|
2b04ee2e62 | ||
|
|
4383be1ab4 | ||
|
|
b4d20e3e18 | ||
|
|
532c7cdf02 | ||
|
|
987861b5e7 | ||
|
|
f07ec7a09c | ||
|
|
b1fa6301f0 | ||
|
|
62fbe4c622 | ||
|
|
a1a87af69d | ||
|
|
a12087243a | ||
|
|
899c1f8360 | ||
|
|
c5d7caf01f | ||
|
|
85a760e561 | ||
|
|
8e9c315729 | ||
|
|
000055ba73 | ||
|
|
2edb2337c2 | ||
|
|
7c8a0162ae | ||
|
|
c10f18b3bd | ||
|
|
313c8eca20 | ||
|
|
eff9e07272 | ||
|
|
f8a3aeccd6 | ||
|
|
9375e0d756 | ||
|
|
72af7bbba2 | ||
|
|
0661c69bd3 | ||
|
|
0c961bfd8b | ||
|
|
0bed17758f | ||
|
|
da1a6035ac | ||
|
|
df050e4786 | ||
|
|
e211647eba | ||
|
|
8579710c82 | ||
|
|
8c35150c3e | ||
|
|
578291e961 | ||
|
|
efe04baf34 | ||
|
|
6e089619e0 | ||
|
|
179a89a211 | ||
|
|
8795ee6c6e | ||
|
|
97e938d545 | ||
|
|
b9a70c8d5c | ||
|
|
082d0b25c5 | ||
|
|
c5797d1d5a | ||
|
|
7ce1fb85ff | ||
|
|
fa6792e5a6 | ||
|
|
3d9b4c4af6 | ||
|
|
e21cbf67ee | ||
|
|
6b2e3f938f | ||
|
|
580d7b938c | ||
|
|
28178a2940 | ||
|
|
04382b2b19 | ||
|
|
4da812c781 | ||
|
|
37b7173481 | ||
|
|
f0ebf3eba8 | ||
|
|
92b8d55c2d | ||
|
|
a125b6cd43 | ||
|
|
01ac207b92 | ||
|
|
6d015a5dca | ||
|
|
275512305d | ||
|
|
3a4bc10b29 | ||
|
|
bbd31b32f3 | ||
|
|
295c6fd629 | ||
|
|
5a809c9b53 | ||
|
|
0b74fd71d9 | ||
|
|
4fa5c329d6 | ||
|
|
5b1dcf83a6 | ||
|
|
b9884f7609 | ||
|
|
99eda0e571 | ||
|
|
5832463088 | ||
|
|
045ec2b95d | ||
|
|
23473070b9 | ||
|
|
63133c0ba9 | ||
|
|
2023fb767f | ||
|
|
23f40a1c01 | ||
|
|
873dddb4e8 | ||
|
|
fe50cd1f9f | ||
|
|
516e2da520 | ||
|
|
1dd6f544bc | ||
|
|
40c52feb5b | ||
|
|
f31ccad48b | ||
|
|
828d169b82 | ||
|
|
a622d27016 | ||
|
|
5507b131fe | ||
|
|
0f102e4c71 | ||
|
|
157a1a24f6 | ||
|
|
fcfbcb64d4 | ||
|
|
931792e87a | ||
|
|
ee701eacc2 | ||
|
|
8907fed78e | ||
|
|
3cc20a2576 | ||
|
|
01cf0d433c | ||
|
|
f6bed82ae2 | ||
|
|
3eae2e2aca | ||
|
|
b45fc522c7 | ||
|
|
0d409c8c24 | ||
|
|
5458ebbd7d | ||
|
|
c411a29db4 | ||
|
|
386e04a2ba | ||
|
|
62c4bab6ba | ||
|
|
e308b6fb6f | ||
|
|
27a660fb6b | ||
|
|
27d761a1fe | ||
|
|
8028e2c2dd | ||
|
|
ff9058e28a | ||
|
|
c45caaef1f | ||
|
|
a3c107daa4 | ||
|
|
040839bdd1 | ||
|
|
aabbbb6c6a | ||
|
|
9747c9e9f8 | ||
|
|
bb85542aca | ||
|
|
6e4ff56934 | ||
|
|
561f308401 | ||
|
|
3733c646af | ||
|
|
cf0f6e5e38 | ||
|
|
77aa843d53 | ||
|
|
affbc49b08 | ||
|
|
9016b9c434 | ||
|
|
e3a96097ba | ||
|
|
6a41a3cb4f | ||
|
|
d1555e093c | ||
|
|
5469d5311d | ||
|
|
eaf4c610b2 | ||
|
|
aad7a612c1 | ||
|
|
23425c85aa | ||
|
|
fb53ae43c0 | ||
|
|
1f8a0180d3 | ||
|
|
8cfcdd7ba3 | ||
|
|
9515ac5e62 | ||
|
|
cebd391b7a | ||
|
|
343b86429e | ||
|
|
09734467c0 | ||
|
|
17d722f3b3 | ||
|
|
e310f6b776 | ||
|
|
5626a22e42 | ||
|
|
cde8aad47f | ||
|
|
efd0267919 | ||
|
|
f8735efadf | ||
|
|
00d7395e09 | ||
|
|
150463e629 | ||
|
|
b7bbf0f5eb | ||
|
|
d2790c8b21 | ||
|
|
3d2138d9ce | ||
|
|
e4cf2eee2d | ||
|
|
79551e67f6 | ||
|
|
f5f988e552 | ||
|
|
0c58f469b4 | ||
|
|
56d7dccec9 | ||
|
|
411b63159f | ||
|
|
5ca0beadfb | ||
|
|
aaff3dd075 | ||
|
|
ef2053011d | ||
|
|
e6499a68f6 | ||
|
|
33cb1d5f3c | ||
|
|
5bdebac741 | ||
|
|
510c1644dd | ||
|
|
ec70af9412 | ||
|
|
761a574b09 | ||
|
|
825a9ba893 | ||
|
|
a6d392322a | ||
|
|
1ddf398a81 | ||
|
|
4de6c782cc | ||
|
|
9fef6f909a | ||
|
|
ff466d0f17 | ||
|
|
4c59cff2a3 | ||
|
|
fa44bdb390 | ||
|
|
dd10f37f66 | ||
|
|
3b26678a77 | ||
|
|
f14f75b064 | ||
|
|
ef8e04aee3 | ||
|
|
23df4a09d2 | ||
|
|
eb93113b7a | ||
|
|
c40b0b9ae1 | ||
|
|
61ebec9ff7 | ||
|
|
c567c11267 | ||
|
|
e628615094 | ||
|
|
50f821f9b9 | ||
|
|
15e0a50ff4 | ||
|
|
e52cdfd70a | ||
|
|
c1b514e9d3 | ||
|
|
8983d719bd | ||
|
|
9dd5463e06 | ||
|
|
d5b2ce18cb | ||
|
|
8d627e52cb | ||
|
|
a1b59b6185 | ||
|
|
b73bac62f2 | ||
|
|
ee88af8563 | ||
|
|
f846b31eb8 | ||
|
|
50a0b1d91d | ||
|
|
3d4d66a8c2 | ||
|
|
f3885cadc1 | ||
|
|
2ec2f2538f | ||
|
|
40d8245089 | ||
|
|
e249b920ff | ||
|
|
d920a69f69 | ||
|
|
a8ce888981 | ||
|
|
e22ddc0dd6 | ||
|
|
c370912f12 |
2
.github/workflows/dummy-agent-test.yml
vendored
2
.github/workflows/dummy-agent-test.yml
vendored
@@ -36,6 +36,8 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install tmux
|
||||
run: sudo apt-get update && sudo apt-get install -y tmux
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Set up Python
|
||||
|
||||
2
.github/workflows/eval-runner.yml
vendored
2
.github/workflows/eval-runner.yml
vendored
@@ -29,6 +29,8 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install tmux
|
||||
run: sudo apt-get update && sudo apt-get install -y tmux
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
|
||||
|
||||
4
.github/workflows/ghcr-build.yml
vendored
4
.github/workflows/ghcr-build.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
docker-images: false
|
||||
swap-storage: true
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
- name: Login to GHCR
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
docker-images: false
|
||||
swap-storage: true
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3.0.0
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
with:
|
||||
image: tonistiigi/binfmt:latest
|
||||
- name: Login to GHCR
|
||||
|
||||
110
.github/workflows/integration-runner.yml
vendored
110
.github/workflows/integration-runner.yml
vendored
@@ -56,6 +56,7 @@ jobs:
|
||||
LLM_MODEL: "litellm_proxy/claude-3-5-haiku-20241022"
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
MAX_ITERATIONS: 10
|
||||
run: |
|
||||
echo "[llm.eval]" > config.toml
|
||||
echo "model = \"$LLM_MODEL\"" >> config.toml
|
||||
@@ -70,7 +71,7 @@ jobs:
|
||||
env:
|
||||
SANDBOX_FORCE_REBUILD_RUNTIME: True
|
||||
run: |
|
||||
poetry run ./evaluation/integration_tests/scripts/run_infer.sh llm.eval HEAD CodeActAgent '' $N_PROCESSES '' 'haiku_run'
|
||||
poetry run ./evaluation/integration_tests/scripts/run_infer.sh llm.eval HEAD CodeActAgent '' 10 $N_PROCESSES '' 'haiku_run'
|
||||
|
||||
# get integration tests report
|
||||
REPORT_FILE_HAIKU=$(find evaluation/evaluation_outputs/outputs/integration_tests/CodeActAgent/*haiku*_maxiter_10_N* -name "report.md" -type f | head -n 1)
|
||||
@@ -88,6 +89,7 @@ jobs:
|
||||
LLM_MODEL: "litellm_proxy/deepseek-chat"
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
MAX_ITERATIONS: 10
|
||||
run: |
|
||||
echo "[llm.eval]" > config.toml
|
||||
echo "model = \"$LLM_MODEL\"" >> config.toml
|
||||
@@ -99,7 +101,7 @@ jobs:
|
||||
env:
|
||||
SANDBOX_FORCE_REBUILD_RUNTIME: True
|
||||
run: |
|
||||
poetry run ./evaluation/integration_tests/scripts/run_infer.sh llm.eval HEAD CodeActAgent '' $N_PROCESSES '' 'deepseek_run'
|
||||
poetry run ./evaluation/integration_tests/scripts/run_infer.sh llm.eval HEAD CodeActAgent '' 10 $N_PROCESSES '' 'deepseek_run'
|
||||
|
||||
# get integration tests report
|
||||
REPORT_FILE_DEEPSEEK=$(find evaluation/evaluation_outputs/outputs/integration_tests/CodeActAgent/deepseek*_maxiter_10_N* -name "report.md" -type f | head -n 1)
|
||||
@@ -109,11 +111,104 @@ jobs:
|
||||
echo >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Run DelegatorAgent tests for Haiku, limited to t01 and t02
|
||||
- name: Wait a little bit (again)
|
||||
run: sleep 5
|
||||
|
||||
- name: Configure config.toml for testing DelegatorAgent (Haiku)
|
||||
env:
|
||||
LLM_MODEL: "litellm_proxy/claude-3-5-haiku-20241022"
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
MAX_ITERATIONS: 30
|
||||
run: |
|
||||
echo "[llm.eval]" > config.toml
|
||||
echo "model = \"$LLM_MODEL\"" >> config.toml
|
||||
echo "api_key = \"$LLM_API_KEY\"" >> config.toml
|
||||
echo "base_url = \"$LLM_BASE_URL\"" >> config.toml
|
||||
echo "temperature = 0.0" >> config.toml
|
||||
|
||||
- name: Run integration test evaluation for DelegatorAgent (Haiku)
|
||||
env:
|
||||
SANDBOX_FORCE_REBUILD_RUNTIME: True
|
||||
run: |
|
||||
poetry run ./evaluation/integration_tests/scripts/run_infer.sh llm.eval HEAD DelegatorAgent '' 30 $N_PROCESSES "t01_fix_simple_typo,t02_add_bash_hello" 'delegator_haiku_run'
|
||||
|
||||
# Find and export the delegator test results
|
||||
REPORT_FILE_DELEGATOR_HAIKU=$(find evaluation/evaluation_outputs/outputs/integration_tests/DelegatorAgent/*haiku*_maxiter_30_N* -name "report.md" -type f | head -n 1)
|
||||
echo "REPORT_FILE_DELEGATOR_HAIKU: $REPORT_FILE_DELEGATOR_HAIKU"
|
||||
echo "INTEGRATION_TEST_REPORT_DELEGATOR_HAIKU<<EOF" >> $GITHUB_ENV
|
||||
cat $REPORT_FILE_DELEGATOR_HAIKU >> $GITHUB_ENV
|
||||
echo >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Run DelegatorAgent tests for DeepSeek, limited to t01 and t02
|
||||
- name: Wait a little bit (again)
|
||||
run: sleep 5
|
||||
|
||||
- name: Configure config.toml for testing DelegatorAgent (DeepSeek)
|
||||
env:
|
||||
LLM_MODEL: "litellm_proxy/deepseek-chat"
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
MAX_ITERATIONS: 30
|
||||
run: |
|
||||
echo "[llm.eval]" > config.toml
|
||||
echo "model = \"$LLM_MODEL\"" >> config.toml
|
||||
echo "api_key = \"$LLM_API_KEY\"" >> config.toml
|
||||
echo "base_url = \"$LLM_BASE_URL\"" >> config.toml
|
||||
echo "temperature = 0.0" >> config.toml
|
||||
- name: Run integration test evaluation for DelegatorAgent (DeepSeek)
|
||||
env:
|
||||
SANDBOX_FORCE_REBUILD_RUNTIME: True
|
||||
run: |
|
||||
poetry run ./evaluation/integration_tests/scripts/run_infer.sh llm.eval HEAD DelegatorAgent '' 30 $N_PROCESSES "t01_fix_simple_typo,t02_add_bash_hello" 'delegator_deepseek_run'
|
||||
|
||||
# Find and export the delegator test results
|
||||
REPORT_FILE_DELEGATOR_DEEPSEEK=$(find evaluation/evaluation_outputs/outputs/integration_tests/DelegatorAgent/deepseek*_maxiter_30_N* -name "report.md" -type f | head -n 1)
|
||||
echo "REPORT_FILE_DELEGATOR_DEEPSEEK: $REPORT_FILE_DELEGATOR_DEEPSEEK"
|
||||
echo "INTEGRATION_TEST_REPORT_DELEGATOR_DEEPSEEK<<EOF" >> $GITHUB_ENV
|
||||
cat $REPORT_FILE_DELEGATOR_DEEPSEEK >> $GITHUB_ENV
|
||||
echo >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
# -------------------------------------------------------------
|
||||
# Run VisualBrowsingAgent tests for DeepSeek, limited to t05 and t06
|
||||
- name: Wait a little bit (again)
|
||||
run: sleep 5
|
||||
|
||||
- name: Configure config.toml for testing VisualBrowsingAgent (DeepSeek)
|
||||
env:
|
||||
LLM_MODEL: "litellm_proxy/deepseek-chat"
|
||||
LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
|
||||
LLM_BASE_URL: ${{ secrets.LLM_BASE_URL }}
|
||||
MAX_ITERATIONS: 15
|
||||
run: |
|
||||
echo "[llm.eval]" > config.toml
|
||||
echo "model = \"$LLM_MODEL\"" >> config.toml
|
||||
echo "api_key = \"$LLM_API_KEY\"" >> config.toml
|
||||
echo "base_url = \"$LLM_BASE_URL\"" >> config.toml
|
||||
echo "temperature = 0.0" >> config.toml
|
||||
- name: Run integration test evaluation for VisualBrowsingAgent (DeepSeek)
|
||||
env:
|
||||
SANDBOX_FORCE_REBUILD_RUNTIME: True
|
||||
run: |
|
||||
poetry run ./evaluation/integration_tests/scripts/run_infer.sh llm.eval HEAD VisualBrowsingAgent '' 15 $N_PROCESSES "t05_simple_browsing,t06_github_pr_browsing.py" 'visualbrowsing_deepseek_run'
|
||||
|
||||
# Find and export the visual browsing agent test results
|
||||
REPORT_FILE_VISUALBROWSING_DEEPSEEK=$(find evaluation/evaluation_outputs/outputs/integration_tests/VisualBrowsingAgent/deepseek*_maxiter_15_N* -name "report.md" -type f | head -n 1)
|
||||
echo "REPORT_FILE_VISUALBROWSING_DEEPSEEK: $REPORT_FILE_VISUALBROWSING_DEEPSEEK"
|
||||
echo "INTEGRATION_TEST_REPORT_VISUALBROWSING_DEEPSEEK<<EOF" >> $GITHUB_ENV
|
||||
cat $REPORT_FILE_VISUALBROWSING_DEEPSEEK >> $GITHUB_ENV
|
||||
echo >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: Create archive of evaluation outputs
|
||||
run: |
|
||||
TIMESTAMP=$(date +'%y-%m-%d-%H-%M')
|
||||
cd evaluation/evaluation_outputs/outputs # Change to the outputs directory
|
||||
tar -czvf ../../../integration_tests_${TIMESTAMP}.tar.gz integration_tests/CodeActAgent/* # Only include the actual result directories
|
||||
tar -czvf ../../../integration_tests_${TIMESTAMP}.tar.gz integration_tests/CodeActAgent/* integration_tests/DelegatorAgent/* integration_tests/VisualBrowsingAgent/* # Only include the actual result directories
|
||||
|
||||
- name: Upload evaluation results as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -154,5 +249,14 @@ jobs:
|
||||
**Integration Tests Report (DeepSeek)**
|
||||
DeepSeek LLM Test Results:
|
||||
${{ env.INTEGRATION_TEST_REPORT_DEEPSEEK }}
|
||||
---
|
||||
**Integration Tests Report Delegator (Haiku)**
|
||||
${{ env.INTEGRATION_TEST_REPORT_DELEGATOR_HAIKU }}
|
||||
---
|
||||
**Integration Tests Report Delegator (DeepSeek)**
|
||||
${{ env.INTEGRATION_TEST_REPORT_DELEGATOR_DEEPSEEK }}
|
||||
---
|
||||
**Integration Tests Report VisualBrowsing (DeepSeek)**
|
||||
${{ env.INTEGRATION_TEST_REPORT_VISUALBROWSING_DEEPSEEK }}
|
||||
---
|
||||
Download testing outputs (includes both Haiku and DeepSeek results): [Download](${{ steps.upload_results_artifact.outputs.artifact-url }})
|
||||
|
||||
12
.github/workflows/openhands-resolver.yml
vendored
12
.github/workflows/openhands-resolver.yml
vendored
@@ -84,6 +84,10 @@ jobs:
|
||||
run: |
|
||||
python -m pip index versions openhands-ai > openhands_versions.txt
|
||||
OPENHANDS_VERSION=$(head -n 1 openhands_versions.txt | awk '{print $2}' | tr -d '()')
|
||||
# Ensure requirements.txt ends with newline before appending
|
||||
if [ -f requirements.txt ] && [ -s requirements.txt ]; then
|
||||
sed -i -e '$a\' requirements.txt
|
||||
fi
|
||||
echo "openhands-ai==${OPENHANDS_VERSION}" >> requirements.txt
|
||||
cat requirements.txt
|
||||
|
||||
@@ -184,6 +188,7 @@ jobs:
|
||||
});
|
||||
|
||||
- name: Install OpenHands
|
||||
id: install_openhands
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
COMMENT_BODY: ${{ github.event.comment.body || '' }}
|
||||
@@ -196,7 +201,6 @@ jobs:
|
||||
const reviewBody = process.env.REVIEW_BODY.trim();
|
||||
const labelName = process.env.LABEL_NAME.trim();
|
||||
const eventName = process.env.EVENT_NAME.trim();
|
||||
|
||||
// Check conditions
|
||||
const isExperimentalLabel = labelName === "fix-me-experimental";
|
||||
const isIssueCommentExperimental =
|
||||
@@ -205,6 +209,9 @@ jobs:
|
||||
const isReviewCommentExperimental =
|
||||
eventName === "pull_request_review" && reviewBody.includes("@openhands-agent-exp");
|
||||
|
||||
// Set output variable
|
||||
core.setOutput('isExperimental', isExperimentalLabel || isIssueCommentExperimental || isReviewCommentExperimental);
|
||||
|
||||
// Perform package installation
|
||||
if (isExperimentalLabel || isIssueCommentExperimental || isReviewCommentExperimental) {
|
||||
console.log("Installing experimental OpenHands...");
|
||||
@@ -230,7 +237,8 @@ jobs:
|
||||
--issue-number ${{ env.ISSUE_NUMBER }} \
|
||||
--issue-type ${{ env.ISSUE_TYPE }} \
|
||||
--max-iterations ${{ env.MAX_ITERATIONS }} \
|
||||
--comment-id ${{ env.COMMENT_ID }}
|
||||
--comment-id ${{ env.COMMENT_ID }} \
|
||||
--is-experimental ${{ steps.install_openhands.outputs.isExperimental }}
|
||||
|
||||
- name: Check resolution result
|
||||
id: check_result
|
||||
|
||||
2
.github/workflows/py-unit-tests-mac.yml
vendored
2
.github/workflows/py-unit-tests-mac.yml
vendored
@@ -31,6 +31,8 @@ jobs:
|
||||
key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-poetry-
|
||||
- name: Install tmux
|
||||
run: brew install tmux
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Install Python dependencies using Poetry
|
||||
|
||||
2
.github/workflows/py-unit-tests.yml
vendored
2
.github/workflows/py-unit-tests.yml
vendored
@@ -30,6 +30,8 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Install tmux
|
||||
run: sudo apt-get update && sudo apt-get install -y tmux
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
- name: Set up Python
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -176,6 +176,7 @@ evaluation/gorilla/data
|
||||
evaluation/toolqa/data
|
||||
evaluation/scienceagentbench/benchmark
|
||||
evaluation/commit0_bench/repos
|
||||
evaluation/visualcodebench/
|
||||
|
||||
# openhands resolver
|
||||
output/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
name: repo
|
||||
agent: CodeAct
|
||||
type: repo
|
||||
agent: CodeActAgent
|
||||
---
|
||||
This repository contains the code for OpenHands, an automated AI software engineer. It has a Python backend
|
||||
(in the `openhands` directory) and React frontend (in the `frontend` directory).
|
||||
|
||||
@@ -18,24 +18,24 @@ diverse, inclusive, and healthy community.
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Demonstrating empathy and kindness toward other people.
|
||||
* Being respectful of differing opinions, viewpoints, and experiences.
|
||||
* Giving and gracefully accepting constructive feedback.
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
and learning from the experience.
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
community.
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
any kind.
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks.
|
||||
* Public or private harassment.
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
without their explicit permission.
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
professional setting.
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
@@ -61,7 +61,7 @@ representative at an online or offline event.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
contact@all-hands.dev
|
||||
contact@all-hands.dev.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
@@ -113,6 +113,20 @@ individual, or aggression toward or disparagement of classes of individuals.
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
### Slack and Discord Etiquettes
|
||||
|
||||
These Slack and Discord etiquette guidelines are designed to foster an inclusive, respectful, and productive environment for all community members. By following these best practices, we ensure effective communication and collaboration while minimizing disruptions. Let’s work together to build a supportive and welcoming community!
|
||||
|
||||
- Communicate respectfully and professionally, avoiding sarcasm or harsh language, and remember that tone can be difficult to interpret in text.
|
||||
- Use threads for specific discussions to keep channels organized and easier to follow.
|
||||
- Tag others only when their input is critical or urgent, and use @here, @channel or @everyone sparingly to minimize disruptions.
|
||||
- Be patient, as open-source contributors and maintainers often have other commitments and may need time to respond.
|
||||
- Post questions or discussions in the most relevant channel (e.g., for [slack - #general](https://app.slack.com/client/T06P212QSEA/C06P5NCGSFP) for general topics, [slack - #questions](https://openhands-ai.slack.com/archives/C06U8UTKSAD) for queries/questions, [discord - #general](https://discord.com/channels/1222935860639563850/1222935861386018885)).
|
||||
- When asking for help or raising issues, include necessary details like links, screenshots, or clear explanations to provide context.
|
||||
- Keep discussions in public channels whenever possible to allow others to benefit from the conversation, unless the matter is sensitive or private.
|
||||
- Always adhere to [our standards](https://github.com/All-Hands-AI/OpenHands/blob/main/CODE_OF_CONDUCT.md#our-standards) to ensure a welcoming and collaborative environment.
|
||||
- If you choose to mute a channel, consider setting up alerts for topics that still interest you to stay engaged. For Slack, Go to Settings → Notifications → My Keywords to add specific keywords that will notify you when mentioned. For example, if you're here for discussions about LLMs, mute the channel if it’s too busy, but set notifications to alert you only when “LLMs” appears in messages. Also for Discord, go to the channel notifications and choose the option that best describes your need.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
|
||||
@@ -11,11 +11,11 @@ To understand the codebase, please refer to the README in each module:
|
||||
- [agenthub](./openhands/agenthub/README.md)
|
||||
- [server](./openhands/server/README.md)
|
||||
|
||||
## Setting up your development environment
|
||||
## Setting up Your Development Environment
|
||||
|
||||
We have a separate doc [Development.md](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md) that tells you how to set up a development workflow.
|
||||
|
||||
## How can I contribute?
|
||||
## How Can I Contribute?
|
||||
|
||||
There are many ways that you can contribute:
|
||||
|
||||
@@ -23,7 +23,7 @@ There are many ways that you can contribute:
|
||||
2. **Send feedback** after each session by [clicking the thumbs-up thumbs-down buttons](https://docs.all-hands.dev/modules/usage/feedback), so we can see where things are working and failing, and also build an open dataset for training code agents.
|
||||
3. **Improve the Codebase** by sending [PRs](#sending-pull-requests-to-openhands) (see details below). In particular, we have some [good first issues](https://github.com/All-Hands-AI/OpenHands/labels/good%20first%20issue) that may be ones to start on.
|
||||
|
||||
## What can I build?
|
||||
## What Can I Build?
|
||||
Here are a few ways you can help improve the codebase.
|
||||
|
||||
#### UI/UX
|
||||
@@ -35,7 +35,7 @@ of the application, please open an issue first, or better, join the #frontend ch
|
||||
to gather consensus from our design team first.
|
||||
|
||||
#### Improving the agent
|
||||
Our main agent is the CodeAct agent. You can [see its prompts here](https://github.com/All-Hands-AI/OpenHands/tree/main/openhands/agenthub/codeact_agent)
|
||||
Our main agent is the CodeAct agent. You can [see its prompts here](https://github.com/All-Hands-AI/OpenHands/tree/main/openhands/agenthub/codeact_agent).
|
||||
|
||||
Changes to these prompts, and to the underlying behavior in Python, can have a huge impact on user experience.
|
||||
You can try modifying the prompts to see how they change the behavior of the agent as you use the app
|
||||
@@ -63,7 +63,7 @@ At the moment, we have two kinds of tests: [`unit`](./tests/unit) and [`integrat
|
||||
## Sending Pull Requests to OpenHands
|
||||
|
||||
You'll need to fork our repository to send us a Pull Request. You can learn more
|
||||
about how to fork a GitHub repo and open a PR with your changes in [this article](https://medium.com/swlh/forks-and-pull-requests-how-to-contribute-to-github-repos-8843fac34ce8)
|
||||
about how to fork a GitHub repo and open a PR with your changes in [this article](https://medium.com/swlh/forks-and-pull-requests-how-to-contribute-to-github-repos-8843fac34ce8).
|
||||
|
||||
### Pull Request title
|
||||
As described [here](https://github.com/commitizen/conventional-commit-types/blob/master/index.json), a valid PR title should begin with one of the following prefixes:
|
||||
@@ -103,7 +103,7 @@ Further, if you see an issue you like, please leave a "thumbs-up" or a comment,
|
||||
|
||||
### Making Pull Requests
|
||||
|
||||
We're generally happy to consider all [PRs](https://github.com/All-Hands-AI/OpenHands/pulls), with the evaluation process varying based on the type of change:
|
||||
We're generally happy to consider all pull requests with the evaluation process varying based on the type of change:
|
||||
|
||||
#### For Small Improvements
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ This guide is for people working on OpenHands and editing the source code.
|
||||
If you wish to contribute your changes, check out the [CONTRIBUTING.md](https://github.com/All-Hands-AI/OpenHands/blob/main/CONTRIBUTING.md) on how to clone and setup the project initially before moving on.
|
||||
Otherwise, you can clone the OpenHands project directly.
|
||||
|
||||
## Start the server for development
|
||||
## Start the Server for Development
|
||||
### 1. Requirements
|
||||
* Linux, Mac OS, or [WSL on Windows](https://learn.microsoft.com/en-us/windows/wsl/install) [Ubuntu <= 22.04]
|
||||
* Linux, Mac OS, or [WSL on Windows](https://learn.microsoft.com/en-us/windows/wsl/install) [Ubuntu >= 22.04]
|
||||
* [Docker](https://docs.docker.com/engine/install/) (For those on MacOS, make sure to allow the default Docker socket to be used from advanced settings!)
|
||||
* [Python](https://www.python.org/downloads/) = 3.12
|
||||
* [NodeJS](https://nodejs.org/en/download/package-manager) >= 20.x
|
||||
@@ -58,7 +58,7 @@ See [our documentation](https://docs.all-hands.dev/modules/usage/llms) for recom
|
||||
|
||||
### 4. Running the application
|
||||
#### Option A: Run the Full Application
|
||||
Once the setup is complete, launching OpenHands is as simple as running a single command. This command starts both the backend and frontend servers seamlessly, allowing you to interact with OpenHands:
|
||||
Once the setup is complete, this command starts both the backend and frontend servers, allowing you to interact with OpenHands:
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
@@ -75,11 +75,11 @@ make run
|
||||
```
|
||||
|
||||
### 6. LLM Debugging
|
||||
If you encounter any issues with the Language Model (LM) or you're simply curious, you can inspect the actual LLM prompts and responses. To do so, export DEBUG=1 in the environment and restart the backend.
|
||||
OpenHands will then log the prompts and responses in the logs/llm/CURRENT_DATE directory, allowing you to identify the causes.
|
||||
If you encounter any issues with the Language Model (LM) or you're simply curious, export DEBUG=1 in the environment and restart the backend.
|
||||
OpenHands will log the prompts and responses in the logs/llm/CURRENT_DATE directory, allowing you to identify the causes.
|
||||
|
||||
### 7. Help
|
||||
Need assistance or information on available targets and commands? The help command provides all the necessary guidance to ensure a smooth experience with OpenHands.
|
||||
Need help or info on available targets and commands? Use the help command for all the guidance you need with OpenHands.
|
||||
```bash
|
||||
make help
|
||||
```
|
||||
@@ -93,14 +93,14 @@ poetry run pytest ./tests/unit/test_*.py
|
||||
```
|
||||
|
||||
### 9. Add or update dependency
|
||||
1. Add your dependency in `pyproject.toml` or use `poetry add xxx`
|
||||
2. Update the poetry.lock file via `poetry lock --no-update`
|
||||
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
|
||||
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.17-nikolaik`
|
||||
Example: `export SANDBOX_RUNTIME_CONTAINER_IMAGE=ghcr.io/all-hands-ai/runtime:0.21-nikolaik`
|
||||
|
||||
## Develop inside Docker container
|
||||
|
||||
@@ -110,7 +110,7 @@ TL;DR
|
||||
make docker-dev
|
||||
```
|
||||
|
||||
See more details [here](./containers/dev/README.md)
|
||||
See more details [here](./containers/dev/README.md).
|
||||
|
||||
If you are just interested in running `OpenHands` without installing all the required tools on your host.
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
These are the procedures and guidelines on how issues are triaged in this repo by the maintainers.
|
||||
|
||||
## General
|
||||
* Most issues must be tagged with **enhancement** or **bug**
|
||||
* Issues may be tagged with what it relates to (**backend**, **frontend**, **agent quality**, etc.)
|
||||
* Most issues must be tagged with **enhancement** or **bug**.
|
||||
* Issues may be tagged with what it relates to (**backend**, **frontend**, **agent quality**, etc.).
|
||||
|
||||
## Severity
|
||||
* **Low**: Minor issues or affecting single user.
|
||||
@@ -11,10 +11,10 @@ These are the procedures and guidelines on how issues are triaged in this repo b
|
||||
* **Critical**: Affecting all users or potential security issues.
|
||||
|
||||
## Effort
|
||||
* Issues may be estimated with effort required (**small effort**, **medium effort**, **large effort**)
|
||||
* Issues may be estimated with effort required (**small effort**, **medium effort**, **large effort**).
|
||||
|
||||
## Difficulty
|
||||
* Issues with low implementation difficulty may be tagged with **good first issue**
|
||||
* Issues with low implementation difficulty may be tagged with **good first issue**.
|
||||
|
||||
## Not Enough Information
|
||||
* User is asked to provide more information (logs, how to reproduce, etc.) when the issue is not clear.
|
||||
|
||||
4
Makefile
4
Makefile
@@ -106,7 +106,7 @@ check-poetry:
|
||||
@if command -v poetry > /dev/null; then \
|
||||
POETRY_VERSION=$(shell poetry --version 2>&1 | sed -E 's/Poetry \(version ([0-9]+\.[0-9]+\.[0-9]+)\)/\1/'); \
|
||||
IFS='.' read -r -a POETRY_VERSION_ARRAY <<< "$$POETRY_VERSION"; \
|
||||
if [ $${POETRY_VERSION_ARRAY[0]} -ge 1 ] && [ $${POETRY_VERSION_ARRAY[1]} -ge 8 ]; then \
|
||||
if [ $${POETRY_VERSION_ARRAY[0]} -gt 1 ] || ([ $${POETRY_VERSION_ARRAY[0]} -eq 1 ] && [ $${POETRY_VERSION_ARRAY[1]} -ge 8 ]); then \
|
||||
echo "$(BLUE)$(shell poetry --version) is already installed.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)Poetry 1.8 or later is required. You can install poetry by running the following command, then adding Poetry to your PATH:"; \
|
||||
@@ -190,7 +190,7 @@ build-frontend:
|
||||
# Start backend
|
||||
start-backend:
|
||||
@echo "$(YELLOW)Starting backend...$(RESET)"
|
||||
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "$(shell pwd)/workspace"
|
||||
@poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "./workspace"
|
||||
|
||||
# Start frontend
|
||||
start-frontend:
|
||||
|
||||
18
README.md
18
README.md
@@ -39,21 +39,21 @@ Learn more at [docs.all-hands.dev](https://docs.all-hands.dev), or jump to the [
|
||||
## ⚡ Quick Start
|
||||
|
||||
The easiest way to run OpenHands is in Docker.
|
||||
See the [Installation](https://docs.all-hands.dev/modules/usage/installation) guide for
|
||||
See the [Running OpenHands](https://docs.all-hands.dev/modules/usage/installation) guide for
|
||||
system requirements and more information.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21
|
||||
```
|
||||
|
||||
You'll find OpenHands running at [http://localhost:3000](http://localhost:3000)!
|
||||
@@ -64,16 +64,16 @@ works best, but you have [many options](https://docs.all-hands.dev/modules/usage
|
||||
|
||||
---
|
||||
|
||||
You can also [connect OpenHands to your local filesystem](https://docs.all-hands.dev/modules/usage/runtimes),
|
||||
You can also [connect OpenHands to your local filesystem](https://docs.all-hands.dev/modules/usage/runtimes#connecting-to-your-filesystem),
|
||||
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
|
||||
interact with it via a [friendly CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode),
|
||||
or run it on tagged issues with [a github action](https://github.com/All-Hands-AI/OpenHands/blob/main/openhands/resolver/README.md).
|
||||
or run it on tagged issues with [a github action](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
|
||||
Visit [Installation](https://docs.all-hands.dev/modules/usage/installation) for more information and setup instructions.
|
||||
Visit [Running OpenHands](https://docs.all-hands.dev/modules/usage/installation) for more information and setup instructions.
|
||||
|
||||
> [!CAUTION]
|
||||
> OpenHands is meant to be run by a single user on their local workstation.
|
||||
> It is not appropriate for multi-tenant deployments, where multiple users share the same instance--there is no built-in isolation or scalability.
|
||||
> It is not appropriate for multi-tenant deployments where multiple users share the same instance. There is no built-in isolation or scalability.
|
||||
>
|
||||
> If you're interested in running OpenHands in a multi-tenant environment, please
|
||||
> [get in touch with us](https://docs.google.com/forms/d/e/1FAIpQLSet3VbGaz8z32gW9Wm-Grl4jpt5WgMXPgJ4EDPVmCETCBpJtQ/viewform)
|
||||
@@ -86,7 +86,7 @@ Having issues? The [Troubleshooting Guide](https://docs.all-hands.dev/modules/us
|
||||
## 📖 Documentation
|
||||
|
||||
To learn more about the project, and for tips on using OpenHands,
|
||||
**check out our [documentation](https://docs.all-hands.dev/modules/usage/getting-started)**.
|
||||
check out our [documentation](https://docs.all-hands.dev/modules/usage/getting-started).
|
||||
|
||||
There you'll find resources on how to use different LLM providers,
|
||||
troubleshooting resources, and advanced configuration options.
|
||||
|
||||
@@ -23,6 +23,9 @@ workspace_base = "./workspace"
|
||||
# Cache directory path
|
||||
#cache_dir = "/tmp/cache"
|
||||
|
||||
# Reasoning effort for o1 models (low, medium, high, or not set)
|
||||
#reasoning_effort = "medium"
|
||||
|
||||
# Debugging enabled
|
||||
#debug = false
|
||||
|
||||
@@ -34,7 +37,12 @@ workspace_base = "./workspace"
|
||||
|
||||
# Path to store trajectories, can be a folder or a file
|
||||
# If it's a folder, the session id will be used as the file name
|
||||
#trajectories_path="./trajectories"
|
||||
#save_trajectory_path="./trajectories"
|
||||
|
||||
# Path to replay a trajectory, must be a file path
|
||||
# If provided, trajectory will be loaded and replayed before the
|
||||
# agent responds to any user instruction
|
||||
#replay_trajectory_path = ""
|
||||
|
||||
# File store path
|
||||
#file_store_path = "/tmp/file_store"
|
||||
@@ -67,7 +75,7 @@ workspace_base = "./workspace"
|
||||
#run_as_openhands = true
|
||||
|
||||
# Runtime environment
|
||||
#runtime = "eventstream"
|
||||
#runtime = "docker"
|
||||
|
||||
# Name of the default agent
|
||||
#default_agent = "CodeActAgent"
|
||||
@@ -180,6 +188,12 @@ model = "gpt-4o"
|
||||
# https://docs.litellm.ai/docs/completion/token_usage
|
||||
#custom_tokenizer = ""
|
||||
|
||||
# Whether to use native tool calling if supported by the model. Can be true, false, or None by default, which chooses the model's default behavior based on the evaluation.
|
||||
# ATTENTION: Based on evaluation, enabling native function calling may lead to worse results
|
||||
# in some scenarios. Use with caution and consider testing with your specific use case.
|
||||
# https://github.com/All-Hands-AI/OpenHands/pull/4711
|
||||
#native_tool_calling = None
|
||||
|
||||
[llm.gpt4o-mini]
|
||||
api_key = "your-api-key"
|
||||
model = "gpt-4o"
|
||||
@@ -192,6 +206,16 @@ model = "gpt-4o"
|
||||
# agent.CodeActAgent
|
||||
##############################################################################
|
||||
[agent]
|
||||
|
||||
# whether the browsing tool is enabled
|
||||
codeact_enable_browsing = true
|
||||
|
||||
# whether the LLM draft editor is enabled
|
||||
codeact_enable_llm_editor = false
|
||||
|
||||
# whether the IPython tool is enabled
|
||||
codeact_enable_jupyter = true
|
||||
|
||||
# Name of the micro agent to use for this agent
|
||||
#micro_agent_name = ""
|
||||
|
||||
@@ -204,6 +228,12 @@ model = "gpt-4o"
|
||||
# LLM config group to use
|
||||
#llm_config = 'your-llm-config-group'
|
||||
|
||||
# Whether to use prompt extension (e.g., microagent, repo/runtime info) at all
|
||||
#enable_prompt_extensions = true
|
||||
|
||||
# List of microagents to disable
|
||||
#disabled_microagents = []
|
||||
|
||||
[agent.RepoExplorerAgent]
|
||||
# Example: use a cheaper model for RepoExplorerAgent to reduce cost, especially
|
||||
# useful when an agent doesn't demand high quality but uses a lot of tokens
|
||||
|
||||
@@ -71,6 +71,7 @@ ENV VIRTUAL_ENV=/app/.venv \
|
||||
COPY --chown=openhands:app --chmod=770 --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||
RUN playwright install --with-deps chromium
|
||||
|
||||
COPY --chown=openhands:app --chmod=770 ./microagents ./microagents
|
||||
COPY --chown=openhands:app --chmod=770 ./openhands ./openhands
|
||||
COPY --chown=openhands:app --chmod=777 ./openhands/runtime/plugins ./openhands/runtime/plugins
|
||||
COPY --chown=openhands:app --chmod=770 ./openhands/agenthub ./openhands/agenthub
|
||||
|
||||
@@ -11,7 +11,7 @@ services:
|
||||
- BACKEND_HOST=${BACKEND_HOST:-"0.0.0.0"}
|
||||
- SANDBOX_API_HOSTNAME=host.docker.internal
|
||||
#
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.17-nikolaik}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.21-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#
|
||||
|
||||
services:
|
||||
openhands:
|
||||
build:
|
||||
@@ -7,8 +7,8 @@ services:
|
||||
image: openhands:latest
|
||||
container_name: openhands-app-${DATE:-}
|
||||
environment:
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-ghcr.io/all-hands-ai/runtime:0.17-nikolaik}
|
||||
- SANDBOX_USER_ID=${SANDBOX_USER_ID:-1234}
|
||||
- SANDBOX_RUNTIME_CONTAINER_IMAGE=${SANDBOX_RUNTIME_CONTAINER_IMAGE:-docker.all-hands.dev/all-hands-ai/runtime:0.21-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-state for this user
|
||||
- WORKSPACE_MOUNT_PATH=${WORKSPACE_BASE:-$PWD/workspace}
|
||||
ports:
|
||||
- "3000:3000"
|
||||
@@ -16,6 +16,7 @@ services:
|
||||
- "host.docker.internal:host-gateway"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ~/.openhands-state:/.openhands-state
|
||||
- ${WORKSPACE_BASE:-$PWD/workspace}:/opt/workspace_base
|
||||
pull_policy: build
|
||||
stdin_open: true
|
||||
48
docs/DOC_STYLE_GUIDE.md
Normal file
48
docs/DOC_STYLE_GUIDE.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Documentation Style Guide
|
||||
|
||||
## General Writing Principles
|
||||
|
||||
- **Clarity & Conciseness**: Always prioritize clarity and brevity. Avoid unnecessary jargon or overly complex explanations.
|
||||
Keep sentences short and to the point.
|
||||
- **Gradual Complexity**: Start with the simplest, most basic setup, and then gradually introduce more advanced
|
||||
concepts and configurations.
|
||||
|
||||
## Formatting Guidelines
|
||||
|
||||
### Headers
|
||||
|
||||
Use **Title Case** for the first and second level headers.
|
||||
|
||||
Example:
|
||||
- **Basic Usage**
|
||||
- **Advanced Configuration Options**
|
||||
|
||||
### Lists
|
||||
|
||||
When listing items or options, use bullet points to enhance readability.
|
||||
|
||||
Example:
|
||||
- Option A
|
||||
- Option B
|
||||
- Option C
|
||||
|
||||
### Procedures
|
||||
|
||||
For instructions or processes that need to be followed in a specific order, use numbered steps.
|
||||
|
||||
Example:
|
||||
1. Step one: Do this.
|
||||
2. Step two: Complete this action.
|
||||
3. Step three: Verify the result.
|
||||
|
||||
### Code Blocks
|
||||
|
||||
* Use code blocks for multi-line inputs, outputs, commands and code samples.
|
||||
|
||||
Example:
|
||||
```bash
|
||||
docker run -it \
|
||||
-e THIS=this \
|
||||
-e THAT=that
|
||||
...
|
||||
```
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
# Options de configuration
|
||||
|
||||
Ce guide détaille toutes les options de configuration disponibles pour OpenHands, vous aidant à personnaliser son comportement et à l'intégrer avec d'autres services.
|
||||
@@ -94,7 +92,7 @@ Les options de configuration de base sont définies dans la section `[core]` du
|
||||
- Description : Désactiver la couleur dans la sortie du terminal
|
||||
|
||||
**Trajectoires**
|
||||
- `trajectories_path`
|
||||
- `save_trajectory_path`
|
||||
- Type : `str`
|
||||
- Valeur par défaut : `"./trajectories"`
|
||||
- Description : Chemin pour stocker les trajectoires (peut être un dossier ou un fichier). Si c'est un dossier, les trajectoires seront enregistrées dans un fichier nommé avec l'ID de session et l'extension .json, dans ce dossier.
|
||||
@@ -184,6 +182,10 @@ Les options de configuration LLM (Large Language Model) sont définies dans la s
|
||||
|
||||
Pour les utiliser avec la commande docker, passez `-e LLM_<option>`. Exemple : `-e LLM_NUM_RETRIES`.
|
||||
|
||||
:::note
|
||||
Pour les configurations de développement, vous pouvez également définir des configurations LLM personnalisées. Voir [Configurations LLM personnalisées](./llms/custom-llm-configs) pour plus de détails.
|
||||
:::
|
||||
|
||||
**Informations d'identification AWS**
|
||||
- `aws_access_key_id`
|
||||
- Type : `str`
|
||||
@@ -368,4 +370,26 @@ Les options de configuration de l'agent sont définies dans les sections `[agent
|
||||
- `codeact_enable_llm_editor`
|
||||
- Type : `bool`
|
||||
- Valeur par défaut : `false`
|
||||
- Description : Si l'éditeur LLM est activé dans l'espace d'action (foncti
|
||||
- Description : Si l'éditeur LLM est activé dans l'espace d'action (fonctionne uniquement avec l'appel de fonction)
|
||||
|
||||
**Utilisation du micro-agent**
|
||||
- `enable_prompt_extensions`
|
||||
- Type : `bool`
|
||||
- Valeur par défaut : `true`
|
||||
- Description : Indique si l'utilisation des micro-agents est activée ou non
|
||||
|
||||
- `disabled_microagents`
|
||||
- Type : `list of str`
|
||||
- Valeur par défaut : `None`
|
||||
- Description : Liste des micro-agents à désactiver
|
||||
|
||||
### Exécution
|
||||
- `timeout`
|
||||
- Type : `int`
|
||||
- Valeur par défaut : `120`
|
||||
- Description : Délai d'expiration du bac à sable, en secondes
|
||||
|
||||
- `user_id`
|
||||
- Type : `int`
|
||||
- Valeur par défaut : `1000`
|
||||
- Description : ID de l'utilisateur du bac à sable
|
||||
|
||||
@@ -52,7 +52,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -61,7 +61,7 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -56,6 +56,6 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
```
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
La façon la plus simple d'exécuter OpenHands est avec Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter OpenHands en mode [headless scriptable](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), en tant que [CLI interactive](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), ou en utilisant l'[Action GitHub OpenHands](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# Configurations LLM personnalisées
|
||||
|
||||
OpenHands permet de définir plusieurs configurations LLM nommées dans votre fichier `config.toml`. Cette fonctionnalité vous permet d'utiliser différentes configurations LLM pour différents usages, comme utiliser un modèle moins coûteux pour les tâches qui ne nécessitent pas de réponses de haute qualité, ou utiliser différents modèles avec différents paramètres pour des agents spécifiques.
|
||||
|
||||
## Comment ça fonctionne
|
||||
|
||||
Les configurations LLM nommées sont définies dans le fichier `config.toml` en utilisant des sections qui commencent par `llm.`. Par exemple :
|
||||
|
||||
```toml
|
||||
# Configuration LLM par défaut
|
||||
[llm]
|
||||
model = "gpt-4"
|
||||
api_key = "votre-clé-api"
|
||||
temperature = 0.0
|
||||
|
||||
# Configuration LLM personnalisée pour un modèle moins coûteux
|
||||
[llm.gpt3]
|
||||
model = "gpt-3.5-turbo"
|
||||
api_key = "votre-clé-api"
|
||||
temperature = 0.2
|
||||
|
||||
# Une autre configuration personnalisée avec des paramètres différents
|
||||
[llm.haute-creativite]
|
||||
model = "gpt-4"
|
||||
api_key = "votre-clé-api"
|
||||
temperature = 0.8
|
||||
top_p = 0.9
|
||||
```
|
||||
|
||||
Chaque configuration nommée hérite de tous les paramètres de la section `[llm]` par défaut et peut remplacer n'importe lequel de ces paramètres. Vous pouvez définir autant de configurations personnalisées que nécessaire.
|
||||
|
||||
## Utilisation des configurations personnalisées
|
||||
|
||||
### Avec les agents
|
||||
|
||||
Vous pouvez spécifier quelle configuration LLM un agent doit utiliser en définissant le paramètre `llm_config` dans la section de configuration de l'agent :
|
||||
|
||||
```toml
|
||||
[agent.RepoExplorerAgent]
|
||||
# Utiliser la configuration GPT-3 moins coûteuse pour cet agent
|
||||
llm_config = 'gpt3'
|
||||
|
||||
[agent.CodeWriterAgent]
|
||||
# Utiliser la configuration haute créativité pour cet agent
|
||||
llm_config = 'haute-creativite'
|
||||
```
|
||||
|
||||
### Options de configuration
|
||||
|
||||
Chaque configuration LLM nommée prend en charge toutes les mêmes options que la configuration LLM par défaut. Celles-ci incluent :
|
||||
|
||||
- Sélection du modèle (`model`)
|
||||
- Configuration de l'API (`api_key`, `base_url`, etc.)
|
||||
- Paramètres du modèle (`temperature`, `top_p`, etc.)
|
||||
- Paramètres de nouvelle tentative (`num_retries`, `retry_multiplier`, etc.)
|
||||
- Limites de jetons (`max_input_tokens`, `max_output_tokens`)
|
||||
- Et toutes les autres options de configuration LLM
|
||||
|
||||
Pour une liste complète des options disponibles, consultez la section Configuration LLM dans la documentation des [Options de configuration](../configuration-options).
|
||||
|
||||
## Cas d'utilisation
|
||||
|
||||
Les configurations LLM personnalisées sont particulièrement utiles dans plusieurs scénarios :
|
||||
|
||||
- **Optimisation des coûts** : Utiliser des modèles moins coûteux pour les tâches qui ne nécessitent pas de réponses de haute qualité, comme l'exploration de dépôt ou les opérations simples sur les fichiers.
|
||||
- **Réglage spécifique aux tâches** : Configurer différentes valeurs de température et de top_p pour les tâches qui nécessitent différents niveaux de créativité ou de déterminisme.
|
||||
- **Différents fournisseurs** : Utiliser différents fournisseurs LLM ou points d'accès API pour différentes tâches.
|
||||
- **Tests et développement** : Basculer facilement entre différentes configurations de modèles pendant le développement et les tests.
|
||||
|
||||
## Exemple : Optimisation des coûts
|
||||
|
||||
Un exemple pratique d'utilisation des configurations LLM personnalisées pour optimiser les coûts :
|
||||
|
||||
```toml
|
||||
# Configuration par défaut utilisant GPT-4 pour des réponses de haute qualité
|
||||
[llm]
|
||||
model = "gpt-4"
|
||||
api_key = "votre-clé-api"
|
||||
temperature = 0.0
|
||||
|
||||
# Configuration moins coûteuse pour l'exploration de dépôt
|
||||
[llm.repo-explorer]
|
||||
model = "gpt-3.5-turbo"
|
||||
temperature = 0.2
|
||||
|
||||
# Configuration pour la génération de code
|
||||
[llm.code-gen]
|
||||
model = "gpt-4"
|
||||
temperature = 0.0
|
||||
max_output_tokens = 2000
|
||||
|
||||
[agent.RepoExplorerAgent]
|
||||
llm_config = 'repo-explorer'
|
||||
|
||||
[agent.CodeWriterAgent]
|
||||
llm_config = 'code-gen'
|
||||
```
|
||||
|
||||
Dans cet exemple :
|
||||
- L'exploration de dépôt utilise un modèle moins coûteux car il s'agit principalement de comprendre et de naviguer dans le code
|
||||
- La génération de code utilise GPT-4 avec une limite de jetons plus élevée pour générer des blocs de code plus importants
|
||||
- La configuration par défaut reste disponible pour les autres tâches
|
||||
|
||||
:::note
|
||||
Les configurations LLM personnalisées ne sont disponibles que lors de l'utilisation d'OpenHands en mode développement, via `main.py` ou `cli.py`. Lors de l'exécution via `docker run`, veuillez utiliser les options de configuration standard.
|
||||
:::
|
||||
@@ -13,7 +13,7 @@ C'est le Runtime par défaut qui est utilisé lorsque vous démarrez OpenHands.
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
- 描述: 禁用终端输出中的颜色
|
||||
|
||||
**轨迹**
|
||||
- `trajectories_path`
|
||||
- `save_trajectory_path`
|
||||
- 类型: `str`
|
||||
- 默认值: `"./trajectories"`
|
||||
- 描述: 存储轨迹的路径(可以是文件夹或文件)。如果是文件夹,轨迹将保存在该文件夹中以会话 ID 命名的 .json 文件中。
|
||||
@@ -373,7 +373,7 @@ Agent 配置选项在 `config.toml` 文件的 `[agent]` 和 `[agent.<agent_name>
|
||||
- 描述: 是否在 action space 中启用 Jupyter
|
||||
|
||||
**Microagent 使用**
|
||||
- `use_microagents`
|
||||
- `enable_prompt_extensions`
|
||||
- 类型: `bool`
|
||||
- 默认值: `true`
|
||||
- 描述: 是否使用 microagents
|
||||
|
||||
@@ -50,7 +50,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -59,7 +59,7 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ LLM_API_KEY="sk_test_12345"
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -57,6 +57,6 @@ docker run -it \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app-$(date +%Y%m%d%H%M%S) \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
```
|
||||
|
||||
@@ -11,16 +11,16 @@
|
||||
在 Docker 中运行 OpenHands 是最简单的方式。
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21
|
||||
```
|
||||
|
||||
你也可以在可脚本化的[无头模式](https://docs.all-hands.dev/modules/usage/how-to/headless-mode)下运行 OpenHands,作为[交互式 CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode),或使用 [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action)。
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
|
||||
Achieving full replication of production-grade applications with LLMs is a complex endeavor. Our strategy involves:
|
||||
|
||||
1. **Core Technical Research:** Focusing on foundational research to understand and improve the technical aspects of code generation and handling
|
||||
2. **Specialist Abilities:** Enhancing the effectiveness of core components through data curation, training methods, and more
|
||||
3. **Task Planning:** Developing capabilities for bug detection, codebase management, and optimization
|
||||
4. **Evaluation:** Establishing comprehensive evaluation metrics to better understand and improve our models
|
||||
- **Core Technical Research:** Focusing on foundational research to understand and improve the technical aspects of code generation and handling.
|
||||
- **Task Planning:** Developing capabilities for bug detection, codebase management, and optimization.
|
||||
- **Evaluation:** Establishing comprehensive evaluation metrics to better understand and improve our agents.
|
||||
|
||||
## Default Agent
|
||||
|
||||
@@ -15,11 +14,14 @@ Our default Agent is currently the [CodeActAgent](agents), which is capable of g
|
||||
|
||||
## Built With
|
||||
|
||||
OpenHands is built using a combination of powerful frameworks and libraries, providing a robust foundation for its development. Here are the key technologies used in the project:
|
||||
OpenHands is built using a combination of powerful frameworks and libraries, providing a robust foundation for its
|
||||
development. Here are the key technologies used in the project:
|
||||
|
||||
       
|
||||
|
||||
Please note that the selection of these technologies is in progress, and additional technologies may be added or existing ones may be removed as the project evolves. We strive to adopt the most suitable and efficient tools to enhance the capabilities of OpenHands.
|
||||
Please note that the selection of these technologies is in progress, and additional technologies may be added or
|
||||
existing ones may be removed as the project evolves. We strive to adopt the most suitable and efficient tools to
|
||||
enhance the capabilities of OpenHands.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -7,53 +7,11 @@ If you are running in [GUI Mode](https://docs.all-hands.dev/modules/usage/how-to
|
||||
take precedence.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
# Table of Contents
|
||||
|
||||
1. [Core Configuration](#core-configuration)
|
||||
- [API Keys](#api-keys)
|
||||
- [Workspace](#workspace)
|
||||
- [Debugging and Logging](#debugging-and-logging)
|
||||
- [Session Management](#session-management)
|
||||
- [Trajectories](#trajectories)
|
||||
- [File Store](#file-store)
|
||||
- [Task Management](#task-management)
|
||||
- [Sandbox Configuration](#sandbox-configuration)
|
||||
- [Miscellaneous](#miscellaneous)
|
||||
2. [LLM Configuration](#llm-configuration)
|
||||
- [AWS Credentials](#aws-credentials)
|
||||
- [API Configuration](#api-configuration)
|
||||
- [Custom LLM Provider](#custom-llm-provider)
|
||||
- [Embeddings](#embeddings)
|
||||
- [Message Handling](#message-handling)
|
||||
- [Model Selection](#model-selection)
|
||||
- [Retrying](#retrying)
|
||||
- [Advanced Options](#advanced-options)
|
||||
3. [Agent Configuration](#agent-configuration)
|
||||
- [Microagent Configuration](#microagent-configuration)
|
||||
- [Memory Configuration](#memory-configuration)
|
||||
- [LLM Configuration](#llm-configuration-2)
|
||||
- [ActionSpace Configuration](#actionspace-configuration)
|
||||
- [Microagent Usage](#microagent-usage)
|
||||
4. [Sandbox Configuration](#sandbox-configuration-2)
|
||||
- [Execution](#execution)
|
||||
- [Container Image](#container-image)
|
||||
- [Networking](#networking)
|
||||
- [Linting and Plugins](#linting-and-plugins)
|
||||
- [Dependencies and Environment](#dependencies-and-environment)
|
||||
- [Evaluation](#evaluation)
|
||||
5. [Security Configuration](#security-configuration)
|
||||
- [Confirmation Mode](#confirmation-mode)
|
||||
- [Security Analyzer](#security-analyzer)
|
||||
|
||||
---
|
||||
|
||||
## Core Configuration
|
||||
|
||||
The core configuration options are defined in the `[core]` section of the `config.toml` file.
|
||||
|
||||
**API Keys**
|
||||
### API Keys
|
||||
- `e2b_api_key`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
@@ -69,7 +27,7 @@ The core configuration options are defined in the `[core]` section of the `confi
|
||||
- Default: `""`
|
||||
- Description: API token secret for Modal
|
||||
|
||||
**Workspace**
|
||||
### Workspace
|
||||
- `workspace_base`
|
||||
- Type: `str`
|
||||
- Default: `"./workspace"`
|
||||
@@ -80,7 +38,7 @@ The core configuration options are defined in the `[core]` section of the `confi
|
||||
- Default: `"/tmp/cache"`
|
||||
- Description: Cache directory path
|
||||
|
||||
**Debugging and Logging**
|
||||
### Debugging and Logging
|
||||
- `debug`
|
||||
- Type: `bool`
|
||||
- Default: `false`
|
||||
@@ -91,13 +49,18 @@ The core configuration options are defined in the `[core]` section of the `confi
|
||||
- Default: `false`
|
||||
- Description: Disable color in terminal output
|
||||
|
||||
**Trajectories**
|
||||
- `trajectories_path`
|
||||
### Trajectories
|
||||
- `save_trajectory_path`
|
||||
- Type: `str`
|
||||
- Default: `"./trajectories"`
|
||||
- Description: Path to store trajectories (can be a folder or a file). If it's a folder, the trajectories will be saved in a file named with the session id name and .json extension, in that folder.
|
||||
|
||||
**File Store**
|
||||
- `replay_trajectory_path`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
- Description: Path to load a trajectory and replay. If given, must be a path to the trajectory file in JSON format. The actions in the trajectory file would be replayed first before any user instruction is executed.
|
||||
|
||||
### File Store
|
||||
- `file_store_path`
|
||||
- Type: `str`
|
||||
- Default: `"/tmp/file_store"`
|
||||
@@ -128,7 +91,7 @@ The core configuration options are defined in the `[core]` section of the `confi
|
||||
- Default: `[".*"]`
|
||||
- Description: List of allowed file extensions for uploads
|
||||
|
||||
**Task Management**
|
||||
### Task Management
|
||||
- `max_budget_per_task`
|
||||
- Type: `float`
|
||||
- Default: `0.0`
|
||||
@@ -139,7 +102,7 @@ The core configuration options are defined in the `[core]` section of the `confi
|
||||
- Default: `100`
|
||||
- Description: Maximum number of iterations
|
||||
|
||||
**Sandbox Configuration**
|
||||
### Sandbox Configuration
|
||||
- `workspace_mount_path_in_sandbox`
|
||||
- Type: `str`
|
||||
- Default: `"/workspace"`
|
||||
@@ -155,7 +118,7 @@ The core configuration options are defined in the `[core]` section of the `confi
|
||||
- Default: `""`
|
||||
- Description: Path to rewrite the workspace mount path to. You can usually ignore this, it refers to special cases of running inside another container.
|
||||
|
||||
**Miscellaneous**
|
||||
### Miscellaneous
|
||||
- `run_as_openhands`
|
||||
- Type: `bool`
|
||||
- Default: `true`
|
||||
@@ -182,6 +145,10 @@ The LLM (Large Language Model) configuration options are defined in the `[llm]`
|
||||
|
||||
To use these with the docker command, pass in `-e LLM_<option>`. Example: `-e LLM_NUM_RETRIES`.
|
||||
|
||||
:::note
|
||||
For development setups, you can also define custom named LLM configurations. See [Custom LLM Configurations](./llms/custom-llm-configs) for details.
|
||||
:::
|
||||
|
||||
**AWS Credentials**
|
||||
- `aws_access_key_id`
|
||||
- Type: `str`
|
||||
@@ -198,7 +165,7 @@ To use these with the docker command, pass in `-e LLM_<option>`. Example: `-e LL
|
||||
- Default: `""`
|
||||
- Description: AWS secret access key
|
||||
|
||||
**API Configuration**
|
||||
### API Configuration
|
||||
- `api_key`
|
||||
- Type: `str`
|
||||
- Default: `None`
|
||||
@@ -224,13 +191,13 @@ To use these with the docker command, pass in `-e LLM_<option>`. Example: `-e LL
|
||||
- Default: `0.0`
|
||||
- Description: Cost per output token
|
||||
|
||||
**Custom LLM Provider**
|
||||
### Custom LLM Provider
|
||||
- `custom_llm_provider`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
- Description: Custom LLM provider
|
||||
|
||||
**Embeddings**
|
||||
### Embeddings
|
||||
- `embedding_base_url`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
@@ -246,7 +213,7 @@ To use these with the docker command, pass in `-e LLM_<option>`. Example: `-e LL
|
||||
- Default: `"local"`
|
||||
- Description: Embedding model to use
|
||||
|
||||
**Message Handling**
|
||||
### Message Handling
|
||||
- `max_message_chars`
|
||||
- Type: `int`
|
||||
- Default: `30000`
|
||||
@@ -262,13 +229,13 @@ To use these with the docker command, pass in `-e LLM_<option>`. Example: `-e LL
|
||||
- Default: `0`
|
||||
- Description: Maximum number of output tokens
|
||||
|
||||
**Model Selection**
|
||||
### Model Selection
|
||||
- `model`
|
||||
- Type: `str`
|
||||
- Default: `"claude-3-5-sonnet-20241022"`
|
||||
- Description: Model to use
|
||||
|
||||
**Retrying**
|
||||
### Retrying
|
||||
- `num_retries`
|
||||
- Type: `int`
|
||||
- Default: `8`
|
||||
@@ -289,7 +256,7 @@ To use these with the docker command, pass in `-e LLM_<option>`. Example: `-e LL
|
||||
- Default: `2.0`
|
||||
- Description: Multiplier for exponential backoff calculation
|
||||
|
||||
**Advanced Options**
|
||||
### Advanced Options
|
||||
- `drop_params`
|
||||
- Type: `bool`
|
||||
- Default: `false`
|
||||
@@ -329,13 +296,13 @@ To use these with the docker command, pass in `-e LLM_<option>`. Example: `-e LL
|
||||
|
||||
The agent configuration options are defined in the `[agent]` and `[agent.<agent_name>]` sections of the `config.toml` file.
|
||||
|
||||
**Microagent Configuration**
|
||||
### Microagent Configuration
|
||||
- `micro_agent_name`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
- Description: Name of the micro agent to use for this agent
|
||||
|
||||
**Memory Configuration**
|
||||
### Memory Configuration
|
||||
- `memory_enabled`
|
||||
- Type: `bool`
|
||||
- Default: `false`
|
||||
@@ -346,13 +313,13 @@ The agent configuration options are defined in the `[agent]` and `[agent.<agent_
|
||||
- Default: `3`
|
||||
- Description: The maximum number of threads indexing at the same time for embeddings
|
||||
|
||||
**LLM Configuration**
|
||||
### LLM Configuration
|
||||
- `llm_config`
|
||||
- Type: `str`
|
||||
- Default: `'your-llm-config-group'`
|
||||
- Description: The name of the LLM config to use
|
||||
|
||||
**ActionSpace Configuration**
|
||||
### ActionSpace Configuration
|
||||
- `function_calling`
|
||||
- Type: `bool`
|
||||
- Default: `true`
|
||||
@@ -373,8 +340,8 @@ The agent configuration options are defined in the `[agent]` and `[agent.<agent_
|
||||
- Default: `false`
|
||||
- Description: Whether Jupyter is enabled in the action space
|
||||
|
||||
**Microagent Usage**
|
||||
- `use_microagents`
|
||||
### Microagent Usage
|
||||
- `enable_prompt_extensions`
|
||||
- Type: `bool`
|
||||
- Default: `true`
|
||||
- Description: Whether to use microagents at all
|
||||
@@ -390,7 +357,7 @@ The sandbox configuration options are defined in the `[sandbox]` section of the
|
||||
|
||||
To use these with the docker command, pass in `-e SANDBOX_<option>`. Example: `-e SANDBOX_TIMEOUT`.
|
||||
|
||||
**Execution**
|
||||
### Execution
|
||||
- `timeout`
|
||||
- Type: `int`
|
||||
- Default: `120`
|
||||
@@ -401,19 +368,19 @@ To use these with the docker command, pass in `-e SANDBOX_<option>`. Example: `-
|
||||
- Default: `1000`
|
||||
- Description: Sandbox user ID
|
||||
|
||||
**Container Image**
|
||||
### Container Image
|
||||
- `base_container_image`
|
||||
- Type: `str`
|
||||
- Default: `"nikolaik/python-nodejs:python3.12-nodejs22"`
|
||||
- Description: Container image to use for the sandbox
|
||||
|
||||
**Networking**
|
||||
### Networking
|
||||
- `use_host_network`
|
||||
- Type: `bool`
|
||||
- Default: `false`
|
||||
- Description: Use host network
|
||||
|
||||
**Linting and Plugins**
|
||||
### Linting and Plugins
|
||||
- `enable_auto_lint`
|
||||
- Type: `bool`
|
||||
- Default: `false`
|
||||
@@ -424,7 +391,7 @@ To use these with the docker command, pass in `-e SANDBOX_<option>`. Example: `-
|
||||
- Default: `true`
|
||||
- Description: Whether to initialize plugins
|
||||
|
||||
**Dependencies and Environment**
|
||||
### Dependencies and Environment
|
||||
- `runtime_extra_deps`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
@@ -435,7 +402,7 @@ To use these with the docker command, pass in `-e SANDBOX_<option>`. Example: `-
|
||||
- Default: `{}`
|
||||
- Description: Environment variables to set at the launch of the runtime
|
||||
|
||||
**Evaluation**
|
||||
### Evaluation
|
||||
- `browsergym_eval_env`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
@@ -447,13 +414,13 @@ The security configuration options are defined in the `[security]` section of th
|
||||
|
||||
To use these with the docker command, pass in `-e SECURITY_<option>`. Example: `-e SECURITY_CONFIRMATION_MODE`.
|
||||
|
||||
**Confirmation Mode**
|
||||
### Confirmation Mode
|
||||
- `confirmation_mode`
|
||||
- Type: `bool`
|
||||
- Default: `false`
|
||||
- Description: Enable confirmation mode
|
||||
|
||||
**Security Analyzer**
|
||||
### Security Analyzer
|
||||
- `security_analyzer`
|
||||
- Type: `str`
|
||||
- Default: `""`
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# ✅ Providing Feedback
|
||||
|
||||
When using OpenHands, you will encounter cases where things work well, and others where they don't. We encourage you to provide feedback when you use OpenHands to help give feedback to the development team, and perhaps more importantly, create an open corpus of coding agent training examples -- Share-OpenHands!
|
||||
When using OpenHands, you will encounter cases where things work well, and others where they don't. We encourage you to
|
||||
provide feedback when you use OpenHands to help give feedback to the development team, and perhaps more importantly,
|
||||
create an open corpus of coding agent training examples -- Share-OpenHands!
|
||||
|
||||
## 📝 How to Provide Feedback
|
||||
|
||||
Providing feedback is easy! When you are using OpenHands, you can press the thumbs-up or thumbs-down button at any point during your interaction. You will be prompted to provide your email address (e.g. so we can contact you if we want to ask any follow-up questions), and you can choose whether you want to provide feedback publicly or privately.
|
||||
Providing feedback is easy! When you are using OpenHands, you can press the thumbs-up or thumbs-down button at any point
|
||||
during your interaction. You will be prompted to provide your email address
|
||||
(e.g. so we can contact you if we want to ask any follow-up questions), and you can choose whether you want to provide feedback publicly or privately.
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/5rFx-StMVV0?si=svo7xzp6LhGK_GXr" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
||||
|
||||
@@ -14,8 +18,11 @@ Providing feedback is easy! When you are using OpenHands, you can press the thum
|
||||
|
||||
When you submit data, you can submit it either publicly or privately.
|
||||
|
||||
* **Public** data will be distributed under the MIT License, like OpenHands itself, and can be used by the community to train and test models. Obviously, feedback that you can make public will be more valuable for the community as a whole, so when you are not dealing with sensitive information, we would encourage you to choose this option!
|
||||
* **Private** data will only be shared with the OpenHands team for the purpose of improving OpenHands.
|
||||
- **Public** data will be distributed under the MIT License, like OpenHands itself, and can be used by the community to
|
||||
train and test models. Obviously, feedback that you can make public will be more valuable for the community as a whole,
|
||||
so when you are not dealing with sensitive information, we would encourage you to choose this option!
|
||||
- **Private** data will be made available to the OpenHands team for the purpose of improving OpenHands.
|
||||
However, a link with a unique ID will still be created that you can share publicly with others.
|
||||
|
||||
### Who collects and stores the data?
|
||||
|
||||
@@ -27,13 +34,17 @@ The public data will be released when we hit fixed milestones, such as 1,000 pub
|
||||
At this time, we will follow the following release process:
|
||||
|
||||
1. All people who contributed public feedback will receive an email describing the data release and being given an opportunity to opt out.
|
||||
2. The person or people in charge of the data release will perform quality control of the data, removing low-quality feedback, removing email submitter email addresses, and attempting to remove any sensitive information.
|
||||
2. The person or people in charge of the data release will perform quality control of the data, removing low-quality feedback,
|
||||
removing email submitter email addresses, and attempting to remove any sensitive information.
|
||||
3. The data will be released publicly under the MIT license through commonly used sites such as github or Hugging Face.
|
||||
|
||||
### What if I want my data deleted?
|
||||
|
||||
For data on the All Hands AI servers, we are happy to delete it at request:
|
||||
|
||||
**One Piece of Data:** If you want one piece of data deleted, we will shortly be adding a mechanism to delete pieces of data using the link and password that is displayed on the interface when you submit data.
|
||||
**One Piece of Data:** If you want one piece of data deleted, we will shortly be adding a mechanism to delete pieces of
|
||||
data using the link and password that is displayed on the interface when you submit data.
|
||||
|
||||
**All Data:** If you would like all pieces of your data deleted, or you do not have the ID and password that you received when submitting the data, please contact `contact@all-hands.dev` from the email address that you registered when you originally submitted the data.
|
||||
**All Data:** If you would like all pieces of your data deleted, or you do not have the ID and password that you
|
||||
received when submitting the data, please contact `contact@all-hands.dev` from the email address that you registered
|
||||
when you originally submitted the data.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Getting Started with OpenHands
|
||||
|
||||
So you've [installed OpenHands](./installation) and have
|
||||
So you've [run OpenHands](./installation) and have
|
||||
[set up your LLM](./installation#setup). Now what?
|
||||
|
||||
OpenHands can help you tackle a wide variety of engineering tasks. But the technology
|
||||
@@ -44,7 +44,7 @@ For example, we might build a TODO app:
|
||||
|
||||
We can keep iterating on the app once the skeleton is there:
|
||||
|
||||
> Please allow adding an optional due date to every task
|
||||
> Please allow adding an optional due date to every task.
|
||||
|
||||
Just like with normal development, it's good to commit and push your code frequently.
|
||||
This way you can always revert back to an old state if the agent goes off track.
|
||||
@@ -59,15 +59,15 @@ OpenHands can also do a great job adding new code to an existing code base.
|
||||
|
||||
For example, you can ask OpenHands to add a new GitHub action to your project
|
||||
which lints your code. OpenHands may take a peek at your codebase to see what language
|
||||
it should use, but then it can just drop a new file into `./github/workflows/lint.yml`
|
||||
it should use and then drop a new file into `./github/workflows/lint.yml`.
|
||||
|
||||
> Please add a GitHub action that lints the code in this repository
|
||||
> Please add a GitHub action that lints the code in this repository.
|
||||
|
||||
Some tasks might require a bit more context. While OpenHands can use `ls` and `grep`
|
||||
to search through your codebase, providing context up front allows it to move faster,
|
||||
and more accurately. And it'll cost you fewer tokens!
|
||||
|
||||
> Please modify ./backend/api/routes.js to add a new route that returns a list of all tasks
|
||||
> Please modify ./backend/api/routes.js to add a new route that returns a list of all tasks.
|
||||
|
||||
> Please add a new React component that displays a list of Widgets to the ./frontend/components
|
||||
> directory. It should use the existing Widget component.
|
||||
@@ -78,15 +78,15 @@ OpenHands does great at refactoring existing code, especially in small chunks.
|
||||
You probably don't want to try rearchitecting your whole codebase, but breaking up
|
||||
long files and functions, renaming variables, etc. tend to work very well.
|
||||
|
||||
> Please rename all the single-letter variables in ./app.go
|
||||
> Please rename all the single-letter variables in ./app.go.
|
||||
|
||||
> Please break the function `build_and_deploy_widgets` into two functions, `build_widgets` and `deploy_widgets` in widget.php
|
||||
> Please break the function `build_and_deploy_widgets` into two functions, `build_widgets` and `deploy_widgets` in widget.php.
|
||||
|
||||
> Please break ./api/routes.js into separate files for each route
|
||||
> Please break ./api/routes.js into separate files for each route.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
OpenHands can also help you track down and fix bugs in your code. But, as any
|
||||
OpenHands can also help you track down and fix bugs in your code. But as any
|
||||
developer knows, bug fixing can be extremely tricky, and often OpenHands will need more context.
|
||||
It helps if you've diagnosed the bug, but want OpenHands to figure out the logic.
|
||||
|
||||
@@ -94,18 +94,18 @@ It helps if you've diagnosed the bug, but want OpenHands to figure out the logic
|
||||
|
||||
> The `search_widgets` function in ./app.py is doing a case-sensitive search. Please make it case-insensitive.
|
||||
|
||||
It often helps to do test-driven development when bugfixing with an agent.
|
||||
It often helps to do test-driven development when bug fixing with an agent.
|
||||
You can ask the agent to write a new test, and then iterate until it fixes the bug:
|
||||
|
||||
> The `hello` function crashes on the empty string. Please write a test that reproduces this bug, then fix the code so it passes.
|
||||
|
||||
## More
|
||||
|
||||
OpenHands is capable of helping out on just about any coding task. But it takes some practice
|
||||
OpenHands is capable of helping out on just about any coding task but it takes some practice
|
||||
to get the most out of it. Remember to:
|
||||
* Keep your tasks small
|
||||
* Be as specific as possible
|
||||
* Provide as much context as possible
|
||||
* Commit and push frequently
|
||||
* Keep your tasks small.
|
||||
* Be as specific as possible.
|
||||
* Provide as much context as possible.
|
||||
* Commit and push frequently.
|
||||
|
||||
See [Prompting Best Practices](./prompting/prompting-best-practices) for more tips on how to get the most out of OpenHands.
|
||||
|
||||
@@ -6,10 +6,9 @@ This mode is different from the [headless mode](headless-mode), which is non-int
|
||||
|
||||
## With Python
|
||||
|
||||
To start an interactive OpenHands session via the command line, follow these steps:
|
||||
To start an interactive OpenHands session via the command line:
|
||||
|
||||
1. Ensure you have followed the [Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
|
||||
2. Run the following command:
|
||||
|
||||
```bash
|
||||
@@ -21,45 +20,32 @@ This command will start an interactive session where you can input tasks and rec
|
||||
You'll need to be sure to set your model, API key, and other settings via environment variables
|
||||
[or the `config.toml` file](https://github.com/All-Hands-AI/OpenHands/blob/main/config.template.toml).
|
||||
|
||||
|
||||
## With Docker
|
||||
|
||||
To run OpenHands in CLI mode with Docker, follow these steps:
|
||||
To run OpenHands in CLI mode with Docker:
|
||||
|
||||
1. Set `WORKSPACE_BASE` to the directory you want OpenHands to edit:
|
||||
1. Set the following environmental variables in your terminal:
|
||||
|
||||
```bash
|
||||
WORKSPACE_BASE=$(pwd)/workspace
|
||||
```
|
||||
- `WORKSPACE_BASE` to the directory you want OpenHands to edit (Ex: `export WORKSPACE_BASE=$(pwd)/workspace`).
|
||||
- `LLM_MODEL` to the model to use (Ex: `export LLM_MODEL="anthropic/claude-3-5-sonnet-20241022"`).
|
||||
- `LLM_API_KEY` to the API key (Ex: `export LLM_API_KEY="sk_test_12345"`).
|
||||
|
||||
2. Set `LLM_MODEL` to the model you want to use:
|
||||
|
||||
```bash
|
||||
LLM_MODEL="anthropic/claude-3-5-sonnet-20241022"
|
||||
|
||||
```
|
||||
|
||||
3. Set `LLM_API_KEY` to your API key:
|
||||
|
||||
```bash
|
||||
LLM_API_KEY="sk_test_12345"
|
||||
```
|
||||
|
||||
4. Run the following Docker command:
|
||||
2. Run the following Docker command:
|
||||
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
-e LLM_MODEL=$LLM_MODEL \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--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.17 \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21 \
|
||||
python -m openhands.core.cli
|
||||
```
|
||||
|
||||
@@ -72,7 +58,7 @@ Here are some examples of CLI commands and their expected outputs:
|
||||
### Example 1: Simple Task
|
||||
|
||||
```bash
|
||||
How can I help? >> Write a Python script that prints "Hello, World!"
|
||||
>> Write a Python script that prints "Hello, World!"
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
@@ -86,7 +72,7 @@ Expected Output:
|
||||
### Example 2: Bash Command
|
||||
|
||||
```bash
|
||||
How can I help? >> Create a directory named "test_dir"
|
||||
>> Create a directory named "test_dir"
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
@@ -100,7 +86,7 @@ Expected Output:
|
||||
### Example 3: Error Handling
|
||||
|
||||
```bash
|
||||
How can I help? >> Delete a non-existent file
|
||||
>> Delete a non-existent file
|
||||
```
|
||||
|
||||
Expected Output:
|
||||
|
||||
@@ -9,8 +9,8 @@ as python and Node.js but may need other software installed by default.
|
||||
|
||||
You have two options for customization:
|
||||
|
||||
1. Use an existing image with the required software.
|
||||
2. Create your own custom Docker image.
|
||||
- Use an existing image with the required software.
|
||||
- Create your own custom Docker image.
|
||||
|
||||
If you choose the first option, you can skip the `Create Your Docker Image` section.
|
||||
|
||||
@@ -18,15 +18,21 @@ If you choose the first option, you can skip the `Create Your Docker Image` sect
|
||||
|
||||
To create a custom Docker image, it must be Debian based.
|
||||
|
||||
For example, if you want OpenHands to have `ruby` installed, create a `Dockerfile` with the following content:
|
||||
For example, if you want OpenHands to have `ruby` installed, you could create a `Dockerfile` with the following content:
|
||||
|
||||
```dockerfile
|
||||
FROM debian:latest
|
||||
FROM nikolaik/python-nodejs:python3.12-nodejs22
|
||||
|
||||
# Install required packages
|
||||
RUN apt-get update && apt-get install -y ruby
|
||||
```
|
||||
|
||||
Or you could use a Ruby-specific base image:
|
||||
|
||||
```dockerfile
|
||||
FROM ruby:latest
|
||||
```
|
||||
|
||||
Save this file in a folder. Then, build your Docker image (e.g., named custom-image) by navigating to the folder in
|
||||
the terminal and running::
|
||||
```bash
|
||||
@@ -55,10 +61,28 @@ This can be an image you’ve already pulled or one you’ve built:
|
||||
sandbox_base_container_image="custom-image"
|
||||
```
|
||||
|
||||
### Additional Configuration Options
|
||||
|
||||
The `config.toml` file supports several other options for customizing your sandbox:
|
||||
|
||||
```toml
|
||||
[core]
|
||||
# Install additional dependencies when the runtime is built
|
||||
# Can contain any valid shell commands
|
||||
# If you need the path to the Python interpreter in any of these commands, you can use the $OH_INTERPRETER_PATH variable
|
||||
runtime_extra_deps = """
|
||||
pip install numpy pandas
|
||||
apt-get update && apt-get install -y ffmpeg
|
||||
"""
|
||||
|
||||
# Set environment variables for the runtime
|
||||
# Useful for configuration that needs to be available at runtime
|
||||
runtime_startup_env_vars = { DATABASE_URL = "postgresql://user:pass@localhost/db" }
|
||||
|
||||
# Specify platform for multi-architecture builds (e.g., "linux/amd64" or "linux/arm64")
|
||||
platform = "linux/amd64"
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
Run OpenHands by running ```make run``` in the top level directory.
|
||||
|
||||
## Technical Explanation
|
||||
|
||||
Please refer to [custom docker image section of the runtime documentation](https://docs.all-hands.dev/modules/usage/architecture/runtime#advanced-how-openhands-builds-and-maintains-od-runtime-images) for more details.
|
||||
|
||||
@@ -21,10 +21,10 @@ the [README for the OpenHands Resolver](https://github.com/All-Hands-AI/OpenHand
|
||||
### Iterative resolution
|
||||
|
||||
1. Create an issue in the repository.
|
||||
2. Add the `fix-me` label to the issue, or leave a comment starting with `@openhands-agent`
|
||||
3. Review the attempt to resolve the issue by checking the pull request
|
||||
4. Follow up with feedback through general comments, review comments, or inline thread comments
|
||||
5. Add the `fix-me` label to the pull request, or address a specific comment by starting with `@openhands-agent`
|
||||
2. Add the `fix-me` label to the issue, or leave a comment starting with `@openhands-agent`.
|
||||
3. Review the attempt to resolve the issue by checking the pull request.
|
||||
4. Follow up with feedback through general comments, review comments, or inline thread comments.
|
||||
5. Add the `fix-me` label to the pull request, or address a specific comment by starting with `@openhands-agent`.
|
||||
|
||||
### Label versus Macro
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
## Introduction
|
||||
|
||||
OpenHands provides a user-friendly Graphical User Interface (GUI) mode for interacting with the AI assistant. This mode offers an intuitive way to set up the environment, manage settings, and communicate with the AI.
|
||||
OpenHands provides a user-friendly Graphical User Interface (GUI) mode for interacting with the AI assistant.
|
||||
This mode offers an intuitive way to set up the environment, manage settings, and communicate with the AI.
|
||||
|
||||
## Installation and Setup
|
||||
|
||||
1. Follow the instructions in the [Installation](../installation) guide to install OpenHands.
|
||||
|
||||
2. After running the command, access OpenHands at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
## Interacting with the GUI
|
||||
@@ -23,39 +23,39 @@ OpenHands provides a user-friendly Graphical User Interface (GUI) mode for inter
|
||||
|
||||
OpenHands automatically exports a `GITHUB_TOKEN` to the shell environment if it is available. This can happen in two ways:
|
||||
|
||||
1. **Locally (OSS)**: The user directly inputs their GitHub token
|
||||
2. **Online (SaaS)**: The token is obtained through GitHub OAuth authentication
|
||||
- **Locally (OSS)**: The user directly inputs their GitHub token.
|
||||
- **Online (SaaS)**: The token is obtained through GitHub OAuth authentication.
|
||||
|
||||
#### Setting Up a Local GitHub Token
|
||||
|
||||
1. **Generate a Personal Access Token (PAT)**:
|
||||
- Go to GitHub Settings > Developer Settings > Personal Access Tokens > Tokens (classic)
|
||||
- Click "Generate new token (classic)"
|
||||
- Go to GitHub Settings > Developer Settings > Personal Access Tokens > Tokens (classic).
|
||||
- Click "Generate new token (classic)".
|
||||
- Required scopes:
|
||||
- `repo` (Full control of private repositories)
|
||||
- `workflow` (Update GitHub Action workflows)
|
||||
- `read:org` (Read organization data)
|
||||
|
||||
2. **Enter Token in OpenHands**:
|
||||
- Click the Settings button (gear icon) in the top right
|
||||
- Navigate to the "GitHub" section
|
||||
- Paste your token in the "GitHub Token" field
|
||||
- Click "Save" to apply the changes
|
||||
- Click the Settings button (gear icon) in the top right.
|
||||
- Navigate to the "GitHub" section.
|
||||
- Paste your token in the "GitHub Token" field.
|
||||
- Click "Save" to apply the changes.
|
||||
|
||||
#### Organizational Token Policies
|
||||
|
||||
If you're working with organizational repositories, additional setup may be required:
|
||||
|
||||
1. **Check Organization Requirements**:
|
||||
- Organization admins may enforce specific token policies
|
||||
- Some organizations require tokens to be created with SSO enabled
|
||||
- Review your organization's [token policy settings](https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization)
|
||||
- Organization admins may enforce specific token policies.
|
||||
- Some organizations require tokens to be created with SSO enabled.
|
||||
- Review your organization's [token policy settings](https://docs.github.com/en/organizations/managing-programmatic-access-to-your-organization/setting-a-personal-access-token-policy-for-your-organization).
|
||||
|
||||
2. **Verify Organization Access**:
|
||||
- Go to your token settings on GitHub
|
||||
- Look for the organization under "Organization access"
|
||||
- If required, click "Enable SSO" next to your organization
|
||||
- Complete the SSO authorization process
|
||||
- Go to your token settings on GitHub.
|
||||
- Look for the organization under "Organization access".
|
||||
- If required, click "Enable SSO" next to your organization.
|
||||
- Complete the SSO authorization process.
|
||||
|
||||
#### OAuth Authentication (Online Mode)
|
||||
|
||||
@@ -67,31 +67,31 @@ When using OpenHands in online mode, the GitHub OAuth flow:
|
||||
- Organization read access
|
||||
|
||||
2. Authentication steps:
|
||||
- Click "Sign in with GitHub" when prompted
|
||||
- Review the requested permissions
|
||||
- Authorize OpenHands to access your GitHub account
|
||||
- If using an organization, authorize organization access if prompted
|
||||
- Click "Sign in with GitHub" when prompted.
|
||||
- Review the requested permissions.
|
||||
- Authorize OpenHands to access your GitHub account.
|
||||
- If using an organization, authorize organization access if prompted.
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
Common issues and solutions:
|
||||
|
||||
1. **Token Not Recognized**:
|
||||
- Ensure the token is properly saved in settings
|
||||
- Check that the token hasn't expired
|
||||
- Verify the token has the required scopes
|
||||
- Try regenerating the token
|
||||
- **Token Not Recognized**:
|
||||
- Ensure the token is properly saved in settings.
|
||||
- Check that the token hasn't expired.
|
||||
- Verify the token has the required scopes.
|
||||
- Try regenerating the token.
|
||||
|
||||
2. **Organization Access Denied**:
|
||||
- Check if SSO is required but not enabled
|
||||
- Verify organization membership
|
||||
- Contact organization admin if token policies are blocking access
|
||||
- **Organization Access Denied**:
|
||||
- Check if SSO is required but not enabled.
|
||||
- Verify organization membership.
|
||||
- Contact organization admin if token policies are blocking access.
|
||||
|
||||
3. **Verifying Token Works**:
|
||||
- The app will show a green checkmark if the token is valid
|
||||
- Try accessing a repository to confirm permissions
|
||||
- Check the browser console for any error messages
|
||||
- Use the "Test Connection" button in settings if available
|
||||
- **Verifying Token Works**:
|
||||
- The app will show a green checkmark if the token is valid.
|
||||
- Try accessing a repository to confirm permissions.
|
||||
- Check the browser console for any error messages.
|
||||
- Use the "Test Connection" button in settings if available.
|
||||
|
||||
### Advanced Settings
|
||||
|
||||
@@ -103,11 +103,11 @@ Common issues and solutions:
|
||||
|
||||
The main interface consists of several key components:
|
||||
|
||||
1. **Chat Window**: The central area where you can view the conversation history with the AI assistant.
|
||||
2. **Input Box**: Located at the bottom of the screen, use this to type your messages or commands to the AI.
|
||||
3. **Send Button**: Click this to send your message to the AI.
|
||||
4. **Settings Button**: A gear icon that opens the settings modal, allowing you to adjust your configuration at any time.
|
||||
5. **Workspace Panel**: Displays the files and folders in your workspace, allowing you to navigate and view files, or the agent's past commands or web browsing history.
|
||||
- **Chat Window**: The central area where you can view the conversation history with the AI assistant.
|
||||
- **Input Box**: Located at the bottom of the screen, use this to type your messages or commands to the AI.
|
||||
- **Send Button**: Click this to send your message to the AI.
|
||||
- **Settings Button**: A gear icon that opens the settings modal, allowing you to adjust your configuration at any time.
|
||||
- **Workspace Panel**: Displays the files and folders in your workspace, allowing you to navigate and view files, or the agent's past commands or web browsing history.
|
||||
|
||||
### Interacting with the AI
|
||||
|
||||
@@ -118,8 +118,9 @@ The main interface consists of several key components:
|
||||
|
||||
## Tips for Effective Use
|
||||
|
||||
1. Be specific in your requests to get the most accurate and helpful responses, as described in the [prompting best practices](../prompting/prompting-best-practices).
|
||||
2. Use the workspace panel to explore your project structure.
|
||||
3. Use one of the recommended models, as described in the [LLMs section](usage/llms/llms.md).
|
||||
- Be specific in your requests to get the most accurate and helpful responses, as described in the [prompting best practices](../prompting/prompting-best-practices).
|
||||
- Use the workspace panel to explore your project structure.
|
||||
- Use one of the recommended models, as described in the [LLMs section](usage/llms/llms.md).
|
||||
|
||||
Remember, the GUI mode of OpenHands is designed to make your interaction with the AI assistant as smooth and intuitive as possible. Don't hesitate to explore its features to maximize your productivity.
|
||||
Remember, the GUI mode of OpenHands is designed to make your interaction with the AI assistant as smooth and intuitive
|
||||
as possible. Don't hesitate to explore its features to maximize your productivity.
|
||||
|
||||
@@ -7,12 +7,11 @@ This is different from [CLI Mode](cli-mode), which is interactive, and better fo
|
||||
|
||||
## With Python
|
||||
|
||||
To run OpenHands in headless mode with Python,
|
||||
[follow the Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md),
|
||||
and then run:
|
||||
|
||||
To run OpenHands in headless mode with Python:
|
||||
1. Ensure you have followed the [Development setup instructions](https://github.com/All-Hands-AI/OpenHands/blob/main/Development.md).
|
||||
2. Run the following command:
|
||||
```bash
|
||||
poetry run python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
poetry run python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
You'll need to be sure to set your model, API key, and other settings via environment variables
|
||||
@@ -20,31 +19,20 @@ You'll need to be sure to set your model, API key, and other settings via enviro
|
||||
|
||||
## With Docker
|
||||
|
||||
1. Set `WORKSPACE_BASE` to the directory you want OpenHands to edit:
|
||||
To run OpenHands in Headless mode with Docker:
|
||||
|
||||
```bash
|
||||
WORKSPACE_BASE=$(pwd)/workspace
|
||||
```
|
||||
1. Set the following environmental variables in your terminal:
|
||||
|
||||
2. Set `LLM_MODEL` to the model you want to use:
|
||||
- `WORKSPACE_BASE` to the directory you want OpenHands to edit (Ex: `export WORKSPACE_BASE=$(pwd)/workspace`).
|
||||
- `LLM_MODEL` to the model to use (Ex: `export LLM_MODEL="anthropic/claude-3-5-sonnet-20241022"`).
|
||||
- `LLM_API_KEY` to the API key (Ex: `export LLM_API_KEY="sk_test_12345"`).
|
||||
|
||||
```bash
|
||||
LLM_MODEL="anthropic/claude-3-5-sonnet-20241022"
|
||||
|
||||
```
|
||||
|
||||
3. Set `LLM_API_KEY` to your API key:
|
||||
|
||||
```bash
|
||||
LLM_API_KEY="sk_test_12345"
|
||||
```
|
||||
|
||||
4. Run the following Docker command:
|
||||
2. Run the following Docker command:
|
||||
|
||||
```bash
|
||||
docker run -it \
|
||||
--pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-e LLM_API_KEY=$LLM_API_KEY \
|
||||
@@ -52,8 +40,17 @@ docker run -it \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
--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.17 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi" --no-auto-continue
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21 \
|
||||
python -m openhands.core.main -t "write a bash script that prints hi"
|
||||
```
|
||||
|
||||
## Advanced Headless Configurations
|
||||
|
||||
To view all available configuration options for headless mode, run the Python command with the `--help` flag.
|
||||
|
||||
### Additional Logs
|
||||
|
||||
For the headless mode to log all the agent actions, in the terminal run: `export LOG_ALL_EVENTS=true`
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Persisting Session Data
|
||||
|
||||
Using the standard installation, the session data is stored in memory. Currently, if OpenHands' service is restarted,
|
||||
previous sessions become invalid (a new secret is generated) and thus not recoverable.
|
||||
|
||||
## How to Persist Session Data
|
||||
|
||||
### Development Workflow
|
||||
In the `config.toml` file, specify the following:
|
||||
```
|
||||
[core]
|
||||
...
|
||||
file_store="local"
|
||||
file_store_path="/absolute/path/to/openhands/cache/directory"
|
||||
jwt_secret="secretpass"
|
||||
```
|
||||
@@ -1,35 +1,77 @@
|
||||
# Installation
|
||||
# Running OpenHands
|
||||
|
||||
## System Requirements
|
||||
|
||||
* Docker version 26.0.0+ or Docker Desktop 4.31.0+.
|
||||
* You must be using Linux or Mac OS.
|
||||
* If you are on Windows, you must use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
- MacOS with [Docker Desktop support](https://docs.docker.com/desktop/setup/install/mac-install/#system-requirements)
|
||||
- Linux
|
||||
- Windows with [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) and [Docker Desktop support](https://docs.docker.com/desktop/setup/install/windows-install/#system-requirements)
|
||||
|
||||
## Start the app
|
||||
## Prerequisites
|
||||
|
||||
<details>
|
||||
<summary>MacOS</summary>
|
||||
### Docker Desktop
|
||||
|
||||
1. [Install Docker Desktop on Mac](https://docs.docker.com/desktop/setup/install/mac-install).
|
||||
2. Open Docker Desktop, go to `Settings > Advanced` and ensure `Allow the default Docker socket to be used` is enabled.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Linux</summary>
|
||||
|
||||
:::note
|
||||
Tested with Ubuntu 22.04.
|
||||
:::
|
||||
|
||||
### Docker Desktop
|
||||
|
||||
1. [Install Docker Desktop on Linux](https://docs.docker.com/desktop/setup/install/linux/).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Windows</summary>
|
||||
### WSL
|
||||
|
||||
1. [Install WSL](https://learn.microsoft.com/en-us/windows/wsl/install).
|
||||
2. Run `wsl --version` in powershell and confirm `Default Version: 2`.
|
||||
|
||||
### Docker Desktop
|
||||
|
||||
1. [Install Docker Desktop on Windows](https://docs.docker.com/desktop/setup/install/windows-install).
|
||||
2. Open Docker Desktop, go to `Settings` and confirm the following:
|
||||
- General: `Use the WSL 2 based engine` is enabled.
|
||||
- Resources > WSL Integration: `Enable integration with my default WSL distro` is enabled.
|
||||
|
||||
</details>
|
||||
|
||||
## Start the App
|
||||
|
||||
The easiest way to run OpenHands is in Docker.
|
||||
|
||||
```bash
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik
|
||||
docker pull docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik
|
||||
|
||||
docker run -it --rm --pull=always \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-e LOG_ALL_EVENTS=true \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-v ~/.openhands-state:/.openhands-state \
|
||||
-p 3000:3000 \
|
||||
--add-host host.docker.internal:host-gateway \
|
||||
--name openhands-app \
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.17
|
||||
docker.all-hands.dev/all-hands-ai/openhands:0.21
|
||||
```
|
||||
|
||||
You can also run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode), as an [interactive CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode), or using the [OpenHands GitHub Action](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
You'll find OpenHands running at http://localhost:3000!
|
||||
|
||||
You can also [connect OpenHands to your local filesystem](https://docs.all-hands.dev/modules/usage/runtimes#connecting-to-your-filesystem),
|
||||
run OpenHands in a scriptable [headless mode](https://docs.all-hands.dev/modules/usage/how-to/headless-mode),
|
||||
interact with it via a [friendly CLI](https://docs.all-hands.dev/modules/usage/how-to/cli-mode),
|
||||
or run it on tagged issues with [a github action](https://docs.all-hands.dev/modules/usage/how-to/github-action).
|
||||
|
||||
## Setup
|
||||
|
||||
After running the command above, you'll find OpenHands running at [http://localhost:3000](http://localhost:3000).
|
||||
|
||||
Upon launching OpenHands, you'll see a settings modal. You **must** select an `LLM Provider` and `LLM Model` and enter a corresponding `API Key`.
|
||||
These can be changed at any time by selecting the `Settings` button (gear icon) in the UI.
|
||||
|
||||
|
||||
@@ -18,17 +18,18 @@ docker run -it --pull=always \
|
||||
...
|
||||
```
|
||||
|
||||
Then set the following in the OpenHands UI through the Settings:
|
||||
Then in the OpenHands UI Settings:
|
||||
|
||||
:::note
|
||||
You will need your ChatGPT deployment name which can be found on the deployments page in Azure. This is referenced as
|
||||
<deployment-name> below.
|
||||
:::
|
||||
|
||||
* Enable `Advanced Options`
|
||||
* `Custom Model` to azure/<deployment-name>
|
||||
* `Base URL` to your Azure API Base URL (e.g. `https://example-endpoint.openai.azure.com`)
|
||||
* `API Key` to your Azure API key
|
||||
1. Enable `Advanced Options`
|
||||
2. Set the following:
|
||||
- `Custom Model` to azure/<deployment-name>
|
||||
- `Base URL` to your Azure API Base URL (e.g. `https://example-endpoint.openai.azure.com`)
|
||||
- `API Key` to your Azure API key
|
||||
|
||||
## Embeddings
|
||||
|
||||
|
||||
136
docs/modules/usage/llms/custom-llm-configs.md
Normal file
136
docs/modules/usage/llms/custom-llm-configs.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Custom LLM Configurations
|
||||
|
||||
OpenHands supports defining multiple named LLM configurations in your `config.toml` file. This feature allows you to use different LLM configurations for different purposes, such as using a cheaper model for tasks that don't require high-quality responses, or using different models with different parameters for specific agents.
|
||||
|
||||
## How It Works
|
||||
|
||||
Named LLM configurations are defined in the `config.toml` file using sections that start with `llm.`. For example:
|
||||
|
||||
```toml
|
||||
# Default LLM configuration
|
||||
[llm]
|
||||
model = "gpt-4"
|
||||
api_key = "your-api-key"
|
||||
temperature = 0.0
|
||||
|
||||
# Custom LLM configuration for a cheaper model
|
||||
[llm.gpt3]
|
||||
model = "gpt-3.5-turbo"
|
||||
api_key = "your-api-key"
|
||||
temperature = 0.2
|
||||
|
||||
# Another custom configuration with different parameters
|
||||
[llm.high-creativity]
|
||||
model = "gpt-4"
|
||||
api_key = "your-api-key"
|
||||
temperature = 0.8
|
||||
top_p = 0.9
|
||||
```
|
||||
|
||||
Each named configuration inherits all settings from the default `[llm]` section and can override any of those settings. You can define as many custom configurations as needed.
|
||||
|
||||
## Using Custom Configurations
|
||||
|
||||
### With Agents
|
||||
|
||||
You can specify which LLM configuration an agent should use by setting the `llm_config` parameter in the agent's configuration section:
|
||||
|
||||
```toml
|
||||
[agent.RepoExplorerAgent]
|
||||
# Use the cheaper GPT-3 configuration for this agent
|
||||
llm_config = 'gpt3'
|
||||
|
||||
[agent.CodeWriterAgent]
|
||||
# Use the high creativity configuration for this agent
|
||||
llm_config = 'high-creativity'
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
Each named LLM configuration supports all the same options as the default LLM configuration. These include:
|
||||
|
||||
- Model selection (`model`)
|
||||
- API configuration (`api_key`, `base_url`, etc.)
|
||||
- Model parameters (`temperature`, `top_p`, etc.)
|
||||
- Retry settings (`num_retries`, `retry_multiplier`, etc.)
|
||||
- Token limits (`max_input_tokens`, `max_output_tokens`)
|
||||
- And all other LLM configuration options
|
||||
|
||||
For a complete list of available options, see the LLM Configuration section in the [Configuration Options](../configuration-options) documentation.
|
||||
|
||||
## Use Cases
|
||||
|
||||
Custom LLM configurations are particularly useful in several scenarios:
|
||||
|
||||
- **Cost Optimization**: Use cheaper models for tasks that don't require high-quality responses, like repository exploration or simple file operations.
|
||||
- **Task-Specific Tuning**: Configure different temperature and top_p values for tasks that require different levels of creativity or determinism.
|
||||
- **Different Providers**: Use different LLM providers or API endpoints for different tasks.
|
||||
- **Testing and Development**: Easily switch between different model configurations during development and testing.
|
||||
|
||||
## Example: Cost Optimization
|
||||
|
||||
A practical example of using custom LLM configurations to optimize costs:
|
||||
|
||||
```toml
|
||||
# Default configuration using GPT-4 for high-quality responses
|
||||
[llm]
|
||||
model = "gpt-4"
|
||||
api_key = "your-api-key"
|
||||
temperature = 0.0
|
||||
|
||||
# Cheaper configuration for repository exploration
|
||||
[llm.repo-explorer]
|
||||
model = "gpt-3.5-turbo"
|
||||
temperature = 0.2
|
||||
|
||||
# Configuration for code generation
|
||||
[llm.code-gen]
|
||||
model = "gpt-4"
|
||||
temperature = 0.0
|
||||
max_output_tokens = 2000
|
||||
|
||||
[agent.RepoExplorerAgent]
|
||||
llm_config = 'repo-explorer'
|
||||
|
||||
[agent.CodeWriterAgent]
|
||||
llm_config = 'code-gen'
|
||||
```
|
||||
|
||||
In this example:
|
||||
- Repository exploration uses a cheaper model since it mainly involves understanding and navigating code
|
||||
- Code generation uses GPT-4 with a higher token limit for generating larger code blocks
|
||||
- The default configuration remains available for other tasks
|
||||
|
||||
# Custom Configurations with Reserved Names
|
||||
|
||||
OpenHands can use custom LLM configurations named with reserved names, for specific use cases. If you specify the model and other settings under the reserved names, then OpenHands will load and them for a specific purpose. As of now, one such configuration is implemented: draft editor.
|
||||
|
||||
## Draft Editor Configuration
|
||||
|
||||
The `draft_editor` configuration is a group of settings you can provide, to specify the model to use for preliminary drafting of code edits, for any tasks that involve editing and refining code. You need to provide it under the section `[llm.draft_editor]`.
|
||||
|
||||
For example, you can define in `config.toml` a draft editor like this:
|
||||
|
||||
```toml
|
||||
[llm.draft_editor]
|
||||
model = "gpt-4"
|
||||
temperature = 0.2
|
||||
top_p = 0.95
|
||||
presence_penalty = 0.0
|
||||
frequency_penalty = 0.0
|
||||
```
|
||||
|
||||
This configuration:
|
||||
- Uses GPT-4 for high-quality edits and suggestions
|
||||
- Sets a low temperature (0.2) to maintain consistency while allowing some flexibility
|
||||
- Uses a high top_p value (0.95) to consider a wide range of token options
|
||||
- Disables presence and frequency penalties to maintain focus on the specific edits needed
|
||||
|
||||
Use this configuration when you want to let an LLM draft edits before making them. In general, it may be useful to:
|
||||
- Review and suggest code improvements
|
||||
- Refine existing content while maintaining its core meaning
|
||||
- Make precise, focused changes to code or text
|
||||
|
||||
:::note
|
||||
Custom LLM configurations are only available when using OpenHands in development mode, via `main.py` or `cli.py`. When running via `docker run`, please use the standard configuration options.
|
||||
:::
|
||||
@@ -8,10 +8,10 @@ OpenHands uses LiteLLM to make calls to Google's chat models. You can find their
|
||||
## Gemini - Google AI Studio Configs
|
||||
|
||||
When running OpenHands, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `Gemini`
|
||||
* `LLM Model` to the model you will be using.
|
||||
- `LLM Provider` to `Gemini`
|
||||
- `LLM Model` to the model you will be using.
|
||||
If the model is not in the list, toggle `Advanced Options`, and enter it in `Custom Model` (e.g. gemini/<model-name> like `gemini/gemini-1.5-pro`).
|
||||
* `API Key` to your Gemini API key
|
||||
- `API Key` to your Gemini API key
|
||||
|
||||
## VertexAI - Google Cloud Platform Configs
|
||||
|
||||
@@ -25,6 +25,6 @@ VERTEXAI_LOCATION="<your-gcp-location>"
|
||||
```
|
||||
|
||||
Then set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `VertexAI`
|
||||
* `LLM Model` to the model you will be using.
|
||||
- `LLM Provider` to `VertexAI`
|
||||
- `LLM Model` to the model you will be using.
|
||||
If the model is not in the list, toggle `Advanced Options`, and enter it in `Custom Model` (e.g. vertex_ai/<model-name>).
|
||||
|
||||
@@ -5,19 +5,20 @@ OpenHands uses LiteLLM to make calls to chat models on Groq. You can find their
|
||||
## Configuration
|
||||
|
||||
When running OpenHands, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* `LLM Provider` to `Groq`
|
||||
* `LLM Model` to the model you will be using. [Visit here to see the list of
|
||||
- `LLM Provider` to `Groq`
|
||||
- `LLM Model` to the model you will be using. [Visit here to see the list of
|
||||
models that Groq hosts](https://console.groq.com/docs/models). If the model is not in the list, toggle
|
||||
`Advanced Options`, and enter it in `Custom Model` (e.g. groq/<model-name> like `groq/llama3-70b-8192`).
|
||||
* `API key` to your Groq API key. To find or create your Groq API Key, [see here](https://console.groq.com/keys).
|
||||
- `API key` to your Groq API key. To find or create your Groq API Key, [see here](https://console.groq.com/keys).
|
||||
|
||||
|
||||
|
||||
## Using Groq as an OpenAI-Compatible Endpoint
|
||||
|
||||
The Groq endpoint for chat completion is [mostly OpenAI-compatible](https://console.groq.com/docs/openai). Therefore, you can access Groq models as you
|
||||
would access any OpenAI-compatible endpoint. You can set the following in the OpenHands UI through the Settings:
|
||||
* Enable `Advanced Options`
|
||||
* `Custom Model` to the prefix `openai/` + the model you will be using (e.g. `openai/llama3-70b-8192`)
|
||||
* `Base URL` to `https://api.groq.com/openai/v1`
|
||||
* `API Key` to your Groq API key
|
||||
would access any OpenAI-compatible endpoint. In the OpenHands UI through the Settings:
|
||||
1. Enable `Advanced Options`
|
||||
2. Set the following:
|
||||
- `Custom Model` to the prefix `openai/` + the model you will be using (e.g. `openai/llama3-70b-8192`)
|
||||
- `Base URL` to `https://api.groq.com/openai/v1`
|
||||
- `API Key` to your Groq API key
|
||||
|
||||
@@ -4,22 +4,15 @@ OpenHands can connect to any LLM supported by LiteLLM. However, it requires a po
|
||||
|
||||
## Model Recommendations
|
||||
|
||||
Based on our evaluations of language models for coding tasks (using the SWE-bench dataset), we can provide some recommendations for model selection. Some analyses can be found in [this blog article comparing LLMs](https://www.all-hands.dev/blog/evaluation-of-llms-as-coding-agents-on-swe-bench-at-30x-speed) and [this blog article with some more recent results](https://www.all-hands.dev/blog/openhands-codeact-21-an-open-state-of-the-art-software-development-agent).
|
||||
|
||||
When choosing a model, consider both the quality of outputs and the associated costs. Here's a summary of the findings:
|
||||
|
||||
- Claude 3.5 Sonnet is the best by a fair amount, achieving a 53% resolve rate on SWE-Bench Verified with the default agent in OpenHands.
|
||||
- GPT-4o lags behind, and o1-mini actually performed somewhat worse than GPT-4o. We went in and analyzed the results a little, and briefly it seemed like o1 was sometimes "overthinking" things, performing extra environment configuration tasks when it could just go ahead and finish the task.
|
||||
- Finally, the strongest open models were Llama 3.1 405 B and deepseek-v2.5, and they performed reasonably, even besting some of the closed models.
|
||||
|
||||
Please refer to the [full article](https://www.all-hands.dev/blog/evaluation-of-llms-as-coding-agents-on-swe-bench-at-30x-speed) for more details.
|
||||
Based on our evaluations of language models for coding tasks (using the SWE-bench dataset), we can provide some
|
||||
recommendations for model selection. Our latest benchmarking results can be found in [this spreadsheet](https://docs.google.com/spreadsheets/d/1wOUdFCMyY6Nt0AIqF705KN4JKOWgeI4wUGUP60krXXs/edit?gid=0).
|
||||
|
||||
Based on these findings and community feedback, the following models have been verified to work reasonably well with OpenHands:
|
||||
|
||||
- claude-3-5-sonnet (recommended)
|
||||
- gpt-4 / gpt-4o
|
||||
- llama-3.1-405b
|
||||
- deepseek-v2.5
|
||||
- anthropic/claude-3-5-sonnet-20241022 (recommended)
|
||||
- anthropic/claude-3-5-haiku-20241022
|
||||
- deepseek/deepseek-chat
|
||||
- gpt-4o
|
||||
|
||||
:::warning
|
||||
OpenHands will issue many prompts to the LLM you configure. Most of these LLMs cost money, so be sure to set spending
|
||||
@@ -69,9 +62,11 @@ We have a few guides for running OpenHands with specific model providers:
|
||||
|
||||
### API retries and rate limits
|
||||
|
||||
LLM providers typically have rate limits, sometimes very low, and may require retries. OpenHands will automatically retry requests if it receives a Rate Limit Error (429 error code), API connection error, or other transient errors.
|
||||
LLM providers typically have rate limits, sometimes very low, and may require retries. OpenHands will automatically
|
||||
retry requests if it receives a Rate Limit Error (429 error code), API connection error, or other transient errors.
|
||||
|
||||
You can customize these options as you need for the provider you're using. Check their documentation, and set the following environment variables to control the number of retries and the time between retries:
|
||||
You can customize these options as you need for the provider you're using. Check their documentation, and set the
|
||||
following environment variables to control the number of retries and the time between retries:
|
||||
|
||||
- `LLM_NUM_RETRIES` (Default of 8)
|
||||
- `LLM_RETRY_MIN_WAIT` (Default of 15 seconds)
|
||||
|
||||
@@ -17,8 +17,9 @@ Just as for OpenAI Chat completions, we use LiteLLM for OpenAI-compatible endpoi
|
||||
|
||||
## Using an OpenAI Proxy
|
||||
|
||||
If you're using an OpenAI proxy, you'll need to set the following in the OpenHands UI through the Settings:
|
||||
* Enable `Advanced Options`
|
||||
* `Custom Model` to openai/<model-name> (e.g. `openai/gpt-4o` or openai/<proxy-prefix>/<model-name>)
|
||||
* `Base URL` to the URL of your OpenAI proxy
|
||||
* `API Key` to your OpenAI API key
|
||||
If you're using an OpenAI proxy, in the OpenHands UI through the Settings:
|
||||
1. Enable `Advanced Options`
|
||||
2. Set the following:
|
||||
- `Custom Model` to openai/<model-name> (e.g. `openai/gpt-4o` or openai/<proxy-prefix>/<model-name>)
|
||||
- `Base URL` to the URL of your OpenAI proxy
|
||||
- `API Key` to your OpenAI API key
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
# Customizing Agent Behavior
|
||||
|
||||
OpenHands can be customized to work more effectively with specific repositories by providing repository-specific context and guidelines. This section explains how to optimize OpenHands for your project.
|
||||
|
||||
## Repository Configuration
|
||||
|
||||
You can customize OpenHands' behavior for your repository by creating a `.openhands` directory in your repository's root. At minimum, it should contain the file
|
||||
`.openhands/microagents/repo.md`, which includes instructions that will
|
||||
be given to the agent every time it works with this repository.
|
||||
|
||||
We suggest including the following information:
|
||||
1. **Repository Overview**: A brief description of your project's purpose and architecture
|
||||
2. **Directory Structure**: Key directories and their purposes
|
||||
3. **Development Guidelines**: Project-specific coding standards and practices
|
||||
4. **Testing Requirements**: How to run tests and what types of tests are required
|
||||
5. **Setup Instructions**: Steps needed to build and run the project
|
||||
|
||||
### Example Repository Configuration
|
||||
Example `.openhands/microagents/repo.md` file:
|
||||
```
|
||||
Repository: MyProject
|
||||
Description: A web application for task management
|
||||
|
||||
Directory Structure:
|
||||
- src/: Main application code
|
||||
- tests/: Test files
|
||||
- docs/: Documentation
|
||||
|
||||
Setup:
|
||||
- Run `npm install` to install dependencies
|
||||
- Use `npm run dev` for development
|
||||
- Run `npm test` for testing
|
||||
|
||||
Guidelines:
|
||||
- Follow ESLint configuration
|
||||
- Write tests for all new features
|
||||
- Use TypeScript for new code
|
||||
```
|
||||
|
||||
### Customizing Prompts
|
||||
|
||||
When working with a customized repository:
|
||||
|
||||
1. **Reference Project Standards**: Mention specific coding standards or patterns used in your project
|
||||
2. **Include Context**: Reference relevant documentation or existing implementations
|
||||
3. **Specify Testing Requirements**: Include project-specific testing requirements in your prompts
|
||||
|
||||
Example customized prompt:
|
||||
```
|
||||
Add a new task completion feature to src/components/TaskList.tsx following our existing component patterns.
|
||||
Include unit tests in tests/components/ and update the documentation in docs/features/.
|
||||
The component should use our shared styling from src/styles/components.
|
||||
```
|
||||
|
||||
### Best Practices for Repository Customization
|
||||
|
||||
1. **Keep Instructions Updated**: Regularly update your `.openhands` directory as your project evolves
|
||||
2. **Be Specific**: Include specific paths, patterns, and requirements unique to your project
|
||||
3. **Document Dependencies**: List all tools and dependencies required for development
|
||||
4. **Include Examples**: Provide examples of good code patterns from your project
|
||||
5. **Specify Conventions**: Document naming conventions, file organization, and code style preferences
|
||||
|
||||
By customizing OpenHands for your repository, you'll get more accurate and consistent results that align with your project's standards and requirements.
|
||||
|
||||
## Other Microagents
|
||||
You can create other instructions in the `.openhands/microagents/` directory
|
||||
that will be sent to the agent if a particular keyword is found, like `test`, `frontend`, or `migration`. See [Microagents](microagents.md) for more information.
|
||||
36
docs/modules/usage/prompting/microagents-overview.md
Normal file
36
docs/modules/usage/prompting/microagents-overview.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Microagents Overview
|
||||
|
||||
Microagents are specialized prompts that enhance OpenHands with domain-specific knowledge, repository-specific context
|
||||
and task-specific workflows. They help by providing expert guidance, automating common tasks, and ensuring
|
||||
consistent practices across projects.
|
||||
|
||||
## Microagent Types
|
||||
|
||||
Currently OpenHands supports the following types of microagents:
|
||||
|
||||
* [Repository Microagents](./microagents-repo): Repository-specific context and guidelines for OpenHands.
|
||||
* [Public Microagents](./microagents-public): General guidelines triggered by keywords for all OpenHands users.
|
||||
|
||||
When OpenHands works with a repository, it:
|
||||
|
||||
1. Loads repository-specific instructions from `.openhands/microagents/` if present in the repository.
|
||||
2. Loads general guidelines triggered by keywords in conversations.
|
||||
See current [Public Microagents](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge).
|
||||
|
||||
## Microagent Format
|
||||
|
||||
All microagents use markdown files with YAML frontmatter that have special instructions to help OpenHands accomplish
|
||||
tasks:
|
||||
```
|
||||
---
|
||||
name: <Name of the microagent>
|
||||
type: <MicroAgent type>
|
||||
version: <MicroAgent version>
|
||||
agent: <The agent type (Typically CodeActAgent)>
|
||||
triggers:
|
||||
- <Optional keywords triggering the microagent. If triggers are removed, it will always be included>
|
||||
---
|
||||
|
||||
<Markdown with any special guidelines, instructions, and prompts that OpenHands should follow.
|
||||
Check out the specific documentation for each microagent on best practices for more information.>
|
||||
```
|
||||
153
docs/modules/usage/prompting/microagents-public.md
Normal file
153
docs/modules/usage/prompting/microagents-public.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Public Microagents
|
||||
|
||||
## Overview
|
||||
|
||||
Public microagents are specialized guidelines triggered by keywords for all OpenHands users.
|
||||
They are defined in markdown files under the
|
||||
[`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge) directory.
|
||||
|
||||
Public microagents:
|
||||
- Monitor incoming commands for their trigger words.
|
||||
- Activate when relevant triggers are detected.
|
||||
- Apply their specialized knowledge and capabilities.
|
||||
- Follow their specific guidelines and restrictions.
|
||||
|
||||
## Current Public Microagents
|
||||
|
||||
For more information about specific microagents, refer to their individual documentation files in
|
||||
the [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/) directory.
|
||||
|
||||
### GitHub Agent
|
||||
**File**: `github.md`
|
||||
**Triggers**: `github`, `git`
|
||||
|
||||
The GitHub agent specializes in GitHub API interactions and repository management. It:
|
||||
- Has access to a `GITHUB_TOKEN` for API authentication.
|
||||
- Follows strict guidelines for repository interactions.
|
||||
- Handles branch management and pull requests.
|
||||
- Uses the GitHub API instead of web browser interactions.
|
||||
|
||||
Key features:
|
||||
- Branch protection (prevents direct pushes to main/master)
|
||||
- Automated PR creation
|
||||
- Git configuration management
|
||||
- API-first approach for GitHub operations
|
||||
|
||||
Usage Example:
|
||||
|
||||
```bash
|
||||
git checkout -b feature-branch
|
||||
git commit -m "Add new feature"
|
||||
git push origin feature-branch
|
||||
```
|
||||
|
||||
### NPM Agent
|
||||
**File**: `npm.md`
|
||||
**Triggers**: `npm`
|
||||
|
||||
Specializes in handling npm package management with specific focus on:
|
||||
- Non-interactive shell operations.
|
||||
- Automated confirmation handling using Unix 'yes' command.
|
||||
- Package installation automation.
|
||||
|
||||
Usage Example:
|
||||
|
||||
```bash
|
||||
yes | npm install package-name
|
||||
```
|
||||
|
||||
## Contributing a Public Microagent
|
||||
|
||||
You can create your own public microagents by adding new markdown files to the
|
||||
[`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/) directory.
|
||||
|
||||
### Public Microagents Best Practices
|
||||
|
||||
- **Clear Scope**: Keep the microagent focused on a specific domain or task.
|
||||
- **Explicit Instructions**: Provide clear, unambiguous guidelines.
|
||||
- **Useful Examples**: Include practical examples of common use cases.
|
||||
- **Safety First**: Include necessary warnings and constraints.
|
||||
- **Integration Awareness**: Consider how the microagent interacts with other components.
|
||||
|
||||
### Steps to Contribute a Public Microagent
|
||||
|
||||
#### 1. Plan the Public Microagent
|
||||
|
||||
Before creating a public microagent, consider:
|
||||
- What specific problem or use case will it address?
|
||||
- What unique capabilities or knowledge should it have?
|
||||
- What trigger words make sense for activating it?
|
||||
- What constraints or guidelines should it follow?
|
||||
|
||||
#### 2. Create File
|
||||
|
||||
Create a new markdown file in [`microagents/knowledge/`](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge/)
|
||||
with a descriptive name (e.g., `docker.md` for a Docker-focused agent).
|
||||
|
||||
Update the file with the required frontmatter [according to the required format](./microagents-overview#microagent-format)
|
||||
and the required specialized guidelines while following the [best practices above](#public-microagents-best-practices).
|
||||
|
||||
#### 3. Testing the Public Microagent
|
||||
|
||||
- Test the agent with various prompts.
|
||||
- Verify trigger words activate the agent correctly.
|
||||
- Ensure instructions are clear and comprehensive.
|
||||
- Check for potential conflicts with existing agents.
|
||||
|
||||
#### 4. Submission Process
|
||||
|
||||
Submit a pull request with:
|
||||
- The new microagent file.
|
||||
- Updated documentation if needed.
|
||||
- Description of the agent's purpose and capabilities.
|
||||
|
||||
### Example Public Microagent Implementation
|
||||
|
||||
Here's a template for a new microagent:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: docker
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- docker
|
||||
- container
|
||||
---
|
||||
|
||||
You are responsible for Docker container management and Dockerfile creation.
|
||||
|
||||
Key responsibilities:
|
||||
1. Create and modify Dockerfiles
|
||||
2. Manage container lifecycle
|
||||
3. Handle Docker Compose configurations
|
||||
|
||||
Guidelines:
|
||||
- Always use official base images when possible
|
||||
- Include necessary security considerations
|
||||
- Follow Docker best practices for layer optimization
|
||||
|
||||
Examples:
|
||||
1. Creating a Dockerfile:
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD ["npm", "start"]
|
||||
|
||||
2. Docker Compose usage:
|
||||
version: '3'
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
Remember to:
|
||||
- Validate Dockerfile syntax
|
||||
- Check for security vulnerabilities
|
||||
- Optimize for build time and image size
|
||||
```
|
||||
|
||||
See the [current public micro-agents](https://github.com/All-Hands-AI/OpenHands/tree/main/microagents/knowledge) for
|
||||
more examples.
|
||||
68
docs/modules/usage/prompting/microagents-repo.md
Normal file
68
docs/modules/usage/prompting/microagents-repo.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Repository Microagents
|
||||
|
||||
## Overview
|
||||
|
||||
OpenHands can be customized to work more effectively with specific repositories by providing repository-specific context
|
||||
and guidelines. This section explains how to optimize OpenHands for your project.
|
||||
|
||||
## Creating a Repository Micro-Agent
|
||||
|
||||
You can customize OpenHands' behavior for your repository by creating a `.openhands/microagents/` directory in your repository's root.
|
||||
At minimum it should contain the file
|
||||
`.openhands/microagents/repo.md`, which includes instructions that will
|
||||
be given to the agent every time it works with this repository.
|
||||
|
||||
### Repository Microagents Best Practices
|
||||
|
||||
- **Keep Instructions Updated**: Regularly update your `.openhands/microagents/` directory as your project evolves.
|
||||
- **Be Specific**: Include specific paths, patterns, and requirements unique to your project.
|
||||
- **Document Dependencies**: List all tools and dependencies required for development.
|
||||
- **Include Examples**: Provide examples of good code patterns from your project.
|
||||
- **Specify Conventions**: Document naming conventions, file organization, and code style preferences.
|
||||
|
||||
### Steps to Create a Repository Microagent
|
||||
|
||||
#### 1. Plan the Repository Microagent
|
||||
When creating a repository-specific micro-agent, we suggest including the following information:
|
||||
- **Repository Overview**: A brief description of your project's purpose and architecture.
|
||||
- **Directory Structure**: Key directories and their purposes.
|
||||
- **Development Guidelines**: Project-specific coding standards and practices.
|
||||
- **Testing Requirements**: How to run tests and what types of tests are required.
|
||||
- **Setup Instructions**: Steps needed to build and run the project.
|
||||
|
||||
#### 2. Create File
|
||||
|
||||
Create a file in your repository under `.openhands/microagents/` (Example: `.openhands/microagents/repo.md`)
|
||||
|
||||
Update the file with the required frontmatter [according to the required format](./microagents-overview#microagent-format)
|
||||
and the required specialized guidelines for your repository.
|
||||
|
||||
### Example Repository Microagent
|
||||
|
||||
```
|
||||
---
|
||||
name: repo
|
||||
type: repo
|
||||
agent: CodeActAgent
|
||||
---
|
||||
|
||||
Repository: MyProject
|
||||
Description: A web application for task management
|
||||
|
||||
Directory Structure:
|
||||
- src/: Main application code
|
||||
- tests/: Test files
|
||||
- docs/: Documentation
|
||||
|
||||
Setup:
|
||||
- Run `npm install` to install dependencies
|
||||
- Use `npm run dev` for development
|
||||
- Run `npm test` for testing
|
||||
|
||||
Guidelines:
|
||||
- Follow ESLint configuration
|
||||
- Write tests for all new features
|
||||
- Use TypeScript for new code
|
||||
|
||||
If adding a new component in src/components, always add appropriate unit tests in tests/components/.
|
||||
```
|
||||
@@ -1,209 +0,0 @@
|
||||
# Micro-Agents
|
||||
|
||||
OpenHands uses specialized micro-agents to handle specific tasks and contexts efficiently. These micro-agents are small, focused components that provide specialized behavior and knowledge for particular scenarios.
|
||||
|
||||
## Overview
|
||||
|
||||
Micro-agents are defined in markdown files under the `openhands/agenthub/codeact_agent/micro/` directory. Each micro-agent is configured with:
|
||||
|
||||
- A unique name
|
||||
- The agent type (typically CodeActAgent)
|
||||
- Trigger keywords that activate the agent
|
||||
- Specific instructions and capabilities
|
||||
|
||||
## Available Micro-Agents
|
||||
|
||||
### GitHub Agent
|
||||
**File**: `github.md`
|
||||
**Triggers**: `github`, `git`
|
||||
|
||||
The GitHub agent specializes in GitHub API interactions and repository management. It:
|
||||
- Has access to a `GITHUB_TOKEN` for API authentication
|
||||
- Follows strict guidelines for repository interactions
|
||||
- Handles branch management and pull requests
|
||||
- Uses the GitHub API instead of web browser interactions
|
||||
|
||||
Key features:
|
||||
- Branch protection (prevents direct pushes to main/master)
|
||||
- Automated PR creation
|
||||
- Git configuration management
|
||||
- API-first approach for GitHub operations
|
||||
|
||||
### NPM Agent
|
||||
**File**: `npm.md`
|
||||
**Triggers**: `npm`
|
||||
|
||||
Specializes in handling npm package management with specific focus on:
|
||||
- Non-interactive shell operations
|
||||
- Automated confirmation handling using Unix 'yes' command
|
||||
- Package installation automation
|
||||
|
||||
### Custom Micro-Agents
|
||||
|
||||
You can create your own micro-agents by adding new markdown files to the micro-agents directory. Each file should follow this structure:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: agent_name
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- trigger_word1
|
||||
- trigger_word2
|
||||
---
|
||||
|
||||
Instructions and capabilities for the micro-agent...
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
When working with micro-agents:
|
||||
|
||||
1. **Use Appropriate Triggers**: Ensure your commands include the relevant trigger words to activate the correct micro-agent
|
||||
2. **Follow Agent Guidelines**: Each agent has specific instructions and limitations - respect these for optimal results
|
||||
3. **API-First Approach**: When available, use API endpoints rather than web interfaces
|
||||
4. **Automation Friendly**: Design commands that work well in non-interactive environments
|
||||
|
||||
## Integration
|
||||
|
||||
Micro-agents are automatically integrated into OpenHands' workflow. They:
|
||||
- Monitor incoming commands for their trigger words
|
||||
- Activate when relevant triggers are detected
|
||||
- Apply their specialized knowledge and capabilities
|
||||
- Follow their specific guidelines and restrictions
|
||||
|
||||
## Example Usage
|
||||
|
||||
```bash
|
||||
# GitHub agent example
|
||||
git checkout -b feature-branch
|
||||
git commit -m "Add new feature"
|
||||
git push origin feature-branch
|
||||
|
||||
# NPM agent example
|
||||
yes | npm install package-name
|
||||
```
|
||||
|
||||
For more information about specific agents, refer to their individual documentation files in the micro-agents directory.
|
||||
|
||||
## Contributing a Micro-Agent
|
||||
|
||||
To contribute a new micro-agent to OpenHands, follow these guidelines:
|
||||
|
||||
### 1. Planning Your Micro-Agent
|
||||
|
||||
Before creating a micro-agent, consider:
|
||||
- What specific problem or use case will it address?
|
||||
- What unique capabilities or knowledge should it have?
|
||||
- What trigger words make sense for activating it?
|
||||
- What constraints or guidelines should it follow?
|
||||
|
||||
### 2. File Structure
|
||||
|
||||
Create a new markdown file in `openhands/agenthub/codeact_agent/micro/` with a descriptive name (e.g., `docker.md` for a Docker-focused agent).
|
||||
|
||||
### 3. Required Components
|
||||
|
||||
Your micro-agent file must include:
|
||||
|
||||
1. **Front Matter**: YAML metadata at the start of the file:
|
||||
```markdown
|
||||
---
|
||||
name: your_agent_name
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- trigger_word1
|
||||
- trigger_word2
|
||||
---
|
||||
```
|
||||
|
||||
2. **Instructions**: Clear, specific guidelines for the agent's behavior:
|
||||
```markdown
|
||||
You are responsible for [specific task/domain].
|
||||
|
||||
Key responsibilities:
|
||||
1. [Responsibility 1]
|
||||
2. [Responsibility 2]
|
||||
|
||||
Guidelines:
|
||||
- [Guideline 1]
|
||||
- [Guideline 2]
|
||||
|
||||
Examples of usage:
|
||||
[Example 1]
|
||||
[Example 2]
|
||||
```
|
||||
|
||||
### 4. Best Practices for Micro-Agent Development
|
||||
|
||||
1. **Clear Scope**: Keep the agent focused on a specific domain or task
|
||||
2. **Explicit Instructions**: Provide clear, unambiguous guidelines
|
||||
3. **Useful Examples**: Include practical examples of common use cases
|
||||
4. **Safety First**: Include necessary warnings and constraints
|
||||
5. **Integration Awareness**: Consider how the agent interacts with other components
|
||||
|
||||
### 5. Testing Your Micro-Agent
|
||||
|
||||
Before submitting:
|
||||
1. Test the agent with various prompts
|
||||
2. Verify trigger words activate the agent correctly
|
||||
3. Ensure instructions are clear and comprehensive
|
||||
4. Check for potential conflicts with existing agents
|
||||
|
||||
### 6. Example Implementation
|
||||
|
||||
Here's a template for a new micro-agent:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: docker
|
||||
agent: CodeActAgent
|
||||
triggers:
|
||||
- docker
|
||||
- container
|
||||
---
|
||||
|
||||
You are responsible for Docker container management and Dockerfile creation.
|
||||
|
||||
Key responsibilities:
|
||||
1. Create and modify Dockerfiles
|
||||
2. Manage container lifecycle
|
||||
3. Handle Docker Compose configurations
|
||||
|
||||
Guidelines:
|
||||
- Always use official base images when possible
|
||||
- Include necessary security considerations
|
||||
- Follow Docker best practices for layer optimization
|
||||
|
||||
Examples:
|
||||
1. Creating a Dockerfile:
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
CMD ["npm", "start"]
|
||||
|
||||
2. Docker Compose usage:
|
||||
version: '3'
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
Remember to:
|
||||
- Validate Dockerfile syntax
|
||||
- Check for security vulnerabilities
|
||||
- Optimize for build time and image size
|
||||
```
|
||||
|
||||
### 7. Submission Process
|
||||
|
||||
1. Create your micro-agent file in the correct directory
|
||||
2. Test thoroughly
|
||||
3. Submit a pull request with:
|
||||
- The new micro-agent file
|
||||
- Updated documentation if needed
|
||||
- Description of the agent's purpose and capabilities
|
||||
|
||||
Remember that micro-agents are a powerful way to extend OpenHands' capabilities in specific domains. Well-designed agents can significantly improve the system's ability to handle specialized tasks.
|
||||
@@ -6,35 +6,31 @@ When working with OpenHands AI software developer, it's crucial to provide clear
|
||||
|
||||
Good prompts are:
|
||||
|
||||
1. **Concrete**: They explain exactly what functionality should be added or what error needs to be fixed.
|
||||
2. **Location-specific**: If known, they explain the locations in the code base that should be modified.
|
||||
3. **Appropriately scoped**: They should be the size of a single feature, typically not exceeding 100 lines of code.
|
||||
- **Concrete**: They explain exactly what functionality should be added or what error needs to be fixed.
|
||||
- **Location-specific**: If known, they explain the locations in the code base that should be modified.
|
||||
- **Appropriately scoped**: They should be the size of a single feature, typically not exceeding 100 lines of code.
|
||||
|
||||
## Examples
|
||||
|
||||
### Good Prompt Examples
|
||||
|
||||
1. "Add a function `calculate_average` in `utils/math_operations.py` that takes a list of numbers as input and returns their average."
|
||||
|
||||
2. "Fix the TypeError in `frontend/src/components/UserProfile.tsx` occurring on line 42. The error suggests we're trying to access a property of undefined."
|
||||
|
||||
3. "Implement input validation for the email field in the registration form. Update `frontend/src/components/RegistrationForm.tsx` to check if the email is in a valid format before submission."
|
||||
- "Add a function `calculate_average` in `utils/math_operations.py` that takes a list of numbers as input and returns their average."
|
||||
- "Fix the TypeError in `frontend/src/components/UserProfile.tsx` occurring on line 42. The error suggests we're trying to access a property of undefined."
|
||||
- "Implement input validation for the email field in the registration form. Update `frontend/src/components/RegistrationForm.tsx` to check if the email is in a valid format before submission."
|
||||
|
||||
### Bad Prompt Examples
|
||||
|
||||
1. "Make the code better." (Too vague, not concrete)
|
||||
|
||||
2. "Rewrite the entire backend to use a different framework." (Not appropriately scoped)
|
||||
|
||||
3. "There's a bug somewhere in the user authentication. Can you find and fix it?" (Lacks specificity and location information)
|
||||
- "Make the code better." (Too vague, not concrete)
|
||||
- "Rewrite the entire backend to use a different framework." (Not appropriately scoped)
|
||||
- "There's a bug somewhere in the user authentication. Can you find and fix it?" (Lacks specificity and location information)
|
||||
|
||||
## Tips for Effective Prompting
|
||||
|
||||
1. Be as specific as possible about the desired outcome or the problem to be solved.
|
||||
2. Provide context, including relevant file paths and line numbers if available.
|
||||
3. Break down large tasks into smaller, manageable prompts.
|
||||
4. Include any relevant error messages or logs.
|
||||
5. Specify the programming language or framework if it's not obvious from the context.
|
||||
- Be as specific as possible about the desired outcome or the problem to be solved.
|
||||
- Provide context, including relevant file paths and line numbers if available.
|
||||
- Break down large tasks into smaller, manageable prompts.
|
||||
- Include any relevant error messages or logs.
|
||||
- Specify the programming language or framework if it's not obvious from the context.
|
||||
|
||||
Remember, the more precise and informative your prompt is, the better the AI can assist you in developing or modifying the OpenHands software.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ some flags being passed to `docker run` that make this possible:
|
||||
|
||||
```
|
||||
docker run # ...
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.17-nikolaik \
|
||||
-e SANDBOX_RUNTIME_CONTAINER_IMAGE=docker.all-hands.dev/all-hands-ai/runtime:0.21-nikolaik \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
# ...
|
||||
```
|
||||
@@ -26,30 +26,29 @@ that contains our Runtime server, as well as some basic utilities for Python and
|
||||
You can also [build your own runtime image](how-to/custom-sandbox-guide).
|
||||
|
||||
### Connecting to Your filesystem
|
||||
One useful feature here is the ability to connect to your local filesystem.
|
||||
One useful feature here is the ability to connect to your local filesystem. To mount your filesystem into the runtime:
|
||||
1. Set `WORKSPACE_BASE`:
|
||||
|
||||
To mount your filesystem into the runtime, first set WORKSPACE_BASE:
|
||||
```bash
|
||||
export WORKSPACE_BASE=/path/to/your/code
|
||||
```bash
|
||||
export WORKSPACE_BASE=/path/to/your/code
|
||||
|
||||
# Linux and Mac Example
|
||||
# export WORKSPACE_BASE=$HOME/OpenHands
|
||||
# Will set $WORKSPACE_BASE to /home/<username>/OpenHands
|
||||
#
|
||||
# WSL on Windows Example
|
||||
# export WORKSPACE_BASE=/mnt/c/dev/OpenHands
|
||||
# Will set $WORKSPACE_BASE to C:\dev\OpenHands
|
||||
```
|
||||
# Linux and Mac Example
|
||||
# export WORKSPACE_BASE=$HOME/OpenHands
|
||||
# Will set $WORKSPACE_BASE to /home/<username>/OpenHands
|
||||
#
|
||||
# WSL on Windows Example
|
||||
# export WORKSPACE_BASE=/mnt/c/dev/OpenHands
|
||||
# Will set $WORKSPACE_BASE to C:\dev\OpenHands
|
||||
```
|
||||
2. Add the following options to the `docker run` command:
|
||||
|
||||
then add the following options to the `docker run` command:
|
||||
|
||||
```bash
|
||||
docker run # ...
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
# ...
|
||||
```
|
||||
```bash
|
||||
docker run # ...
|
||||
-e SANDBOX_USER_ID=$(id -u) \
|
||||
-e WORKSPACE_MOUNT_PATH=$WORKSPACE_BASE \
|
||||
-v $WORKSPACE_BASE:/opt/workspace_base \
|
||||
# ...
|
||||
```
|
||||
|
||||
Be careful! There's nothing stopping the OpenHands agent from deleting or modifying
|
||||
any files that are mounted into its workspace.
|
||||
@@ -59,7 +58,7 @@ but seems to work well on most systems.
|
||||
|
||||
## All Hands Runtime
|
||||
The All Hands Runtime is currently in beta. You can request access by joining
|
||||
the #remote-runtime-limited-beta channel on Slack ([see the README](https://github.com/All-Hands-AI/OpenHands?tab=readme-ov-file#-join-our-community) for an invite).
|
||||
the #remote-runtime-limited-beta channel on Slack ([see the README](https://github.com/All-Hands-AI/OpenHands?tab=readme-ov-file#-how-to-join-the-community) for an invite).
|
||||
|
||||
To use the All Hands Runtime, set the following environment variables when
|
||||
starting OpenHands:
|
||||
|
||||
1322
docs/package-lock.json
generated
1322
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,10 +15,10 @@
|
||||
"typecheck": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.6.3",
|
||||
"@docusaurus/plugin-content-pages": "^3.6.3",
|
||||
"@docusaurus/preset-classic": "^3.6.3",
|
||||
"@docusaurus/theme-mermaid": "^3.6.3",
|
||||
"@docusaurus/core": "^3.7.0",
|
||||
"@docusaurus/plugin-content-pages": "^3.7.0",
|
||||
"@docusaurus/preset-classic": "^3.7.0",
|
||||
"@docusaurus/theme-mermaid": "^3.7.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"clsx": "^2.0.0",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.5.1",
|
||||
"@docusaurus/tsconfig": "^3.6.3",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.5.1",
|
||||
"typescript": "~5.7.2"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ const sidebars: SidebarsConfig = {
|
||||
docsSidebar: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Installation',
|
||||
label: 'Running OpenHands',
|
||||
id: 'usage/installation',
|
||||
},
|
||||
{
|
||||
@@ -23,15 +23,26 @@ const sidebars: SidebarsConfig = {
|
||||
id: 'usage/prompting/prompting-best-practices',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Customization',
|
||||
id: 'usage/prompting/customization',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
type: 'category',
|
||||
label: 'Microagents',
|
||||
id: 'usage/prompting/microagents',
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Overview',
|
||||
id: 'usage/prompting/microagents-overview',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Repository',
|
||||
id: 'usage/prompting/microagents-repo',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Public',
|
||||
id: 'usage/prompting/microagents-public',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -126,11 +137,6 @@ const sidebars: SidebarsConfig = {
|
||||
label: 'Custom Sandbox',
|
||||
id: 'usage/how-to/custom-sandbox-guide',
|
||||
},
|
||||
{
|
||||
type: 'doc',
|
||||
label: 'Persist Session Data',
|
||||
id: 'usage/how-to/persist-session-data',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
1
docs/static/img/backend_architecture.puml
vendored
1
docs/static/img/backend_architecture.puml
vendored
@@ -123,7 +123,6 @@ class openhands.state.State {
|
||||
updated_info: List[Tuple[Action, Observation]]
|
||||
}
|
||||
class openhands.observation.CmdOutputObservation {
|
||||
command_id: int
|
||||
command: str
|
||||
exit_code: int
|
||||
observation: str
|
||||
|
||||
@@ -75,6 +75,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
@@ -135,7 +137,6 @@ def complete_runtime(
|
||||
|
||||
action = CmdRunAction(
|
||||
command=f'chmod +x ./{script_name} && ./{script_name}',
|
||||
keep_prompt=False,
|
||||
)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
@@ -162,8 +163,7 @@ def complete_runtime(
|
||||
logger.info(f'Running get ground truth cmd: {script_name}')
|
||||
|
||||
action = CmdRunAction(
|
||||
command=f'chmod +x ./{script_name} && ./{script_name}',
|
||||
keep_prompt=False,
|
||||
command=f'chmod +x ./{script_name} && ./{script_name}'
|
||||
)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
|
||||
@@ -67,6 +67,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
|
||||
# copy 'draft_editor' config if exists
|
||||
config_copy = copy.deepcopy(config)
|
||||
@@ -143,10 +145,7 @@ def complete_runtime(
|
||||
)
|
||||
logger.info(f'Running test file: {script_name}')
|
||||
|
||||
action = CmdRunAction(
|
||||
command=f'python3 -m unittest {script_name}',
|
||||
keep_prompt=False,
|
||||
)
|
||||
action = CmdRunAction(command=f'python3 -m unittest {script_name}')
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
|
||||
@@ -73,6 +73,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
@@ -197,7 +199,7 @@ def complete_runtime(
|
||||
if obs.exit_code == 0:
|
||||
test_result['metadata']['1_copy_change_success'] = True
|
||||
|
||||
action = CmdRunAction(command=f'cat {generated_path}', keep_prompt=False)
|
||||
action = CmdRunAction(command=f'cat {generated_path}')
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
assert obs.exit_code == 0
|
||||
@@ -221,9 +223,7 @@ def complete_runtime(
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(
|
||||
command='cat /testing_files/results_biocoder.json', keep_prompt=False
|
||||
)
|
||||
action = CmdRunAction(command='cat /testing_files/results_biocoder.json')
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
if obs.exit_code == 0:
|
||||
|
||||
@@ -127,7 +127,6 @@ For each problem, OpenHands is given a set number of iterations to fix the faili
|
||||
"observation": "run",
|
||||
"content": "california_schools/california_schools.sqlite\r\n[(1.0,)]",
|
||||
"extras": {
|
||||
"command_id": -1,
|
||||
"command": "python3 0.py",
|
||||
"exit_code": 0
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
@@ -266,10 +268,7 @@ def initialize_runtime(
|
||||
runtime.copy_to(db_file, '/workspace')
|
||||
|
||||
# Check the database is copied
|
||||
action = CmdRunAction(
|
||||
command='cd /workspace && ls -l',
|
||||
keep_prompt=False,
|
||||
)
|
||||
action = CmdRunAction(command='cd /workspace && ls -l')
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
@@ -298,10 +297,7 @@ def complete_runtime(
|
||||
instance_id = instance.instance_id.replace('/', '__')
|
||||
path = os.path.join('/workspace', f'{instance_id}.py')
|
||||
|
||||
action = CmdRunAction(
|
||||
command=f'cat {path}',
|
||||
keep_prompt=False,
|
||||
)
|
||||
action = CmdRunAction(command=f'cat {path}')
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
|
||||
|
||||
@@ -50,6 +50,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ def initialize_runtime(
|
||||
action = CmdRunAction(
|
||||
command=f'git clone -b commit0_combined https://github.com/{instance["repo"]}.git'
|
||||
)
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -181,7 +181,7 @@ def initialize_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -191,7 +191,7 @@ def initialize_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command='git checkout -b openhands')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -201,7 +201,7 @@ def initialize_runtime(
|
||||
|
||||
# Install commit0
|
||||
action = CmdRunAction(command='/root/.cargo/bin/uv pip install commit0')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -231,7 +231,7 @@ def complete_runtime(
|
||||
workspace_dir_name = _get_commit0_workspace_dir_name(instance)
|
||||
|
||||
action = CmdRunAction(command='git add .')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -241,7 +241,7 @@ def complete_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command='git commit -m "openhands edits"')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -258,7 +258,7 @@ def complete_runtime(
|
||||
action = CmdRunAction(
|
||||
command=f"git diff {instance['base_commit']} HEAD -- . ':(exclude)spec.pdf.bz2'"
|
||||
)
|
||||
action.timeout = 600 + 100 * n_retries
|
||||
action.set_hard_timeout(600 + 100 * n_retries)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -282,7 +282,7 @@ def complete_runtime(
|
||||
action = CmdRunAction(
|
||||
command=f"{instance['test']['test_cmd']} --json-report --json-report-file=report.json --continue-on-collection-errors {test_dir} > test_output.txt 2>&1"
|
||||
)
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -292,7 +292,7 @@ def complete_runtime(
|
||||
)
|
||||
# Read test output
|
||||
action = CmdRunAction(command='cat test_output.txt')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -305,7 +305,7 @@ def complete_runtime(
|
||||
|
||||
# Save pytest exit code
|
||||
action = CmdRunAction(command='echo $?')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -318,7 +318,7 @@ def complete_runtime(
|
||||
|
||||
# Read the test report
|
||||
action = CmdRunAction(command='cat report.json')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -330,7 +330,7 @@ def complete_runtime(
|
||||
repo_name = instance['repo'].split('/')[1]
|
||||
repo_name = repo_name.replace('.', '-')
|
||||
action = CmdRunAction(command=f'commit0 get-tests {repo_name}')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
# logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
|
||||
@@ -77,6 +77,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
agent_config = AgentConfig(
|
||||
function_calling=False,
|
||||
codeact_enable_jupyter=True,
|
||||
|
||||
@@ -62,6 +62,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -76,6 +76,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,6 @@ For each problem, OpenHands is given a set number of iterations to fix the faili
|
||||
"observation": "run",
|
||||
"content": "[File: /workspace/Python__2.py (14 lines total)]\r\n1:def truncate_number(number: float) -> float:\r\n2: return number % 1.0 + 1.0\r\n3:\r\n4:\r\n5:\r\n6:\r\n7:\r\n8:\r\n9:def check(truncate_number):\r\n10: assert truncate_number(3.5) == 0.5\r\n11: assert abs(truncate_number(1.33) - 0.33) < 1e-6\r\n12: assert abs(truncate_number(123.456) - 0.456) < 1e-6\r\n13:\r\n14:check(truncate_number)",
|
||||
"extras": {
|
||||
"command_id": -1,
|
||||
"command": "open Python__2.py",
|
||||
"exit_code": 0
|
||||
}
|
||||
@@ -98,7 +97,6 @@ For each problem, OpenHands is given a set number of iterations to fix the faili
|
||||
"observation": "run",
|
||||
"content": "> > [File: /workspace/Python__2.py (14 lines total)]\r\n1:def truncate_number(number: float) -> float:\r\n2: return number % 1.0\r\n3:\r\n4:\r\n5:\r\n6:\r\n7:\r\n8:\r\n9:def check(truncate_number):\r\n10: assert truncate_number(3.5) == 0.5\r\n11: assert abs(truncate_number(1.33) - 0.33) < 1e-6\r\n12: assert abs(truncate_number(123.456) - 0.456) < 1e-6\r\n13:\r\n14:check(truncate_number)\r\nFile updated. Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.",
|
||||
"extras": {
|
||||
"command_id": -1,
|
||||
"command": "edit 2:2 <<EOF\n return number % 1.0\nEOF",
|
||||
"exit_code": 0
|
||||
}
|
||||
@@ -125,7 +123,6 @@ For each problem, OpenHands is given a set number of iterations to fix the faili
|
||||
"observation": "run",
|
||||
"content": "",
|
||||
"extras": {
|
||||
"command_id": -1,
|
||||
"command": "python3 Python__2.py",
|
||||
"exit_code": 0
|
||||
}
|
||||
|
||||
@@ -97,6 +97,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
@@ -169,9 +171,7 @@ def complete_runtime(
|
||||
num_workers = LANGUAGE_TO_NUM_WORKERS[language]
|
||||
python_imports = '\n'.join(IMPORT_HELPER[language])
|
||||
|
||||
action = CmdRunAction(
|
||||
command=f'cat /workspace/{_get_instance_id(instance)}.py', keep_prompt=False
|
||||
)
|
||||
action = CmdRunAction(command=f'cat /workspace/{_get_instance_id(instance)}.py')
|
||||
obs = runtime.run_action(action)
|
||||
assert obs.exit_code == 0
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ Please follow instruction [here](../../README.md#setup) to setup your local deve
|
||||
|
||||
## Test if your environment works
|
||||
|
||||
Follow the instructions here https://miniwob.farama.org/content/getting_started/ & https://miniwob.farama.org/content/viewing/
|
||||
to set up MiniWoB server in your local environment at http://localhost:8080/miniwob/
|
||||
|
||||
Access with browser the above MiniWoB URLs and see if they load correctly.
|
||||
|
||||
## Run Evaluation
|
||||
|
||||
@@ -119,6 +119,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
@@ -161,7 +163,7 @@ def complete_runtime(
|
||||
eval_script = os.path.join(task_path, 'run.sh')
|
||||
logger.info(f'Running evaluation script: {eval_script}')
|
||||
|
||||
action = CmdRunAction(command=f'cat {eval_script}', keep_prompt=False)
|
||||
action = CmdRunAction(command=f'cat {eval_script}')
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
if obs.exit_code == 0:
|
||||
|
||||
@@ -121,10 +121,7 @@ def initialize_runtime(
|
||||
runtime.copy_to(dataset_dir, '/workspace/benchmark/datasets', recursive=True)
|
||||
|
||||
# Check the dataset exists
|
||||
action = CmdRunAction(
|
||||
command='cd /workspace/benchmark/datasets && ls',
|
||||
keep_prompt=False,
|
||||
)
|
||||
action = CmdRunAction(command='cd /workspace/benchmark/datasets && ls')
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
@@ -154,10 +151,7 @@ def complete_runtime(
|
||||
|
||||
assert obs.exit_code == 0
|
||||
|
||||
action = CmdRunAction(
|
||||
command=f'cat pred_programs/{instance.pred_program_name}',
|
||||
keep_prompt=False,
|
||||
)
|
||||
action = CmdRunAction(command=f'cat pred_programs/{instance.pred_program_name}')
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
|
||||
|
||||
@@ -204,7 +204,7 @@ Then, in a separate Python environment with `streamlit` library, you can run the
|
||||
```bash
|
||||
# Make sure you are inside the cloned `evaluation` repo
|
||||
conda activate streamlit # if you follow the optional conda env setup above
|
||||
streamlit app.py --server.port 8501 --server.address 0.0.0.0
|
||||
streamlit run app.py --server.port 8501 --server.address 0.0.0.0
|
||||
```
|
||||
|
||||
Then you can access the SWE-Bench trajectory visualizer at `localhost:8501`.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
@@ -11,7 +12,11 @@ from swebench.harness.run_evaluation import (
|
||||
)
|
||||
from swebench.harness.test_spec import SWEbenchInstance, TestSpec, make_test_spec
|
||||
from swebench.harness.utils import load_swebench_dataset
|
||||
from tqdm import tqdm
|
||||
|
||||
from evaluation.benchmarks.swe_bench.resource.mapping import (
|
||||
get_instance_resource_factor,
|
||||
)
|
||||
from evaluation.benchmarks.swe_bench.run_infer import get_instance_docker_image
|
||||
from evaluation.utils.shared import (
|
||||
EvalMetadata,
|
||||
@@ -66,7 +71,7 @@ def process_git_patch(patch):
|
||||
return patch
|
||||
|
||||
|
||||
def get_config(instance: pd.Series) -> AppConfig:
|
||||
def get_config(metadata: EvalMetadata, instance: pd.Series) -> AppConfig:
|
||||
# We use a different instance image for the each instance of swe-bench eval
|
||||
base_container_image = get_instance_docker_image(instance['instance_id'])
|
||||
logger.info(
|
||||
@@ -81,10 +86,14 @@ def get_config(instance: pd.Series) -> AppConfig:
|
||||
base_container_image=base_container_image,
|
||||
use_host_network=False,
|
||||
# large enough timeout, since some testcases take very long to run
|
||||
timeout=1800,
|
||||
timeout=600,
|
||||
api_key=os.environ.get('ALLHANDS_API_KEY', None),
|
||||
remote_runtime_api_url=os.environ.get('SANDBOX_REMOTE_RUNTIME_API_URL'),
|
||||
remote_runtime_init_timeout=3600,
|
||||
remote_runtime_resource_factor=get_instance_resource_factor(
|
||||
dataset_name=metadata.dataset,
|
||||
instance_id=instance['instance_id'],
|
||||
),
|
||||
),
|
||||
# do not mount workspace
|
||||
workspace_base=None,
|
||||
@@ -98,6 +107,7 @@ def process_instance(
|
||||
metadata: EvalMetadata,
|
||||
reset_logger: bool = True,
|
||||
log_dir: str | None = None,
|
||||
runtime_failure_count: int = 0,
|
||||
) -> EvalOutput:
|
||||
"""
|
||||
Evaluate agent performance on a SWE-bench problem instance.
|
||||
@@ -122,7 +132,7 @@ def process_instance(
|
||||
else:
|
||||
logger.info(f'Starting evaluation for instance {instance.instance_id}.')
|
||||
|
||||
config = get_config(instance)
|
||||
config = get_config(metadata, instance)
|
||||
instance_id = instance.instance_id
|
||||
model_patch = instance['model_patch']
|
||||
test_spec: TestSpec = instance['test_spec']
|
||||
@@ -146,46 +156,56 @@ def process_instance(
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
# Get patch and save it to /tmp/patch.diff
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Patch file
|
||||
patch_file_path = os.path.join(temp_dir, 'patch.diff')
|
||||
with open(patch_file_path, 'w') as f:
|
||||
f.write(model_patch)
|
||||
runtime.copy_to(patch_file_path, '/tmp')
|
||||
# Eval script
|
||||
eval_script_path = os.path.join(temp_dir, 'eval.sh')
|
||||
with open(eval_script_path, 'w') as f:
|
||||
f.write(test_spec.eval_script)
|
||||
runtime.copy_to(eval_script_path, '/tmp')
|
||||
|
||||
# Set +x
|
||||
action = CmdRunAction(command='chmod +x /tmp/eval.sh')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
# Apply patch
|
||||
exec_command = (
|
||||
'cd /testbed && '
|
||||
"(git apply -v /tmp/patch.diff && echo 'APPLY_PATCH_PASS' || "
|
||||
"(echo 'Failed to apply patch with git apply, trying with patch command...' && "
|
||||
"(patch --batch --fuzz=5 -p1 -i /tmp/patch.diff && echo 'APPLY_PATCH_PASS' || "
|
||||
"echo 'APPLY_PATCH_FAIL')))"
|
||||
)
|
||||
action = CmdRunAction(command=exec_command, keep_prompt=False)
|
||||
action.timeout = 600
|
||||
obs = runtime.run_action(action)
|
||||
assert isinstance(obs, CmdOutputObservation)
|
||||
apply_patch_output = obs.content
|
||||
assert isinstance(apply_patch_output, str)
|
||||
instance['test_result']['apply_patch_output'] = apply_patch_output
|
||||
# Increase resource_factor with increasing attempt_id
|
||||
if runtime_failure_count > 0:
|
||||
config.sandbox.remote_runtime_resource_factor = min(
|
||||
config.sandbox.remote_runtime_resource_factor * (2**runtime_failure_count),
|
||||
8,
|
||||
)
|
||||
logger.warning(
|
||||
f'This is the {runtime_failure_count + 1}th attempt for instance {instance.instance_id}, setting resource factor to {config.sandbox.remote_runtime_resource_factor}'
|
||||
)
|
||||
|
||||
try:
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
# Get patch and save it to /tmp/patch.diff
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
# Patch file
|
||||
patch_file_path = os.path.join(temp_dir, 'patch.diff')
|
||||
with open(patch_file_path, 'w') as f:
|
||||
f.write(model_patch)
|
||||
runtime.copy_to(patch_file_path, '/tmp')
|
||||
# Eval script
|
||||
eval_script_path = os.path.join(temp_dir, 'eval.sh')
|
||||
with open(eval_script_path, 'w') as f:
|
||||
f.write(test_spec.eval_script)
|
||||
runtime.copy_to(eval_script_path, '/tmp')
|
||||
|
||||
# Set +x
|
||||
action = CmdRunAction(command='chmod +x /tmp/eval.sh')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert obs.exit_code == 0
|
||||
|
||||
# Apply patch
|
||||
exec_command = (
|
||||
'cd /testbed && '
|
||||
"(git apply -v /tmp/patch.diff && echo 'APPLY_PATCH_PASS' || "
|
||||
"(echo 'Failed to apply patch with git apply, trying with patch command...' && "
|
||||
"(patch --batch --fuzz=5 -p1 -i /tmp/patch.diff && echo 'APPLY_PATCH_PASS' || "
|
||||
"echo 'APPLY_PATCH_FAIL')))"
|
||||
)
|
||||
action = CmdRunAction(command=exec_command)
|
||||
action.set_hard_timeout(600)
|
||||
obs = runtime.run_action(action)
|
||||
assert isinstance(obs, CmdOutputObservation)
|
||||
apply_patch_output = obs.content
|
||||
assert isinstance(apply_patch_output, str)
|
||||
instance['test_result']['apply_patch_output'] = apply_patch_output
|
||||
|
||||
if 'APPLY_PATCH_FAIL' in apply_patch_output:
|
||||
logger.info(f'[{instance_id}] {APPLY_PATCH_FAIL}:\n{apply_patch_output}')
|
||||
instance['test_result']['report']['failed_apply_patch'] = True
|
||||
@@ -200,10 +220,8 @@ def process_instance(
|
||||
|
||||
# Run eval script in background and save output to log file
|
||||
log_file = '/tmp/eval_output.log'
|
||||
action = CmdRunAction(
|
||||
command=f'/tmp/eval.sh > {log_file} 2>&1 & echo $!', keep_prompt=False
|
||||
)
|
||||
action.timeout = 60 # Short timeout just to get the process ID
|
||||
action = CmdRunAction(command=f'/tmp/eval.sh > {log_file} 2>&1 & echo $!')
|
||||
action.set_hard_timeout(300) # Short timeout just to get the process ID
|
||||
obs = runtime.run_action(action)
|
||||
|
||||
if isinstance(obs, CmdOutputObservation) and obs.exit_code == 0:
|
||||
@@ -224,9 +242,9 @@ def process_instance(
|
||||
instance['test_result']['report']['test_timeout'] = True
|
||||
break
|
||||
check_action = CmdRunAction(
|
||||
command=f'ps -p {pid} > /dev/null; echo $?', keep_prompt=False
|
||||
command=f'ps -p {pid} > /dev/null; echo $?'
|
||||
)
|
||||
check_action.timeout = 60
|
||||
check_action.set_hard_timeout(300)
|
||||
check_obs = runtime.run_action(check_action)
|
||||
if (
|
||||
isinstance(check_obs, CmdOutputObservation)
|
||||
@@ -242,8 +260,8 @@ def process_instance(
|
||||
time.sleep(30) # Wait for 30 seconds before checking again
|
||||
|
||||
# Read the log file
|
||||
cat_action = CmdRunAction(command=f'cat {log_file}', keep_prompt=False)
|
||||
cat_action.timeout = 300
|
||||
cat_action = CmdRunAction(command=f'cat {log_file}')
|
||||
cat_action.set_hard_timeout(300)
|
||||
cat_obs = runtime.run_action(cat_action)
|
||||
|
||||
# Grade answer
|
||||
@@ -343,7 +361,14 @@ if __name__ == '__main__':
|
||||
|
||||
# Load predictions
|
||||
assert args.input_file.endswith('.jsonl'), 'Input file must be a jsonl file.'
|
||||
predictions = pd.read_json(args.input_file, lines=True)
|
||||
required_fields = ['instance_id', 'model_patch', 'test_result']
|
||||
with open(args.input_file) as f:
|
||||
predictions = pd.DataFrame.from_records(
|
||||
[
|
||||
{k: v for k, v in json.loads(line).items() if k in required_fields}
|
||||
for line in tqdm(f, desc='Loading predictions')
|
||||
]
|
||||
)
|
||||
assert (
|
||||
'instance_id' in predictions.columns
|
||||
), 'Input file must contain instance_id column.'
|
||||
|
||||
38
evaluation/benchmarks/swe_bench/resource/mapping.py
Normal file
38
evaluation/benchmarks/swe_bench/resource/mapping.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""Mapping instance_id to resource_factor.
|
||||
|
||||
Different instances may have different resource requirements.
|
||||
e.g., some instances may require more memory/CPU to run inference.
|
||||
This file tracks the resource requirements of different instances.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
DEFAULT_RUNTIME_RESOURCE_FACTOR = int(
|
||||
os.environ.get('DEFAULT_RUNTIME_RESOURCE_FACTOR', 1)
|
||||
)
|
||||
|
||||
# dataset to resource mapping
|
||||
_global_resource_mapping: dict[str, dict[str, float]] = {}
|
||||
|
||||
|
||||
def get_resource_mapping(dataset_name: str) -> dict[str, float]:
|
||||
if dataset_name not in _global_resource_mapping:
|
||||
file_path = os.path.join(CUR_DIR, f'{dataset_name}.json')
|
||||
if not os.path.exists(file_path):
|
||||
logger.warning(f'Resource mapping for {dataset_name} not found.')
|
||||
return None
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
_global_resource_mapping[dataset_name] = json.load(f)
|
||||
logger.info(f'Loaded resource mapping for {dataset_name}')
|
||||
return _global_resource_mapping[dataset_name]
|
||||
|
||||
|
||||
def get_instance_resource_factor(dataset_name: str, instance_id: str) -> int:
|
||||
resource_mapping = get_resource_mapping(dataset_name)
|
||||
if resource_mapping is None:
|
||||
return DEFAULT_RUNTIME_RESOURCE_FACTOR
|
||||
return int(resource_mapping.get(instance_id, DEFAULT_RUNTIME_RESOURCE_FACTOR))
|
||||
@@ -0,0 +1 @@
|
||||
{"pydata__xarray-6721": 8, "pytest-dev__pytest-7236": 8, "matplotlib__matplotlib-24627": 4, "django__django-15561": 4, "django__django-15098": 4, "django__django-14771": 4, "sympy__sympy-21612": 4, "sympy__sympy-15345": 4, "psf__requests-5414": 4, "astropy__astropy-14508": 2, "django__django-11451": 2, "django__django-11477": 2, "django__django-10880": 2, "django__django-11163": 2, "django__django-11815": 2, "astropy__astropy-14369": 2, "django__django-10097": 2, "django__django-10554": 2, "django__django-12304": 2, "django__django-12325": 2, "django__django-11551": 2, "django__django-11734": 2, "django__django-13109": 2, "django__django-13089": 2, "django__django-13343": 2, "django__django-13363": 2, "django__django-13809": 2, "django__django-13810": 2, "django__django-13786": 2, "django__django-13807": 2, "django__django-14493": 2, "django__django-11820": 2, "django__django-11951": 2, "django__django-11964": 2, "astropy__astropy-14309": 2, "astropy__astropy-14365": 2, "astropy__astropy-12907": 2, "astropy__astropy-14182": 2, "django__django-15161": 2, "django__django-15128": 2, "django__django-14999": 2, "django__django-14915": 2, "django__django-14752": 2, "django__django-14765": 2, "django__django-14089": 2, "django__django-15252": 2, "django__django-15380": 2, "django__django-15382": 2, "django__django-15499": 2, "django__django-15467": 2, "django__django-15280": 2, "django__django-15315": 2, "django__django-15277": 2, "django__django-15268": 2, "django__django-15629": 2, "django__django-15695": 2, "django__django-15732": 2, "django__django-15863": 2, "django__django-16082": 2, "django__django-16145": 2, "django__django-16256": 2, "django__django-16429": 2, "django__django-16454": 2, "django__django-16493": 2, "matplotlib__matplotlib-13989": 2, "matplotlib__matplotlib-20488": 2, "django__django-15503": 2, "django__django-15525": 2, "django__django-15375": 2, "django__django-15278": 2, "matplotlib__matplotlib-21568": 2, "matplotlib__matplotlib-20859": 2, "matplotlib__matplotlib-20826": 2, "matplotlib__matplotlib-20676": 2, "matplotlib__matplotlib-23412": 2, "matplotlib__matplotlib-22719": 2, "matplotlib__matplotlib-23299": 2, "matplotlib__matplotlib-22865": 2, "matplotlib__matplotlib-24149": 2, "matplotlib__matplotlib-24177": 2, "matplotlib__matplotlib-24570": 2, "matplotlib__matplotlib-24637": 2, "matplotlib__matplotlib-24970": 2, "matplotlib__matplotlib-23476": 2, "matplotlib__matplotlib-24026": 2, "matplotlib__matplotlib-23314": 2, "matplotlib__matplotlib-25332": 2, "matplotlib__matplotlib-25311": 2, "matplotlib__matplotlib-25122": 2, "matplotlib__matplotlib-25479": 2, "matplotlib__matplotlib-26342": 2, "psf__requests-2317": 2, "matplotlib__matplotlib-25960": 2, "matplotlib__matplotlib-25775": 2, "pydata__xarray-4356": 2, "pydata__xarray-4075": 2, "pydata__xarray-6461": 2, "pydata__xarray-4687": 2, "pydata__xarray-6599": 2, "pylint-dev__pylint-4661": 2, "django__django-15554": 2, "django__django-15563": 2, "pytest-dev__pytest-5262": 2, "pytest-dev__pytest-10081": 2, "scikit-learn__scikit-learn-12973": 2, "scikit-learn__scikit-learn-13124": 2, "scikit-learn__scikit-learn-13779": 2, "scikit-learn__scikit-learn-14141": 2, "scikit-learn__scikit-learn-13439": 2, "scikit-learn__scikit-learn-13496": 2, "scikit-learn__scikit-learn-15100": 2, "scikit-learn__scikit-learn-25102": 2, "scikit-learn__scikit-learn-25232": 2, "scikit-learn__scikit-learn-25747": 2, "scikit-learn__scikit-learn-26323": 2, "scikit-learn__scikit-learn-9288": 2, "scikit-learn__scikit-learn-14496": 2, "scikit-learn__scikit-learn-14629": 2, "sphinx-doc__sphinx-8265": 2, "sphinx-doc__sphinx-8548": 2, "sphinx-doc__sphinx-8593": 2, "sphinx-doc__sphinx-8595": 2, "sphinx-doc__sphinx-8621": 2, "sphinx-doc__sphinx-8638": 2, "sphinx-doc__sphinx-9229": 2, "sphinx-doc__sphinx-9281": 2, "sphinx-doc__sphinx-9461": 2, "sphinx-doc__sphinx-9591": 2, "sphinx-doc__sphinx-9658": 2, "sphinx-doc__sphinx-9673": 2, "sympy__sympy-12096": 2, "sympy__sympy-12481": 2, "sphinx-doc__sphinx-10323": 2, "sphinx-doc__sphinx-7590": 2, "sympy__sympy-13877": 2, "sympy__sympy-12489": 2, "sympy__sympy-15809": 2, "sympy__sympy-14711": 2, "sympy__sympy-16597": 2, "sympy__sympy-16766": 2, "sympy__sympy-16792": 2, "sympy__sympy-15875": 2, "sympy__sympy-17655": 2, "sympy__sympy-18189": 2, "sympy__sympy-18763": 2, "sympy__sympy-19040": 2, "sympy__sympy-19495": 2, "sympy__sympy-19637": 2, "sympy__sympy-19783": 2, "sympy__sympy-17630": 2, "sympy__sympy-20428": 2, "sympy__sympy-20590": 2, "sympy__sympy-20801": 2, "sympy__sympy-21379": 2, "sympy__sympy-21847": 2, "sympy__sympy-22456": 2, "sympy__sympy-22714": 2, "sympy__sympy-22914": 2, "sympy__sympy-23262": 2, "sympy__sympy-23413": 2, "sympy__sympy-23534": 2, "sympy__sympy-24066": 2, "sympy__sympy-24213": 2, "sympy__sympy-24443": 2, "sympy__sympy-24562": 2, "sympy__sympy-24661": 2}
|
||||
@@ -9,12 +9,16 @@ import toml
|
||||
from datasets import load_dataset
|
||||
|
||||
import openhands.agenthub
|
||||
from evaluation.benchmarks.swe_bench.resource.mapping import (
|
||||
get_instance_resource_factor,
|
||||
)
|
||||
from evaluation.utils.shared import (
|
||||
EvalException,
|
||||
EvalMetadata,
|
||||
EvalOutput,
|
||||
assert_and_raise,
|
||||
codeact_user_response,
|
||||
get_metrics,
|
||||
is_fatal_evaluation_error,
|
||||
make_metadata,
|
||||
prepare_dataset,
|
||||
@@ -40,9 +44,10 @@ from openhands.utils.async_utils import call_async_from_sync
|
||||
from openhands.utils.shutdown_listener import sleep_if_should_continue
|
||||
|
||||
USE_HINT_TEXT = os.environ.get('USE_HINT_TEXT', 'false').lower() == 'true'
|
||||
USE_INSTANCE_IMAGE = os.environ.get('USE_INSTANCE_IMAGE', 'false').lower() == 'true'
|
||||
USE_INSTANCE_IMAGE = os.environ.get('USE_INSTANCE_IMAGE', 'true').lower() == 'true'
|
||||
RUN_WITH_BROWSING = os.environ.get('RUN_WITH_BROWSING', 'false').lower() == 'true'
|
||||
|
||||
|
||||
AGENT_CLS_TO_FAKE_USER_RESPONSE_FN = {
|
||||
'CodeActAgent': codeact_user_response,
|
||||
}
|
||||
@@ -134,6 +139,10 @@ def get_config(
|
||||
remote_runtime_api_url=os.environ.get('SANDBOX_REMOTE_RUNTIME_API_URL'),
|
||||
keep_runtime_alive=False,
|
||||
remote_runtime_init_timeout=3600,
|
||||
remote_runtime_resource_factor=get_instance_resource_factor(
|
||||
dataset_name=metadata.dataset,
|
||||
instance_id=instance['instance_id'],
|
||||
),
|
||||
),
|
||||
# do not mount workspace
|
||||
workspace_base=None,
|
||||
@@ -148,6 +157,8 @@ def get_config(
|
||||
codeact_enable_jupyter=False,
|
||||
codeact_enable_browsing=RUN_WITH_BROWSING,
|
||||
codeact_enable_llm_editor=False,
|
||||
condenser=metadata.condenser_config,
|
||||
enable_prompt_extensions=False,
|
||||
)
|
||||
config.set_agent_config(agent_config)
|
||||
return config
|
||||
@@ -171,7 +182,7 @@ def initialize_runtime(
|
||||
action = CmdRunAction(
|
||||
command=f"""echo 'export SWE_INSTANCE_ID={instance['instance_id']}' >> ~/.bashrc && echo 'export PIP_CACHE_DIR=~/.cache/pip' >> ~/.bashrc && echo "alias git='git --no-pager'" >> ~/.bashrc"""
|
||||
)
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -180,7 +191,7 @@ def initialize_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command="""export USER=$(whoami); echo USER=${USER} """)
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -192,7 +203,7 @@ def initialize_runtime(
|
||||
|
||||
# inject the instance info
|
||||
action = CmdRunAction(command='mkdir -p /swe_util/eval_data/instances')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -221,14 +232,14 @@ def initialize_runtime(
|
||||
'/swe_util/',
|
||||
)
|
||||
action = CmdRunAction(command='cat ~/.bashrc')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to cat ~/.bashrc: {str(obs)}')
|
||||
|
||||
action = CmdRunAction(command='source ~/.bashrc')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -237,7 +248,7 @@ def initialize_runtime(
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to source ~/.bashrc: {str(obs)}')
|
||||
|
||||
action = CmdRunAction(command='source /swe_util/instance_swe_entry.sh')
|
||||
action.timeout = 3600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -247,7 +258,7 @@ def initialize_runtime(
|
||||
)
|
||||
else:
|
||||
action = CmdRunAction(command='source /swe_util/swe_entry.sh')
|
||||
action.timeout = 1800
|
||||
action.set_hard_timeout(1800)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -257,7 +268,7 @@ def initialize_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -267,7 +278,7 @@ def initialize_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command='git reset --hard')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -276,12 +287,22 @@ def initialize_runtime(
|
||||
action = CmdRunAction(
|
||||
command='for remote_name in $(git remote); do git remote remove "${remote_name}"; done'
|
||||
)
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(obs.exit_code == 0, f'Failed to remove git remotes: {str(obs)}')
|
||||
|
||||
action = CmdRunAction(command='which python')
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0 and 'testbed' in obs.content,
|
||||
f'Expected to find python interpreter from testbed, but got: {str(obs)}',
|
||||
)
|
||||
|
||||
logger.info('-' * 30)
|
||||
logger.info('END Runtime Initialization Fn')
|
||||
logger.info('-' * 30)
|
||||
@@ -304,7 +325,7 @@ def complete_runtime(
|
||||
workspace_dir_name = _get_swebench_workspace_dir_name(instance)
|
||||
|
||||
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -314,7 +335,7 @@ def complete_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command='git config --global core.pager ""')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -324,7 +345,7 @@ def complete_runtime(
|
||||
)
|
||||
|
||||
action = CmdRunAction(command='git add -A')
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -337,10 +358,9 @@ def complete_runtime(
|
||||
git_patch = None
|
||||
while n_retries < 5:
|
||||
action = CmdRunAction(
|
||||
command=f'git diff --no-color --cached {instance["base_commit"]}',
|
||||
keep_prompt=False,
|
||||
command=f'git diff --no-color --cached {instance["base_commit"]}'
|
||||
)
|
||||
action.timeout = 600 + 100 * n_retries
|
||||
action.set_hard_timeout(max(300 + 100 * n_retries, 600))
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -385,10 +405,10 @@ def process_instance(
|
||||
if runtime_failure_count > 0:
|
||||
config.sandbox.remote_runtime_resource_factor = min(
|
||||
config.sandbox.remote_runtime_resource_factor * (2**runtime_failure_count),
|
||||
2, # hardcode maximum resource factor to 2
|
||||
8,
|
||||
)
|
||||
logger.warning(
|
||||
f'This is the second attempt for instance {instance.instance_id}, setting resource factor to {config.sandbox.remote_runtime_resource_factor}'
|
||||
f'This is the {runtime_failure_count + 1}th attempt for instance {instance.instance_id}, setting resource factor to {config.sandbox.remote_runtime_resource_factor}'
|
||||
)
|
||||
runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
@@ -439,7 +459,7 @@ def process_instance(
|
||||
|
||||
# NOTE: this is NO LONGER the event stream, but an agent history that includes delegate agent's events
|
||||
histories = [event_to_dict(event) for event in state.history]
|
||||
metrics = state.metrics.get() if state.metrics else None
|
||||
metrics = get_metrics(state)
|
||||
|
||||
# Save the output
|
||||
output = EvalOutput(
|
||||
@@ -468,6 +488,10 @@ def filter_dataset(dataset: pd.DataFrame, filter_column: str) -> pd.DataFrame:
|
||||
subset = dataset[dataset[filter_column].isin(selected_ids)]
|
||||
logger.info(f'Retained {subset.shape[0]} tasks after filtering')
|
||||
return subset
|
||||
skip_ids = os.environ.get('SKIP_IDS', '').split(',')
|
||||
if len(skip_ids) > 0:
|
||||
logger.info(f'Filtering {len(skip_ids)} tasks from "SKIP_IDS"...')
|
||||
return dataset[~dataset[filter_column].isin(skip_ids)]
|
||||
return dataset
|
||||
|
||||
|
||||
@@ -490,8 +514,10 @@ if __name__ == '__main__':
|
||||
# NOTE: It is preferable to load datasets from huggingface datasets and perform post-processing
|
||||
# so we don't need to manage file uploading to OpenHands's repo
|
||||
dataset = load_dataset(args.dataset, split=args.split)
|
||||
logger.info(f'Loaded dataset {args.dataset} with split {args.split}')
|
||||
swe_bench_tests = filter_dataset(dataset.to_pandas(), 'instance_id')
|
||||
logger.info(
|
||||
f'Loaded dataset {args.dataset} with split {args.split}: {len(swe_bench_tests)} tasks'
|
||||
)
|
||||
|
||||
llm_config = None
|
||||
if args.llm_config:
|
||||
@@ -520,6 +546,7 @@ if __name__ == '__main__':
|
||||
)
|
||||
|
||||
output_file = os.path.join(metadata.eval_output_dir, 'output.jsonl')
|
||||
print(f'### OUTPUT FILE: {output_file} ###')
|
||||
instances = prepare_dataset(swe_bench_tests, output_file, args.eval_n_limit)
|
||||
|
||||
if len(instances) > 0 and not isinstance(
|
||||
@@ -535,4 +562,5 @@ if __name__ == '__main__':
|
||||
args.eval_num_workers,
|
||||
process_instance,
|
||||
timeout_seconds=120 * 60, # 2 hour PER instance should be more than enough
|
||||
max_retries=5,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import argparse
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
from glob import glob
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
tqdm.pandas()
|
||||
|
||||
|
||||
# Load trajectories for resolved instances
|
||||
def load_completions(output_dir: str, instance_id: str):
|
||||
glob_path = os.path.join(output_dir, 'llm_completions', instance_id, '*.json')
|
||||
files = sorted(glob(glob_path)) # this is ascending order
|
||||
# pick the last file (last turn)
|
||||
try:
|
||||
file_path = files[-1]
|
||||
except IndexError:
|
||||
# print(f'No files found for instance {instance_id}: files={files}')
|
||||
return None
|
||||
with open(file_path, 'r') as f:
|
||||
result = json.load(f)
|
||||
# create messages
|
||||
messages = result['messages']
|
||||
messages.append(result['response']['choices'][0]['message'])
|
||||
tools = result['kwargs']['tools']
|
||||
return {
|
||||
'messages': messages,
|
||||
'tools': tools,
|
||||
}
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('jsonl_path', type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
output_dir = os.path.dirname(args.jsonl_path)
|
||||
output_path = os.path.join(output_dir, 'output.with_completions.jsonl.gz')
|
||||
|
||||
# Check if output would be different from input
|
||||
needs_update = False
|
||||
with open(args.jsonl_path, 'r') as f_in:
|
||||
for line in tqdm(f_in, desc='Checking for changes'):
|
||||
data = json.loads(line)
|
||||
new_completions = load_completions(output_dir, data['instance_id'])
|
||||
current_completions = data.get('raw_completions')
|
||||
if current_completions != new_completions:
|
||||
needs_update = True
|
||||
break
|
||||
|
||||
if not needs_update:
|
||||
print('No updates required. Skipping file update.')
|
||||
exit(0)
|
||||
|
||||
if os.path.exists(output_path):
|
||||
print(f'Output file already exists at {output_path}, overwriting? (y/n)')
|
||||
if input() != 'y':
|
||||
print('Exiting...')
|
||||
exit(0)
|
||||
|
||||
# Process line by line
|
||||
with open(args.jsonl_path, 'r') as f_in, gzip.open(output_path, 'wt') as f_out:
|
||||
for line in tqdm(f_in):
|
||||
data = json.loads(line)
|
||||
data['raw_completions'] = load_completions(output_dir, data['instance_id'])
|
||||
f_out.write(json.dumps(data) + '\n')
|
||||
|
||||
print(f'Saved compressed output to {output_path}')
|
||||
@@ -1,13 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import pandas as pd
|
||||
from termcolor import colored
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Compare two swe_bench output JSONL files and print the resolved diff'
|
||||
)
|
||||
parser.add_argument('input_file_1', type=str)
|
||||
parser.add_argument('input_file_2', type=str)
|
||||
parser.add_argument(
|
||||
'--show-paths',
|
||||
action='store_true',
|
||||
help='Show visualization paths for failed instances',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
df1 = pd.read_json(args.input_file_1, orient='records', lines=True)
|
||||
@@ -58,10 +65,60 @@ df_diff_y_only = df_diff[~df_diff['resolved_x'] & df_diff['resolved_y']].sort_va
|
||||
print(f'# y resolved but x not={df_diff_y_only.shape[0]}')
|
||||
print(df_diff_y_only[['instance_id', 'report_x', 'report_y']])
|
||||
# get instance_id from df_diff_y_only
|
||||
print('-' * 100)
|
||||
print('Instances that x resolved but y not:')
|
||||
print(df_diff_x_only['instance_id'].tolist())
|
||||
|
||||
x_only_by_repo = {}
|
||||
for instance_id in df_diff_x_only['instance_id'].tolist():
|
||||
repo = instance_id.split('__')[0]
|
||||
x_only_by_repo.setdefault(repo, []).append(instance_id)
|
||||
y_only_by_repo = {}
|
||||
for instance_id in df_diff_y_only['instance_id'].tolist():
|
||||
repo = instance_id.split('__')[0]
|
||||
y_only_by_repo.setdefault(repo, []).append(instance_id)
|
||||
|
||||
print('-' * 100)
|
||||
print('Instances that y resolved but x not:')
|
||||
print(df_diff_y_only['instance_id'].tolist())
|
||||
print(
|
||||
colored('Repository comparison (x resolved vs y resolved):', 'cyan', attrs=['bold'])
|
||||
)
|
||||
all_repos = sorted(set(list(x_only_by_repo.keys()) + list(y_only_by_repo.keys())))
|
||||
|
||||
# Calculate diffs and sort repos by diff magnitude
|
||||
repo_diffs = []
|
||||
for repo in all_repos:
|
||||
x_count = len(x_only_by_repo.get(repo, []))
|
||||
y_count = len(y_only_by_repo.get(repo, []))
|
||||
diff = abs(x_count - y_count)
|
||||
repo_diffs.append((repo, diff))
|
||||
|
||||
# Sort by diff (descending) and then by repo name
|
||||
repo_diffs.sort(key=lambda x: (-x[1], x[0]))
|
||||
threshold = max(
|
||||
3, sum(d[1] for d in repo_diffs) / len(repo_diffs) * 1.5 if repo_diffs else 0
|
||||
)
|
||||
|
||||
x_input_file_folder = os.path.join(os.path.dirname(args.input_file_1), 'output.viz')
|
||||
|
||||
for repo, diff in repo_diffs:
|
||||
x_instances = x_only_by_repo.get(repo, [])
|
||||
y_instances = y_only_by_repo.get(repo, [])
|
||||
|
||||
# Determine if this repo has a significant diff
|
||||
is_significant = diff >= threshold
|
||||
repo_color = 'red' if is_significant else 'yellow'
|
||||
|
||||
print(f"\n{colored(repo, repo_color, attrs=['bold'])}:")
|
||||
print(colored(f'Difference: {diff} instances!', repo_color, attrs=['bold']))
|
||||
print(colored(f'X resolved but Y failed: ({len(x_instances)} instances)', 'green'))
|
||||
if x_instances:
|
||||
print(' ' + str(x_instances))
|
||||
print(colored(f'Y resolved but X failed: ({len(y_instances)} instances)', 'red'))
|
||||
if y_instances:
|
||||
print(' ' + str(y_instances))
|
||||
if args.show_paths:
|
||||
print(
|
||||
colored(' Visualization path for X failed:', 'cyan', attrs=['bold'])
|
||||
)
|
||||
for instance_id in y_instances:
|
||||
instance_file = os.path.join(
|
||||
x_input_file_folder, f'false.{instance_id}.md'
|
||||
)
|
||||
print(f' {instance_file}')
|
||||
|
||||
@@ -20,6 +20,13 @@ output_md_folder = args.oh_output_file.replace('.jsonl', '.viz')
|
||||
print(f'Converting {args.oh_output_file} to markdown files in {output_md_folder}')
|
||||
|
||||
oh_format = pd.read_json(args.oh_output_file, orient='records', lines=True)
|
||||
|
||||
swebench_eval_file = args.oh_output_file.replace('.jsonl', '.swebench_eval.jsonl')
|
||||
if os.path.exists(swebench_eval_file):
|
||||
eval_output_df = pd.read_json(swebench_eval_file, orient='records', lines=True)
|
||||
else:
|
||||
eval_output_df = None
|
||||
|
||||
# model name is the folder name of oh_output_file
|
||||
model_name = os.path.basename(os.path.dirname(args.oh_output_file))
|
||||
|
||||
@@ -50,7 +57,7 @@ def convert_history_to_str(history):
|
||||
return ret
|
||||
|
||||
|
||||
def write_row_to_md_file(row):
|
||||
def write_row_to_md_file(row, instance_id_to_test_result):
|
||||
if 'git_patch' in row:
|
||||
model_patch = row['git_patch']
|
||||
elif 'test_result' in row and 'git_patch' in row['test_result']:
|
||||
@@ -58,8 +65,21 @@ def write_row_to_md_file(row):
|
||||
else:
|
||||
raise ValueError(f'Row {row} does not have a git_patch')
|
||||
|
||||
if 'report' in row:
|
||||
resolved = row['report'].get('resolved', False)
|
||||
test_output = None
|
||||
if row['instance_id'] in instance_id_to_test_result:
|
||||
report = instance_id_to_test_result[row['instance_id']].get('report', {})
|
||||
resolved = report.get('resolved', False)
|
||||
test_output = instance_id_to_test_result[row['instance_id']].get(
|
||||
'test_output', None
|
||||
)
|
||||
elif 'report' in row and row['report'] is not None:
|
||||
if not isinstance(row['report'], dict):
|
||||
resolved = None
|
||||
print(
|
||||
f'ERROR: Report is not a dict, but a {type(row["report"])}. Row: {row}'
|
||||
)
|
||||
else:
|
||||
resolved = row['report'].get('resolved', False)
|
||||
else:
|
||||
resolved = None
|
||||
|
||||
@@ -84,5 +104,18 @@ def write_row_to_md_file(row):
|
||||
f.write('## Model Patch\n')
|
||||
f.write(f'{process_git_patch(model_patch)}\n')
|
||||
|
||||
f.write('## Test Output\n')
|
||||
f.write(str(test_output))
|
||||
|
||||
oh_format.progress_apply(write_row_to_md_file, axis=1)
|
||||
|
||||
instance_id_to_test_result = {}
|
||||
if eval_output_df is not None:
|
||||
instance_id_to_test_result = (
|
||||
eval_output_df[['instance_id', 'test_result']]
|
||||
.set_index('instance_id')['test_result']
|
||||
.to_dict()
|
||||
)
|
||||
|
||||
oh_format.progress_apply(
|
||||
write_row_to_md_file, axis=1, instance_id_to_test_result=instance_id_to_test_result
|
||||
)
|
||||
|
||||
@@ -22,7 +22,8 @@ def convert_row_to_swebench_format(row):
|
||||
elif 'test_result' in row and 'git_patch' in row['test_result']:
|
||||
model_patch = row['test_result']['git_patch']
|
||||
else:
|
||||
raise ValueError(f'Row {row} does not have a git_patch')
|
||||
print(f'WARNING: Row {row} does not have a git_patch')
|
||||
model_patch = ''
|
||||
|
||||
return {
|
||||
'instance_id': row['instance_id'],
|
||||
|
||||
@@ -3,7 +3,7 @@ import json
|
||||
import os
|
||||
from collections import defaultdict
|
||||
|
||||
import pandas as pd
|
||||
from tqdm import tqdm
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('input_file', type=str)
|
||||
@@ -11,8 +11,7 @@ args = parser.parse_args()
|
||||
|
||||
dirname = os.path.dirname(args.input_file)
|
||||
|
||||
df = pd.read_json(args.input_file, lines=True)
|
||||
|
||||
# Initialize counters and data structures
|
||||
instance_id_to_status = defaultdict(
|
||||
lambda: {
|
||||
'empty_generation': False,
|
||||
@@ -23,15 +22,7 @@ instance_id_to_status = defaultdict(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# Apply the status to the dataframe
|
||||
def apply_report(row):
|
||||
instance_id = row['instance_id']
|
||||
if instance_id in instance_id_to_status:
|
||||
return dict(instance_id_to_status[instance_id])
|
||||
return row.get('report', {})
|
||||
|
||||
|
||||
# Process official report if it exists
|
||||
swebench_official_report_json = os.path.join(dirname, 'report.json')
|
||||
openhands_remote_report_jsonl = args.input_file.replace(
|
||||
'.jsonl', '.swebench_eval.jsonl'
|
||||
@@ -90,108 +81,159 @@ if os.path.exists(swebench_official_report_json):
|
||||
f'- [{instance_id}](./eval_outputs/{instance_id}/run_instance.log)\n'
|
||||
)
|
||||
|
||||
df['report'] = df.apply(apply_report, axis=1)
|
||||
|
||||
with open(output_md_filepath, 'w') as f:
|
||||
f.write(output_md)
|
||||
|
||||
elif os.path.exists(openhands_remote_report_jsonl):
|
||||
output_md_filepath = args.input_file.replace('.jsonl', '.swebench_eval.md')
|
||||
|
||||
df_eval = pd.read_json(openhands_remote_report_jsonl, lines=True, orient='records')
|
||||
# First pass: Read eval report and count instances
|
||||
instance_ids = set()
|
||||
eval_instance_ids = set()
|
||||
|
||||
assert len(df['instance_id'].unique()) == len(
|
||||
df
|
||||
), 'There are duplicate instance ids in the original output which is not allowed'
|
||||
assert len(df_eval['instance_id'].unique()) == len(
|
||||
df_eval
|
||||
), 'There are duplicate instance ids in the eval report which is not allowed'
|
||||
# Count instances in original file
|
||||
n_instances = 0
|
||||
with open(args.input_file, 'r') as f:
|
||||
for line in tqdm(f, desc='Counting instances in original file'):
|
||||
data = json.loads(line)
|
||||
instance_ids.add(data['instance_id'])
|
||||
n_instances += 1
|
||||
print(f'Total instances in original file: {n_instances}')
|
||||
|
||||
for _, row in df_eval.iterrows():
|
||||
instance_id_to_status[row['instance_id']] = row['test_result']['report']
|
||||
df['report'] = df.apply(apply_report, axis=1)
|
||||
# Process eval report
|
||||
n_eval_instances = 0
|
||||
with open(openhands_remote_report_jsonl, 'r') as f:
|
||||
for line in tqdm(f, desc='Processing eval report'):
|
||||
data = json.loads(line)
|
||||
instance_id = data['instance_id']
|
||||
eval_instance_ids.add(instance_id)
|
||||
n_eval_instances += 1
|
||||
instance_id_to_status[instance_id] = data['test_result']['report']
|
||||
print(f'Total instances in eval report: {n_eval_instances}')
|
||||
|
||||
_n_instances = len(df)
|
||||
_n_resolved = len(df[df['report'].apply(lambda x: x.get('resolved', False))])
|
||||
_n_unresolved = _n_instances - _n_resolved
|
||||
_n_empty_patch = len(
|
||||
df[df['report'].apply(lambda x: x.get('empty_generation', False))]
|
||||
)
|
||||
_n_error = len(df[df['report'].apply(lambda x: x.get('error_eval', False))])
|
||||
# Verify no duplicates
|
||||
assert (
|
||||
len(instance_ids) == n_instances
|
||||
), 'Duplicate instance ids found in original output'
|
||||
assert (
|
||||
len(eval_instance_ids) == n_eval_instances
|
||||
), 'Duplicate instance ids found in eval report'
|
||||
|
||||
# Initialize counters
|
||||
stats = {'total': len(instance_ids), 'resolved': 0, 'empty_patch': 0, 'error': 0}
|
||||
|
||||
# Collect instance IDs by category
|
||||
resolved_ids = []
|
||||
unresolved_ids = []
|
||||
error_ids = []
|
||||
empty_patch_ids = []
|
||||
timeout_ids = []
|
||||
|
||||
# Process original file and categorize instances
|
||||
with open(args.input_file, 'r') as f:
|
||||
for line in f:
|
||||
data = json.loads(line)
|
||||
instance_id = data['instance_id']
|
||||
report = instance_id_to_status[instance_id]
|
||||
|
||||
if report.get('resolved', False):
|
||||
stats['resolved'] += 1
|
||||
resolved_ids.append(instance_id)
|
||||
else:
|
||||
unresolved_ids.append(instance_id)
|
||||
|
||||
if report.get('empty_generation', False):
|
||||
stats['empty_patch'] += 1
|
||||
empty_patch_ids.append(instance_id)
|
||||
if report.get('error_eval', False):
|
||||
stats['error'] += 1
|
||||
error_ids.append(instance_id)
|
||||
if report.get('test_timeout', False):
|
||||
timeout_ids.append(instance_id)
|
||||
|
||||
# Generate markdown report
|
||||
def _instance_id_to_log_path(instance_id):
|
||||
path = f"{args.input_file.replace('.jsonl', '.swebench_eval.logs')}/instance_{instance_id}.log"
|
||||
return os.path.relpath(path, start=dirname)
|
||||
|
||||
# ... rest of markdown generation code remains the same ...
|
||||
output_md = (
|
||||
'# SWE-bench Report\n'
|
||||
'This folder contains the evaluation results of the SWE-bench using the [official evaluation docker containerization](https://github.com/princeton-nlp/SWE-bench/blob/main/docs/20240627_docker/README.md#choosing-the-right-cache_level).\n\n'
|
||||
'## Summary\n'
|
||||
f'- submitted instances: {_n_instances}\n'
|
||||
f'- empty patch instances: {_n_empty_patch}\n'
|
||||
f'- resolved instances: {_n_resolved}\n'
|
||||
f'- unresolved instances: {_n_unresolved}\n'
|
||||
f'- error instances: {_n_error}\n'
|
||||
f'- submitted instances: {stats["total"]}\n'
|
||||
f'- empty patch instances: {stats["empty_patch"]}\n'
|
||||
f'- resolved instances: {stats["resolved"]}\n'
|
||||
f'- unresolved instances: {len(unresolved_ids)}\n'
|
||||
f'- error instances: {stats["error"]}\n'
|
||||
)
|
||||
|
||||
def _instance_id_to_log_path(instance_id):
|
||||
path = f"{args.input_file.replace('.jsonl', '.swebench_eval.logs')}/instance_{instance_id}.log"
|
||||
# make it relative path
|
||||
path = os.path.relpath(path, start=dirname)
|
||||
return path
|
||||
|
||||
output_md += '\n## Resolved Instances\n'
|
||||
# instance_id to status
|
||||
for instance_id in sorted(
|
||||
df[df['report'].apply(lambda x: x.get('resolved', False))][
|
||||
'instance_id'
|
||||
].unique()
|
||||
):
|
||||
for instance_id in resolved_ids:
|
||||
instance_id_to_status[instance_id]['resolved'] = True
|
||||
output_md += f'- [{instance_id}]({_instance_id_to_log_path(instance_id)})\n'
|
||||
|
||||
output_md += '\n## Unresolved Instances\n'
|
||||
for instance_id in sorted(
|
||||
df[~df['report'].apply(lambda x: x.get('resolved', False))][
|
||||
'instance_id'
|
||||
].unique()
|
||||
):
|
||||
for instance_id in unresolved_ids:
|
||||
output_md += f'- [{instance_id}]({_instance_id_to_log_path(instance_id)})\n'
|
||||
|
||||
output_md += '\n## Error Instances\n'
|
||||
for instance_id in sorted(
|
||||
df[df['report'].apply(lambda x: x.get('error_eval', False))][
|
||||
'instance_id'
|
||||
].unique()
|
||||
):
|
||||
for instance_id in error_ids:
|
||||
instance_id_to_status[instance_id]['error_eval'] = True
|
||||
output_md += f'- [{instance_id}]({_instance_id_to_log_path(instance_id)})\n'
|
||||
|
||||
output_md += '\n## Empty Patch Instances\n'
|
||||
for instance_id in sorted(
|
||||
df[df['report'].apply(lambda x: x.get('empty_generation', False))][
|
||||
'instance_id'
|
||||
].unique()
|
||||
):
|
||||
for instance_id in empty_patch_ids:
|
||||
instance_id_to_status[instance_id]['empty_generation'] = True
|
||||
output_md += f'- [{instance_id}]({_instance_id_to_log_path(instance_id)})\n'
|
||||
|
||||
output_md += '\n## Incomplete Instances\n'
|
||||
for instance_id in sorted(
|
||||
df[df['report'].apply(lambda x: x.get('test_timeout', False))][
|
||||
'instance_id'
|
||||
].unique()
|
||||
):
|
||||
for instance_id in timeout_ids:
|
||||
output_md += f'- [{instance_id}]({_instance_id_to_log_path(instance_id)})\n'
|
||||
|
||||
with open(output_md_filepath, 'w') as f:
|
||||
f.write(output_md)
|
||||
|
||||
else:
|
||||
print(
|
||||
f'No report file found: Both {swebench_official_report_json} and {openhands_remote_report_jsonl} do not exist.'
|
||||
)
|
||||
exit()
|
||||
|
||||
# Before backup and update, check if any changes would be made
|
||||
needs_update = False
|
||||
with open(args.input_file, 'r') as infile:
|
||||
for line in tqdm(infile, desc='Checking for changes'):
|
||||
data = json.loads(line)
|
||||
instance_id = data['instance_id']
|
||||
if instance_id in instance_id_to_status:
|
||||
current_report = data.get('report', {})
|
||||
new_report = instance_id_to_status[instance_id]
|
||||
if current_report != new_report:
|
||||
needs_update = True
|
||||
break
|
||||
|
||||
if not needs_update:
|
||||
print('No updates detected. Skipping file update.')
|
||||
exit()
|
||||
|
||||
# Backup and update the original file row by row
|
||||
if os.path.exists(args.input_file + '.bak'):
|
||||
conf = input('Existing backup file found. Do you want to overwrite it? (y/n)')
|
||||
if conf != 'y':
|
||||
exit()
|
||||
os.remove(args.input_file + '.bak')
|
||||
|
||||
# backup the original file
|
||||
os.rename(args.input_file, args.input_file + '.bak')
|
||||
df.to_json(args.input_file, orient='records', lines=True)
|
||||
|
||||
# Process and write file row by row
|
||||
with open(args.input_file + '.bak', 'r') as infile, open(
|
||||
args.input_file, 'w'
|
||||
) as outfile:
|
||||
for line in tqdm(infile, desc='Updating output file'):
|
||||
data = json.loads(line)
|
||||
instance_id = data['instance_id']
|
||||
if instance_id in instance_id_to_status:
|
||||
data['report'] = instance_id_to_status[instance_id]
|
||||
outfile.write(json.dumps(data) + '\n')
|
||||
|
||||
@@ -108,7 +108,14 @@ if [ -z "$N_RUNS" ]; then
|
||||
echo "N_RUNS not specified, use default $N_RUNS"
|
||||
fi
|
||||
|
||||
# Skip runs if the run number is in the SKIP_RUNS list
|
||||
# read from env variable SKIP_RUNS as a comma separated list of run numbers
|
||||
SKIP_RUNS=(${SKIP_RUNS//,/ })
|
||||
for i in $(seq 1 $N_RUNS); do
|
||||
if [[ " ${SKIP_RUNS[@]} " =~ " $i " ]]; then
|
||||
echo "Skipping run $i"
|
||||
continue
|
||||
fi
|
||||
current_eval_note="$EVAL_NOTE-run_$i"
|
||||
echo "EVAL_NOTE: $current_eval_note"
|
||||
run_eval $current_eval_note
|
||||
|
||||
@@ -262,7 +262,7 @@ def pre_login(
|
||||
instruction = action.to_instruction()
|
||||
|
||||
browser_action = BrowseInteractiveAction(browser_actions=instruction)
|
||||
browser_action.timeout = 10000
|
||||
browser_action.set_hard_timeout(10000)
|
||||
logger.info(browser_action, extra={'msg_type': 'ACTION'})
|
||||
obs: BrowserOutputObservation = runtime.run_action(browser_action)
|
||||
logger.debug(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
|
||||
@@ -39,7 +39,7 @@ def get_config(
|
||||
run_as_openhands=False,
|
||||
max_budget_per_task=4,
|
||||
max_iterations=100,
|
||||
trajectories_path=os.path.join(
|
||||
save_trajectory_path=os.path.join(
|
||||
mount_path_on_host, f'traj_{task_short_name}.json'
|
||||
),
|
||||
sandbox=SandboxConfig(
|
||||
@@ -80,13 +80,13 @@ def load_dependencies(runtime: Runtime) -> List[str]:
|
||||
def init_task_env(runtime: Runtime, hostname: str, env_llm_config: LLMConfig):
|
||||
command = (
|
||||
f'SERVER_HOSTNAME={hostname} '
|
||||
f'LITELLM_API_KEY={env_llm_config.api_key} '
|
||||
f'LITELLM_API_KEY={env_llm_config.api_key.get_secret_value() if env_llm_config.api_key else None} '
|
||||
f'LITELLM_BASE_URL={env_llm_config.base_url} '
|
||||
f'LITELLM_MODEL={env_llm_config.model} '
|
||||
'bash /utils/init.sh'
|
||||
)
|
||||
action = CmdRunAction(command=command)
|
||||
action.timeout = 900
|
||||
action.set_hard_timeout(900)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
@@ -165,14 +165,14 @@ def run_evaluator(
|
||||
runtime: Runtime, env_llm_config: LLMConfig, trajectory_path: str, result_path: str
|
||||
):
|
||||
command = (
|
||||
f'LITELLM_API_KEY={env_llm_config.api_key} '
|
||||
f'LITELLM_API_KEY={env_llm_config.api_key.get_secret_value() if env_llm_config.api_key else None} '
|
||||
f'LITELLM_BASE_URL={env_llm_config.base_url} '
|
||||
f'LITELLM_MODEL={env_llm_config.model} '
|
||||
f"DECRYPTION_KEY='theagentcompany is all you need' " # Hardcoded Key
|
||||
f'python_default /utils/eval.py --trajectory_path {trajectory_path} --result_path {result_path}'
|
||||
)
|
||||
action = CmdRunAction(command=command)
|
||||
action.timeout = 600
|
||||
action.set_hard_timeout(600)
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
|
||||
@@ -56,6 +56,8 @@ def get_config(
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
agent_config = config.get_agent_config(metadata.agent_class)
|
||||
agent_config.enable_prompt_extensions = False
|
||||
return config
|
||||
|
||||
|
||||
|
||||
674
evaluation/benchmarks/visualcodebench/eval.py
Normal file
674
evaluation/benchmarks/visualcodebench/eval.py
Normal file
@@ -0,0 +1,674 @@
|
||||
from collections import Counter
|
||||
from copy import deepcopy
|
||||
from difflib import SequenceMatcher
|
||||
from io import BytesIO
|
||||
|
||||
from bs4 import BeautifulSoup, Comment, NavigableString, Tag
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
from colormath.color_conversions import convert_color
|
||||
from colormath.color_diff import delta_e_cie2000
|
||||
from colormath.color_objects import LabColor, sRGBColor
|
||||
from PIL import Image, ImageChops, ImageColor
|
||||
from scipy.optimize import linear_sum_assignment
|
||||
from transformers import CLIPModel, CLIPProcessor
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
|
||||
def calculate_similarity(block1, block2):
|
||||
"""Calculate text similarity between two blocks using SequenceMatcher."""
|
||||
text_similarity = SequenceMatcher(None, block1['text'], block2['text']).ratio()
|
||||
return text_similarity
|
||||
|
||||
|
||||
def adjust_cost_for_context(cost_matrix, consecutive_bonus=1.0, window_size=20):
|
||||
"""Adjust cost matrix by considering context similarity."""
|
||||
if window_size <= 0:
|
||||
return cost_matrix
|
||||
|
||||
n, m = cost_matrix.shape
|
||||
adjusted_cost_matrix = np.copy(cost_matrix)
|
||||
|
||||
for i in range(n):
|
||||
for j in range(m):
|
||||
if adjusted_cost_matrix[i][j] >= -0.5:
|
||||
continue
|
||||
nearby_matrix = cost_matrix[
|
||||
max(0, i - window_size) : min(n, i + window_size + 1),
|
||||
max(0, j - window_size) : min(m, j + window_size + 1),
|
||||
]
|
||||
flattened_array = nearby_matrix.flatten()
|
||||
sorted_array = np.sort(flattened_array)[::-1]
|
||||
sorted_array = np.delete(
|
||||
sorted_array, np.where(sorted_array == cost_matrix[i, j])[0][0]
|
||||
)
|
||||
top_k_elements = sorted_array[-window_size * 2 :]
|
||||
bonus = consecutive_bonus * np.sum(top_k_elements)
|
||||
adjusted_cost_matrix[i][j] += bonus
|
||||
return adjusted_cost_matrix
|
||||
|
||||
|
||||
def create_cost_matrix(A, B):
|
||||
"""Create cost matrix for block matching."""
|
||||
n = len(A)
|
||||
m = len(B)
|
||||
cost_matrix = np.zeros((n, m))
|
||||
for i in range(n):
|
||||
for j in range(m):
|
||||
cost_matrix[i, j] = -calculate_similarity(A[i], B[j])
|
||||
return cost_matrix
|
||||
|
||||
|
||||
def calculate_distance_max_1d(x1, y1, x2, y2):
|
||||
"""Calculate maximum 1D distance between points."""
|
||||
return max(abs(x2 - x1), abs(y2 - y1))
|
||||
|
||||
|
||||
def calculate_ratio(h1, h2):
|
||||
"""Calculate ratio between two heights."""
|
||||
return max(h1, h2) / min(h1, h2)
|
||||
|
||||
|
||||
def rgb_to_lab(rgb):
|
||||
"""Convert RGB color to Lab color space."""
|
||||
rgb_color = sRGBColor(rgb[0], rgb[1], rgb[2], is_upscaled=True)
|
||||
lab_color = convert_color(rgb_color, LabColor)
|
||||
return lab_color
|
||||
|
||||
|
||||
def color_similarity_ciede2000(rgb1, rgb2):
|
||||
"""Calculate color similarity using CIEDE2000 formula."""
|
||||
lab1 = rgb_to_lab(rgb1)
|
||||
lab2 = rgb_to_lab(rgb2)
|
||||
delta_e = delta_e_cie2000(lab1, lab2)
|
||||
similarity = max(0, 1 - (delta_e / 100))
|
||||
return similarity
|
||||
|
||||
|
||||
def merge_blocks_wo_check(block1, block2):
|
||||
"""Merge two blocks without additional checks."""
|
||||
merged_text = block1['text'] + ' ' + block2['text']
|
||||
x_min = min(block1['bbox'][0], block2['bbox'][0])
|
||||
y_min = min(block1['bbox'][1], block2['bbox'][1])
|
||||
x_max = max(
|
||||
block1['bbox'][0] + block1['bbox'][2], block2['bbox'][0] + block2['bbox'][2]
|
||||
)
|
||||
y_max = max(
|
||||
block1['bbox'][1] + block1['bbox'][3], block2['bbox'][1] + block2['bbox'][3]
|
||||
)
|
||||
merged_bbox = (x_min, y_min, x_max - x_min, y_max - y_min)
|
||||
merged_color = tuple(
|
||||
(color1 + color2) // 2
|
||||
for color1, color2 in zip(block1['color'], block2['color'])
|
||||
)
|
||||
return {'text': merged_text, 'bbox': merged_bbox, 'color': merged_color}
|
||||
|
||||
|
||||
def find_maximum_matching(A, B, consecutive_bonus, window_size):
|
||||
"""Find maximum matching between two sets of blocks."""
|
||||
cost_matrix = create_cost_matrix(A, B)
|
||||
cost_matrix = adjust_cost_for_context(cost_matrix, consecutive_bonus, window_size)
|
||||
row_ind, col_ind = linear_sum_assignment(cost_matrix)
|
||||
current_cost = cost_matrix[row_ind, col_ind].tolist()
|
||||
return list(zip(row_ind, col_ind)), current_cost, cost_matrix
|
||||
|
||||
|
||||
def remove_indices(lst, indices):
|
||||
"""Remove indices from list in reverse order."""
|
||||
for index in sorted(indices, reverse=True):
|
||||
if index < len(lst):
|
||||
lst.pop(index)
|
||||
return lst
|
||||
|
||||
|
||||
def merge_blocks_by_list(blocks, merge_list):
|
||||
"""Merge blocks according to merge list."""
|
||||
pop_list = []
|
||||
while merge_list:
|
||||
i = merge_list[0][0]
|
||||
j = merge_list[0][1]
|
||||
blocks[i] = merge_blocks_wo_check(blocks[i], blocks[j])
|
||||
pop_list.append(j)
|
||||
merge_list.pop(0)
|
||||
if merge_list:
|
||||
new_merge_list = []
|
||||
for k in range(len(merge_list)):
|
||||
if (
|
||||
merge_list[k][0] != i
|
||||
and merge_list[k][1] != i
|
||||
and merge_list[k][0] != j
|
||||
and merge_list[k][1] != j
|
||||
):
|
||||
new_merge_list.append(merge_list[k])
|
||||
merge_list = new_merge_list
|
||||
remove_indices(blocks, pop_list)
|
||||
return blocks
|
||||
|
||||
|
||||
def difference_of_means(list1, list2):
|
||||
"""Calculate difference of means between two lists."""
|
||||
counter1 = Counter(list1)
|
||||
counter2 = Counter(list2)
|
||||
|
||||
for element in set(list1) & set(list2):
|
||||
common_count = min(counter1[element], counter2[element])
|
||||
counter1[element] -= common_count
|
||||
counter2[element] -= common_count
|
||||
|
||||
unique_list1 = [item for item in counter1.elements()]
|
||||
unique_list2 = [item for item in counter2.elements()]
|
||||
|
||||
mean_list1 = sum(unique_list1) / len(unique_list1) if unique_list1 else 0
|
||||
mean_list2 = sum(unique_list2) / len(unique_list2) if unique_list2 else 0
|
||||
|
||||
if mean_list1 - mean_list2 > 0:
|
||||
if min(unique_list1) > min(unique_list2):
|
||||
return mean_list1 - mean_list2
|
||||
return 0.0
|
||||
return mean_list1 - mean_list2
|
||||
|
||||
|
||||
def find_possible_merge(A, B, consecutive_bonus, window_size, debug=False):
|
||||
"""Find possible merges between blocks."""
|
||||
merge_bonus = 0.0
|
||||
merge_windows = 1
|
||||
|
||||
def sortFn(value):
|
||||
return value[2]
|
||||
|
||||
while True:
|
||||
A_changed = False
|
||||
B_changed = False
|
||||
|
||||
matching, current_cost, cost_matrix = find_maximum_matching(
|
||||
A, B, merge_bonus, merge_windows
|
||||
)
|
||||
|
||||
if len(A) >= 2:
|
||||
merge_list = []
|
||||
for i in range(len(A) - 1):
|
||||
new_A = deepcopy(A)
|
||||
new_A[i] = merge_blocks_wo_check(new_A[i], new_A[i + 1])
|
||||
new_A.pop(i + 1)
|
||||
updated_matching, updated_cost, _ = find_maximum_matching(
|
||||
new_A, B, merge_bonus, merge_windows
|
||||
)
|
||||
diff = difference_of_means(current_cost, updated_cost)
|
||||
if diff > 0.05:
|
||||
merge_list.append([i, i + 1, diff])
|
||||
|
||||
merge_list.sort(key=sortFn, reverse=True)
|
||||
if merge_list:
|
||||
A_changed = True
|
||||
A = merge_blocks_by_list(A, merge_list)
|
||||
matching, current_cost, cost_matrix = find_maximum_matching(
|
||||
A, B, merge_bonus, merge_windows
|
||||
)
|
||||
|
||||
if len(B) >= 2:
|
||||
merge_list = []
|
||||
for i in range(len(B) - 1):
|
||||
new_B = deepcopy(B)
|
||||
new_B[i] = merge_blocks_wo_check(new_B[i], new_B[i + 1])
|
||||
new_B.pop(i + 1)
|
||||
updated_matching, updated_cost, _ = find_maximum_matching(
|
||||
A, new_B, merge_bonus, merge_windows
|
||||
)
|
||||
diff = difference_of_means(current_cost, updated_cost)
|
||||
if diff > 0.05:
|
||||
merge_list.append([i, i + 1, diff])
|
||||
|
||||
merge_list.sort(key=sortFn, reverse=True)
|
||||
if merge_list:
|
||||
B_changed = True
|
||||
B = merge_blocks_by_list(B, merge_list)
|
||||
matching, current_cost, cost_matrix = find_maximum_matching(
|
||||
A, B, merge_bonus, merge_windows
|
||||
)
|
||||
|
||||
if not A_changed and not B_changed:
|
||||
break
|
||||
|
||||
matching, _, _ = find_maximum_matching(A, B, consecutive_bonus, window_size)
|
||||
return A, B, matching
|
||||
|
||||
|
||||
def merge_blocks_by_bbox(blocks):
|
||||
"""Merge blocks with same bounding box."""
|
||||
merged_blocks = {}
|
||||
for block in blocks:
|
||||
bbox = tuple(block['bbox'])
|
||||
if bbox in merged_blocks:
|
||||
existing_block = merged_blocks[bbox]
|
||||
existing_block['text'] += ' ' + block['text']
|
||||
existing_block['color'] = [
|
||||
(ec + c) / 2 for ec, c in zip(existing_block['color'], block['color'])
|
||||
]
|
||||
else:
|
||||
merged_blocks[bbox] = block
|
||||
return list(merged_blocks.values())
|
||||
|
||||
|
||||
def mask_bounding_boxes_with_inpainting(image, bounding_boxes):
|
||||
"""Mask bounding boxes in image using inpainting."""
|
||||
image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||||
mask = np.zeros(image_cv.shape[:2], dtype=np.uint8)
|
||||
height, width = image_cv.shape[:2]
|
||||
|
||||
for bbox in bounding_boxes:
|
||||
x_ratio, y_ratio, w_ratio, h_ratio = bbox
|
||||
x = int(x_ratio * width)
|
||||
y = int(y_ratio * height)
|
||||
w = int(w_ratio * width)
|
||||
h = int(h_ratio * height)
|
||||
mask[y : y + h, x : x + w] = 255
|
||||
|
||||
inpainted_image = cv2.inpaint(image_cv, mask, 3, cv2.INPAINT_TELEA)
|
||||
return Image.fromarray(cv2.cvtColor(inpainted_image, cv2.COLOR_BGR2RGB))
|
||||
|
||||
|
||||
def rescale_and_mask(image, blocks):
|
||||
"""Rescale image and mask blocks."""
|
||||
if blocks:
|
||||
image = mask_bounding_boxes_with_inpainting(image, blocks)
|
||||
|
||||
width, height = image.size
|
||||
if width < height:
|
||||
new_size = (width, width)
|
||||
else:
|
||||
new_size = (height, height)
|
||||
|
||||
return image.resize(new_size, Image.LANCZOS)
|
||||
|
||||
|
||||
def calculate_clip_similarity(image1, image2, blocks1, blocks2):
|
||||
"""Calculate CLIP similarity between two images."""
|
||||
model = CLIPModel.from_pretrained('openai/clip-vit-base-patch32')
|
||||
processor = CLIPProcessor.from_pretrained('openai/clip-vit-base-patch32')
|
||||
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
||||
model = model.to(device)
|
||||
|
||||
# Mask and preprocess images
|
||||
image1_masked = rescale_and_mask(image1, [block['bbox'] for block in blocks1])
|
||||
image2_masked = rescale_and_mask(image2, [block['bbox'] for block in blocks2])
|
||||
inputs = processor(
|
||||
images=[image1_masked, image2_masked], return_tensors='pt', padding=True
|
||||
)
|
||||
inputs = {k: v.to(device) for k, v in inputs.items()}
|
||||
|
||||
# Calculate features and similarity
|
||||
with torch.no_grad():
|
||||
image_features = model.get_image_features(**inputs)
|
||||
image_features1 = image_features[0].unsqueeze(0)
|
||||
image_features2 = image_features[1].unsqueeze(0)
|
||||
image_features1 /= image_features1.norm(dim=-1, keepdim=True)
|
||||
image_features2 /= image_features2.norm(dim=-1, keepdim=True)
|
||||
similarity = (image_features1 @ image_features2.T).item()
|
||||
|
||||
return similarity
|
||||
|
||||
|
||||
def rgb_to_hex(rgb):
|
||||
"""Convert an RGB tuple to hexadecimal format."""
|
||||
return '{:02X}{:02X}{:02X}'.format(*rgb)
|
||||
|
||||
|
||||
class ColorPool:
|
||||
def __init__(self, offset=0):
|
||||
color_values = list(range(10, 251, 16))
|
||||
color_list = [((r + offset) % 256, (g + offset) % 256, (b + offset) % 256)
|
||||
for r in color_values for g in color_values for b in color_values]
|
||||
self.color_pool = [rgb_to_hex(color) for color in color_list]
|
||||
|
||||
def pop_color(self):
|
||||
if self.color_pool:
|
||||
return self.color_pool.pop()
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def process_html_str(html_str, offset=0):
|
||||
"""Process HTML string to assign unique colors to text elements."""
|
||||
soup = BeautifulSoup(html_str, 'html.parser')
|
||||
|
||||
def update_style(element, property_name, value):
|
||||
important_value = f"{value} !important"
|
||||
styles = element.attrs.get('style', '').split(';')
|
||||
updated_styles = [s for s in styles if not s.strip().startswith(property_name) and len(s.strip()) > 0]
|
||||
updated_styles.append(f"{property_name}: {important_value}")
|
||||
element['style'] = '; '.join(updated_styles).strip()
|
||||
|
||||
# Set background color of all elements to transparent white
|
||||
for element in soup.find_all(True):
|
||||
update_style(element, 'background-color', 'rgba(255, 255, 255, 0.0)')
|
||||
|
||||
color_pool = ColorPool(offset)
|
||||
text_tags = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'a', 'b', 'li',
|
||||
'table', 'td', 'th', 'button', 'footer', 'header', 'figcaption']
|
||||
|
||||
for tag in soup.find_all(text_tags):
|
||||
color = f"#{color_pool.pop_color()}"
|
||||
update_style(tag, 'color', color)
|
||||
update_style(tag, 'opacity', '1.0')
|
||||
|
||||
return str(soup)
|
||||
|
||||
|
||||
def similar(n1, n2):
|
||||
"""Check if two numbers are similar within a threshold."""
|
||||
return abs(n1 - n2) <= 8
|
||||
|
||||
|
||||
def find_different_pixels(image1, image2):
|
||||
"""Find pixels that differ between two images."""
|
||||
if image1.size != image2.size:
|
||||
logger.warning("Images are not the same size")
|
||||
return None
|
||||
|
||||
image1 = image1.convert('RGB')
|
||||
image2 = image2.convert('RGB')
|
||||
pixels1 = image1.load()
|
||||
pixels2 = image2.load()
|
||||
different_pixels = []
|
||||
|
||||
for x in range(image1.size[0]):
|
||||
for y in range(image1.size[1]):
|
||||
r1, g1, b1 = pixels1[x, y]
|
||||
r2, g2, b2 = pixels2[x, y]
|
||||
if similar((r1 + 50) % 256, r2) and similar((g1 + 50) % 256, g2) and similar((b1 + 50) % 256, b2):
|
||||
different_pixels.append((y, x))
|
||||
|
||||
return np.stack(different_pixels) if different_pixels else None
|
||||
|
||||
|
||||
def extract_text_with_color(html_str):
|
||||
"""Extract text and color information from HTML string."""
|
||||
def get_color(tag):
|
||||
if 'style' in tag.attrs:
|
||||
styles = tag['style'].split(';')
|
||||
color_style = [s for s in styles if 'color' in s and 'background-color' not in s]
|
||||
if color_style:
|
||||
color = color_style[-1].split(':')[1].strip().replace(" !important", "")
|
||||
if color[0] == "#":
|
||||
return color
|
||||
else:
|
||||
try:
|
||||
if color.startswith('rgb'):
|
||||
color = tuple(map(int, color[4:-1].split(',')))
|
||||
else:
|
||||
color = ImageColor.getrgb(color)
|
||||
return '#{:02x}{:02x}{:02x}'.format(*color)
|
||||
except ValueError:
|
||||
logger.warning(f"Unable to identify or convert color: {color}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def extract_text_recursive(element, parent_color='#000000'):
|
||||
if isinstance(element, Comment):
|
||||
return None
|
||||
elif isinstance(element, NavigableString):
|
||||
text = element.strip()
|
||||
return (text, parent_color) if text else None
|
||||
elif isinstance(element, Tag):
|
||||
current_color = get_color(element) or parent_color
|
||||
children_texts = filter(None, [extract_text_recursive(child, current_color)
|
||||
for child in element.children])
|
||||
return list(children_texts)
|
||||
|
||||
soup = BeautifulSoup(html_str, 'html.parser')
|
||||
body = soup.body
|
||||
return extract_text_recursive(body) if body else []
|
||||
|
||||
|
||||
def flatten_tree(tree):
|
||||
"""Flatten a nested tree structure into a list."""
|
||||
flat_list = []
|
||||
def flatten(node):
|
||||
if isinstance(node, list):
|
||||
for item in node:
|
||||
flatten(item)
|
||||
else:
|
||||
flat_list.append(node)
|
||||
flatten(tree)
|
||||
return flat_list
|
||||
|
||||
|
||||
def get_blocks_from_image_diff_pixels(image, html_text_color_tree, different_pixels):
|
||||
"""Extract text blocks from image using color differences."""
|
||||
image_cv = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||||
x_w = image_cv.shape[0]
|
||||
y_w = image_cv.shape[1]
|
||||
|
||||
def hex_to_bgr(hex_color):
|
||||
hex_color = hex_color.lstrip('#')
|
||||
rgb = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
|
||||
return rgb[::-1]
|
||||
|
||||
def get_intersect(arr1, arr2):
|
||||
arr1_reshaped = arr1.view([('', arr1.dtype)] * arr1.shape[1])
|
||||
arr2_reshaped = arr2.view([('', arr2.dtype)] * arr2.shape[1])
|
||||
common_rows = np.intersect1d(arr1_reshaped, arr2_reshaped)
|
||||
return common_rows.view(arr1.dtype).reshape(-1, arr1.shape[1])
|
||||
|
||||
blocks = []
|
||||
for item in html_text_color_tree:
|
||||
try:
|
||||
color = np.array(hex_to_bgr(item[1]), dtype="uint8")
|
||||
except:
|
||||
continue
|
||||
|
||||
lower = color - 4
|
||||
upper = color + 4
|
||||
mask = cv2.inRange(image_cv, lower, upper)
|
||||
coords = np.column_stack(np.where(mask > 0))
|
||||
coords = get_intersect(coords, different_pixels)
|
||||
|
||||
if coords.size == 0:
|
||||
continue
|
||||
|
||||
x_min, y_min = np.min(coords, axis=0)
|
||||
x_max, y_max = np.max(coords, axis=0)
|
||||
|
||||
# Get average color from original image
|
||||
color_coords = coords.copy()
|
||||
color_coords = color_coords[color_coords[:, 0] <= x_max]
|
||||
color_coords = color_coords[color_coords[:, 1] <= y_max]
|
||||
colors = [image_cv[x, y] for x, y in color_coords]
|
||||
avg_color = tuple(map(int, np.mean(colors, axis=0)))[::-1] # Convert BGR to RGB
|
||||
|
||||
blocks.append({
|
||||
'text': item[0].lower(),
|
||||
'bbox': (y_min / y_w, x_min / x_w, (y_max - y_min + 1) / y_w, (x_max - x_min + 1) / x_w),
|
||||
'color': avg_color
|
||||
})
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
def get_blocks_from_html(html_str, image1):
|
||||
"""Extract text blocks from HTML and image."""
|
||||
# Process HTML with two different color offsets
|
||||
html_str_1 = process_html_str(html_str, offset=0)
|
||||
html_str_2 = process_html_str(html_str, offset=50)
|
||||
|
||||
# Render both HTML versions to images
|
||||
# TODO: Screenshot html_str_2
|
||||
filter_color = (255, 0, 0)
|
||||
image2 = Image.new("RGB", image1.size, filter_color)
|
||||
|
||||
|
||||
# Find pixels that differ between the two rendered images
|
||||
different_pixels = find_different_pixels(image1, image2)
|
||||
if different_pixels is None:
|
||||
logger.warning("Unable to get pixels with different colors")
|
||||
return []
|
||||
|
||||
# Extract text and color information from HTML
|
||||
html_text_color_tree = flatten_tree(extract_text_with_color(html_str_1))
|
||||
try:
|
||||
blocks = get_blocks_from_image_diff_pixels(image1, html_text_color_tree, different_pixels)
|
||||
except Exception as e:
|
||||
logger.warning(f"Unable to get blocks: {e}")
|
||||
return []
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
def evaluate(task, generated_img):
|
||||
"""Evaluate generated image against reference image using multiple metrics."""
|
||||
# Load reference image
|
||||
post_image = task['post_image']
|
||||
|
||||
# Extract blocks from HTML and images
|
||||
post_blocks = get_blocks_from_html(task['post_html'], post_image)
|
||||
gen_blocks = get_blocks_from_html(task['gen_html'], generated_img)
|
||||
|
||||
print("block details", post_blocks, gen_blocks)
|
||||
if not post_blocks or not gen_blocks:
|
||||
# Fallback to basic CLIP and pixel comparison if no blocks available
|
||||
clip_score = calculate_clip_similarity(post_image, generated_img, [], [])
|
||||
logger.info(f'CLIP similarity score: {clip_score}')
|
||||
|
||||
# Pixel comparison
|
||||
diff = ImageChops.difference(generated_img, post_image)
|
||||
pixel_match = not diff.getbbox()
|
||||
logger.info(
|
||||
f"Pixel difference analysis: {'No difference' if pixel_match else 'Differences found'}"
|
||||
)
|
||||
|
||||
return clip_score > 0.95 or pixel_match
|
||||
|
||||
# Merge blocks with same bounding boxes
|
||||
post_blocks = merge_blocks_by_bbox(post_blocks)
|
||||
gen_blocks = merge_blocks_by_bbox(gen_blocks)
|
||||
|
||||
# Find optimal block matching
|
||||
consecutive_bonus, window_size = 0.1, 1
|
||||
gen_blocks_m, post_blocks_m, matching = find_possible_merge(
|
||||
gen_blocks, deepcopy(post_blocks), consecutive_bonus, window_size
|
||||
)
|
||||
|
||||
# Filter matches with low similarity
|
||||
filtered_matching = []
|
||||
for i, j in matching:
|
||||
text_similarity = calculate_similarity(gen_blocks_m[i], post_blocks_m[j])
|
||||
if text_similarity >= 0.5:
|
||||
filtered_matching.append([i, j, text_similarity])
|
||||
matching = filtered_matching
|
||||
|
||||
if not matching:
|
||||
logger.warning('No matching blocks found')
|
||||
clip_score = calculate_clip_similarity(
|
||||
post_image, generated_img, gen_blocks, post_blocks
|
||||
)
|
||||
return clip_score > 0.95
|
||||
|
||||
# Calculate metrics for matched blocks
|
||||
indices1 = [item[0] for item in matching]
|
||||
indices2 = [item[1] for item in matching]
|
||||
|
||||
# Calculate unmatched areas
|
||||
unmatched_area_1 = sum(
|
||||
block['bbox'][2] * block['bbox'][3]
|
||||
for i, block in enumerate(gen_blocks_m)
|
||||
if i not in indices1
|
||||
)
|
||||
unmatched_area_2 = sum(
|
||||
block['bbox'][2] * block['bbox'][3]
|
||||
for j, block in enumerate(post_blocks_m)
|
||||
if j not in indices2
|
||||
)
|
||||
total_unmatched_area = unmatched_area_1 + unmatched_area_2
|
||||
|
||||
# Calculate metrics for matched blocks
|
||||
matched_areas = []
|
||||
text_scores = []
|
||||
position_scores = []
|
||||
color_scores = []
|
||||
|
||||
for i, j, text_similarity in matching:
|
||||
# Area
|
||||
block_area = (
|
||||
gen_blocks_m[i]['bbox'][2] * gen_blocks_m[i]['bbox'][3]
|
||||
+ post_blocks_m[j]['bbox'][2] * post_blocks_m[j]['bbox'][3]
|
||||
)
|
||||
matched_areas.append(block_area)
|
||||
|
||||
# Position similarity
|
||||
position_similarity = 1 - calculate_distance_max_1d(
|
||||
gen_blocks_m[i]['bbox'][0] + gen_blocks_m[i]['bbox'][2] / 2,
|
||||
gen_blocks_m[i]['bbox'][1] + gen_blocks_m[i]['bbox'][3] / 2,
|
||||
post_blocks_m[j]['bbox'][0] + post_blocks_m[j]['bbox'][2] / 2,
|
||||
post_blocks_m[j]['bbox'][1] + post_blocks_m[j]['bbox'][3] / 2,
|
||||
)
|
||||
|
||||
# Color similarity
|
||||
color_similarity = color_similarity_ciede2000(
|
||||
gen_blocks_m[i]['color'], post_blocks_m[j]['color']
|
||||
)
|
||||
|
||||
text_scores.append(text_similarity)
|
||||
position_scores.append(position_similarity)
|
||||
color_scores.append(color_similarity)
|
||||
|
||||
# Calculate final scores
|
||||
total_area = sum(matched_areas) + total_unmatched_area
|
||||
size_score = sum(matched_areas) / total_area if total_area > 0 else 0
|
||||
text_score = np.mean(text_scores) if text_scores else 0
|
||||
position_score = np.mean(position_scores) if position_scores else 0
|
||||
color_score = np.mean(color_scores) if color_scores else 0
|
||||
clip_score = calculate_clip_similarity(
|
||||
post_image, generated_img, gen_blocks, post_blocks
|
||||
)
|
||||
|
||||
# Combine scores with equal weights
|
||||
final_score = 0.2 * (
|
||||
size_score + text_score + position_score + color_score + clip_score
|
||||
)
|
||||
|
||||
logger.info('Evaluation scores:')
|
||||
logger.info(f'- Size score: {size_score:.3f}')
|
||||
logger.info(f'- Text score: {text_score:.3f}')
|
||||
logger.info(f'- Position score: {position_score:.3f}')
|
||||
logger.info(f'- Color score: {color_score:.3f}')
|
||||
logger.info(f'- CLIP score: {clip_score:.3f}')
|
||||
logger.info(f'- Final score: {final_score:.3f}')
|
||||
|
||||
return final_score > 0.8 # Consider it a match if final score > 80%
|
||||
|
||||
|
||||
def png_to_bytes(png):
|
||||
buffer = BytesIO()
|
||||
png.save(buffer, format='PNG')
|
||||
image_bytes = buffer.getvalue()
|
||||
return image_bytes
|
||||
|
||||
|
||||
def bytes_to_image(image_bytes):
|
||||
"""Convert bytes to a Pillow Image object."""
|
||||
return Image.open(BytesIO(image_bytes))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
first_image = Image.open('./evaluation/visualcodebench/data/1/post.png')
|
||||
image = Image.open('./evaluation/visualcodebench/data/1/prev.png')
|
||||
|
||||
|
||||
html_file = open('./evaluation/visualcodebench/data/1/post/index.html', 'r')
|
||||
first_html = html_file.read()
|
||||
html_file.close()
|
||||
|
||||
html_file = open('./evaluation/visualcodebench/data/1/prev/index.html', 'r')
|
||||
gen_html = html_file.read()
|
||||
html_file.close()
|
||||
|
||||
|
||||
|
||||
sample = {'post_image': first_image, "post_html": first_html, "gen_html": gen_html}
|
||||
|
||||
|
||||
|
||||
evaluate(sample, image)
|
||||
|
||||
97
evaluation/benchmarks/visualcodebench/prepare.py
Normal file
97
evaluation/benchmarks/visualcodebench/prepare.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import base64
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
import pandas as pd
|
||||
from huggingface_hub import snapshot_download
|
||||
from PIL import PngImagePlugin
|
||||
from tqdm import tqdm
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
REPO_DOWNLOAD_DIR = (
|
||||
'./evaluation/visualcodebench/' # Directory to store the downloaded repository
|
||||
)
|
||||
|
||||
|
||||
def download_repository():
|
||||
"""
|
||||
Download the entire repository from Hugging Face Hub.
|
||||
This function clones the repository into REPO_DOWNLOAD_DIR.
|
||||
"""
|
||||
repo_id = 'rvmalhot/VisualCodeBench'
|
||||
try:
|
||||
logger.info(f"Downloading repository '{repo_id}'...")
|
||||
snapshot_download(
|
||||
repo_id=repo_id,
|
||||
local_dir=REPO_DOWNLOAD_DIR,
|
||||
repo_type='dataset',
|
||||
ignore_patterns=None, # Download all files
|
||||
)
|
||||
logger.info(f"Repository downloaded to '{REPO_DOWNLOAD_DIR}'.")
|
||||
except Exception as e:
|
||||
logger.error(f"Error downloading repository '{repo_id}': {e}")
|
||||
raise e
|
||||
|
||||
|
||||
def format_task_dict(example):
|
||||
instance_id = example['id']
|
||||
prev_remote_path = os.path.join(REPO_DOWNLOAD_DIR, f'data/{instance_id}/prev')
|
||||
post_remote_path = os.path.join(REPO_DOWNLOAD_DIR, f'data/{instance_id}/post')
|
||||
|
||||
# Check if 'prev' and 'post' directories exist
|
||||
prev_exists = os.path.exists(prev_remote_path)
|
||||
post_exists = os.path.exists(post_remote_path)
|
||||
|
||||
if prev_exists and post_exists:
|
||||
skip = False
|
||||
else:
|
||||
skip = True
|
||||
|
||||
task = {
|
||||
'instance_id': instance_id,
|
||||
'prev_image': example['prev_image'],
|
||||
'post_image': example['post_image'],
|
||||
'changes': example['changes'],
|
||||
'prev_code_files': example['prev_code_files'],
|
||||
'post_code_files': example['post_code_files'],
|
||||
'skip': skip,
|
||||
}
|
||||
|
||||
return task
|
||||
|
||||
|
||||
def prepare_visualcodebench(dataset):
|
||||
logger.info('Processing dataset')
|
||||
dataset_processed = []
|
||||
for example in tqdm(dataset['train']):
|
||||
formatted_example = format_task_dict(example)
|
||||
if formatted_example['skip']:
|
||||
continue
|
||||
del formatted_example['skip']
|
||||
dataset_processed.append(formatted_example)
|
||||
|
||||
return pd.DataFrame(dataset_processed)
|
||||
|
||||
|
||||
def pil_image_to_base64(image: PngImagePlugin.PngImageFile) -> str:
|
||||
"""
|
||||
Converts a PIL image to a Base64-encoded string.
|
||||
|
||||
Parameters:
|
||||
- image (PngImagePlugin.PngImageFile): The PIL image to convert.
|
||||
|
||||
Returns:
|
||||
- str: The Base64-encoded string of the image.
|
||||
"""
|
||||
if not isinstance(image, PngImagePlugin.PngImageFile):
|
||||
raise ValueError(
|
||||
'The provided image is not a PIL.PngImagePlugin.PngImageFile instance.'
|
||||
)
|
||||
|
||||
buffered = BytesIO()
|
||||
image.save(buffered, format='PNG')
|
||||
img_bytes = buffered.getvalue()
|
||||
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
|
||||
base64_with_prefix = f'data:image/png;base64,{img_base64}'
|
||||
return [base64_with_prefix]
|
||||
247
evaluation/benchmarks/visualcodebench/run_infer.py
Normal file
247
evaluation/benchmarks/visualcodebench/run_infer.py
Normal file
@@ -0,0 +1,247 @@
|
||||
# FILE: run_infer.py
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from functools import partial
|
||||
|
||||
import pandas as pd
|
||||
from datasets import load_dataset
|
||||
|
||||
# from evaluation.benchmarks.visualcodebench.eval import capture_screenshot
|
||||
from evaluation.benchmarks.visualcodebench.prepare import (
|
||||
REPO_DOWNLOAD_DIR,
|
||||
download_repository,
|
||||
pil_image_to_base64,
|
||||
prepare_visualcodebench,
|
||||
)
|
||||
from evaluation.utils.shared import (
|
||||
EvalMetadata,
|
||||
assert_and_raise,
|
||||
codeact_user_response,
|
||||
make_metadata,
|
||||
prepare_dataset,
|
||||
reset_logger_for_multiprocessing,
|
||||
run_evaluation,
|
||||
)
|
||||
from openhands.controller.state.state import State
|
||||
from openhands.core.config import (
|
||||
AppConfig,
|
||||
SandboxConfig,
|
||||
get_llm_config_arg,
|
||||
)
|
||||
from openhands.core.config.utils import parse_arguments
|
||||
from openhands.core.logger import openhands_logger as logger # Import OpenHands logger
|
||||
from openhands.core.main import create_runtime, run_controller
|
||||
from openhands.events.action.commands import CmdRunAction
|
||||
from openhands.events.action.message import MessageAction
|
||||
from openhands.events.observation.commands import CmdOutputObservation
|
||||
from openhands.runtime.base import Runtime
|
||||
from openhands.utils.async_utils import call_async_from_sync
|
||||
|
||||
# Define workspace and output directories
|
||||
WORKSPACE_DIR = './workspace'
|
||||
|
||||
FAKE_RESPONSES = {
|
||||
'CodeActAgent': partial(codeact_user_response, encapsulate_solution=True),
|
||||
}
|
||||
|
||||
|
||||
def get_config(
|
||||
metadata: EvalMetadata,
|
||||
) -> AppConfig:
|
||||
config = AppConfig(
|
||||
default_agent=metadata.agent_class,
|
||||
run_as_openhands=False,
|
||||
runtime='eventstream',
|
||||
max_iterations=metadata.max_iterations,
|
||||
sandbox=SandboxConfig(
|
||||
base_container_image='python:3.12-bookworm',
|
||||
enable_auto_lint=True,
|
||||
use_host_network=False,
|
||||
),
|
||||
# do not mount workspace
|
||||
workspace_base=None,
|
||||
workspace_mount_path=None,
|
||||
)
|
||||
config.set_llm_config(metadata.llm_config)
|
||||
return config
|
||||
|
||||
|
||||
def initialize_runtime(
|
||||
runtime: Runtime,
|
||||
instance: pd.Series, # this argument is not required
|
||||
):
|
||||
"""Initialize the runtime for the agent.
|
||||
|
||||
This function is called before the runtime is used to run the agent.
|
||||
"""
|
||||
logger.info('-' * 30)
|
||||
logger.info('BEGIN Runtime Initialization Fn')
|
||||
logger.info('-' * 30)
|
||||
workspace_dir_name = instance['instance_id']
|
||||
obs: CmdOutputObservation
|
||||
|
||||
action = CmdRunAction(command='mkdir -p /workspace/{workspace_dir_name}')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0,
|
||||
f'Failed to create /workspace/{workspace_dir_name}: {str(obs)}',
|
||||
)
|
||||
|
||||
file_path = REPO_DOWNLOAD_DIR + f'data/{workspace_dir_name}/prev/index.html'
|
||||
runtime.copy_to(file_path, f'/workspace/{workspace_dir_name}')
|
||||
logger.info(f'Copied code file for instance {workspace_dir_name}')
|
||||
|
||||
action = CmdRunAction(command=f'cd /workspace/{workspace_dir_name}')
|
||||
action.timeout = 600
|
||||
logger.info(action, extra={'msg_type': 'ACTION'})
|
||||
obs = runtime.run_action(action)
|
||||
logger.info(obs, extra={'msg_type': 'OBSERVATION'})
|
||||
assert_and_raise(
|
||||
obs.exit_code == 0,
|
||||
f'Failed to cd to /workspace/{workspace_dir_name}: {str(obs)}',
|
||||
)
|
||||
|
||||
logger.info('-' * 30)
|
||||
logger.info('END Runtime Initialization Fn')
|
||||
logger.info('-' * 30)
|
||||
|
||||
|
||||
def complete_runtime(
|
||||
runtime: Runtime,
|
||||
instance: pd.Series, # this argument is not required, but it is used to get the workspace_dir_name
|
||||
) -> str:
|
||||
# TODO: extract edited HTML file from agent workspace
|
||||
# temp_zip = runtime.copy_from(f'/workspace/{instance.instance_id}')
|
||||
# file_name = f'/workspace/{instance.instance_id}/index.html'
|
||||
# with zipfile.ZipFile(temp_zip, 'r') as zip_ref:
|
||||
# if file_name in zip_ref.namelist():
|
||||
# with zip_ref.open(file_name) as file:
|
||||
# file_content = file.read().decode('utf-8') # Decode bytes to string
|
||||
# else:
|
||||
# raise FileNotFoundError(f"'{file_name}' not found in the ZIP archive.")
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
src_folder = REPO_DOWNLOAD_DIR + f'data/{instance.instance_id}/post/'
|
||||
shutil.copytree(src_folder, tmpdir, dirs_exist_ok=True)
|
||||
|
||||
# image = capture_screenshot(tmpdir)
|
||||
# if image is not None:
|
||||
# shutil.copy(os.path.join(tmpdir, 'final_screenshot.png'), REPO_DOWNLOAD_DIR)
|
||||
|
||||
|
||||
def process_instance(
|
||||
instance: pd.Series, metadata: EvalMetadata, reset_logger: bool = True
|
||||
):
|
||||
config = get_config(metadata)
|
||||
|
||||
# Setup the logger properly, so you can run multi-processing to parallelize the evaluation
|
||||
if reset_logger:
|
||||
log_dir = os.path.join(metadata.eval_output_dir, 'infer_logs')
|
||||
reset_logger_for_multiprocessing(logger, instance.instance_id, log_dir)
|
||||
else:
|
||||
logger.info(f'Starting evaluation for instance {instance.instance_id}.')
|
||||
|
||||
# =============================================
|
||||
# build instruction
|
||||
# =============================================
|
||||
|
||||
# Prepare instruction
|
||||
instruction = (
|
||||
f"Modify the HTML/CSS according to the following instruction:\n\n"
|
||||
f"{instance['changes']}\n\n"
|
||||
)
|
||||
instruction += (
|
||||
'IMPORTANT: You should ONLY interact with the environment provided '
|
||||
'to you AND NEVER ASK FOR HUMAN HELP.\n'
|
||||
)
|
||||
|
||||
# =============================================
|
||||
# create sandbox and run the agent
|
||||
# =============================================
|
||||
|
||||
runtime: Runtime = create_runtime(config)
|
||||
call_async_from_sync(runtime.connect)
|
||||
|
||||
try:
|
||||
initialize_runtime(runtime, instance=instance)
|
||||
|
||||
image_urls = pil_image_to_base64(instance['prev_image'])
|
||||
|
||||
action = MessageAction(content=instruction, image_urls=image_urls)
|
||||
state: State | None = asyncio.run(
|
||||
run_controller(
|
||||
config=config,
|
||||
initial_user_action=action,
|
||||
runtime=runtime,
|
||||
fake_user_response_fn=FAKE_RESPONSES[metadata.agent_class],
|
||||
)
|
||||
)
|
||||
if state is None:
|
||||
raise ValueError('State should not be None.')
|
||||
|
||||
# =============================================
|
||||
# result evaluation
|
||||
# =============================================
|
||||
|
||||
return_val = complete_runtime(runtime, instance)
|
||||
logger.info(f'Return value {return_val}')
|
||||
finally:
|
||||
runtime.close()
|
||||
|
||||
# TODO: return EVAL output
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the evaluation."""
|
||||
# args = parse_args()
|
||||
args = parse_arguments()
|
||||
|
||||
logger.info(f"\n{'='*80}\nStarting VisualCodeBench Evaluation\n{'='*80}")
|
||||
logger.info(f'Agent: {args.agent_cls}')
|
||||
logger.info(f'Model: {args.llm_config}')
|
||||
logger.info(f'Max iterations: {args.max_iterations}')
|
||||
logger.info(f'Eval limit: {args.eval_n_limit}')
|
||||
logger.info(f'Num workers: {args.eval_num_workers}\n')
|
||||
logger.info(f'Eval output: {args.eval_output_dir}\n')
|
||||
|
||||
# Step 1: Download the entire repository once
|
||||
logger.info('Downloading repository...')
|
||||
download_repository()
|
||||
|
||||
# Step 2: Load Dataset
|
||||
logger.info('Loading dataset...')
|
||||
dataset = load_dataset(REPO_DOWNLOAD_DIR)
|
||||
|
||||
# Step 3: Prepare dataset
|
||||
llm_config = get_llm_config_arg(args.llm_config)
|
||||
if llm_config is None:
|
||||
logger.error(f'Could not find LLM config: {args.llm_config}')
|
||||
raise ValueError(f'Could not find LLM config: {args.llm_config}')
|
||||
|
||||
metadata = make_metadata(
|
||||
llm_config,
|
||||
'VisualCodeBench',
|
||||
args.agent_cls,
|
||||
args.max_iterations,
|
||||
args.eval_note,
|
||||
'evaluation/output/',
|
||||
)
|
||||
|
||||
output_file = os.path.join(metadata.eval_output_dir, 'output.jsonl')
|
||||
dataset = prepare_visualcodebench(dataset)
|
||||
instances = prepare_dataset(dataset, output_file, eval_n_limit=args.eval_n_limit)
|
||||
|
||||
# Step 4: Run eval
|
||||
run_evaluation(
|
||||
instances, metadata, output_file, args.eval_num_workers, process_instance
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
46
evaluation/benchmarks/visualcodebench/scripts/run_infer.sh
Executable file
46
evaluation/benchmarks/visualcodebench/scripts/run_infer.sh
Executable file
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
source "evaluation/utils/version_control.sh"
|
||||
|
||||
# Check if required arguments are provided
|
||||
if [ "$#" -lt 4 ]; then
|
||||
echo "Usage: $0 [model_config] [commit_hash] [agent_cls] [eval_limit] [num_workers]"
|
||||
echo "Example: $0 llm.eval_gpt_4o_mini HEAD CodeActAgent 5 1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MODEL_CONFIG=$1
|
||||
COMMIT_HASH=$2
|
||||
AGENT_CLS=$3
|
||||
EVAL_LIMIT=$4
|
||||
NUM_WORKERS=${5:-1} # Default to 1 worker if not specified
|
||||
|
||||
# Checkout the specified commit
|
||||
checkout_eval_branch
|
||||
|
||||
if [ -z "$AGENT" ]; then
|
||||
echo "Agent not specified, use default CodeActAgent"
|
||||
AGENT="CodeActAgent"
|
||||
fi
|
||||
|
||||
get_openhands_version
|
||||
|
||||
echo "AGENT: $AGENT"
|
||||
echo "OPENHANDS_VERSION: $OPENHANDS_VERSION"
|
||||
echo "MODEL_CONFIG: $MODEL_CONFIG"
|
||||
|
||||
COMMAND="export PYTHONPATH=evaluation/benchmarks/visualcodebench:\$PYTHONPATH && poetry run python evaluation/benchmarks/visualcodebench/run_infer.py \
|
||||
--agent-cls $AGENT \
|
||||
--llm-config $MODEL_CONFIG \
|
||||
--max-iterations 5 \
|
||||
--eval-num-workers $NUM_WORKERS \
|
||||
--eval-note $OPENHANDS_VERSION" \
|
||||
|
||||
if [ -n "$EVAL_LIMIT" ]; then
|
||||
echo "EVAL_LIMIT: $EVAL_LIMIT"
|
||||
COMMAND="$COMMAND --eval-n-limit $EVAL_LIMIT"
|
||||
fi
|
||||
|
||||
# Run the command
|
||||
eval $COMMAND
|
||||
167
evaluation/benchmarks/visualcodebench/server.py
Normal file
167
evaluation/benchmarks/visualcodebench/server.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import http
|
||||
import os
|
||||
import socket
|
||||
import socketserver
|
||||
import threading
|
||||
import time
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
from PIL import Image, ImageChops
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
from openhands.core.logger import openhands_logger as logger
|
||||
|
||||
|
||||
def get_free_port():
|
||||
"""Find a free port to run the HTTP server."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(('', 0))
|
||||
return s.getsockname()[1]
|
||||
|
||||
|
||||
def start_http_server(tmpdir):
|
||||
port = get_free_port()
|
||||
|
||||
class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def translate_path(self, path):
|
||||
# Serve files from the specified directory instead of the current working directory
|
||||
path = super().translate_path(path)
|
||||
relative_path = os.path.relpath(path, os.getcwd())
|
||||
return os.path.join(tmpdir, relative_path)
|
||||
|
||||
handler = CustomHTTPRequestHandler
|
||||
server = socketserver.TCPServer(('', port), handler)
|
||||
return server, port
|
||||
|
||||
|
||||
def capture_screenshot(tmpdir):
|
||||
server, port = start_http_server(tmpdir)
|
||||
server_thread = threading.Thread(target=server.serve_forever)
|
||||
server_thread.daemon = True
|
||||
server_thread.start()
|
||||
time.sleep(10)
|
||||
|
||||
image = None
|
||||
try:
|
||||
server_url = f'http://localhost:{port}/'
|
||||
|
||||
if not is_server_reachable(server_url):
|
||||
raise RuntimeError(f'Server not reachable at {server_url}')
|
||||
|
||||
screenshot_path = os.path.join(tmpdir, 'final_screenshot.png')
|
||||
capture_screenshot_playwright(server_url, screenshot_path)
|
||||
image = Image.open(screenshot_path)
|
||||
image.load()
|
||||
finally:
|
||||
# Shut down the server and clean up
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def is_server_reachable(url):
|
||||
"""
|
||||
Check if the local server is reachable.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(url, timeout=5) # Set a 5-second timeout
|
||||
if response.status_code == 200:
|
||||
logger.info(f'Server is reachable at {url}')
|
||||
return True
|
||||
else:
|
||||
logger.warning(
|
||||
f'Server responded with status code {response.status_code} at {url}'
|
||||
)
|
||||
return False
|
||||
except requests.ConnectionError as e:
|
||||
logger.error(f'Failed to connect to server at {url}: {e}')
|
||||
return False
|
||||
|
||||
|
||||
def capture_screenshot_playwright(url, screenshot_path):
|
||||
"""Capture a screenshot of the given URL using Playwright."""
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
logger.info('Launching browser...')
|
||||
browser = p.chromium.launch(timeout=10000) # 10 seconds for browser launch
|
||||
|
||||
logger.info('Creating a new page...')
|
||||
page = browser.new_page()
|
||||
|
||||
logger.info(f'Navigating to URL: {url}')
|
||||
try:
|
||||
page.goto(url, timeout=60 * 1000) # Set timeout to 5 seconds
|
||||
logger.info('Page navigation completed.')
|
||||
except Exception as e:
|
||||
logger.warning(f'Page navigation timed out. {e}. Continuing...')
|
||||
|
||||
logger.info('Waiting for network to be idle...')
|
||||
try:
|
||||
page.wait_for_load_state(
|
||||
'networkidle', timeout=60 * 1000
|
||||
) # Set timeout to 5 seconds
|
||||
logger.info('Page load state reached.')
|
||||
except Exception as e:
|
||||
logger.warning(f'Page load state timed out. {e}. Continuing...')
|
||||
|
||||
logger.info('Capturing screenshot...')
|
||||
page.screenshot(
|
||||
path=screenshot_path, full_page=True
|
||||
) # Capture full page screenshot
|
||||
|
||||
logger.info(f'Screenshot saved to {screenshot_path}')
|
||||
browser.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f'Error capturing screenshot with Playwright: {e}')
|
||||
return False
|
||||
|
||||
|
||||
def evaluate(task, screenshot_path):
|
||||
"""Compare generated screenshot with post_image using CLIP score."""
|
||||
try:
|
||||
import torch
|
||||
from transformers import CLIPModel, CLIPProcessor
|
||||
|
||||
# Load CLIP model and processor
|
||||
model = CLIPModel.from_pretrained('openai/clip-vit-base-patch32')
|
||||
processor = CLIPProcessor.from_pretrained('openai/clip-vit-base-patch32')
|
||||
|
||||
# Load images
|
||||
post_image = Image.open(BytesIO(task['post_image']))
|
||||
generated_img = Image.open(screenshot_path)
|
||||
|
||||
# Process images
|
||||
inputs = processor(
|
||||
images=[post_image, generated_img], return_tensors='pt', padding=True
|
||||
)
|
||||
|
||||
# Get image features
|
||||
image_features = model.get_image_features(**inputs)
|
||||
|
||||
# Calculate cosine similarity
|
||||
similarity = torch.nn.functional.cosine_similarity(
|
||||
image_features[0].unsqueeze(0), image_features[1].unsqueeze(0)
|
||||
).item()
|
||||
|
||||
logger.info(f'CLIP similarity score: {similarity}')
|
||||
|
||||
return similarity > 0.95 # Consider it a match if similarity > 95%
|
||||
except Exception as e:
|
||||
logger.error(f'Error in CLIP evaluation: {e}')
|
||||
# Fallback to pixel comparison if CLIP fails
|
||||
try:
|
||||
post_image = Image.open(BytesIO(task['post_image']))
|
||||
generated_img = Image.open(screenshot_path)
|
||||
|
||||
# Compare images directly without converting to bytes
|
||||
diff = ImageChops.difference(generated_img, post_image)
|
||||
logger.info(
|
||||
f"Pixel difference analysis: {'No difference' if not diff.getbbox() else 'Differences found'}"
|
||||
)
|
||||
return not diff.getbbox()
|
||||
except Exception as ex:
|
||||
logger.error(f'Error in fallback evaluation: {ex}')
|
||||
return False
|
||||
50
evaluation/benchmarks/visualwebarena/README.md
Normal file
50
evaluation/benchmarks/visualwebarena/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# VisualWebArena Evaluation with OpenHands Browsing Agents
|
||||
|
||||
This folder contains evaluation for [VisualWebArena](https://github.com/web-arena-x/visualwebarena) benchmark, powered by [BrowserGym](https://github.com/ServiceNow/BrowserGym) for easy evaluation of how well an agent capable of browsing can perform on realistic web browsing tasks.
|
||||
|
||||
## Setup Environment and LLM Configuration
|
||||
|
||||
Please follow instruction [here](../../README.md#setup) to setup your local development environment and LLM.
|
||||
|
||||
## Setup VisualWebArena Environment
|
||||
|
||||
VisualWebArena requires you to set up websites containing pre-populated content that is accessible via URL to the machine running the OpenHands agents.
|
||||
Follow [this document](https://github.com/web-arena-x/visualwebarena/blob/main/environment_docker/README.md) to set up your own VisualWebArena environment through local servers or AWS EC2 instances.
|
||||
Take note of the base URL (`$VISUALWEBARENA_BASE_URL`) of the machine where the environment is installed.
|
||||
|
||||
## Test if your environment works
|
||||
|
||||
Access with browser the above VisualWebArena website URLs and see if they load correctly.
|
||||
If you cannot access the website, make sure the firewall allows public access of the aforementioned ports on your server
|
||||
Check the network security policy if you are using an AWS machine.
|
||||
Follow the VisualWebArena environment setup guide carefully, and make sure the URL fields are populated with the correct base URL of your server.
|
||||
|
||||
## Run Evaluation
|
||||
|
||||
```bash
|
||||
export VISUALWEBARENA_BASE_URL=<YOUR_SERVER_URL_HERE>
|
||||
export OPENAI_API_KEY="yourkey" # this OpenAI API key is required for some visualWebArena validators that utilize LLMs
|
||||
export OPENAI_BASE_URL="https://api.openai.com/v1/" # base URL for OpenAI model used for VisualWebArena evaluation
|
||||
bash evaluation/benchmarks/visualwebarena/scripts/run_infer.sh llm.claude HEAD VisualBrowsingAgent
|
||||
```
|
||||
|
||||
Results will be in `evaluation/evaluation_outputs/outputs/visualwebarena/`
|
||||
|
||||
To calculate the success rate, run:
|
||||
|
||||
```sh
|
||||
poetry run python evaluation/benchmarks/visualwebarena/get_success_rate.py evaluation/evaluation_outputs/outputs/visualwebarena/SOME_AGENT/EXP_NAME/output.jsonl
|
||||
```
|
||||
|
||||
## Submit your evaluation results
|
||||
|
||||
You can start your own fork of [our huggingface evaluation outputs](https://huggingface.co/spaces/OpenHands/evaluation) and submit a PR of your evaluation results following the guide [here](https://huggingface.co/docs/hub/en/repositories-pull-requests-discussions#pull-requests-and-discussions).
|
||||
|
||||
## VisualBrowsingAgent V1.0 result
|
||||
|
||||
Tested on VisualBrowsingAgent V1.0
|
||||
|
||||
VisualWebArena, 910 tasks (high cost, single run due to fixed task), max step 15. Resolve rates are:
|
||||
|
||||
- GPT4o: 26.15%
|
||||
- Claude-3.5 Sonnet: 25.27%
|
||||
0
evaluation/benchmarks/visualwebarena/__init__.py
Normal file
0
evaluation/benchmarks/visualwebarena/__init__.py
Normal file
40
evaluation/benchmarks/visualwebarena/get_success_rate.py
Normal file
40
evaluation/benchmarks/visualwebarena/get_success_rate.py
Normal file
@@ -0,0 +1,40 @@
|
||||
import argparse
|
||||
import json
|
||||
|
||||
import browsergym.visualwebarena # noqa F401 register visualwebarena tasks as gym environments
|
||||
import gymnasium as gym
|
||||
|
||||
parser = argparse.ArgumentParser(description='Calculate average reward.')
|
||||
parser.add_argument('output_path', type=str, help='path to output.jsonl')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if __name__ == '__main__':
|
||||
env_ids = [
|
||||
id
|
||||
for id in gym.envs.registry.keys()
|
||||
if id.startswith('browsergym/visualwebarena')
|
||||
]
|
||||
total_num = len(env_ids)
|
||||
print('Total number of tasks: ', total_num)
|
||||
total_reward = 0
|
||||
total_cost = 0
|
||||
actual_num = 0
|
||||
with open(args.output_path, 'r') as f:
|
||||
for line in f:
|
||||
data = json.loads(line)
|
||||
actual_num += 1
|
||||
total_cost += data['metrics']['accumulated_cost']
|
||||
reward = data['test_result']['reward']
|
||||
if reward >= 0:
|
||||
total_reward += data['test_result']['reward']
|
||||
else:
|
||||
actual_num -= 1
|
||||
avg_reward = total_reward / total_num
|
||||
print('Total reward: ', total_reward)
|
||||
print('Success Rate: ', avg_reward)
|
||||
|
||||
avg_cost = total_cost / actual_num
|
||||
print('Avg Cost: ', avg_cost)
|
||||
print('Total Cost: ', total_cost)
|
||||
print('Actual number of tasks finished: ', actual_num)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user