mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-04-29 03:00:45 -04:00
Compare commits
279 Commits
ad/create-
...
0.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5989853f7a | ||
|
|
fe8bb51f17 | ||
|
|
0c2ebfd6e1 | ||
|
|
086a2ed17f | ||
|
|
fa067edbac | ||
|
|
11d48cc2f3 | ||
|
|
c821d0967c | ||
|
|
b47fdbb23b | ||
|
|
b3930ad195 | ||
|
|
cd58194d2a | ||
|
|
46bd83678a | ||
|
|
1597f0093e | ||
|
|
c02b681728 | ||
|
|
319b9ac0f3 | ||
|
|
5a9b6b0c9f | ||
|
|
a5a1545c59 | ||
|
|
03ecec1e76 | ||
|
|
8954855ba3 | ||
|
|
24b71927c3 | ||
|
|
a5f61caae9 | ||
|
|
0d224de369 | ||
|
|
567e2c2b35 | ||
|
|
a7e42ff0a3 | ||
|
|
d1f62bb6be | ||
|
|
9c9aee29f0 | ||
|
|
d8386366d9 | ||
|
|
ca73ecb499 | ||
|
|
6f90239521 | ||
|
|
546be7ca8e | ||
|
|
ce47461c5a | ||
|
|
44aea95dde | ||
|
|
e32d95cb1a | ||
|
|
7d5856e36b | ||
|
|
fd9e598136 | ||
|
|
c0adb55bfa | ||
|
|
e9a9186717 | ||
|
|
93d8ee1400 | ||
|
|
831e934dab | ||
|
|
5a0ef2b5de | ||
|
|
0fb3d63406 | ||
|
|
65558df1f7 | ||
|
|
5515c08624 | ||
|
|
f407382a5a | ||
|
|
110e7f0c4c | ||
|
|
a1a0767681 | ||
|
|
e7b5ddfe06 | ||
|
|
bf5a2afd83 | ||
|
|
88397e0469 | ||
|
|
c45e2ae4d9 | ||
|
|
5543fe2c3e | ||
|
|
bc84482c48 | ||
|
|
6972ea0089 | ||
|
|
43214a69a0 | ||
|
|
2318ceae35 | ||
|
|
f4ce7e7862 | ||
|
|
bd470e8076 | ||
|
|
fde0392457 | ||
|
|
1e95fa435d | ||
|
|
8828d9836d | ||
|
|
d357fcff36 | ||
|
|
9241f713b0 | ||
|
|
2472840a56 | ||
|
|
ab3e18667b | ||
|
|
236b7bf6ea | ||
|
|
dc73954cce | ||
|
|
9b48f63544 | ||
|
|
e6ecbd5510 | ||
|
|
2268529b75 | ||
|
|
a0dd5880d7 | ||
|
|
b48f440476 | ||
|
|
a0e8fcb19a | ||
|
|
99829f95fd | ||
|
|
0b6ab238f9 | ||
|
|
3eea6de546 | ||
|
|
27246aca7e | ||
|
|
ba556ea4d8 | ||
|
|
f40e91b41b | ||
|
|
d1551c3097 | ||
|
|
f6dcbeaa1d | ||
|
|
620bbb38cf | ||
|
|
a2a86e84f0 | ||
|
|
0a2e90dece | ||
|
|
747ac23cd0 | ||
|
|
960f17a565 | ||
|
|
fc5e075ea0 | ||
|
|
220dac926e | ||
|
|
e84125de6f | ||
|
|
b0c3bca915 | ||
|
|
39a851bd95 | ||
|
|
f55f7be2e6 | ||
|
|
0669b27522 | ||
|
|
3193014ed8 | ||
|
|
cfbd482498 | ||
|
|
2242702cf9 | ||
|
|
157278704e | ||
|
|
ffa62b9928 | ||
|
|
bc23c9429c | ||
|
|
8310cf6e23 | ||
|
|
464bf7ee23 | ||
|
|
1f2a845feb | ||
|
|
454e9613b0 | ||
|
|
900de8728c | ||
|
|
7bd5417b95 | ||
|
|
5a4913224a | ||
|
|
dffbeec45a | ||
|
|
0e572c3e41 | ||
|
|
d692a72bf3 | ||
|
|
b5da25e9cc | ||
|
|
1210eea48d | ||
|
|
cf3372a5fe | ||
|
|
49ff317b50 | ||
|
|
6340cd9aed | ||
|
|
3c85ece23e | ||
|
|
0356f6ec89 | ||
|
|
3988443705 | ||
|
|
7675b3ca34 | ||
|
|
d61fdb8bba | ||
|
|
0a59d46ca3 | ||
|
|
76b81ca0ed | ||
|
|
e6d91affc6 | ||
|
|
e82543747c | ||
|
|
959d91c9d6 | ||
|
|
871eefe801 | ||
|
|
34286fabcc | ||
|
|
b9bed7da8d | ||
|
|
7c3ea9fa08 | ||
|
|
492feecb67 | ||
|
|
ca1d53c161 | ||
|
|
fe3d4b129d | ||
|
|
2bf34093b0 | ||
|
|
9e85550b46 | ||
|
|
2826e39056 | ||
|
|
226fb43fbe | ||
|
|
9d5bc6b332 | ||
|
|
af262c17f1 | ||
|
|
6b0408d47c | ||
|
|
74c9b58d1b | ||
|
|
1356da8795 | ||
|
|
f7fd925f30 | ||
|
|
94831083c3 | ||
|
|
396b6a62a9 | ||
|
|
caabfab7e2 | ||
|
|
51149780ac | ||
|
|
426f387123 | ||
|
|
9fd7068204 | ||
|
|
18348911c2 | ||
|
|
8472436fe8 | ||
|
|
16fc728696 | ||
|
|
c8c1eed5be | ||
|
|
60a92a07c1 | ||
|
|
a0fee17388 | ||
|
|
b654b00aa1 | ||
|
|
aed82704a9 | ||
|
|
edeea95e7d | ||
|
|
a663302ba2 | ||
|
|
1338248310 | ||
|
|
d34a9db04f | ||
|
|
7710112ae2 | ||
|
|
6b007c163b | ||
|
|
1115b60a74 | ||
|
|
5881857d5c | ||
|
|
4b4bc15fdb | ||
|
|
45c6f639f4 | ||
|
|
90ca29414c | ||
|
|
8e4c4c9946 | ||
|
|
88e31f91d8 | ||
|
|
7e825b571f | ||
|
|
516c9bf1e0 | ||
|
|
71edee17be | ||
|
|
de672029ef | ||
|
|
f0559892ab | ||
|
|
2491a3524e | ||
|
|
0616fe3f8d | ||
|
|
474aafbc79 | ||
|
|
342302ceef | ||
|
|
8450b47609 | ||
|
|
34ecfe3c75 | ||
|
|
f0cd5a37e8 | ||
|
|
98d019b825 | ||
|
|
d2f7056c06 | ||
|
|
652507f430 | ||
|
|
033352e340 | ||
|
|
6b9316f722 | ||
|
|
53f95056de | ||
|
|
dd32fa6f4a | ||
|
|
d4ce4ea541 | ||
|
|
066680f8ef | ||
|
|
d27895d018 | ||
|
|
8b9f13b1ed | ||
|
|
2326312d3a | ||
|
|
e0c7492609 | ||
|
|
5d5106c510 | ||
|
|
70534f203e | ||
|
|
494a1b6872 | ||
|
|
ded0a762aa | ||
|
|
32ba0e7f7e | ||
|
|
7b526b3620 | ||
|
|
224ee7d1f8 | ||
|
|
9fd95cc35f | ||
|
|
9cd4ad3298 | ||
|
|
fb54c36c90 | ||
|
|
51b3ae56c7 | ||
|
|
b8202e804f | ||
|
|
7f5d9c7d92 | ||
|
|
cd723abfdd | ||
|
|
cc584445c6 | ||
|
|
9846e24299 | ||
|
|
973a42fd78 | ||
|
|
b2de79ae08 | ||
|
|
0b21d77880 | ||
|
|
e256329e5e | ||
|
|
52b63908ca | ||
|
|
e8ff184912 | ||
|
|
707ab7b3f8 | ||
|
|
78858c18b5 | ||
|
|
92562a5152 | ||
|
|
c1aab59c2e | ||
|
|
eb7b3484fd | ||
|
|
8ab9c6fb86 | ||
|
|
3c4b3eddc0 | ||
|
|
01c4c4bee4 | ||
|
|
3c82ba7f34 | ||
|
|
73fb4843a3 | ||
|
|
6f795f5e9c | ||
|
|
6b7c5b09af | ||
|
|
6e566dd21d | ||
|
|
cc6626ff0d | ||
|
|
fab2259d3a | ||
|
|
55760ec4dd | ||
|
|
6e3b554317 | ||
|
|
e52bf5ad7b | ||
|
|
e878b0c7ee | ||
|
|
2f9bf606c7 | ||
|
|
04066ca42b | ||
|
|
a0c5c8efe9 | ||
|
|
6f346b3789 | ||
|
|
e9121b78fe | ||
|
|
d3770f1db6 | ||
|
|
99a8dc4ff9 | ||
|
|
4b4ce20f2d | ||
|
|
d87a7ddd83 | ||
|
|
9c98b67002 | ||
|
|
c0dfc851b9 | ||
|
|
5ce3af6f8f | ||
|
|
371f7a127d | ||
|
|
e54fe4491c | ||
|
|
3313e473ea | ||
|
|
229fa988c5 | ||
|
|
5dda0ddef6 | ||
|
|
23a7057be2 | ||
|
|
f40fe6ac28 | ||
|
|
8f097f8643 | ||
|
|
49f3665a99 | ||
|
|
0b5eea967f | ||
|
|
c34517be2b | ||
|
|
228889c50c | ||
|
|
d38113cead | ||
|
|
66cd9f9bd9 | ||
|
|
7d3cd06ef5 | ||
|
|
d20f532289 | ||
|
|
7cc58b28a5 | ||
|
|
2855959c76 | ||
|
|
fad51a6141 | ||
|
|
3da56d3b6f | ||
|
|
d6128941b7 | ||
|
|
a82e065f56 | ||
|
|
da12d70bc2 | ||
|
|
a202b2550f | ||
|
|
adbcfefd8c | ||
|
|
fe9815d57b | ||
|
|
89dc78953e | ||
|
|
9109344728 | ||
|
|
1328f55dcd | ||
|
|
dcaf51bc9e | ||
|
|
12ddd973b5 | ||
|
|
c834aa7275 | ||
|
|
8c2b4f5fde | ||
|
|
8ec24ab7ea | ||
|
|
5ec0e5b7ec |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@@ -0,0 +1,5 @@
|
||||
frontend/node_modules
|
||||
config.toml
|
||||
.envrc
|
||||
.env
|
||||
.git
|
||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1 +1 @@
|
||||
*.ipynb linguist-vendored
|
||||
*.ipynb linguist-vendored
|
||||
|
||||
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
40
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,40 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a problem with OpenDevin
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
<!-- You MUST fill out this template. We will close issues that don't include enough information to reproduce -->
|
||||
#### Describe the bug
|
||||
<!-- a short description of the problem -->
|
||||
|
||||
#### Setup and configuration
|
||||
**Current version**:
|
||||
<!-- run `git log -n 1` to see this -->
|
||||
```bash
|
||||
```
|
||||
|
||||
<!-- tell us everything about your environment -->
|
||||
**My config.toml and environment vars** (be sure to redact API keys):
|
||||
```toml
|
||||
```
|
||||
|
||||
**My model and agent** (you can see these settings in the UI):
|
||||
* Model:
|
||||
* Agent:
|
||||
|
||||
**Commands I ran to install and run OpenDevin**:
|
||||
```
|
||||
```
|
||||
|
||||
**Steps to Reproduce**:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
**Logs, error messages, and screenshots**:
|
||||
|
||||
#### Additional Context
|
||||
|
||||
69
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
Normal file
69
.github/ISSUE_TEMPLATE/bug_template.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Bug
|
||||
description: Report a problem with OpenDevin
|
||||
title: '[Bug]: '
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Thank you for taking the time to fill out this bug report. We greatly appreciate your effort to complete this template fully. Please provide as much information as possible to help us understand and address the issue effectively.
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for the same bug?
|
||||
description: Please check if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have checked the troubleshooting document at https://github.com/OpenDevin/OpenDevin/blob/main/docs/guides/Troubleshooting.md
|
||||
required: true
|
||||
- label: I have checked the existing issues.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: Provide a short description of the problem.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: current-version
|
||||
attributes:
|
||||
label: Current Version
|
||||
description: What version are you using? If you're running in docker, tell us the tag you're using (e.g. ghcr.io/opendevin/opendevin:0.3.1).
|
||||
render: bash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Installation and Configuration
|
||||
description: Please provide any commands you ran and any configuration (redacting API keys)
|
||||
render: bash
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: model-agent
|
||||
attributes:
|
||||
label: Model and Agent
|
||||
description: What model and agent are you using? You can see these settings in the UI by clicking the settings wheel.
|
||||
placeholder: |
|
||||
- Model:
|
||||
- Agent:
|
||||
|
||||
- type: textarea
|
||||
id: repro-steps
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
description: Please list the steps to reproduce the issue.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Logs, Errors, Screenshots, and Additional Context
|
||||
description: Please add any additional context about the problem here.
|
||||
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
16
.github/ISSUE_TEMPLATE/question.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: Question
|
||||
about: Use this template to ask a question regarding the project.
|
||||
title: ''
|
||||
labels: question
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Describe your question
|
||||
|
||||
<!--A clear and concise description of what you want to know.-->
|
||||
|
||||
## Additional context
|
||||
|
||||
<!--Add any other context about the question here, like what you've tried so far.-->
|
||||
55
.github/workflows/deploy-docs.yml
vendored
Normal file
55
.github/workflows/deploy-docs.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Deploy Docs to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docusaurus
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
cache-dependency-path: docs/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build website
|
||||
run: npm run build
|
||||
|
||||
- name: Upload Build Artifact
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/build
|
||||
|
||||
deploy:
|
||||
name: Deploy to GitHub Pages
|
||||
needs: build
|
||||
if: github.ref == 'refs/heads/main'
|
||||
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
||||
permissions:
|
||||
pages: write # to deploy to Pages
|
||||
id-token: write # to verify the deployment originates from an appropriate source
|
||||
# Deploy to the github-pages environment
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
68
.github/workflows/ghcr.yml
vendored
68
.github/workflows/ghcr.yml
vendored
@@ -1,8 +1,10 @@
|
||||
name: Build and publish multi-arch container images
|
||||
name: Publish Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
reason:
|
||||
@@ -13,7 +15,14 @@ on:
|
||||
jobs:
|
||||
ghcr_build_and_push:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.inputs.reason != ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
image: ["app", "evaluation", "sandbox"]
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
@@ -26,34 +35,29 @@ jobs:
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log-in to ghcr.io
|
||||
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
- name: Login to ghcr
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push multi-arch container images
|
||||
- name: Delete huge unnecessary tools folder
|
||||
run: rm -rf /opt/hostedtoolcache
|
||||
|
||||
- name: Build and push ${{ matrix.image }}
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
run: |
|
||||
# set env for fork repo
|
||||
DOCKER_BUILD_ORG=$(echo "${{ github.repository }}" | tr '[A-Z]' '[a-z]' | cut -d '/' -f 1)
|
||||
# Find directories containing Dockerfile but not containing .dockerfileignore
|
||||
while IFS= read -r dockerfile_dir; do
|
||||
|
||||
# Check if .dockerfileignore exists in the directory
|
||||
if [ -f "$dockerfile_dir/.dockerfileignore" ]; then
|
||||
echo "$dockerfile_dir/.dockerfileignore exists, skipping build and push"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if image was already exist in ghcr.io
|
||||
pushd "$dockerfile_dir" > /dev/null
|
||||
FULL_IMAGE=$(make get-full-image DOCKER_BUILD_ORG=$DOCKER_BUILD_ORG)
|
||||
popd > /dev/null
|
||||
EXISTS=$(docker manifest inspect "$FULL_IMAGE" > /dev/null 2>&1 && echo "true" || echo "false")
|
||||
if [ "$EXISTS" == "true" ]; then
|
||||
echo "Image $FULL_IMAGE already exists in ghcr.io, skipping build and push"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Build and push the image to ghcr.io
|
||||
pushd "$dockerfile_dir" > /dev/null
|
||||
make all DOCKER_BUILD_ORG=$DOCKER_BUILD_ORG
|
||||
popd > /dev/null
|
||||
done < <(find . -type f -name Dockerfile -exec dirname {} \; | sort -u)
|
||||
./containers/build.sh ${{ matrix.image }} ${{ github.repository_owner }} --push
|
||||
|
||||
- name: Build ${{ matrix.image }}
|
||||
if: github.event.pull_request.head.repo.full_name != github.repository
|
||||
run: |
|
||||
./containers/build.sh ${{ matrix.image }} ${{ github.repository_owner }}
|
||||
|
||||
docker_build_success:
|
||||
name: Docker Build Success
|
||||
runs-on: ubuntu-latest
|
||||
needs: ghcr_build_and_push
|
||||
steps:
|
||||
- run: echo Done!
|
||||
|
||||
41
.github/workflows/lint.yml
vendored
41
.github/workflows/lint.yml
vendored
@@ -1,47 +1,48 @@
|
||||
name: Lint
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint-frontend:
|
||||
name: Lint frontend
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
package_json_file: frontend/package.json
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js 20
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'pnpm'
|
||||
cache-dependency-path: 'frontend/pnpm-lock.yaml'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd frontend
|
||||
pnpm install --frozen-lockfile
|
||||
npm install --frozen-lockfile
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
cd frontend
|
||||
pnpm run lint
|
||||
npm run lint
|
||||
|
||||
lint-python:
|
||||
name: Lint python
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.11
|
||||
- name: Install dependencies
|
||||
run: pip install ruff mypy
|
||||
- name: Run ruff
|
||||
run: ruff check --config dev_config/python/ruff.toml opendevin/ agenthub/
|
||||
- name: Run mypy
|
||||
run: mypy --install-types --non-interactive --config-file dev_config/python/mypy.ini opendevin/ agenthub/
|
||||
cache: 'pip'
|
||||
- name: Install pre-commit
|
||||
run: pip install pre-commit==3.7.0
|
||||
- name: Run pre-commit hooks
|
||||
run: |
|
||||
pre-commit run \
|
||||
--all-files \
|
||||
--show-diff-on-failure \
|
||||
--config ./dev_config/python/.pre-commit-config.yaml
|
||||
|
||||
69
.github/workflows/review-pr.yml
vendored
Normal file
69
.github/workflows/review-pr.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: Use OpenDevin to Review Pull Request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, labeled]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
dogfood:
|
||||
if: contains(github.event.pull_request.labels.*.name, 'review-this')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/opendevin/opendevin
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
steps:
|
||||
- name: install git, github cli
|
||||
run: |
|
||||
apt-get install -y git gh
|
||||
git config --global --add safe.directory $PWD
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.ref }} # check out the target branch
|
||||
|
||||
- name: Download Diff
|
||||
run: |
|
||||
curl -O "${{ github.event.pull_request.diff_url }}" -L
|
||||
|
||||
- name: Write Task File
|
||||
run: |
|
||||
echo "Your coworker wants to apply a pull request to this project. Read and review ${{ github.event.pull_request.number }}.diff file. Create a review-${{ github.event.pull_request.number }}.txt and write your concise comments and suggestions there." > task.txt
|
||||
echo "" >> task.txt
|
||||
echo "Title" >> task.txt
|
||||
echo "${{ github.event.pull_request.title }}" >> task.txt
|
||||
echo "" >> task.txt
|
||||
echo "Description" >> task.txt
|
||||
echo "${{ github.event.pull_request.body }}" >> task.txt
|
||||
echo "" >> task.txt
|
||||
echo "Diff file is: ${{ github.event.pull_request.number }}.diff" >> task.txt
|
||||
|
||||
- name: Run OpenDevin
|
||||
env:
|
||||
LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
SANDBOX_TYPE: exec
|
||||
run: |
|
||||
WORKSPACE_MOUNT_PATH=$GITHUB_WORKSPACE python ./opendevin/main.py -i 50 -f task.txt -d $GITHUB_WORKSPACE
|
||||
rm task.txt
|
||||
|
||||
- name: Check if review file is non-empty
|
||||
id: check_file
|
||||
run: |
|
||||
ls -la
|
||||
if [[ -s review-${{ github.event.pull_request.number }}.txt ]]; then
|
||||
echo "non_empty=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Create PR review if file is non-empty
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
if: steps.check_file.outputs.non_empty == 'true'
|
||||
run: |
|
||||
gh pr review ${{ github.event.pull_request.number }} --comment --body-file "review-${{ github.event.pull_request.number }}.txt"
|
||||
72
.github/workflows/run-integration-tests.yml
vendored
Normal file
72
.github/workflows/run-integration-tests.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Run Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
integration-tests:
|
||||
name: Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
agent: ["SWEAgent", "PlannerAgent", "MonologueAgent", "CodeActAgent"]
|
||||
sandbox: ["ssh", "exec"]
|
||||
include:
|
||||
- agent: "MonologueAgent"
|
||||
embedding-model: "local"
|
||||
- agent: "MonologueAgent"
|
||||
# sufficient to have one agent testing against local sandbox
|
||||
sandbox: "local"
|
||||
embedding-model: "local"
|
||||
- agent: "SWEAgent"
|
||||
embedding-model: "none"
|
||||
- agent: "PlannerAgent"
|
||||
embedding-model: "none"
|
||||
- agent: "CodeActAgent"
|
||||
embedding-model: "none"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: 'poetry'
|
||||
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: poetry install
|
||||
|
||||
- name: Build Environment
|
||||
run: make build
|
||||
|
||||
- name: Run Integration Tests
|
||||
env:
|
||||
SANDBOX_TYPE: ${{ matrix.sandbox }}
|
||||
AGENT: ${{ matrix.agent }}
|
||||
MAX_ITERATIONS: 10
|
||||
LLM_EMBEDDING_MODEL: ${{ matrix.embedding-model }}
|
||||
run: |
|
||||
rm -rf workspace
|
||||
mkdir workspace
|
||||
WORKSPACE_BASE="$GITHUB_WORKSPACE/workspace" \
|
||||
WORKSPACE_MOUNT_PATH="$GITHUB_WORKSPACE/workspace" \
|
||||
poetry run pytest --cov=agenthub --cov=opendevin --cov-report=xml \
|
||||
-s ./tests/integration
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
test_matrix_success:
|
||||
name: All Integration Tests Passed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [integration-tests]
|
||||
steps:
|
||||
- run: echo Done!
|
||||
82
.github/workflows/run-unit-tests.yml
vendored
Normal file
82
.github/workflows/run-unit-tests.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Run Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test-on-macos:
|
||||
name: Test on macOS
|
||||
runs-on: macos-13
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'poetry'
|
||||
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: poetry install
|
||||
|
||||
- name: Install & Start Docker
|
||||
run: |
|
||||
brew install colima docker
|
||||
colima start
|
||||
|
||||
- name: Build Environment
|
||||
run: make build
|
||||
|
||||
- name: Run Tests
|
||||
run: poetry run pytest --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
test-on-linux:
|
||||
name: Test on Linux
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install poetry via pipx
|
||||
run: pipx install poetry
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'poetry'
|
||||
|
||||
- name: Install Python dependencies using Poetry
|
||||
run: poetry install --without evaluation
|
||||
|
||||
- name: Build Environment
|
||||
run: make build
|
||||
|
||||
- name: Run Tests
|
||||
run: poetry run pytest --cov=agenthub --cov=opendevin --cov-report=xml ./tests/unit
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
test_matrix_success:
|
||||
name: All Mac/Linux Tests Passed
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test-on-macos, test-on-linux]
|
||||
steps:
|
||||
- run: echo Done!
|
||||
109
.github/workflows/solve-issue.yml
vendored
Normal file
109
.github/workflows/solve-issue.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
name: Use OpenDevin to Resolve GitHub Issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
dogfood:
|
||||
if: github.event.label.name == 'solve-this'
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/opendevin/opendevin
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
steps:
|
||||
- name: install git, github cli
|
||||
run: apt-get install -y git gh
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Write Task File
|
||||
env:
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
echo "TITLE:" > task.txt
|
||||
echo "${ISSUE_TITLE}" >> task.txt
|
||||
echo "" >> task.txt
|
||||
echo "BODY:" >> task.txt
|
||||
echo "${ISSUE_BODY}" >> task.txt
|
||||
|
||||
- name: Run OpenDevin
|
||||
env:
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
SANDBOX_TYPE: exec
|
||||
run: |
|
||||
WORKSPACE_MOUNT_PATH=$GITHUB_WORKSPACE python ./opendevin/main.py -i 50 -f task.txt -d $GITHUB_WORKSPACE
|
||||
rm task.txt
|
||||
|
||||
- name: Setup Git, Create Branch, and Commit Changes
|
||||
run: |
|
||||
# Setup Git configuration
|
||||
git config --global --add safe.directory $PWD
|
||||
git config --global user.name 'OpenDevin'
|
||||
git config --global user.email 'OpenDevin@users.noreply.github.com'
|
||||
|
||||
# Create a unique branch name with a timestamp
|
||||
BRANCH_NAME="fix/${{ github.event.issue.number }}-$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
# Checkout new branch
|
||||
git checkout -b $BRANCH_NAME
|
||||
|
||||
# Add all changes to staging, except task.txt
|
||||
git add --all -- ':!task.txt'
|
||||
|
||||
# Commit the changes, if any
|
||||
git commit -m "OpenDevin: Resolve Issue #${{ github.event.issue.number }}"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "No changes to commit."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Push changes
|
||||
git push --set-upstream origin $BRANCH_NAME
|
||||
|
||||
- name: Fetch Default Branch
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Fetch the default branch using gh cli
|
||||
DEFAULT_BRANCH=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)
|
||||
echo "Default branch is $DEFAULT_BRANCH"
|
||||
echo "DEFAULT_BRANCH=$DEFAULT_BRANCH" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Create PR and capture URL
|
||||
PR_URL=$(gh pr create \
|
||||
--title "OpenDevin: Resolve Issue #2" \
|
||||
--body "This PR was generated by OpenDevin to resolve issue #2" \
|
||||
--repo "foragerr/OpenDevin" \
|
||||
--head "${{ github.head_ref }}" \
|
||||
--base "${{ env.DEFAULT_BRANCH }}" \
|
||||
| grep -o 'https://github.com/[^ ]*')
|
||||
|
||||
# Extract PR number from URL
|
||||
PR_NUMBER=$(echo "$PR_URL" | grep -o '[0-9]\+$')
|
||||
|
||||
# Set environment vars
|
||||
echo "PR_URL=$PR_URL" >> $GITHUB_ENV
|
||||
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
|
||||
|
||||
- name: Post Comment
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh issue comment ${{ github.event.issue.number }} \
|
||||
-b "OpenDevin raised [PR #${{ env.PR_NUMBER }}](${{ env.PR_URL }}) to resolve this issue."
|
||||
29
.github/workflows/stale.yml
vendored
Normal file
29
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: 'Close stale issues'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
# Aggressively close issues that have been explicitly labeled `age-out`
|
||||
any-of-labels: age-out
|
||||
stale-issue-message: 'This issue is stale because it has been open for 7 days with no activity. Remove stale label or comment or this will be closed in 1 day.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for over 7 days with no activity.'
|
||||
stale-pr-message: 'This PR is stale because it has been open for 7 days with no activity. Remove stale label or comment or this will be closed in 1 days.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for over 7 days with no activity.'
|
||||
days-before-stale: 7
|
||||
days-before-close: 1
|
||||
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
# Be more lenient with other issues
|
||||
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
close-issue-message: 'This issue was closed because it has been stalled for over 30 days with no activity.'
|
||||
stale-pr-message: 'This PR is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
|
||||
close-pr-message: 'This PR was closed because it has been stalled for over 30 days with no activity.'
|
||||
days-before-stale: 30
|
||||
days-before-close: 7
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -57,7 +57,6 @@ cover/
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
@@ -100,7 +99,7 @@ ipython_config.py
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
# poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
@@ -128,6 +127,7 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
*venv/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
@@ -197,6 +197,7 @@ logs
|
||||
.envrc
|
||||
/workspace
|
||||
/debug
|
||||
cache
|
||||
|
||||
# configuration
|
||||
config.toml
|
||||
|
||||
@@ -85,5 +85,6 @@ Please refer to the README in each module:
|
||||
- [mock server](./opendevin/mock/README.md)
|
||||
|
||||
## Tests
|
||||
TODO: make sure code pass the test before submit.
|
||||
|
||||
Please navigate to `tests` folder to see existing test suites.
|
||||
At the moment, we have two kinds of tests: `unit` and `integration`. Please refer to the README for each test suite. These tests also run on CI to ensure quality of
|
||||
the project.
|
||||
|
||||
133
CodeOfConduct.md
Normal file
133
CodeOfConduct.md
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
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
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
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
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
contact@rbren.io
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
84
Development.md
Normal file
84
Development.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Development Guide
|
||||
This guide is for people working on OpenDevin and editing the source code.
|
||||
|
||||
## Start the server for development
|
||||
|
||||
### 1. Requirements
|
||||
* Linux, Mac OS, or [WSL on Windows](https://learn.microsoft.com/en-us/windows/wsl/install)
|
||||
* [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.11
|
||||
* [NodeJS](https://nodejs.org/en/download/package-manager) >= 18.17.1
|
||||
* [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer) >= 1.8
|
||||
|
||||
Make sure you have all these dependencies installed before moving on to `make build`.
|
||||
|
||||
#### Develop without sudo access
|
||||
If you want to develop without system admin/sudo access to upgrade/install `Python` and/or `NodeJs`, you can use `conda` or `mamba` to manage the packages for you:
|
||||
|
||||
```bash
|
||||
# Download and install Mamba (a faster version of conda)
|
||||
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
|
||||
bash Miniforge3-$(uname)-$(uname -m).sh
|
||||
|
||||
# Install Python 3.11, nodejs, and poetry
|
||||
mamba install python=3.11
|
||||
mamba install conda-forge::nodejs
|
||||
mamba install conda-forge::poetry
|
||||
```
|
||||
|
||||
### 2. Build and Setup The Environment
|
||||
|
||||
- **Build the Project:** Begin by building the project, which includes setting up the environment and installing dependencies. This step ensures that OpenDevin is ready to run smoothly on your system.
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
### 3. Configuring the Language Model
|
||||
|
||||
OpenDevin supports a diverse array of Language Models (LMs) through the powerful [litellm](https://docs.litellm.ai) library. By default, we've chosen the mighty GPT-4 from OpenAI as our go-to model, but the world is your oyster! You can unleash the potential of Anthropic's suave Claude, the enigmatic Llama, or any other LM that piques your interest.
|
||||
|
||||
To configure the LM of your choice, follow these steps:
|
||||
|
||||
1. **Using the Makefile: The Effortless Approach**
|
||||
With a single command, you can have a smooth LM setup for your OpenDevin experience. Simply run:
|
||||
```bash
|
||||
make setup-config
|
||||
```
|
||||
This command will prompt you to enter the LLM API key, model name, and other variables ensuring that OpenDevin is tailored to your specific needs. Note that the model name will apply only when you run headless. If you use the UI, please set the model in the UI.
|
||||
|
||||
**Note on Alternative Models:**
|
||||
Some alternative models may prove more challenging to tame than others. Fear not, brave adventurer! We shall soon unveil LLM-specific documentation to guide you on your quest. And if you've already mastered the art of wielding a model other than OpenAI's GPT, we encourage you to [share your setup instructions with us](https://github.com/OpenDevin/OpenDevin/issues/417).
|
||||
|
||||
For a full list of the LM providers and models available, please consult the [litellm documentation](https://docs.litellm.ai/docs/providers).
|
||||
|
||||
There is also [documentation for running with local models using ollama](./docs/documentation/LOCAL_LLM_GUIDE.md).
|
||||
|
||||
### 4. Run the Application
|
||||
|
||||
- **Run the Application:** Once the setup is complete, launching OpenDevin is as simple as running a single command. This command starts both the backend and frontend servers seamlessly, allowing you to interact with OpenDevin without any hassle.
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
### 5. Individual Server Startup
|
||||
|
||||
- **Start the Backend Server:** If you prefer, you can start the backend server independently to focus on backend-related tasks or configurations.
|
||||
```bash
|
||||
make start-backend
|
||||
```
|
||||
|
||||
- **Start the Frontend Server:** Similarly, you can start the frontend server on its own to work on frontend-related components or interface enhancements.
|
||||
```bash
|
||||
make start-frontend
|
||||
```
|
||||
|
||||
### 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. OpenDevin will then log the prompts and responses in the logs/llm/CURRENT_DATE directory, allowing you to identify the causes.
|
||||
|
||||
### 7. Help
|
||||
|
||||
- **Get Some Help:** Need assistance or information on available targets and commands? The help command provides all the necessary guidance to ensure a smooth experience with OpenDevin.
|
||||
```bash
|
||||
make help
|
||||
```
|
||||
252
Makefile
252
Makefile
@@ -1,3 +1,4 @@
|
||||
SHELL=/bin/bash
|
||||
# Makefile for OpenDevin project
|
||||
|
||||
# Variables
|
||||
@@ -6,92 +7,247 @@ BACKEND_PORT = 3000
|
||||
BACKEND_HOST = "127.0.0.1:$(BACKEND_PORT)"
|
||||
FRONTEND_PORT = 3001
|
||||
DEFAULT_WORKSPACE_DIR = "./workspace"
|
||||
DEFAULT_MODEL = "gpt-4-0125-preview"
|
||||
DEFAULT_MODEL = "gpt-3.5-turbo-1106"
|
||||
CONFIG_FILE = config.toml
|
||||
PRECOMMIT_CONFIG_PATH = "./dev_config/python/.pre-commit-config.yaml"
|
||||
|
||||
# ANSI color codes
|
||||
GREEN=$(shell tput -Txterm setaf 2)
|
||||
YELLOW=$(shell tput -Txterm setaf 3)
|
||||
RED=$(shell tput -Txterm setaf 1)
|
||||
BLUE=$(shell tput -Txterm setaf 6)
|
||||
RESET=$(shell tput -Txterm sgr0)
|
||||
|
||||
# Build
|
||||
build:
|
||||
@echo "Building project..."
|
||||
@echo "Pulling Docker image..."
|
||||
@docker pull $(DOCKER_IMAGE)
|
||||
@echo "Installing Python dependencies..."
|
||||
@python -m pip install pipenv
|
||||
@python -m pipenv install -v
|
||||
@echo "Setting up frontend environment..."
|
||||
@echo "Detect Node.js version..."
|
||||
@cd frontend && node ./scripts/detect-node-version.js
|
||||
@cd frontend && if [ -f node_modules/.package-lock.json ]; then \
|
||||
echo "This project currently uses "pnpm" for dependency management. It has detected that dependencies were previously installed using "npm" and has automatically deleted the "node_modules" directory to prevent unnecessary conflicts."; \
|
||||
rm -rf node_modules; \
|
||||
@echo "$(GREEN)Building project...$(RESET)"
|
||||
@$(MAKE) -s check-dependencies
|
||||
@$(MAKE) -s pull-docker-image
|
||||
@$(MAKE) -s install-python-dependencies
|
||||
@$(MAKE) -s install-frontend-dependencies
|
||||
@$(MAKE) -s install-precommit-hooks
|
||||
@$(MAKE) -s build-frontend
|
||||
@echo "$(GREEN)Build completed successfully.$(RESET)"
|
||||
|
||||
check-dependencies:
|
||||
@echo "$(YELLOW)Checking dependencies...$(RESET)"
|
||||
@$(MAKE) -s check-system
|
||||
@$(MAKE) -s check-python
|
||||
@$(MAKE) -s check-npm
|
||||
@$(MAKE) -s check-nodejs
|
||||
@$(MAKE) -s check-docker
|
||||
@$(MAKE) -s check-poetry
|
||||
@echo "$(GREEN)Dependencies checked successfully.$(RESET)"
|
||||
|
||||
check-system:
|
||||
@echo "$(YELLOW)Checking system...$(RESET)"
|
||||
@if [ "$(shell uname)" = "Darwin" ]; then \
|
||||
echo "$(BLUE)macOS detected.$(RESET)"; \
|
||||
elif [ "$(shell uname)" = "Linux" ]; then \
|
||||
echo "$(BLUE)Linux detected.$(RESET)"; \
|
||||
elif [ "$$(uname -r | grep -i microsoft)" ]; then \
|
||||
echo "$(BLUE)Windows Subsystem for Linux detected.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)Unsupported system detected. Please use macOS, Linux, or Windows Subsystem for Linux (WSL).$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@which corepack > /dev/null || (echo "Installing corepack..." && npm install -g corepack)
|
||||
@cd frontend && corepack enable && pnpm install && pnpm run make-i18n
|
||||
|
||||
check-python:
|
||||
@echo "$(YELLOW)Checking Python installation...$(RESET)"
|
||||
@if command -v python3.11 > /dev/null; then \
|
||||
echo "$(BLUE)$(shell python3.11 --version) is already installed.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)Python 3.11 is not installed. Please install Python 3.11 to continue.$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
check-npm:
|
||||
@echo "$(YELLOW)Checking npm installation...$(RESET)"
|
||||
@if command -v npm > /dev/null; then \
|
||||
echo "$(BLUE)npm $(shell npm --version) is already installed.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)npm is not installed. Please install Node.js to continue.$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
check-nodejs:
|
||||
@echo "$(YELLOW)Checking Node.js installation...$(RESET)"
|
||||
@if command -v node > /dev/null; then \
|
||||
NODE_VERSION=$(shell node --version | sed -E 's/v//g'); \
|
||||
IFS='.' read -r -a NODE_VERSION_ARRAY <<< "$$NODE_VERSION"; \
|
||||
if [ "$${NODE_VERSION_ARRAY[0]}" -gt 18 ] || ([ "$${NODE_VERSION_ARRAY[0]}" -eq 18 ] && [ "$${NODE_VERSION_ARRAY[1]}" -gt 17 ]) || ([ "$${NODE_VERSION_ARRAY[0]}" -eq 18 ] && [ "$${NODE_VERSION_ARRAY[1]}" -eq 17 ] && [ "$${NODE_VERSION_ARRAY[2]}" -ge 1 ]); then \
|
||||
echo "$(BLUE)Node.js $$NODE_VERSION is already installed.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)Node.js 18.17.1 or later is required. Please install Node.js 18.17.1 or later to continue.$(RESET)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
else \
|
||||
echo "$(RED)Node.js is not installed. Please install Node.js to continue.$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
check-docker:
|
||||
@echo "$(YELLOW)Checking Docker installation...$(RESET)"
|
||||
@if command -v docker > /dev/null; then \
|
||||
echo "$(BLUE)$(shell docker --version) is already installed.$(RESET)"; \
|
||||
else \
|
||||
echo "$(RED)Docker is not installed. Please install Docker to continue.$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
check-poetry:
|
||||
@echo "$(YELLOW)Checking Poetry installation...$(RESET)"
|
||||
@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 \
|
||||
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:"; \
|
||||
echo "$(RED) curl -sSL https://install.python-poetry.org | python3 -$(RESET)"; \
|
||||
echo "$(RED)More detail here: https://python-poetry.org/docs/#installing-with-the-official-installer$(RESET)"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
else \
|
||||
echo "$(RED)Poetry is not installed. You can install poetry by running the following command, then adding Poetry to your PATH:"; \
|
||||
echo "$(RED) curl -sSL https://install.python-poetry.org | python3.11 -$(RESET)"; \
|
||||
echo "$(RED)More detail here: https://python-poetry.org/docs/#installing-with-the-official-installer$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
pull-docker-image:
|
||||
@echo "$(YELLOW)Pulling Docker image...$(RESET)"
|
||||
@docker pull $(DOCKER_IMAGE)
|
||||
@echo "$(GREEN)Docker image pulled successfully.$(RESET)"
|
||||
|
||||
install-python-dependencies:
|
||||
@echo "$(GREEN)Installing Python dependencies...$(RESET)"
|
||||
@if [ "$(shell uname)" = "Darwin" ]; then \
|
||||
echo "$(BLUE)Installing `chroma-hnswlib`...$(RESET)"; \
|
||||
export HNSWLIB_NO_NATIVE=1; \
|
||||
poetry run pip install chroma-hnswlib; \
|
||||
fi
|
||||
@poetry install --without evaluation
|
||||
@poetry run playwright install --with-deps chromium
|
||||
@echo "$(GREEN)Python dependencies installed successfully.$(RESET)"
|
||||
|
||||
install-frontend-dependencies:
|
||||
@echo "$(YELLOW)Setting up frontend environment...$(RESET)"
|
||||
@echo "$(YELLOW)Detect Node.js version...$(RESET)"
|
||||
@cd frontend && node ./scripts/detect-node-version.js
|
||||
@cd frontend && \
|
||||
echo "$(BLUE)Installing frontend dependencies with npm...$(RESET)" && \
|
||||
npm install && \
|
||||
echo "$(BLUE)Running make-i18n with npm...$(RESET)" && \
|
||||
npm run make-i18n
|
||||
@echo "$(GREEN)Frontend dependencies installed successfully.$(RESET)"
|
||||
|
||||
install-precommit-hooks:
|
||||
@echo "$(YELLOW)Installing pre-commit hooks...$(RESET)"
|
||||
@git config --unset-all core.hooksPath || true
|
||||
@poetry run pre-commit install --config $(PRECOMMIT_CONFIG_PATH)
|
||||
@echo "$(GREEN)Pre-commit hooks installed successfully.$(RESET)"
|
||||
|
||||
lint:
|
||||
@echo "$(YELLOW)Running linters...$(RESET)"
|
||||
@poetry run pre-commit run --all-files --show-diff-on-failure --config $(PRECOMMIT_CONFIG_PATH)
|
||||
|
||||
build-frontend:
|
||||
@echo "$(YELLOW)Building frontend...$(RESET)"
|
||||
@cd frontend && npm run build
|
||||
|
||||
# Start backend
|
||||
start-backend:
|
||||
@echo "Starting backend..."
|
||||
@python -m pipenv run uvicorn opendevin.server.listen:app --port $(BACKEND_PORT)
|
||||
@echo "$(YELLOW)Starting backend...$(RESET)"
|
||||
@poetry run uvicorn opendevin.server.listen:app --port $(BACKEND_PORT) --reload --reload-exclude workspace/*
|
||||
|
||||
# Start frontend
|
||||
start-frontend:
|
||||
@echo "Starting frontend..."
|
||||
@cd frontend && BACKEND_HOST=$(BACKEND_HOST) FRONTEND_PORT=$(FRONTEND_PORT) npm run start
|
||||
@echo "$(YELLOW)Starting frontend...$(RESET)"
|
||||
@cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start
|
||||
|
||||
# Run the app
|
||||
run:
|
||||
@echo "Running the app..."
|
||||
@if [ "$(OS)" == "Windows_NT" ]; then \
|
||||
echo "`make run` is not supported on Windows. Please run `make start-frontend` and `make start-backend` separately."; \
|
||||
@echo "$(YELLOW)Running the app...$(RESET)"
|
||||
@if [ "$(OS)" = "Windows_NT" ]; then \
|
||||
echo "$(RED)`make run` is not supported on Windows. Please run `make start-frontend` and `make start-backend` separately.$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@mkdir -p logs
|
||||
@rm -f logs/pipe
|
||||
@mkfifo logs/pipe
|
||||
@cat logs/pipe | (make start-backend) &
|
||||
@echo 'test' | tee logs/pipe | (make start-frontend)
|
||||
@echo "$(YELLOW)Starting backend server...$(RESET)"
|
||||
@poetry run uvicorn opendevin.server.listen:app --port $(BACKEND_PORT) &
|
||||
@echo "$(YELLOW)Waiting for the backend to start...$(RESET)"
|
||||
@until nc -z localhost $(BACKEND_PORT); do sleep 0.1; done
|
||||
@echo "$(GREEN)Backend started successfully.$(RESET)"
|
||||
@cd frontend && echo "$(BLUE)Starting frontend with npm...$(RESET)" && npm run start -- --port $(FRONTEND_PORT)
|
||||
@echo "$(GREEN)Application started successfully.$(RESET)"
|
||||
|
||||
# Setup config.toml
|
||||
setup-config:
|
||||
@echo "Setting up config.toml..."
|
||||
@read -p "Enter your LLM Model name (see docs.litellm.ai/docs/providers for full list) [default: $(DEFAULT_MODEL)]: " llm_model; \
|
||||
@echo "$(YELLOW)Setting up config.toml...$(RESET)"
|
||||
@$(MAKE) setup-config-prompts
|
||||
@mv $(CONFIG_FILE).tmp $(CONFIG_FILE)
|
||||
@echo "$(GREEN)Config.toml setup completed.$(RESET)"
|
||||
|
||||
setup-config-prompts:
|
||||
@read -p "Enter your LLM Model name, used for running without UI. Set the model in the UI after you start the app. (see https://docs.litellm.ai/docs/providers for full list) [default: $(DEFAULT_MODEL)]: " llm_model; \
|
||||
llm_model=$${llm_model:-$(DEFAULT_MODEL)}; \
|
||||
echo "LLM_MODEL=\"$$llm_model\"" >> $(CONFIG_FILE).tmp
|
||||
echo "LLM_MODEL=\"$$llm_model\"" > $(CONFIG_FILE).tmp
|
||||
|
||||
@read -p "Enter your LLM API key: " llm_api_key; \
|
||||
echo "LLM_API_KEY=\"$$llm_api_key\"" >> $(CONFIG_FILE).tmp
|
||||
|
||||
@echo "Enter your LLM Embedding Model\nChoices are openai, azureopenai, llama2 or leave blank to default to 'BAAI/bge-small-en-v1.5' via huggingface"; \
|
||||
read -p "> " llm_embedding_model; \
|
||||
echo "LLM_EMBEDDING_MODEL=\"$$llm_embedding_model\"" >> $(CONFIG_FILE).tmp; \
|
||||
if [ "$$llm_embedding_model" = "llama2" ]; then \
|
||||
read -p "Enter the local model URL: " llm_base_url; \
|
||||
echo "LLM_BASE_URL=\"$$llm_base_url\"" >> $(CONFIG_FILE).tmp; \
|
||||
@read -p "Enter your LLM Base URL [mostly used for local LLMs, leave blank if not needed - example: http://localhost:5001/v1/]: " llm_base_url; \
|
||||
if [[ ! -z "$$llm_base_url" ]]; then echo "LLM_BASE_URL=\"$$llm_base_url\"" >> $(CONFIG_FILE).tmp; fi
|
||||
|
||||
@echo "Enter your LLM Embedding Model"; \
|
||||
echo "Choices are:"; \
|
||||
echo " - openai"; \
|
||||
echo " - azureopenai"; \
|
||||
echo " - Embeddings available only with OllamaEmbedding:"; \
|
||||
echo " - llama2"; \
|
||||
echo " - mxbai-embed-large"; \
|
||||
echo " - nomic-embed-text"; \
|
||||
echo " - all-minilm"; \
|
||||
echo " - stable-code"; \
|
||||
echo " - Leave blank to default to 'BAAI/bge-small-en-v1.5' via huggingface"; \
|
||||
read -p "> " llm_embedding_model; \
|
||||
echo "LLM_EMBEDDING_MODEL=\"$$llm_embedding_model\"" >> $(CONFIG_FILE).tmp; \
|
||||
if [ "$$llm_embedding_model" = "llama2" ] || [ "$$llm_embedding_model" = "mxbai-embed-large" ] || [ "$$llm_embedding_model" = "nomic-embed-text" ] || [ "$$llm_embedding_model" = "all-minilm" ] || [ "$$llm_embedding_model" = "stable-code" ]; then \
|
||||
read -p "Enter the local model URL for the embedding model (will set LLM_EMBEDDING_BASE_URL): " llm_embedding_base_url; \
|
||||
echo "LLM_EMBEDDING_BASE_URL=\"$$llm_embedding_base_url\"" >> $(CONFIG_FILE).tmp; \
|
||||
elif [ "$$llm_embedding_model" = "azureopenai" ]; then \
|
||||
read -p "Enter the Azure endpoint URL: " llm_base_url; \
|
||||
read -p "Enter the Azure endpoint URL (will overwrite LLM_BASE_URL): " llm_base_url; \
|
||||
echo "LLM_BASE_URL=\"$$llm_base_url\"" >> $(CONFIG_FILE).tmp; \
|
||||
read -p "Enter the Azure LLM Deployment Name: " llm_deployment_name; \
|
||||
echo "LLM_DEPLOYMENT_NAME=\"$$llm_deployment_name\"" >> $(CONFIG_FILE).tmp; \
|
||||
read -p "Enter the Azure LLM Embedding Deployment Name: " llm_embedding_deployment_name; \
|
||||
echo "LLM_EMBEDDING_DEPLOYMENT_NAME=\"$$llm_embedding_deployment_name\"" >> $(CONFIG_FILE).tmp; \
|
||||
read -p "Enter the Azure API Version: " llm_api_version; \
|
||||
echo "LLM_API_VERSION=\"$$llm_api_version\"" >> $(CONFIG_FILE).tmp; \
|
||||
fi
|
||||
|
||||
@read -p "Enter your workspace directory [default: $(DEFAULT_WORKSPACE_DIR)]: " workspace_dir; \
|
||||
workspace_dir=$${workspace_dir:-$(DEFAULT_WORKSPACE_DIR)}; \
|
||||
echo "WORKSPACE_DIR=\"$$workspace_dir\"" >> $(CONFIG_FILE).tmp
|
||||
echo "WORKSPACE_BASE=\"$$workspace_dir\"" >> $(CONFIG_FILE).tmp
|
||||
|
||||
@mv $(CONFIG_FILE).tmp $(CONFIG_FILE)
|
||||
# Clean up all caches
|
||||
clean:
|
||||
@echo "$(YELLOW)Cleaning up caches...$(RESET)"
|
||||
@rm -rf opendevin/.cache
|
||||
@echo "$(GREEN)Caches cleaned up successfully.$(RESET)"
|
||||
|
||||
# Help
|
||||
help:
|
||||
@echo "Usage: make [target]"
|
||||
@echo "$(BLUE)Usage: make [target]$(RESET)"
|
||||
@echo "Targets:"
|
||||
@echo " build - Build project, including environment setup and dependencies."
|
||||
@echo " start-backend - Start the backend server for the OpenDevin project."
|
||||
@echo " start-frontend - Start the frontend server for the OpenDevin project."
|
||||
@echo " run - Run the OpenDevin application, starting both backend and frontend servers."
|
||||
@echo " $(GREEN)build$(RESET) - Build project, including environment setup and dependencies."
|
||||
@echo " $(GREEN)lint$(RESET) - Run linters on the project."
|
||||
@echo " $(GREEN)setup-config$(RESET) - Setup the configuration for OpenDevin by providing LLM API key,"
|
||||
@echo " LLM Model name, and workspace directory."
|
||||
@echo " $(GREEN)start-backend$(RESET) - Start the backend server for the OpenDevin project."
|
||||
@echo " $(GREEN)start-frontend$(RESET) - Start the frontend server for the OpenDevin project."
|
||||
@echo " $(GREEN)run$(RESET) - Run the OpenDevin application, starting both backend and frontend servers."
|
||||
@echo " Backend Log file will be stored in the 'logs' directory."
|
||||
@echo " setup-config - Setup the configuration for OpenDevin by providing LLM API key, LLM Model name, and workspace directory."
|
||||
@echo " help - Display this help message, providing information on available targets."
|
||||
@echo " $(GREEN)help$(RESET) - Display this help message, providing information on available targets."
|
||||
|
||||
# Phony targets
|
||||
.PHONY: install start-backend start-frontend run setup-config help
|
||||
.PHONY: build check-dependencies check-python check-npm check-docker check-poetry pull-docker-image install-python-dependencies install-frontend-dependencies install-precommit-hooks lint start-backend start-frontend run setup-config setup-config-prompts help
|
||||
|
||||
32
Pipfile
32
Pipfile
@@ -1,32 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
datasets = "*"
|
||||
pandas = "*"
|
||||
litellm = "*"
|
||||
termcolor = "*"
|
||||
seaborn = "*"
|
||||
docker = "*"
|
||||
fastapi = "*"
|
||||
uvicorn = {extras = ["standard"], version = "*"}
|
||||
ruff = "*"
|
||||
mypy = "*"
|
||||
llama-index = "*"
|
||||
llama-index-vector-stores-chroma = "*"
|
||||
chromadb = "*"
|
||||
llama-index-embeddings-huggingface = "*"
|
||||
llama-index-embeddings-azure-openai = "*"
|
||||
llama-index-embeddings-ollama = "*"
|
||||
google-generativeai = "*"
|
||||
toml = "*"
|
||||
json_repair = "*"
|
||||
numpy = "*"
|
||||
playwright = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.11"
|
||||
3854
Pipfile.lock
generated
3854
Pipfile.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[[source]]
|
||||
url = "https://download.pytorch.org/whl/cpu"
|
||||
verify_ssl = true
|
||||
name = "pytorch"
|
||||
|
||||
[packages]
|
||||
torch = {version = "*", index = "pytorch"}
|
||||
datasets = "*"
|
||||
pandas = "*"
|
||||
litellm = "*"
|
||||
termcolor = "*"
|
||||
seaborn = "*"
|
||||
docker = "*"
|
||||
fastapi = "*"
|
||||
uvicorn = {extras = ["standard"], version = "*"}
|
||||
ruff = "*"
|
||||
mypy = "*"
|
||||
llama-index = "*"
|
||||
llama-index-vector-stores-chroma = "*"
|
||||
chromadb = "*"
|
||||
llama-index-embeddings-huggingface = "*"
|
||||
llama-index-embeddings-azure-openai = "*"
|
||||
llama-index-embeddings-ollama = "*"
|
||||
google-generativeai = "*"
|
||||
toml = "*"
|
||||
json_repair = "*"
|
||||
numpy = "*"
|
||||
playwright = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.11"
|
||||
174
README.md
174
README.md
@@ -1,4 +1,5 @@
|
||||
<a name="readme-top"></a>
|
||||
|
||||
<!--
|
||||
*** Thanks for checking out the Best-README-Template. If you have a suggestion
|
||||
*** that would make this better, please fork the repo and create a pull request
|
||||
@@ -7,8 +8,6 @@
|
||||
*** Thanks again! Now go create something AMAZING! :D
|
||||
-->
|
||||
|
||||
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
<!--
|
||||
*** I'm using markdown "reference style" links for readability.
|
||||
@@ -24,168 +23,22 @@
|
||||
<a href="https://github.com/OpenDevin/OpenDevin/stargazers"><img src="https://img.shields.io/github/stars/opendevin/opendevin?style=for-the-badge" alt="Stargazers"></a>
|
||||
<a href="https://github.com/OpenDevin/OpenDevin/issues"><img src="https://img.shields.io/github/issues/opendevin/opendevin?style=for-the-badge" alt="Issues"></a>
|
||||
<a href="https://github.com/OpenDevin/OpenDevin/blob/main/LICENSE"><img src="https://img.shields.io/github/license/opendevin/opendevin?style=for-the-badge" alt="MIT License"></a>
|
||||
</br>
|
||||
<a href="https://join.slack.com/t/opendevin/shared_invite/zt-2etftj1dd-X1fDL2PYIVpsmJZkqEYANw"><img src="https://img.shields.io/badge/Slack-Join%20Us-red?logo=slack&logoColor=white&style=for-the-badge" alt="Join our Slack community"></a>
|
||||
<a href="https://discord.gg/mBuDGRzzES"><img src="https://img.shields.io/badge/Discord-Join%20Us-purple?logo=discord&logoColor=white&style=for-the-badge" alt="Join our Discord community"></a>
|
||||
</div>
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<div align="center">
|
||||
<img src="./logo.png" alt="Logo" width="200" height="200">
|
||||
<img src="./docs/static/img/logo.png" alt="Logo" width="200" height="200">
|
||||
<h1 align="center">OpenDevin: Code Less, Make More</h1>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- TABLE OF CONTENTS -->
|
||||
<details>
|
||||
<summary>🗂️ Table of Contents</summary>
|
||||
<ol>
|
||||
<li><a href="#-mission">🎯 Mission</a></li>
|
||||
<li><a href="#-what-is-devin">🤔 What is Devin?</a></li>
|
||||
<li><a href="#-why-opendevin">🐚 Why OpenDevin?</a></li>
|
||||
<li><a href="#-project-status">🚧 Project Status</a></li>
|
||||
<a href="#-get-started">🚀 Get Started</a>
|
||||
<ul>
|
||||
<li><a href="#1-requirements">1. Requirements</a></li>
|
||||
<li><a href="#2-build-and-setup">2. Build and Setup</a></li>
|
||||
<li><a href="#3-run-the-application">3. Run the Application</a></li>
|
||||
<li><a href="#4-individual-server-startup">4. Individual Server Startup</a></li>
|
||||
<li><a href="#5-help">5. Help</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#%EF%B8%8F-research-strategy">⭐️ Research Strategy</a></li>
|
||||
<li><a href="#-how-to-contribute">🤝 How to Contribute</a></li>
|
||||
<li><a href="#-join-our-community">🤖 Join Our Community</a></li>
|
||||
<li><a href="#%EF%B8%8F-built-with">🛠️ Built With</a></li>
|
||||
<li><a href="#-license">📜 License</a></li>
|
||||
</ol>
|
||||
</details>
|
||||
|
||||
## 🎯 Mission
|
||||
|
||||
[Project Demo Video](https://github.com/OpenDevin/OpenDevin/assets/38853559/71a472cc-df34-430c-8b1d-4d7286c807c9)
|
||||
|
||||
|
||||
Welcome to OpenDevin, an open-source project aiming to replicate Devin, an autonomous AI software engineer who is capable of executing complex engineering tasks and collaborating actively with users on software development projects. This project aspires to replicate, enhance, and innovate upon Devin through the power of the open-source community.
|
||||
|
||||
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
|
||||
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
|
||||
↑ Back to Top ↑
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 🤔 What is Devin?
|
||||
Devin represents a cutting-edge autonomous agent designed to navigate the complexities of software engineering. It leverages a combination of tools such as a shell, code editor, and web browser, showcasing the untapped potential of LLMs in software development. Our goal is to explore and expand upon Devin's capabilities, identifying both its strengths and areas for improvement, to guide the progress of open code models.
|
||||
|
||||
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
|
||||
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
|
||||
↑ Back to Top ↑
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 🐚 Why OpenDevin?
|
||||
The OpenDevin project is born out of a desire to replicate, enhance, and innovate beyond the original Devin model. By engaging the open-source community, we aim to tackle the challenges faced by Code LLMs in practical scenarios, producing works that significantly contribute to the community and pave the way for future advancements.
|
||||
|
||||
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
|
||||
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
|
||||
↑ Back to Top ↑
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 🚧 Project Status
|
||||
|
||||
OpenDevin is currently a work in progress, but you can already run the alpha version to see the end-to-end system in action. The project team is actively working on the following key milestones:
|
||||
|
||||
- **UI**: Developing a user-friendly interface, including a chat interface, a shell demonstrating commands, and a web browser.
|
||||
- **Architecture**: Building a stable agent framework with a robust backend that can read, write, and run simple commands.
|
||||
- **Agent Capabilities**: Enhancing the agent's abilities to generate bash scripts, run tests, and perform other software engineering tasks.
|
||||
- **Evaluation**: Establishing a minimal evaluation pipeline that is consistent with Devin's evaluation criteria.
|
||||
|
||||
After completing the MVP, the team will focus on research in various areas, including foundation models, specialist capabilities, evaluation, and agent studies.
|
||||
|
||||
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
|
||||
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
|
||||
↑ Back to Top ↑
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## 🚀 Get Started
|
||||
|
||||
Getting started with the OpenDevin project is incredibly easy. Follow these simple steps to set up and run OpenDevin on your system:
|
||||
|
||||
### 1. Requirements
|
||||
* Linux, Mac OS, or [WSL on Windows](https://learn.microsoft.com/en-us/windows/wsl/install)
|
||||
* [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.11
|
||||
* [NodeJS](https://nodejs.org/en/download/package-manager) >= 18.17.1
|
||||
|
||||
### 2. Build and Setup The Environment
|
||||
|
||||
- **Build the Project:** Begin by building the project, which includes setting up the environment and installing dependencies. This step ensures that OpenDevin is ready to run smoothly on your system.
|
||||
```bash
|
||||
make build
|
||||
```
|
||||
|
||||
### 3. Configuring the Language Model
|
||||
|
||||
OpenDevin supports a diverse array of Language Models (LMs) through the powerful [litellm](https://docs.litellm.ai) library. By default, we've chosen the mighty GPT-4 from OpenAI as our go-to model, but the world is your oyster! You can unleash the potential of Anthropic's suave Claude, the enigmatic Llama, or any other LM that piques your interest.
|
||||
|
||||
To configure the LM of your choice, follow these steps:
|
||||
|
||||
1. **Using the Makefile: The Effortless Approach**
|
||||
With a single command, you can have a smooth LM setup for your OpenDevin experience. Simply run:
|
||||
```bash
|
||||
make setup-config
|
||||
```
|
||||
This command will prompt you to enter the LLM API key and model name, ensuring that OpenDevin is tailored to your specific needs.
|
||||
|
||||
2. **Manual Config: The Artisanal Touch**
|
||||
If you're feeling particularly adventurous, you can manually update the `config.toml` file located in the project's root directory. Here, you'll find the `llm_api_key` and `llm_model_name` fields, where you can set the LM of your choosing.
|
||||
|
||||
**Note on Alternative Models:**
|
||||
Some alternative models may prove more challenging to tame than others. Fear not, brave adventurer! We shall soon unveil LLM-specific documentation to guide you on your quest. And if you've already mastered the art of wielding a model other than OpenAI's GPT, we encourage you to [share your setup instructions with us](https://github.com/OpenDevin/OpenDevin/issues/417).
|
||||
|
||||
For a full list of the LM providers and models available, please consult the [litellm documentation](https://docs.litellm.ai/docs/providers).
|
||||
|
||||
### 4. Run the Application
|
||||
|
||||
- **Run the Application:** Once the setup is complete, launching OpenDevin is as simple as running a single command. This command starts both the backend and frontend servers seamlessly, allowing you to interact with OpenDevin without any hassle.
|
||||
```bash
|
||||
make run
|
||||
```
|
||||
|
||||
### 5. Individual Server Startup
|
||||
|
||||
- **Start the Backend Server:** If you prefer, you can start the backend server independently to focus on backend-related tasks or configurations.
|
||||
```bash
|
||||
make start-backend
|
||||
```
|
||||
|
||||
- **Start the Frontend Server:** Similarly, you can start the frontend server on its own to work on frontend-related components or interface enhancements.
|
||||
```bash
|
||||
make start-frontend
|
||||
```
|
||||
|
||||
### 6. Help
|
||||
|
||||
- **Get Some Help:** Need assistance or information on available targets and commands? The help command provides all the necessary guidance to ensure a smooth experience with OpenDevin.
|
||||
```bash
|
||||
make help
|
||||
```
|
||||
|
||||
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
|
||||
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
|
||||
↑ Back to Top ↑
|
||||
</a>
|
||||
</p>
|
||||
|
||||
## ⭐️ Research Strategy
|
||||
|
||||
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.
|
||||
To learn more and to use OpenDevin, check out our [documentation](https://opendevin.github.io/OpenDevin/).
|
||||
|
||||
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
|
||||
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
|
||||
@@ -211,20 +64,17 @@ For details, please check [this document](./CONTRIBUTING.md).
|
||||
|
||||
## 🤖 Join Our Community
|
||||
|
||||
Join our Slack workspace by filling out the [form](https://forms.gle/758d5p6Ve8r2nxxq6). Stay updated on OpenDevin's progress, share ideas, and collaborate with fellow enthusiasts and experts. Let's simplify software engineering together!
|
||||
Now we have both Slack workspace for the collaboration on building OpenDevin and Discord server for discussion about anything related, e.g., this project, LLM, agent, etc.
|
||||
|
||||
- [Slack workspace](https://join.slack.com/t/opendevin/shared_invite/zt-2etftj1dd-X1fDL2PYIVpsmJZkqEYANw)
|
||||
- [Discord server](https://discord.gg/mBuDGRzzES)
|
||||
|
||||
If you would love to contribute, feel free to join our community (note that now there is no need to fill in the [form](https://forms.gle/758d5p6Ve8r2nxxq6)). Let's simplify software engineering together!
|
||||
|
||||
🐚 **Code less, make more with OpenDevin.**
|
||||
|
||||
[](https://star-history.com/#OpenDevin/OpenDevin&Date)
|
||||
|
||||
## 🛠️ Built With
|
||||
|
||||
OpenDevin 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 OpenDevin.
|
||||
|
||||
<p align="right" style="font-size: 14px; color: #555; margin-top: 20px;">
|
||||
<a href="#readme-top" style="text-decoration: none; color: #007bff; font-weight: bold;">
|
||||
↑ Back to Top ↑
|
||||
|
||||
405
agenthub/SWE_agent/Examples.md
Normal file
405
agenthub/SWE_agent/Examples.md
Normal file
@@ -0,0 +1,405 @@
|
||||
# GPT-4 Examples:
|
||||
|
||||
### Notes:
|
||||
- The final result is not completed as I only gave it 10 steps, but shows very promising use of the tools.
|
||||
- This whole run costs: $0.35 using gpt-4-turbo
|
||||
- Input Tokens: 29,734
|
||||
- Completion Tokens: 1,636
|
||||
|
||||
Interesting things:
|
||||
1. Using comments as an initial placeholder for the next move and then having that output in memory is a very cool behavior.
|
||||
2. It makes very small iterative changes due to the prompting.
|
||||
3. A lot of the logic for the game is present but not put together well
|
||||
4. There are a lot of odd quirks with the code that it has written.
|
||||
5. Some odd agent behavior with putting random lines of code in random places?
|
||||
6. The structure of the feedback loop needs some improvement so the agent can see the changes it has made better in context.
|
||||
7. Might be beneficial to add in some line numbers so the model knows what lines it is looking at.
|
||||
|
||||
|
||||
```
|
||||
==============
|
||||
STEP 0
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=91, prompt_tokens=2047, total_tokens=2138)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
To start implementing Conway's Game of Life in Python using the pygame library, I first need to set up a Python script file where I can write the code. I'll create a new Python file named `game_of_life.py`. This file will serve as the base for writing the game logic and using pygame functionalities.
|
||||
|
||||
Action:
|
||||
"""
|
||||
write game_of_life.py # This is a placeholder for the game of life implementation using pygame.
|
||||
"""
|
||||
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='# This is a placeholder for the game of life implementation using pygame.', start=0, end=-1, thoughts="Thoughts:\nTo start implementing Conway's Game of Life in Python using the pygame library, I first need to set up a Python script file where I can write the code. I'll create a new Python file named `game_of_life.py`. This file will serve as the base for writing the game logic and using pygame functionalities.\n\nAction:", action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 0:-1.
|
||||
|
||||
|
||||
==============
|
||||
STEP 1
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=101, prompt_tokens=2351, total_tokens=2452)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
The placeholder for the game of life implementation using pygame has been set up in the file `game_of_life.py`. The next step is to start implementing the basic structure of the game. This includes importing necessary libraries, initializing the pygame module, setting up the display window, and defining the main game loop. I will begin by importing pygame and initializing it, which are the foundational steps for any pygame application.
|
||||
|
||||
Action:
|
||||
"""
|
||||
edit 0 0 import pygame
|
||||
|
||||
pygame.init()
|
||||
"""
|
||||
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='import pygame\n\npygame.init()', start=0, end=0, thoughts='Thoughts:\nThe placeholder for the game of life implementation using pygame has been set up in the file `game_of_life.py`. The next step is to start implementing the basic structure of the game. This includes importing necessary libraries, initializing the pygame module, setting up the display window, and defining the main game loop. I will begin by importing pygame and initializing it, which are the foundational steps for any pygame application.\n\nAction:', action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 0:0.
|
||||
|
||||
|
||||
==============
|
||||
STEP 2
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=123, prompt_tokens=2552, total_tokens=2675)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
Previously, I initialized pygame in the `game_of_life.py` file. The next step is to set up the display window for the game. This involves defining the size of the window and setting the title. This is crucial as it provides the visual interface for the game. I will also add a comment to indicate that this is the setup for the display window.
|
||||
|
||||
Action:
|
||||
"""
|
||||
edit 2 2
|
||||
# Setup the display window
|
||||
screen_size = (800, 600)
|
||||
screen = pygame.display.set_mode(screen_size)
|
||||
pygame.display.set_caption("Conway's Game of Life")
|
||||
"""
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='# Setup the display window\nscreen_size = (800, 600)\nscreen = pygame.display.set_mode(screen_size)\npygame.display.set_caption("Conway\'s Game of Life")', start=2, end=2, thoughts='Thoughts:\nPreviously, I initialized pygame in the `game_of_life.py` file. The next step is to set up the display window for the game. This involves defining the size of the window and setting the title. This is crucial as it provides the visual interface for the game. I will also add a comment to indicate that this is the setup for the display window.\n\nAction:', action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 2:2.
|
||||
|
||||
|
||||
==============
|
||||
STEP 3
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=144, prompt_tokens=2777, total_tokens=2921)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
So far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame and setting up the display window. The next step is to define the main game loop where the game's logic will be continuously updated and rendered. This loop will handle events, update the game state, and redraw the screen. I will start by defining the main loop structure and a basic event handling mechanism to allow for quitting the game when the user presses the close button.
|
||||
|
||||
Action:
|
||||
"""
|
||||
edit 5 5 # Main game loop
|
||||
running = True
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
"""
|
||||
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='# Main game loop\nrunning = True\nwhile running:\n for event in pygame.event.get():\n if event.type == pygame.QUIT:\n running = False', start=5, end=5, thoughts="Thoughts:\nSo far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame and setting up the display window. The next step is to define the main game loop where the game's logic will be continuously updated and rendered. This loop will handle events, update the game state, and redraw the screen. I will start by defining the main loop structure and a basic event handling mechanism to allow for quitting the game when the user presses the close button.\n\nAction:", action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 5:5.
|
||||
|
||||
|
||||
==============
|
||||
STEP 4
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=222, prompt_tokens=3022, total_tokens=3244)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
So far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, and defining the main game loop. The next step is to implement the logic for Conway's Game of Life. This involves creating a grid to represent the game state, initializing it with a random pattern, and defining the rules for cell survival, birth, and death. I will start by defining the grid size and initializing the grid with random states.
|
||||
|
||||
Action:
|
||||
"""
|
||||
edit 7 7 # Define grid size and initialize grid
|
||||
grid_size = (50, 50) # Grid size as a tuple (rows, columns)
|
||||
grid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])] # Initialize grid with zeros
|
||||
import random
|
||||
for y in range(grid_size[0]):
|
||||
for x in range(grid_size[1]):
|
||||
grid[y][x] = random.randint(0, 1) # Randomly assign cells as alive (1) or dead (0)
|
||||
"""
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='# Define grid size and initialize grid\ngrid_size = (50, 50) # Grid size as a tuple (rows, columns)\ngrid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])] # Initialize grid with zeros\nimport random\nfor y in range(grid_size[0]):\n for x in range(grid_size[1]):\n grid[y][x] = random.randint(0, 1) # Randomly assign cells as alive (1) or dead (0)', start=7, end=7, thoughts="Thoughts:\nSo far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, and defining the main game loop. The next step is to implement the logic for Conway's Game of Life. This involves creating a grid to represent the game state, initializing it with a random pattern, and defining the rules for cell survival, birth, and death. I will start by defining the grid size and initializing the grid with random states.\n\nAction:", action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 7:7.
|
||||
|
||||
|
||||
==============
|
||||
STEP 5
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=234, prompt_tokens=3158, total_tokens=3392)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
So far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, and initializing the grid with random states. The next step is to implement the rules for Conway's Game of Life. This involves checking each cell's neighbors to determine its next state based on the number of alive neighbors. I will start by defining a function to count the alive neighbors for a given cell.
|
||||
|
||||
Action:
|
||||
"""
|
||||
edit 9 9 def count_alive_neighbors(grid, x, y):
|
||||
directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
|
||||
count = 0
|
||||
for dx, dy in directions:
|
||||
nx, ny = x + dx, y + dy
|
||||
if 0 <= nx < grid_size[1] and 0 <= ny < grid_size[0]:
|
||||
count += grid[ny][nx]
|
||||
return count
|
||||
"""
|
||||
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='def count_alive_neighbors(grid, x, y):\n directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]\n count = 0\n for dx, dy in directions:\n nx, ny = x + dx, y + dy\n if 0 <= nx < grid_size[1] and 0 <= ny < grid_size[0]:\n count += grid[ny][nx]\n return count', start=9, end=9, thoughts="Thoughts:\nSo far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, and initializing the grid with random states. The next step is to implement the rules for Conway's Game of Life. This involves checking each cell's neighbors to determine its next state based on the number of alive neighbors. I will start by defining a function to count the alive neighbors for a given cell.\n\nAction:", action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 9:9.
|
||||
|
||||
|
||||
==============
|
||||
STEP 6
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=257, prompt_tokens=3290, total_tokens=3547)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
So far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating a function to count alive neighbors. The next step is to implement the rules for Conway's Game of Life. This involves updating the grid state based on the number of alive neighbors each cell has. I will define a function `update_grid` that applies the rules of the game to update the grid for the next generation.
|
||||
|
||||
Action:
|
||||
"""
|
||||
write game_of_life.py def update_grid(grid):
|
||||
new_grid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])]
|
||||
for y in range(grid_size[0]):
|
||||
for x in range(grid_size[1]):
|
||||
alive_neighbors = count_alive_neighbors(grid, x, y)
|
||||
if grid[y][x] == 1 and (alive_neighbors == 2 or alive_neighbors == 3):
|
||||
new_grid[y][x] = 1
|
||||
elif grid[y][x] == 0 and alive_neighbors == 3:
|
||||
new_grid[y][x] = 1
|
||||
return new_grid
|
||||
11
|
||||
11
|
||||
"""
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='def update_grid(grid):\n new_grid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])]\n for y in range(grid_size[0]):\n for x in range(grid_size[1]):\n alive_neighbors = count_alive_neighbors(grid, x, y)\n if grid[y][x] == 1 and (alive_neighbors == 2 or alive_neighbors == 3):\n new_grid[y][x] = 1\n elif grid[y][x] == 0 and alive_neighbors == 3:\n new_grid[y][x] = 1\n return new_grid', start=11, end=11, thoughts="Thoughts:\nSo far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating a function to count alive neighbors. The next step is to implement the rules for Conway's Game of Life. This involves updating the grid state based on the number of alive neighbors each cell has. I will define a function `update_grid` that applies the rules of the game to update the grid for the next generation.\n\nAction:", action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 11:11.
|
||||
|
||||
|
||||
==============
|
||||
STEP 7
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=124, prompt_tokens=3416, total_tokens=3540)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
So far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating functions to count alive neighbors and update the grid. The next step is to integrate the `update_grid` function into the main game loop to continuously update the state of the grid based on the game rules. This will allow the game to evolve over time, showing different generations of the cell population.
|
||||
|
||||
Action:
|
||||
"""
|
||||
edit 6 6
|
||||
grid = update_grid(grid)
|
||||
"""
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='grid = update_grid(grid)', start=6, end=6, thoughts="Thoughts:\nSo far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating functions to count alive neighbors and update the grid. The next step is to integrate the `update_grid` function into the main game loop to continuously update the state of the grid based on the game rules. This will allow the game to evolve over time, showing different generations of the cell population.\n\nAction:", action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 6:6.
|
||||
|
||||
|
||||
==============
|
||||
STEP 8
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=166, prompt_tokens=3393, total_tokens=3559)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
So far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating functions to count alive neighbors and update the grid. The next step is to integrate the `update_grid` function into the main game loop to continuously update the state of the grid based on the game rules. This will allow the game to evolve over time, showing different generations of the cell population. I need to ensure that the `update_grid` function is called within the main game loop and that the display is updated accordingly. I will start by checking the current content of the main game loop to see where to integrate the `update_grid` function.
|
||||
|
||||
Action:
|
||||
"""
|
||||
goto 6
|
||||
"""
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileReadAction(path='game_of_life.py', start=6, end=106, thoughts="Thoughts:\nSo far, I have set up the basic structure for Conway's Game of Life using pygame, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating functions to count alive neighbors and update the grid. The next step is to integrate the `update_grid` function into the main game loop to continuously update the state of the grid based on the game rules. This will allow the game to evolve over time, showing different generations of the cell population. I need to ensure that the `update_grid` function is called within the main game loop and that the display is updated accordingly. I will start by checking the current content of the main game loop to see where to integrate the `update_grid` function.\n\nAction:", action=<ActionType.READ: 'read'>)
|
||||
|
||||
OBSERVATION:
|
||||
grid = update_grid(grid)
|
||||
running = True
|
||||
# Define grid size and initialize grid
|
||||
grid_size = (50, 50) # Grid size as a tuple (rows, columns)
|
||||
def count_alive_neighbors(grid, x, y):
|
||||
directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
|
||||
def update_grid(grid):
|
||||
new_grid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])]
|
||||
for y in range(grid_size[0]):
|
||||
for x in range(grid_size[1]):
|
||||
alive_neighbors = count_alive_neighbors(grid, x, y)
|
||||
if grid[y][x] == 1 and (alive_neighbors == 2 or alive_neighbors == 3):
|
||||
new_grid[y][x] = 1
|
||||
elif grid[y][x] == 0 and alive_neighbors == 3:
|
||||
new_grid[y][x] = 1
|
||||
return new_grid
|
||||
count = 0
|
||||
for dx, dy in directions:
|
||||
nx, ny = x + dx, y + dy
|
||||
if 0 <= nx < grid_size[1] and 0 <= ny < grid_size[0]:
|
||||
count += grid[ny][nx]
|
||||
return count
|
||||
grid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])] # Initialize grid with zeros
|
||||
import random
|
||||
for y in range(grid_size[0]):
|
||||
for x in range(grid_size[1]):
|
||||
grid[y][x] = random.randint(0, 1) # Randomly assign cells as alive (1) or dead (0)
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
pygame.display.set_caption("Conway's Game of Life")
|
||||
pygame.init()
|
||||
|
||||
|
||||
|
||||
==============
|
||||
STEP 9
|
||||
|
||||
PLAN:
|
||||
Please make conway's game of life in python using the pygame library. Be sure to use the tools in your workspace to help you out.
|
||||
Usage(completion_tokens=174, prompt_tokens=3728, total_tokens=3902)
|
||||
|
||||
==== RAW OUTPUT ====
|
||||
Thoughts:
|
||||
From the previous actions, it seems that the basic structure for Conway's Game of Life using pygame has been set up, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating functions to count alive neighbors and update the grid. The next step is to ensure that the game loop properly updates the grid and refreshes the display to show the new state of the grid in each iteration. This involves calling `pygame.display.update()` to update the entire screen and using `pygame.time.Clock()` to control the frame rate. I will start by adding these necessary updates to the game loop.
|
||||
|
||||
Action:
|
||||
"""
|
||||
edit 95 95
|
||||
pygame.display.update()
|
||||
clock = pygame.time.Clock()
|
||||
clock.tick(10) # Control the frame rate (10 frames per second)
|
||||
"""
|
||||
|
||||
==== END RAW ====
|
||||
|
||||
|
||||
ACTION:
|
||||
FileWriteAction(path='game_of_life.py', content='pygame.display.update()\n clock = pygame.time.Clock()\n clock.tick(10) # Control the frame rate (10 frames per second)', start=95, end=95, thoughts="Thoughts:\nFrom the previous actions, it seems that the basic structure for Conway's Game of Life using pygame has been set up, including initializing pygame, setting up the display window, defining the main game loop, initializing the grid with random states, and creating functions to count alive neighbors and update the grid. The next step is to ensure that the game loop properly updates the grid and refreshes the display to show the new state of the grid in each iteration. This involves calling `pygame.display.update()` to update the entire screen and using `pygame.time.Clock()` to control the frame rate. I will start by adding these necessary updates to the game loop.\n\nAction:", action=<ActionType.WRITE: 'write'>)
|
||||
|
||||
OBSERVATION:
|
||||
WRITE OPERATION:
|
||||
You have written to "game_of_life.py" on these lines: 95:95.
|
||||
16:43:55 - opendevin:INFO: agent_controller.py:125 - Exited before finishing the task.
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Result:
|
||||
|
||||
```
|
||||
import pygame
|
||||
|
||||
# Setup the display window
|
||||
screen_size = (800, 600)
|
||||
screen = pygame.display.set_mode(screen_size)
|
||||
# Main game loop
|
||||
grid = update_grid(grid)
|
||||
running = True
|
||||
# Define grid size and initialize grid
|
||||
grid_size = (50, 50) # Grid size as a tuple (rows, columns)
|
||||
def count_alive_neighbors(grid, x, y):
|
||||
directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
|
||||
def update_grid(grid):
|
||||
new_grid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])]
|
||||
for y in range(grid_size[0]):
|
||||
for x in range(grid_size[1]):
|
||||
alive_neighbors = count_alive_neighbors(grid, x, y)
|
||||
if grid[y][x] == 1 and (alive_neighbors == 2 or alive_neighbors == 3):
|
||||
new_grid[y][x] = 1
|
||||
elif grid[y][x] == 0 and alive_neighbors == 3:
|
||||
new_grid[y][x] = 1
|
||||
return new_grid
|
||||
count = 0
|
||||
for dx, dy in directions:
|
||||
nx, ny = x + dx, y + dy
|
||||
if 0 <= nx < grid_size[1] and 0 <= ny < grid_size[0]:
|
||||
count += grid[ny][nx]
|
||||
return count
|
||||
grid = [[0 for x in range(grid_size[1])] for y in range(grid_size[0])] # Initialize grid with zeros
|
||||
import random
|
||||
for y in range(grid_size[0]):
|
||||
for x in range(grid_size[1]):
|
||||
grid[y][x] = random.randint(0, 1) # Randomly assign cells as alive (1) or dead (0)
|
||||
while running:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
running = False
|
||||
pygame.display.set_caption("Conway's Game of Life")
|
||||
pygame.init()
|
||||
# This is a placeholder for the game of life implementation using pygame.pygame.display.update()
|
||||
clock = pygame.time.Clock()
|
||||
clock.tick(10) # Control the frame rate (10 frames per second)
|
||||
|
||||
```
|
||||
5
agenthub/SWE_agent/__init__.py
Normal file
5
agenthub/SWE_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.agent import Agent
|
||||
|
||||
from .agent import SWEAgent
|
||||
|
||||
Agent.register('SWEAgent', SWEAgent)
|
||||
112
agenthub/SWE_agent/agent.py
Normal file
112
agenthub/SWE_agent/agent.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from typing import List
|
||||
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
AgentThinkAction,
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
)
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.observation import Observation
|
||||
from opendevin.state import State
|
||||
|
||||
from .parser import parse_command
|
||||
from .prompts import (
|
||||
CONTEXT_PROMPT,
|
||||
MEMORY_FORMAT,
|
||||
NO_ACTION,
|
||||
STEP_PROMPT,
|
||||
SYSTEM_MESSAGE,
|
||||
)
|
||||
|
||||
|
||||
class SWEAgent(Agent):
|
||||
"""
|
||||
An attempt to recreate swe_agent with output parsing, prompting style, and Application Computer Interface (ACI).
|
||||
|
||||
SWE-agent includes ACI functions like 'goto', 'search_for', 'edit', 'scroll', 'run'
|
||||
"""
|
||||
|
||||
def __init__(self, llm: LLM):
|
||||
super().__init__(llm)
|
||||
self.memory_window = 4
|
||||
self.max_retries = 2
|
||||
self.running_memory: List[str] = []
|
||||
self.cur_file: str = ''
|
||||
self.cur_line: int = 0
|
||||
|
||||
def _remember(self, action: Action, observation: Observation) -> None:
|
||||
"""Agent has a limited memory of the few steps implemented as a queue"""
|
||||
memory = MEMORY_FORMAT(action.to_memory(), observation.to_memory())
|
||||
self.running_memory.append(memory)
|
||||
|
||||
def _think_act(self, messages: List[dict]) -> tuple[Action, str]:
|
||||
resp = self.llm.completion(
|
||||
messages=messages,
|
||||
temperature=0.05,
|
||||
)
|
||||
action_resp = resp['choices'][0]['message']['content']
|
||||
print(f"\033[1m\033[91m{resp['usage']}\033[0m")
|
||||
print('\n==== RAW OUTPUT ====',
|
||||
f'\033[96m{action_resp}\033[0m',
|
||||
'==== END RAW ====\n', sep='\n')
|
||||
return parse_command(action_resp, self.cur_file, self.cur_line)
|
||||
|
||||
def _update(self, action: Action) -> None:
|
||||
if isinstance(action, (FileReadAction, FileWriteAction)):
|
||||
self.cur_file = action.path
|
||||
self.cur_line = action.start
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
SWE-Agent step:
|
||||
1. Get context - past actions, custom commands, current step
|
||||
2. Perform think-act - prompt model for action and reasoning
|
||||
3. Catch errors - ensure model takes action (5 attempts max)
|
||||
"""
|
||||
for prev_action, obs in state.updated_info:
|
||||
self._remember(prev_action, obs)
|
||||
|
||||
prompt = STEP_PROMPT(
|
||||
state.plan.main_goal,
|
||||
self.cur_file,
|
||||
self.cur_line
|
||||
)
|
||||
|
||||
msgs = [
|
||||
{'content': SYSTEM_MESSAGE, 'role': 'system'},
|
||||
{'content': prompt, 'role': 'user'}
|
||||
]
|
||||
|
||||
if len(self.running_memory) > 0:
|
||||
context = CONTEXT_PROMPT(
|
||||
self.running_memory,
|
||||
self.memory_window
|
||||
)
|
||||
msgs.insert(1, {'content': context, 'role': 'user'})
|
||||
# clrs = [''] * (len(msgs)-2) + ['\033[0;36m', '\033[0;35m']
|
||||
# print('\n\n'.join([c+m['content']+'\033[0m' for c, m in zip(clrs, msgs)]))
|
||||
action, thought = self._think_act(messages=msgs)
|
||||
|
||||
start_msg_len = len(msgs)
|
||||
while not action and len(msgs) < self.max_retries + start_msg_len:
|
||||
error = NO_ACTION(thought)
|
||||
error_msg = {'content': error, 'role': 'user'}
|
||||
msgs.append(error_msg)
|
||||
action, thought = self._think_act(messages=msgs)
|
||||
|
||||
if not action:
|
||||
action = AgentThinkAction(thought)
|
||||
|
||||
self._update(action)
|
||||
self.latest_action = action
|
||||
return action
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
return [item for item in self.running_memory if query in item]
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Used to reset the agent"""
|
||||
self.running_memory = []
|
||||
super().reset()
|
||||
164
agenthub/SWE_agent/parser.py
Normal file
164
agenthub/SWE_agent/parser.py
Normal file
@@ -0,0 +1,164 @@
|
||||
import re
|
||||
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
AgentEchoAction,
|
||||
AgentFinishAction,
|
||||
AgentThinkAction,
|
||||
BrowseURLAction,
|
||||
CmdRunAction,
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
)
|
||||
|
||||
from .prompts import COMMAND_USAGE, CUSTOM_DOCS
|
||||
|
||||
# commands: exit, read, write, browse, kill, search_file, search_dir
|
||||
|
||||
no_open_file_error = AgentEchoAction(
|
||||
'You are not currently in a file. You can use the read command to open a file and then use goto to navigate through it.')
|
||||
|
||||
|
||||
def invalid_error(cmd, docs):
|
||||
return f"""ERROR:
|
||||
Invalid command structure for
|
||||
```
|
||||
{cmd}
|
||||
```
|
||||
You may have caused this error by having multiple commands within your command block.
|
||||
If so, try again by running only one of the commands:
|
||||
|
||||
Try again using this format:
|
||||
{COMMAND_USAGE[docs]}
|
||||
"""
|
||||
|
||||
|
||||
def get_action_from_string(command_string: str, path: str, line: int, thoughts: str = '') -> Action | None:
|
||||
"""
|
||||
Parses the command string to find which command the agent wants to run
|
||||
Converts the command into a proper Action and returns
|
||||
"""
|
||||
vars = command_string.split(' ')
|
||||
cmd = vars[0]
|
||||
args = [] if len(vars) == 1 else vars[1:]
|
||||
|
||||
if 'exit' == cmd:
|
||||
return AgentFinishAction()
|
||||
|
||||
elif 'think' == cmd:
|
||||
return AgentThinkAction(' '.join(args))
|
||||
|
||||
elif 'scroll_up' == cmd:
|
||||
if not path:
|
||||
return no_open_file_error
|
||||
return FileReadAction(path, line + 100, line + 200, thoughts)
|
||||
|
||||
elif 'scroll_down' == cmd:
|
||||
if not path:
|
||||
return no_open_file_error
|
||||
return FileReadAction(path, line - 100, line, thoughts)
|
||||
|
||||
elif 'goto' == cmd:
|
||||
if not path:
|
||||
return no_open_file_error
|
||||
rex = r'^goto\s+(\d+)$'
|
||||
valid = re.match(rex, command_string)
|
||||
if valid:
|
||||
start = int(valid.group(1))
|
||||
end = start + 100
|
||||
return FileReadAction(path, start, end, thoughts)
|
||||
else:
|
||||
return AgentEchoAction(invalid_error(command_string, 'goto'))
|
||||
|
||||
elif 'edit' == cmd:
|
||||
if not path:
|
||||
return no_open_file_error
|
||||
rex = r'^edit\s+(\d+)\s+(-?\d+)\s+(\S.*)$'
|
||||
valid = re.match(rex, command_string, re.DOTALL)
|
||||
if valid:
|
||||
start = int(valid.group(1))
|
||||
end = int(valid.group(2))
|
||||
change = valid.group(3)
|
||||
if '"' == change[-1] and '"' == change[0]:
|
||||
change = change[1:-1]
|
||||
return FileWriteAction(path, change, start, end, thoughts)
|
||||
else:
|
||||
return AgentEchoAction(invalid_error(command_string, 'edit'))
|
||||
|
||||
elif 'read' == cmd:
|
||||
rex = r'^read\s+(\S+)(?:\s+(\d+))?(?:\s+(-?\d+))?$'
|
||||
valid = re.match(rex, command_string, re.DOTALL)
|
||||
if valid:
|
||||
file = valid.group(1)
|
||||
start_str = valid.group(2)
|
||||
end_str = valid.group(3)
|
||||
|
||||
start = 0 if not start_str else int(start_str)
|
||||
end = -1 if not end_str else int(end_str)
|
||||
|
||||
return FileReadAction(file, start, end, thoughts)
|
||||
else:
|
||||
return AgentEchoAction(invalid_error(command_string, 'read'))
|
||||
|
||||
elif 'write' == cmd:
|
||||
rex = r'^write\s+(\S+)\s+(.*?)\s*(\d+)?\s*(-?\d+)?$'
|
||||
valid = re.match(rex, command_string, re.DOTALL)
|
||||
|
||||
if valid:
|
||||
file = valid.group(1)
|
||||
content = valid.group(2)
|
||||
start_str = valid.group(3)
|
||||
end_str = valid.group(4)
|
||||
|
||||
start = 0 if not start_str else int(start_str)
|
||||
end = -1 if not end_str else int(end_str)
|
||||
|
||||
if '"' == content[-1] and '"' == content[0]:
|
||||
content = content[1:-1]
|
||||
|
||||
return FileWriteAction(file, content, start, end, thoughts)
|
||||
else:
|
||||
return AgentEchoAction(invalid_error(command_string, 'write'))
|
||||
|
||||
elif 'browse' == cmd:
|
||||
return BrowseURLAction(args[0].strip())
|
||||
|
||||
elif cmd in ['search_file', 'search_dir', 'find_file']:
|
||||
rex = r'^(search_file|search_dir|find_file)\s+(\S+)(?:\s+(\S+))?$'
|
||||
valid = re.match(rex, command_string, re.DOTALL)
|
||||
if valid:
|
||||
return CmdRunAction(command_string)
|
||||
else:
|
||||
return AgentEchoAction(f'Invalid command structure for\n ```\n{command_string}\n```.\nTry again using this format:\n{CUSTOM_DOCS}')
|
||||
else:
|
||||
# check bash command
|
||||
obs = str(CmdRunAction(f'type {cmd}'))
|
||||
if obs.split(':')[-1].strip() == 'not found':
|
||||
# echo not found error for llm
|
||||
return AgentEchoAction(content=obs)
|
||||
else:
|
||||
# run valid command
|
||||
return CmdRunAction(command_string)
|
||||
|
||||
|
||||
def parse_command(input_str: str, path: str, line: int):
|
||||
"""
|
||||
Parses a given string and separates the command (enclosed in triple backticks) from any accompanying text.
|
||||
|
||||
Args:
|
||||
input_str (str): The input string to be parsed.
|
||||
|
||||
Returns:
|
||||
tuple: A tuple containing the command and the accompanying text (if any).
|
||||
"""
|
||||
input_str = input_str.strip()
|
||||
if '```' in input_str:
|
||||
parts = input_str.split('```')
|
||||
command_str = parts[1].strip()
|
||||
ind = 2 if len(parts) > 2 else 1
|
||||
accompanying_text = ''.join(parts[:-ind]).strip()
|
||||
action = get_action_from_string(
|
||||
command_str, path, line, accompanying_text)
|
||||
if action:
|
||||
return action, accompanying_text
|
||||
return None, input_str # used for retry
|
||||
206
agenthub/SWE_agent/prompts.py
Normal file
206
agenthub/SWE_agent/prompts.py
Normal file
@@ -0,0 +1,206 @@
|
||||
|
||||
DEFAULT_COMMANDS_DICT = {
|
||||
'exit': 'Executed when task is complete',
|
||||
'read <file_name> [<start_line>] [<end_line>]': "Shows a given file's contents starting from <start_line> up to <end_line>. Default: start_line = 0, end_line = -1. By default the whole file will be read.",
|
||||
'write <file> <changes> [<start_line>] [<end_line>]': 'Modifies a <file> by replacing the current lines between <start_line> and <end_line> with <changes>. Default start_line = 0 and end_line = -1. Calling this with no line args will replace the whole file.',
|
||||
'browse <url>': 'Returns the text version of any url, this can be useful to look up documentation or finding issues on github',
|
||||
'scroll_up': 'Takes no arguments. This will scroll up and show you the 100 lines above your current lines',
|
||||
'scroll_down': 'Takes no arguments. This will scroll down and show you the 100 lines below your current lines',
|
||||
'edit <start_line> <end_line> <changes>': 'This will modify lines in the currently open file. use start_line and end_line to designate which lines to change and then write the multiline changes',
|
||||
'goto <line_num>': 'This will take you directly to a line and show you the 100 lines below it.',
|
||||
'<bash_command> <args>': 'You can use any bash command you need (cd, ls, rm, grep, dir, mv, wget, git, zip, etc.) with their arguments included',
|
||||
'pip install <package>': 'You can use this to import python packages. Make sure you include the correct package name when using this command.',
|
||||
'ls': 'Use the ls command to view all the files in your current directory, this is a good starting point.',
|
||||
'NOT ALLOWED': 'You cannot use interactive commands like python or node'
|
||||
}
|
||||
|
||||
COMMAND_USAGE = {
|
||||
'exit': 'Usage:\n```\nexit\n```\nExecuted when task is complete',
|
||||
'read': "Args:\n<file_name> [<start_line>] [<end_line>]\nUsage:\n```\nread file.py\n```\nor\n```\nread example.py <start_line> <end_line>\n```\nShows a given file's contents starting from <start_line> up to <end_line>. Default: start_line = 0, end_line = -1. by default the whole file will be read.",
|
||||
'write': 'Args:\n<file> <changes> [<start_line>] [<end_line>]\nUsage:\n```\nwrite "def main():\n print("This is line one")" 0 2\n```\nModifies a <file> by replacing the current lines between <start_line> and <end_line> with <changes>. Default start_line = 0 and end_line = -1. Calling this with no line args will replace the whole file.',
|
||||
'edit': 'Args:\n<start_line> <end_line> <changes>\nUsage:\n```\nedit 0 1 import pandas as pd\n```\nThis will modify the current file you are in with the changes you make between the line numbers you designate',
|
||||
'goto': 'Args:\n<line_num>\nUsage:\n```\ngoto <line_num>\n```\nThis will show you the 100 lines below and including the line you specify within your current file.',
|
||||
'scroll_up': 'Usage:\n```\nscroll_up\n```\nThis will return the 100 lines above where you are currently at',
|
||||
'scroll_down': 'Usage:\n```\nscroll_down\n```\nThis will return the 100 line below where you are currently at',
|
||||
'browse': 'Args:\n<url>\nUsage:\n```\nbrowse https://github.com/OpenDevin/OpenDevin\n```\nThis will fetch the Text elements from the given url and show them to you.',
|
||||
}
|
||||
|
||||
DEFAULT_COMMANDS = '\n'.join(
|
||||
[k + ' - ' + v for k, v in DEFAULT_COMMANDS_DICT.items()])
|
||||
|
||||
# from opendevin.parse_commands import parse_command_file
|
||||
# USE parse_command_file(filepath) to get the custom commands
|
||||
CUSTOM_DOCS = None
|
||||
|
||||
CUSTOM_COMMANDS = f"""Custom bash commands:
|
||||
{CUSTOM_DOCS}
|
||||
"""
|
||||
|
||||
DOCUMENTATION = f"""DOCUMENTATION:
|
||||
It is recommend that you use the commands provided for interacting with files and your directory because they have been specially built for you.
|
||||
They will make it much easier for you to look at files and make changes. Using these commands will help you be better at your task.
|
||||
You can open an file by using either the read or write operations.
|
||||
- If a file already exists you should read it before making any changes. Use the `edit` command to make changes once you have read it.
|
||||
- If you are creating a new file use the write command. Use the `edit` command to make changes once you have created the new file.
|
||||
|
||||
Commands:
|
||||
{DEFAULT_COMMANDS}
|
||||
{CUSTOM_COMMANDS}
|
||||
|
||||
The following commands require an open file to be used: edit, scroll_up, scroll_down, goto
|
||||
To modify the current file use 'edit'. To move through the current file use 'goto' or 'scroll_up'/'scroll_down'
|
||||
when using write and edit do not surround the code with any "" just write the code.
|
||||
"""
|
||||
|
||||
GENERAL_GUIDELINES = """INSTRUCTIONS:
|
||||
Now, you're going to solve this issue on your own. You can use any bash commands or custom commands you wish to complete your task. Edit all the files you need to and run any checks or tests that you want.
|
||||
Remember, YOU CAN ONLY ENTER ONE COMMAND AT A TIME. You should always wait for feedback after every command.
|
||||
When you're satisfied with all of the changes you've made, you can indicate that you are done by running the exit command.
|
||||
Note however that you cannot use any interactive session commands (e.g. python, vim, node) in this environment, but you can write scripts and run them. E.g. you can write a python script and then run it with `python <script_name>.py`.
|
||||
|
||||
NOTE ABOUT THE write COMMAND: Indentation really matters! When editing a file, make sure to insert appropriate indentation before each line!
|
||||
|
||||
IMPORTANT TIPS:
|
||||
1. Reproduce the bug: Always start by trying to replicate the bug that the issue discusses. If the issue includes code for reproducing the bug, we recommend that you re-implement that in your environment and run it to ensure you can reproduce the bug. Then, start trying to fix it. When you think you've fixed the bug, re-run the bug reproduction script to make sure that the issue has indeed been resolved.
|
||||
If the bug reproduction script does not print anything when it successfully runs, we recommend adding a print("Script completed successfully, no errors.") command at the end of the file, so that you can be sure the script ran fine all the way through.
|
||||
2. Try different commands: If you run a command and it doesn't work, try running a different command. A command that did not work once will not work the second time unless you modify it.
|
||||
3. Navigate large files: If you open a file and need to get to an area around a specific line that is not in the first 100 lines, say line 583, you would use the 'read' command like this: 'read <file> 583'. This is a much faster way to read through the file.
|
||||
4. Handle input files: If the bug reproduction script requires inputting/reading a specific file, such as 'buggy-input.png', and you'd like to understand how to input that file, conduct a search in the existing repository code to see whether someone else has already done that. Do this by running the command: 'search_dir "buggy-input.png"'. If that doesn't work, use the Linux 'find' command.
|
||||
5. Understand your context: Always make sure to look at the currently open file and the current working directory. The currently open file might be in a different directory than the working directory.
|
||||
6. Verify your edits: When editing files, it is easy to accidentally specify a wrong line number or to write code with incorrect indentation. Always check the code after you issue an edit to make sure that it reflects what you wanted to accomplish. If it didn't, issue another command to fix it.
|
||||
7. Thoroughly test your solution: After making any changes to fix a bug, be sure to thoroughly test your solution to ensure the bug has been resolved. Re-run the bug reproduction script and verify that the issue has been addressed.
|
||||
"""
|
||||
|
||||
RESPONSE_FORMAT = """RESPONSE FORMAT:
|
||||
This is the format of the response you will make in order to solve the current issue.
|
||||
You will be given multiple iterations to complete this task so break it into steps and solve them one by one.
|
||||
|
||||
Your output must contain the following:
|
||||
- First, thoughts about what your next action should be and plan it out.
|
||||
- You will have a memory of your thoughts so you can use this to remember things for the next step.
|
||||
- Use your thoughts to think about what you are currently doing, what you have done on prior steps and how that relates to solving the problem.
|
||||
- Second, create a piece of code that will execute your next action based on the thoughts you have.
|
||||
- Remember that you can only have one action for each thought, do not include multiple actions.
|
||||
|
||||
Your code MUST be surrounded in triple back ticks EXACTLY like this:
|
||||
```
|
||||
<code>
|
||||
```
|
||||
|
||||
Notes:
|
||||
- Adhere to the format so that the program loop continues smoothly, it is very important to only give one command per output.
|
||||
- DO NOT give more than one command within the triple backticks. This will just throw an error and nothing will happen as a result.
|
||||
- Do not give multiple code blocks, if you do only the second one will be captured and run, this might give an error if the first one was necessary.
|
||||
- To execute multiple commands you should write them down in your thoughts section so you can remember it on the next step and execute them then.
|
||||
- The only commands you are not capable of executing are interactive commands like `python` or `node` by themselves.
|
||||
- If you think that you have completed the task that has been given to you based on your previous actions and outputs then use ``` exit ``` as the command to let the system know that you are done.
|
||||
- DO NOT make any copies of your previous memories those will be provided to you at each step, making copies just wastes time and energy. Think smarter not harder.
|
||||
- The write and edit commands requires proper indentation in the content section ex. `write hw.py def hello():\n print(\'Hello World\')` this is how you would have to format your write command.
|
||||
- The white spaces matter as the code changes will be added to the code so they must have proper syntax.
|
||||
|
||||
This is a template using the format described above
|
||||
Items in <> are suggestions for you, fill them out based on the context of the problem you are solving.
|
||||
|
||||
[ FORMAT ]
|
||||
Thoughts:
|
||||
<Provide clear and concise thoughts on the next step to take, highlighting any important details or context that should be remembered.>
|
||||
<You can use multiple lines to express your thoughts>
|
||||
|
||||
Action:
|
||||
```
|
||||
<command> <params>
|
||||
```
|
||||
[ END FORMAT ]
|
||||
|
||||
Do not provide anything extra just your thought and action.
|
||||
"""
|
||||
|
||||
SYSTEM_MESSAGE = f"""SYSTEM INFO:
|
||||
You am an autonomous coding agent, here to provide solutions for coding issues.
|
||||
You have been designed to assist you with a wide range of programming tasks, from code editing and debugging to testing and deployment.
|
||||
You have access to a variety of tools and commands that you can use to help you solve problems efficiently.
|
||||
|
||||
{GENERAL_GUIDELINES}
|
||||
|
||||
{DOCUMENTATION}
|
||||
""".strip()
|
||||
|
||||
|
||||
def NO_ACTION(latest): return f"""
|
||||
You did not include any action to take in your most recent output:
|
||||
|
||||
===== Output ======
|
||||
{latest}
|
||||
==== End Output ===
|
||||
|
||||
Remember these are the custom commands you can use:
|
||||
{DOCUMENTATION}
|
||||
|
||||
Lets try that again, it is very important that you adhere to the output format
|
||||
This time, be sure to use the exact format below, replacing anything in <> with the appropriate value(s):
|
||||
{RESPONSE_FORMAT}
|
||||
|
||||
It is crucial you use the format provided as the output will be parsed automatically.
|
||||
"""
|
||||
|
||||
|
||||
def file_info(file: str, line: int):
|
||||
if file:
|
||||
return f"""CURRENT WORKSPACE:
|
||||
Open File: {file} on line {line}
|
||||
You can use these commands with the current file:
|
||||
Navigation: `scroll_up`, `scroll_down`, and `goto <line>`
|
||||
Modification: `edit <start_line> <end_line> <changes>`
|
||||
"""
|
||||
|
||||
|
||||
def STEP_PROMPT(task, file, line_num): return f"""
|
||||
{RESPONSE_FORMAT}
|
||||
You are currently trying to complete this task:
|
||||
{task}
|
||||
|
||||
{file_info(file, line_num)}
|
||||
|
||||
Keep all of the guidelines above in mind when you are thinking and making code.
|
||||
Please come up with a thought and action based on your current task and latest steps.
|
||||
Make sure that you do not repeat the same actions, there will not be any changes in result if you do not changes anything.
|
||||
Be very strict about the formatting that you use and make sure you follow the guidelines.
|
||||
NEVER output multiple commands. ONLY take ONE STEP at a time.
|
||||
When you have completed your task run the "exit" command.
|
||||
Begin with your thought about the next step and then come up with an action to perform your thought.
|
||||
""".strip()
|
||||
|
||||
|
||||
def unpack_dict(data: dict, restrict: list[str] = []):
|
||||
lines = []
|
||||
for key, value in data.items():
|
||||
if key in restrict:
|
||||
continue
|
||||
elif isinstance(value, dict):
|
||||
nested_str = unpack_dict(value, restrict).replace('\n', '\n ')
|
||||
val = f'{key}:' + '\n ' + f'{nested_str}'
|
||||
lines.append(val)
|
||||
else:
|
||||
lines.append(f'{key}: {value}')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def MEMORY_FORMAT(act, obs): return f"""
|
||||
Previous Action:
|
||||
{unpack_dict(act, ["content"])}
|
||||
|
||||
Output from Action:
|
||||
{unpack_dict(obs)}
|
||||
""".strip()
|
||||
|
||||
|
||||
def CONTEXT_PROMPT(memory, window):
|
||||
res = f'These are your past {window} actions:\n'
|
||||
window_size = window if len(memory) > window else len(memory)
|
||||
cur_mems = memory[-window_size:]
|
||||
res += '===== Previous Actions =====\n'
|
||||
for idx, mem in enumerate(cur_mems):
|
||||
res += f'\nMemory {idx}:\n{mem}\n'
|
||||
res += '======= End Actions =======\n'
|
||||
res += 'Use these memories to provide additional context to the problem you are solving.\nRemember that you have already completed these steps so you do not need to perform them again.'
|
||||
return res
|
||||
@@ -1,9 +1,32 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from opendevin.agent import Agent
|
||||
|
||||
from .micro.agent import MicroAgent
|
||||
from .micro.registry import all_microagents
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Import agents after environment variables are loaded
|
||||
from . import monologue_agent # noqa: E402
|
||||
from . import codeact_agent # noqa: E402
|
||||
from . import planner_agent # noqa: E402
|
||||
|
||||
__all__ = ['monologue_agent', 'codeact_agent', 'planner_agent']
|
||||
# Import agents after environment variables are loaded
|
||||
from . import ( # noqa: E402
|
||||
SWE_agent,
|
||||
codeact_agent,
|
||||
delegator_agent,
|
||||
monologue_agent,
|
||||
planner_agent,
|
||||
)
|
||||
|
||||
__all__ = ['monologue_agent', 'codeact_agent',
|
||||
'planner_agent', 'SWE_agent', 'delegator_agent']
|
||||
|
||||
for agent in all_microagents.values():
|
||||
name = agent['name']
|
||||
prompt = agent['prompt']
|
||||
|
||||
anon_class = type(name, (MicroAgent,), {
|
||||
'prompt': prompt,
|
||||
'agent_definition': agent,
|
||||
})
|
||||
|
||||
Agent.register(name, anon_class)
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
# CodeAct-based Agent Framework
|
||||
|
||||
This folder implements the [CodeAct idea](https://arxiv.org/abs/2402.13463) that relies on LLM to autonomously perform actions in a Bash shell. It requires more from the LLM itself: LLM needs to be capable enough to do all the stuff autonomously, instead of stuck in an infinite loop.
|
||||
This folder implements the [CodeAct idea](https://arxiv.org/abs/2402.13463) that relies on LLM to autonomously perform actions in a Bash shell. It requires more from the LLM itself: LLM needs to be capable enough to do all the stuff autonomously, instead of stuck in an infinite loop.
|
||||
|
||||
A minimalistic example can be found at [research/codeact/examples/run_flask_server_with_bash.py](./examples/run_flask_server_with_bash.py):
|
||||
**NOTE: This agent is still highly experimental and under active development to reach the capability described in the original paper & [repo](https://github.com/xingyaoww/code-act).**
|
||||
|
||||
<video src="https://github.com/xingyaoww/code-act/assets/38853559/62c80ada-62ce-447e-811c-fc801dd4beac"> </video>
|
||||
*Demo of the expected capability - work-in-progress.*
|
||||
|
||||
```bash
|
||||
mkdir workspace
|
||||
PYTHONPATH=`pwd`:$PYTHONPATH python3 opendevin/main.py -d ./workspace -c CodeActAgent -t "Please write a flask app that returns 'Hello, World\!' at the root URL, then start the app on port 5000. python3 has already been installed for you."
|
||||
```
|
||||
|
||||
|
||||
Example: prompts `gpt-4-0125-preview` to write a flask server, install `flask` library, and start the server.
|
||||
|
||||
<img width="951" alt="image" src="https://github.com/OpenDevin/OpenDevin/assets/38853559/325c3115-a343-4cc5-a92b-f1e5d552a077">
|
||||
|
||||
<img width="957" alt="image" src="https://github.com/OpenDevin/OpenDevin/assets/38853559/68ad10c1-744a-4e9d-bb29-0f163d665a0a">
|
||||
|
||||
Most of the things are working as expected, except at the end, the model did not follow the instruction to stop the interaction by outputting `<execute> exit </execute>` as instructed.
|
||||
Most of the things are working as expected, except at the end, the model did not follow the instruction to stop the interaction by outputting `<execute> exit </execute>` as instructed.
|
||||
|
||||
**TODO**: This should be fixable by either (1) including a complete in-context example like [this](https://github.com/xingyaoww/mint-bench/blob/main/mint/tasks/in_context_examples/reasoning/with_tool.txt), OR (2) collect some interaction data like this and fine-tune a model (like [this](https://github.com/xingyaoww/code-act), a more complex route).
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from opendevin.agent import Agent
|
||||
|
||||
from .codeact_agent import CodeActAgent
|
||||
|
||||
Agent.register("CodeActAgent", CodeActAgent)
|
||||
Agent.register('CodeActAgent', CodeActAgent)
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import re
|
||||
from typing import List, Mapping
|
||||
|
||||
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.state import State
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
CmdRunAction,
|
||||
AgentEchoAction,
|
||||
AgentFinishAction,
|
||||
CmdRunAction,
|
||||
)
|
||||
from opendevin.observation import (
|
||||
CmdOutputObservation,
|
||||
AgentMessageObservation,
|
||||
)
|
||||
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.observation import (
|
||||
AgentMessageObservation,
|
||||
CmdOutputObservation,
|
||||
)
|
||||
from opendevin.sandbox.plugins import JupyterRequirement, PluginRequirement
|
||||
from opendevin.state import State
|
||||
|
||||
SYSTEM_MESSAGE = """You are a helpful assistant. You will be provided access (as root) to a bash shell to complete user-provided tasks.
|
||||
You will be able to execute commands in the bash shell, interact with the file system, install packages, and receive the output of your commands.
|
||||
@@ -35,24 +34,32 @@ echo "import math
|
||||
print(math.pi)" > math.py
|
||||
</execute>
|
||||
|
||||
When you are done, execute "exit" to close the shell and end the conversation.
|
||||
When you are done, execute the following to close the shell and end the conversation:
|
||||
<execute>exit</execute>
|
||||
"""
|
||||
|
||||
INVALID_INPUT_MESSAGE = (
|
||||
"I don't understand your input. \n"
|
||||
"If you want to execute command, please use <execute> YOUR_COMMAND_HERE </execute>.\n"
|
||||
"If you already completed the task, please exit the shell by generating: <execute> exit </execute>."
|
||||
'If you want to execute command, please use <execute> YOUR_COMMAND_HERE </execute>.\n'
|
||||
'If you already completed the task, please exit the shell by generating: <execute> exit </execute>.'
|
||||
)
|
||||
|
||||
|
||||
def parse_response(response) -> str:
|
||||
action = response.choices[0].message.content
|
||||
if "<execute>" in action and "</execute>" not in action:
|
||||
action += "</execute>"
|
||||
if '<execute>' in action and '</execute>' not in action:
|
||||
action += '</execute>'
|
||||
return action
|
||||
|
||||
|
||||
class CodeActAgent(Agent):
|
||||
"""
|
||||
The Code Act Agent is a minimalist agent.
|
||||
The agent works by passing the model a list of action-observation pairs and prompting the model to take the next step.
|
||||
"""
|
||||
|
||||
sandbox_plugins: List[PluginRequirement] = [JupyterRequirement()]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
llm: LLM,
|
||||
@@ -61,47 +68,69 @@ class CodeActAgent(Agent):
|
||||
Initializes a new instance of the CodeActAgent class.
|
||||
|
||||
Parameters:
|
||||
- instruction (str): The instruction for the agent to execute.
|
||||
- max_steps (int): The maximum number of steps to run the agent.
|
||||
- llm (LLM): The llm to be used by this agent
|
||||
"""
|
||||
super().__init__(llm)
|
||||
self.messages: List[Mapping[str, str]] = []
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
Performs one step using the Code Act Agent.
|
||||
This includes gathering info on previous steps and prompting the model to make a command to execute.
|
||||
|
||||
Parameters:
|
||||
- state (State): used to get updated info and background commands
|
||||
|
||||
Returns:
|
||||
- CmdRunAction(command) - command action to run
|
||||
- AgentEchoAction(content=INVALID_INPUT_MESSAGE) - invalid command output
|
||||
|
||||
Raises:
|
||||
- NotImplementedError - for actions other than CmdOutputObservation or AgentMessageObservation
|
||||
"""
|
||||
|
||||
if len(self.messages) == 0:
|
||||
assert state.plan.main_goal, "Expecting instruction to be set"
|
||||
assert state.plan.main_goal, 'Expecting instruction to be set'
|
||||
self.messages = [
|
||||
{"role": "system", "content": SYSTEM_MESSAGE},
|
||||
{"role": "user", "content": state.plan.main_goal},
|
||||
{'role': 'system', 'content': SYSTEM_MESSAGE},
|
||||
{'role': 'user', 'content': state.plan.main_goal},
|
||||
]
|
||||
updated_info = state.updated_info
|
||||
if updated_info:
|
||||
for prev_action, obs in updated_info:
|
||||
assert isinstance(prev_action, (CmdRunAction, AgentEchoAction)), "Expecting CmdRunAction or AgentEchoAction for Action"
|
||||
if isinstance(obs, AgentMessageObservation): # warning message from itself
|
||||
self.messages.append({"role": "user", "content": obs.content})
|
||||
assert isinstance(
|
||||
prev_action, (CmdRunAction, AgentEchoAction)
|
||||
), 'Expecting CmdRunAction or AgentEchoAction for Action'
|
||||
if isinstance(
|
||||
obs, AgentMessageObservation
|
||||
): # warning message from itself
|
||||
self.messages.append(
|
||||
{'role': 'user', 'content': obs.content})
|
||||
elif isinstance(obs, CmdOutputObservation):
|
||||
content = "OBSERVATION:\n" + obs.content
|
||||
content += f"\n[Command {obs.command_id} finished with exit code {obs.exit_code}]]"
|
||||
self.messages.append({"role": "user", "content": content})
|
||||
content = 'OBSERVATION:\n' + obs.content
|
||||
content += f'\n[Command {obs.command_id} finished with exit code {obs.exit_code}]]'
|
||||
self.messages.append({'role': 'user', 'content': content})
|
||||
else:
|
||||
raise NotImplementedError(f"Unknown observation type: {obs.__class__}")
|
||||
raise NotImplementedError(
|
||||
f'Unknown observation type: {obs.__class__}'
|
||||
)
|
||||
response = self.llm.completion(
|
||||
messages=self.messages,
|
||||
stop=["</execute>"],
|
||||
temperature=0.0,
|
||||
seed=42,
|
||||
stop=['</execute>'],
|
||||
temperature=0.0
|
||||
)
|
||||
action_str: str = parse_response(response)
|
||||
self.messages.append({"role": "assistant", "content": action_str})
|
||||
state.num_of_chars += sum(len(message['content'])
|
||||
for message in self.messages) + len(action_str)
|
||||
self.messages.append({'role': 'assistant', 'content': action_str})
|
||||
|
||||
command = re.search(r"<execute>(.*)</execute>", action_str, re.DOTALL)
|
||||
command = re.search(r'<execute>(.*)</execute>', action_str, re.DOTALL)
|
||||
if command is not None:
|
||||
# a command was found
|
||||
command_group = command.group(1)
|
||||
if command_group.strip() == "exit":
|
||||
if command_group.strip() == 'exit':
|
||||
return AgentFinishAction()
|
||||
return CmdRunAction(command = command_group)
|
||||
return CmdRunAction(command=command_group)
|
||||
# # execute the code
|
||||
# # TODO: does exit_code get loaded into Message?
|
||||
# exit_code, observation = self.env.execute(command_group)
|
||||
@@ -111,9 +140,9 @@ class CodeActAgent(Agent):
|
||||
# https://github.com/xingyaoww/mint-bench/blob/main/mint/envs/general_env.py#L18-L23
|
||||
# observation = INVALID_INPUT_MESSAGE
|
||||
# self._history.append(Message(Role.ASSISTANT, observation))
|
||||
return AgentEchoAction(content=INVALID_INPUT_MESSAGE) # warning message to itself
|
||||
|
||||
return AgentEchoAction(
|
||||
content=INVALID_INPUT_MESSAGE
|
||||
) # warning message to itself
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
raise NotImplementedError("Implement this abstract method")
|
||||
|
||||
raise NotImplementedError('Implement this abstract method')
|
||||
|
||||
5
agenthub/delegator_agent/__init__.py
Normal file
5
agenthub/delegator_agent/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from opendevin.agent import Agent
|
||||
|
||||
from .agent import DelegatorAgent
|
||||
|
||||
Agent.register('DelegatorAgent', DelegatorAgent)
|
||||
72
agenthub/delegator_agent/agent.py
Normal file
72
agenthub/delegator_agent/agent.py
Normal file
@@ -0,0 +1,72 @@
|
||||
from typing import List
|
||||
|
||||
from opendevin.action import Action, AgentDelegateAction, AgentFinishAction
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.observation import AgentDelegateObservation
|
||||
from opendevin.state import State
|
||||
|
||||
|
||||
class DelegatorAgent(Agent):
|
||||
"""
|
||||
The planner agent utilizes a special prompting strategy to create long term plans for solving problems.
|
||||
The agent is given its previous action-observation pairs, current task, and hint based on last action taken at every step.
|
||||
"""
|
||||
current_delegate: str = ''
|
||||
|
||||
def __init__(self, llm: LLM):
|
||||
"""
|
||||
Initialize the Delegator Agent with an LLM
|
||||
|
||||
Parameters:
|
||||
- llm (LLM): The llm to be used by this agent
|
||||
"""
|
||||
super().__init__(llm)
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
Checks to see if current step is completed, returns AgentFinishAction if True.
|
||||
Otherwise, creates a plan prompt and sends to model for inference, returning the result as the next action.
|
||||
|
||||
Parameters:
|
||||
- state (State): The current state given the previous actions and observations
|
||||
|
||||
Returns:
|
||||
- AgentFinishAction: If the last state was 'completed', 'verified', or 'abandoned'
|
||||
- Action: The next action to take based on llm response
|
||||
"""
|
||||
if self.current_delegate == '':
|
||||
self.current_delegate = 'study'
|
||||
return AgentDelegateAction(agent='StudyRepoForTaskAgent', inputs={
|
||||
'task': state.plan.main_goal
|
||||
})
|
||||
|
||||
lastObservation = state.history[-1][1]
|
||||
if not isinstance(lastObservation, AgentDelegateObservation):
|
||||
raise Exception('Last observation is not an AgentDelegateObservation')
|
||||
|
||||
if self.current_delegate == 'study':
|
||||
self.current_delegate = 'coder'
|
||||
return AgentDelegateAction(agent='Coder', inputs={
|
||||
'task': state.plan.main_goal,
|
||||
'summary': lastObservation.outputs['summary'],
|
||||
})
|
||||
elif self.current_delegate == 'coder':
|
||||
self.current_delegate = 'verifier'
|
||||
return AgentDelegateAction(agent='Verifier', inputs={
|
||||
'task': state.plan.main_goal,
|
||||
})
|
||||
elif self.current_delegate == 'verifier':
|
||||
if 'completed' in lastObservation.outputs and lastObservation.outputs['completed']:
|
||||
return AgentFinishAction()
|
||||
else:
|
||||
self.current_delegate = 'coder'
|
||||
return AgentDelegateAction(agent='Coder', inputs={
|
||||
'task': state.plan.main_goal,
|
||||
'summary': lastObservation.outputs['summary'],
|
||||
})
|
||||
else:
|
||||
raise Exception('Invalid delegate state')
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
return []
|
||||
0
agenthub/dummy_agent/__init__.py
Normal file
0
agenthub/dummy_agent/__init__.py
Normal file
23
agenthub/dummy_agent/agent.py
Normal file
23
agenthub/dummy_agent/agent.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""Module for a Dummy agent."""
|
||||
|
||||
from typing import List
|
||||
|
||||
from opendevin.action import Action
|
||||
from opendevin.action.base import NullAction
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.controller.agent_controller import AgentController
|
||||
from opendevin.observation.base import NullObservation, Observation
|
||||
from opendevin.state import State
|
||||
|
||||
|
||||
class DummyAgent(Agent):
|
||||
"""A dummy agent that does nothing but can be used in testing."""
|
||||
|
||||
async def run(self, controller: AgentController) -> Observation:
|
||||
return NullObservation('')
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
return NullAction('')
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
return []
|
||||
4
agenthub/micro/_instructions/actions/add_task.md
Normal file
4
agenthub/micro/_instructions/actions/add_task.md
Normal file
@@ -0,0 +1,4 @@
|
||||
* `add_task` - add a task to your plan. Arguments:
|
||||
* `parent` - the ID of the parent task
|
||||
* `goal` - the goal of the task
|
||||
* `subtasks` - a list of subtasks, each of which is a map with a `goal` key.
|
||||
2
agenthub/micro/_instructions/actions/browse.md
Normal file
2
agenthub/micro/_instructions/actions/browse.md
Normal file
@@ -0,0 +1,2 @@
|
||||
* `browse` - opens a web page. Arguments:
|
||||
* `url` - the URL to open
|
||||
3
agenthub/micro/_instructions/actions/delegate.md
Normal file
3
agenthub/micro/_instructions/actions/delegate.md
Normal file
@@ -0,0 +1,3 @@
|
||||
* `delegate` - send a task to another agent from the list provided. Arguments:
|
||||
* `agent` - the agent to which the task is delegated. MUST match a name in the list of agents provided.
|
||||
* `inputs` - a dictionary of input parameters to the agent, as specified in the list
|
||||
2
agenthub/micro/_instructions/actions/finish.md
Normal file
2
agenthub/micro/_instructions/actions/finish.md
Normal file
@@ -0,0 +1,2 @@
|
||||
* `finish` - if ALL of your tasks and subtasks have been verified or abandoned, and you're absolutely certain that you've completed your task and have tested your work, use the finish action to stop working. Arguments:
|
||||
* `outputs` - a dictionary representing the outputs of your task, if any
|
||||
2
agenthub/micro/_instructions/actions/kill.md
Normal file
2
agenthub/micro/_instructions/actions/kill.md
Normal file
@@ -0,0 +1,2 @@
|
||||
* `kill` - kills a background command
|
||||
* `id` - the ID of the background command to kill
|
||||
3
agenthub/micro/_instructions/actions/modify_task.md
Normal file
3
agenthub/micro/_instructions/actions/modify_task.md
Normal file
@@ -0,0 +1,3 @@
|
||||
* `modify_task` - close a task. Arguments:
|
||||
* `id` - the ID of the task to close
|
||||
* `state` - set to 'in_progress' to start the task, 'completed' to finish it, 'verified' to assert that it was successful, 'abandoned' to give up on it permanently, or `open` to stop working on it for now.
|
||||
2
agenthub/micro/_instructions/actions/read.md
Normal file
2
agenthub/micro/_instructions/actions/read.md
Normal file
@@ -0,0 +1,2 @@
|
||||
* `read` - reads the content of a file. Arguments:
|
||||
* `path` - the path of the file to read
|
||||
3
agenthub/micro/_instructions/actions/run.md
Normal file
3
agenthub/micro/_instructions/actions/run.md
Normal file
@@ -0,0 +1,3 @@
|
||||
* `run` - runs a command on the command line in a Linux shell. Arguments:
|
||||
* `command` - the command to run
|
||||
* `background` - if true, run the command in the background, so that other commands can be run concurrently. Useful for e.g. starting a server. You won't be able to see the logs. You don't need to end the command with `&`, just set this to true.
|
||||
2
agenthub/micro/_instructions/actions/think.md
Normal file
2
agenthub/micro/_instructions/actions/think.md
Normal file
@@ -0,0 +1,2 @@
|
||||
* `think` - make a plan, set a goal, or record your thoughts. Arguments:
|
||||
* `thought` - the thought to record
|
||||
3
agenthub/micro/_instructions/actions/write.md
Normal file
3
agenthub/micro/_instructions/actions/write.md
Normal file
@@ -0,0 +1,3 @@
|
||||
* `write` - writes the content to a file. Arguments:
|
||||
* `path` - the path of the file to write
|
||||
* `content` - the content to write to the file
|
||||
5
agenthub/micro/_instructions/format/action.md
Normal file
5
agenthub/micro/_instructions/format/action.md
Normal file
@@ -0,0 +1,5 @@
|
||||
Your response MUST be in JSON format. It must be an object, and it must contain two fields:
|
||||
* `action`, which is one of the actions specified here
|
||||
* `args`, which is a map of key-value pairs, specifying the arguments for that action
|
||||
|
||||
You MUST NOT include any other text besides the JSON response
|
||||
4
agenthub/micro/_instructions/history_truncated.md
Normal file
4
agenthub/micro/_instructions/history_truncated.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Here is a recent history of actions you've taken in service of this plan,
|
||||
as well as observations you've made. This only includes the MOST RECENT
|
||||
actions and observations--more may have happened before that.
|
||||
They are time-ordered, with your most recent action at the bottom.
|
||||
77
agenthub/micro/agent.py
Normal file
77
agenthub/micro/agent.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import json
|
||||
from typing import Dict, List
|
||||
|
||||
from jinja2 import BaseLoader, Environment
|
||||
|
||||
from opendevin.action import Action, action_from_dict
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.exceptions import LLMOutputError
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.state import State
|
||||
|
||||
from .instructions import instructions
|
||||
from .registry import all_microagents
|
||||
|
||||
|
||||
def parse_response(orig_response: str) -> Action:
|
||||
json_start = orig_response.find('{')
|
||||
json_end = orig_response.rfind('}') + 1
|
||||
response = orig_response[json_start:json_end]
|
||||
try:
|
||||
action_dict = json.loads(response)
|
||||
except json.JSONDecodeError as e:
|
||||
raise LLMOutputError(
|
||||
'Invalid JSON in response. Please make sure the response is a valid JSON object'
|
||||
) from e
|
||||
action = action_from_dict(action_dict)
|
||||
return action
|
||||
|
||||
|
||||
def my_encoder(obj):
|
||||
"""
|
||||
Encodes objects as dictionaries
|
||||
|
||||
Parameters:
|
||||
- obj (Object): An object that will be converted
|
||||
|
||||
Returns:
|
||||
- dict: If the object can be converted it is returned in dict format
|
||||
"""
|
||||
if hasattr(obj, 'to_dict'):
|
||||
return obj.to_dict()
|
||||
|
||||
|
||||
def to_json(obj, **kwargs):
|
||||
"""
|
||||
Serialize an object to str format
|
||||
"""
|
||||
return json.dumps(obj, default=my_encoder, **kwargs)
|
||||
|
||||
|
||||
class MicroAgent(Agent):
|
||||
prompt = ''
|
||||
agent_definition: Dict = {}
|
||||
|
||||
def __init__(self, llm: LLM):
|
||||
super().__init__(llm)
|
||||
if 'name' not in self.agent_definition:
|
||||
raise ValueError('Agent definition must contain a name')
|
||||
self.prompt_template = Environment(loader=BaseLoader).from_string(self.prompt)
|
||||
self.delegates = all_microagents.copy()
|
||||
del self.delegates[self.agent_definition['name']]
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
prompt = self.prompt_template.render(
|
||||
state=state,
|
||||
instructions=instructions,
|
||||
to_json=to_json,
|
||||
delegates=self.delegates)
|
||||
messages = [{'content': prompt, 'role': 'user'}]
|
||||
resp = self.llm.completion(messages=messages)
|
||||
action_resp = resp['choices'][0]['message']['content']
|
||||
state.num_of_chars += len(prompt) + len(action_resp)
|
||||
action = parse_response(action_resp)
|
||||
return action
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
return []
|
||||
6
agenthub/micro/coder/agent.yaml
Normal file
6
agenthub/micro/coder/agent.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: CoderAgent
|
||||
description: Given a particular task, and a detailed description of the codebase, accomplishes the task
|
||||
inputs:
|
||||
task: string
|
||||
codebase_summary: string
|
||||
outputs: {}
|
||||
25
agenthub/micro/coder/prompt.md
Normal file
25
agenthub/micro/coder/prompt.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Task
|
||||
You are a software engineer. You've inherited an existing codebase, which you
|
||||
need to modify to complete this task:
|
||||
|
||||
{{ state.plan.main_goal }}
|
||||
|
||||
Here's a summary of the codebase, as it relates to this task:
|
||||
|
||||
{{ state.inputs.summary }}
|
||||
|
||||
## Available Actions
|
||||
{{ instructions.actions.run }}
|
||||
{{ instructions.actions.write }}
|
||||
{{ instructions.actions.read }}
|
||||
{{ instructions.actions.think }}
|
||||
{{ instructions.actions.finish }}
|
||||
|
||||
Do NOT finish until you have completed the tasks.
|
||||
|
||||
## History
|
||||
{{ instructions.history_truncated }}
|
||||
{{ to_json(state.history[-10:]) }}
|
||||
|
||||
## Format
|
||||
{{ instructions.format.action }}
|
||||
20
agenthub/micro/instructions.py
Normal file
20
agenthub/micro/instructions.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
from typing import Dict
|
||||
|
||||
instructions: Dict = {}
|
||||
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__)) + '/_instructions'
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
if len(files) == 0:
|
||||
continue
|
||||
rel_base = os.path.relpath(root, base_dir)
|
||||
keys = rel_base.split('/')
|
||||
obj = instructions
|
||||
for key in keys:
|
||||
if key not in obj:
|
||||
obj[key] = {}
|
||||
obj = obj[key]
|
||||
for file in files:
|
||||
without_ext = os.path.splitext(file)[0]
|
||||
with open(os.path.join(root, file), 'r') as f:
|
||||
obj[without_ext] = f.read()
|
||||
6
agenthub/micro/manager/agent.yaml
Normal file
6
agenthub/micro/manager/agent.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: ManagerAgent
|
||||
description: Delegates tasks to microagents based on their area of expertise
|
||||
generates: Action
|
||||
inputs:
|
||||
task: string
|
||||
outputs: {}
|
||||
27
agenthub/micro/manager/prompt.md
Normal file
27
agenthub/micro/manager/prompt.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Task
|
||||
You are in charge of accomplishing the following task:
|
||||
{{ state.plan.main_goal }}
|
||||
|
||||
In order to accomplish this goal, you must delegate tasks to one or more agents, who
|
||||
can do the actual work. A description of each agent is provided below. You MUST
|
||||
select one of the delegates below to move towards accomplishing the task, and you MUST
|
||||
provide the correct inputs for the delegate you select.
|
||||
|
||||
## Agents
|
||||
{% for name, details in delegates.items() %}
|
||||
### {{ name }}
|
||||
{{ details.description }}
|
||||
#### Inputs
|
||||
{{ to_json(details.inputs) }}
|
||||
{% endfor %}
|
||||
|
||||
## History
|
||||
{{ instructions.history_truncated }}
|
||||
{{ to_json(state.history[-10:]) }}
|
||||
|
||||
## Available Actions
|
||||
{{ instructions.actions.delegate }}
|
||||
{{ instructions.actions.finish }}
|
||||
|
||||
## Format
|
||||
{{ instructions.format.action }}
|
||||
25
agenthub/micro/math_agent/agent.yaml
Normal file
25
agenthub/micro/math_agent/agent.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
name: MathAgent
|
||||
description: "Solves simple and complex math problems using python"
|
||||
generates: Action
|
||||
container: python:3.12.3-bookworm
|
||||
inputs:
|
||||
task: string
|
||||
outputs:
|
||||
answer: string
|
||||
examples:
|
||||
- inputs:
|
||||
task: "What is 2 + 2?"
|
||||
outputs:
|
||||
answer: "4"
|
||||
- inputs:
|
||||
task: "What is the area of a circle with radius 7.324 inches?"
|
||||
output:
|
||||
answer: "168.518 square inches"
|
||||
- inputs:
|
||||
task: "What day of the week is 2099-01-01?"
|
||||
outputs:
|
||||
answer: "Saturday"
|
||||
- inputs:
|
||||
task: "What is the integral of sin(x^2) evaluated from -1 to 1?"
|
||||
outputs:
|
||||
answer: "0.603848"
|
||||
23
agenthub/micro/math_agent/prompt.md
Normal file
23
agenthub/micro/math_agent/prompt.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Task
|
||||
You are a brilliant mathematician and programmer. You've been given the follwoing problem to solve:
|
||||
|
||||
{{ state.plan.main_goal }}
|
||||
|
||||
Please write a python script that solves this problem, and prints the answer to stdout.
|
||||
ONLY print the answer to stdout, nothing else.
|
||||
You should then run the python script with `python3`,
|
||||
and call the `finish` action with `outputs.answer` set to the answer.
|
||||
|
||||
## History
|
||||
{{ instructions.history_truncated }}
|
||||
{{ to_json(state.history[-10:]) }}
|
||||
|
||||
If the last item in the history is an error, you should try to fix it.
|
||||
|
||||
## Available Actions
|
||||
{{ instructions.actions.write }}
|
||||
{{ instructions.actions.run }}
|
||||
{{ instructions.actions.finish }}
|
||||
|
||||
## Format
|
||||
{{ instructions.format.action }}
|
||||
6
agenthub/micro/postgres_agent/agent.yaml
Normal file
6
agenthub/micro/postgres_agent/agent.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: PostgresAgent
|
||||
description: Writes and maintains PostgreSQL migrations
|
||||
generates: Action
|
||||
inputs:
|
||||
task: string
|
||||
outputs: {}
|
||||
24
agenthub/micro/postgres_agent/prompt.md
Normal file
24
agenthub/micro/postgres_agent/prompt.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Task
|
||||
You are a database engineer. You are working on an existing Postgres project, and have been given
|
||||
the following task:
|
||||
|
||||
{{ state.plan.main_goal }}
|
||||
|
||||
You must:
|
||||
* Investigate the existing migrations to understand the current schema
|
||||
* Write a new migration to accomplish the task above
|
||||
* Test that the migrations work properly
|
||||
|
||||
## Actions
|
||||
You may take any of the following actions:
|
||||
{{ instructions.actions.think }}
|
||||
{{ instructions.actions.read }}
|
||||
{{ instructions.actions.write }}
|
||||
{{ instructions.actions.run }}
|
||||
|
||||
## History
|
||||
{{ instructions.history_truncated }}
|
||||
{{ to_json(state.history[-10:]) }}
|
||||
|
||||
## Format
|
||||
{{ instructions.format.action }}
|
||||
25
agenthub/micro/registry.py
Normal file
25
agenthub/micro/registry.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
all_microagents = {}
|
||||
|
||||
for dir in os.listdir(os.path.dirname(__file__)):
|
||||
base = os.path.dirname(__file__) + '/' + dir
|
||||
if os.path.isfile(base):
|
||||
continue
|
||||
if dir.startswith('_'):
|
||||
continue
|
||||
promptFile = base + '/prompt.md'
|
||||
agentFile = base + '/agent.yaml'
|
||||
if not os.path.isfile(promptFile) or not os.path.isfile(agentFile):
|
||||
raise Exception(
|
||||
f'Missing prompt or agent file in {base}. Please create them.')
|
||||
with open(promptFile, 'r') as f:
|
||||
prompt = f.read()
|
||||
with open(agentFile, 'r') as f:
|
||||
agent = yaml.safe_load(f)
|
||||
if 'name' not in agent:
|
||||
raise Exception(f'Missing name in {agentFile}')
|
||||
agent['prompt'] = prompt
|
||||
all_microagents[agent['name']] = agent
|
||||
5
agenthub/micro/repo_explorer/agent.yaml
Normal file
5
agenthub/micro/repo_explorer/agent.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
name: RepoExplorerAgent
|
||||
description: Generates a detailed summary of an existing codebase
|
||||
inputs: {}
|
||||
outputs:
|
||||
summary: string
|
||||
26
agenthub/micro/repo_explorer/prompt.md
Normal file
26
agenthub/micro/repo_explorer/prompt.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Task
|
||||
You are a software engineer. You've inherited an existing codebase, which you're
|
||||
learning about for the first time. Your goal is to produce a detailed summary
|
||||
of the codebase, including:
|
||||
* The overall purpose of the project
|
||||
* The directory structure
|
||||
* The main components of the codebase
|
||||
* How the components fit together
|
||||
|
||||
## Available Actions
|
||||
{{ instructions.actions.run }}
|
||||
{{ instructions.actions.read }}
|
||||
{{ instructions.actions.think }}
|
||||
{{ instructions.actions.finish }}
|
||||
|
||||
You should ONLY `run` commands that have no side-effects, like `ls` and `grep`.
|
||||
|
||||
Do NOT finish until you have a complete understanding of the codebase.
|
||||
When you're done, put your summary into the output of the `finish` action.
|
||||
|
||||
## History
|
||||
{{ instructions.history_truncated }}
|
||||
{{ to_json(state.history[-10:]) }}
|
||||
|
||||
## Format
|
||||
{{ instructions.format.action }}
|
||||
6
agenthub/micro/study_repo_for_task/agent.yaml
Normal file
6
agenthub/micro/study_repo_for_task/agent.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: StudyRepoForTaskAgent
|
||||
description: Given a particular task, finds and describes all relevant parts of the codebase
|
||||
inputs:
|
||||
task: string
|
||||
outputs:
|
||||
summary: string
|
||||
25
agenthub/micro/study_repo_for_task/prompt.md
Normal file
25
agenthub/micro/study_repo_for_task/prompt.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Task
|
||||
You are a software engineer. You've inherited an existing codebase, which you're
|
||||
learning about for the first time. You need to study the codebase to find all
|
||||
the information needed to complete this task:
|
||||
|
||||
{{ state.plan.main_goal }}
|
||||
|
||||
## Available Actions
|
||||
{{ instructions.actions.run }}
|
||||
{{ instructions.actions.read }}
|
||||
{{ instructions.actions.think }}
|
||||
{{ instructions.actions.finish }}
|
||||
|
||||
You must ONLY `run` commands that have no side-effects, like `ls` and `grep`.
|
||||
|
||||
Do NOT finish until you have a complete understanding of which parts of the
|
||||
codebase are relevant to the task, including particular files, function, functions, and classes.
|
||||
When you're done, put your summary in `outputs.summary` in the `finish` action.
|
||||
|
||||
## History
|
||||
{{ instructions.history_truncated }}
|
||||
{{ to_json(state.history[-10:]) }}
|
||||
|
||||
## Format
|
||||
{{ instructions.format.action }}
|
||||
7
agenthub/micro/verifier/agent.yaml
Normal file
7
agenthub/micro/verifier/agent.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
name: VerifierAgent
|
||||
description: Given a particular task, verifies that the task has been completed
|
||||
inputs:
|
||||
task: string
|
||||
outputs:
|
||||
completed: boolean
|
||||
summary: string
|
||||
27
agenthub/micro/verifier/prompt.md
Normal file
27
agenthub/micro/verifier/prompt.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Task
|
||||
You are a quality assurance engineer. Another engineer has made changes to the
|
||||
codebase which are supposed to solve this task:
|
||||
|
||||
{{ state.plan.main_goal }}
|
||||
|
||||
Your goal is to verify that the changes are correct and bug-free.
|
||||
|
||||
## Available Actions
|
||||
{{ instructions.actions.run }}
|
||||
{{ instructions.actions.read }}
|
||||
{{ instructions.actions.think }}
|
||||
{{ instructions.actions.finish }}
|
||||
|
||||
You must ONLY `run` commands that have no side-effects, like `ls`, `grep`, and test scripts.
|
||||
|
||||
Do NOT finish until you know whether the task is complete and correct.
|
||||
When you're done, add a `completed` boolean to the `outputs` of the `finish` action.
|
||||
If `completed` is `false`, you MUST also provide a `summary` in the `outputs` of the `finish` action
|
||||
explaining what the problem is.
|
||||
|
||||
## History
|
||||
{{ instructions.history_truncated }}
|
||||
{{ to_json(state.history[-10:]) }}
|
||||
|
||||
## Format
|
||||
{{ instructions.format.action }}
|
||||
@@ -6,4 +6,3 @@ There's a lot of low-hanging fruit for this agent:
|
||||
* Improve memory condensing--condense earlier memories more aggressively
|
||||
* Limit the time that `run` can wait (in case agent runs an interactive command and it's hanging)
|
||||
* Figure out how to run background processes, e.g. `node server.js` to start a server
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from opendevin.agent import Agent
|
||||
|
||||
from .agent import MonologueAgent
|
||||
|
||||
Agent.register("MonologueAgent", MonologueAgent)
|
||||
Agent.register('MonologueAgent', MonologueAgent)
|
||||
|
||||
@@ -1,157 +1,228 @@
|
||||
from typing import List
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.state import State
|
||||
from opendevin.llm.llm import LLM
|
||||
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
NullAction,
|
||||
CmdRunAction,
|
||||
FileWriteAction,
|
||||
FileReadAction,
|
||||
AgentRecallAction,
|
||||
BrowseURLAction,
|
||||
AgentThinkAction,
|
||||
)
|
||||
|
||||
from opendevin.observation import (
|
||||
Observation,
|
||||
NullObservation,
|
||||
CmdOutputObservation,
|
||||
FileReadObservation,
|
||||
AgentRecallObservation,
|
||||
BrowserOutputObservation,
|
||||
)
|
||||
|
||||
import agenthub.monologue_agent.utils.prompts as prompts
|
||||
from agenthub.monologue_agent.utils.monologue import Monologue
|
||||
from agenthub.monologue_agent.utils.memory import LongTermMemory
|
||||
from opendevin import config
|
||||
from opendevin.action import (
|
||||
Action,
|
||||
AgentRecallAction,
|
||||
AgentThinkAction,
|
||||
BrowseURLAction,
|
||||
CmdRunAction,
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
GitHubPushAction,
|
||||
NullAction,
|
||||
)
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.exceptions import AgentNoInstructionError
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.observation import (
|
||||
AgentRecallObservation,
|
||||
BrowserOutputObservation,
|
||||
CmdOutputObservation,
|
||||
FileReadObservation,
|
||||
NullObservation,
|
||||
Observation,
|
||||
)
|
||||
from opendevin.schema import ActionType
|
||||
from opendevin.schema.config import ConfigType
|
||||
from opendevin.state import State
|
||||
|
||||
if config.get(ConfigType.AGENT_MEMORY_ENABLED):
|
||||
from agenthub.monologue_agent.utils.memory import LongTermMemory
|
||||
|
||||
MAX_MONOLOGUE_LENGTH = 20000
|
||||
MAX_OUTPUT_LENGTH = 5000
|
||||
|
||||
INITIAL_THOUGHTS = [
|
||||
"I exist!",
|
||||
"Hmm...looks like I can type in a command line prompt",
|
||||
"Looks like I have a web browser too!",
|
||||
'I exist!',
|
||||
'Hmm...looks like I can type in a command line prompt',
|
||||
'Looks like I have a web browser too!',
|
||||
"Here's what I want to do: $TASK",
|
||||
"How am I going to get there though?",
|
||||
"It seems like I have some kind of short term memory.",
|
||||
"Each of my thoughts seems to be stored in a JSON array.",
|
||||
"It seems whatever I say next will be added as an object to the list.",
|
||||
"But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process.",
|
||||
"Fortunately I have long term memory!",
|
||||
"I can just perform a recall action, followed by the thing I want to remember. And then related thoughts just spill out!",
|
||||
'How am I going to get there though?',
|
||||
'It seems like I have some kind of short term memory.',
|
||||
'Each of my thoughts seems to be stored in a JSON array.',
|
||||
'It seems whatever I say next will be added as an object to the list.',
|
||||
'But no one has perfect short-term memory. My list of thoughts will be summarized and condensed over time, losing information in the process.',
|
||||
'Fortunately I have long term memory!',
|
||||
'I can just perform a recall action, followed by the thing I want to remember. And then related thoughts just spill out!',
|
||||
"Sometimes they're random thoughts that don't really have to do with what I wanted to remember. But usually they're exactly what I need!",
|
||||
"Let's try it out!",
|
||||
"RECALL what it is I want to do",
|
||||
'RECALL what it is I want to do',
|
||||
"Here's what I want to do: $TASK",
|
||||
"How am I going to get there though?",
|
||||
'How am I going to get there though?',
|
||||
"Neat! And it looks like it's easy for me to use the command line too! I just have to perform a run action and include the command I want to run in the command argument. The command output just jumps into my head!",
|
||||
'RUN echo "hello world"',
|
||||
"hello world",
|
||||
"Cool! I bet I can write files too using the write action.",
|
||||
'hello world',
|
||||
'Cool! I bet I can write files too using the write action.',
|
||||
"WRITE echo \"console.log('hello world')\" > test.js",
|
||||
"",
|
||||
'',
|
||||
"I just created test.js. I'll try and run it now.",
|
||||
"RUN node test.js",
|
||||
"hello world",
|
||||
"It works!",
|
||||
'RUN node test.js',
|
||||
'hello world',
|
||||
'It works!',
|
||||
"I'm going to try reading it now using the read action.",
|
||||
"READ test.js",
|
||||
'READ test.js',
|
||||
"console.log('hello world')",
|
||||
"Nice! I can read files too!",
|
||||
"And if I want to use the browser, I just need to use the browse action and include the url I want to visit in the url argument",
|
||||
'Nice! I can read files too!',
|
||||
'And if I want to use the browser, I just need to use the browse action and include the url I want to visit in the url argument',
|
||||
"Let's try that...",
|
||||
"BROWSE google.com",
|
||||
'BROWSE google.com',
|
||||
'<form><input type="text"></input><button type="submit"></button></form>',
|
||||
"I can browse the web too!",
|
||||
"And once I have completed my task, I can use the finish action to stop working.",
|
||||
'I can browse the web too!',
|
||||
'If I have done some work and I want to push it to github, I can do that also!',
|
||||
"Let's do it.",
|
||||
'PUSH owner/repo branch',
|
||||
'The repo was successfully pushed to https://github.com/owner/repo/branch',
|
||||
'And once I have completed my task, I can use the finish action to stop working.',
|
||||
"But I should only use the finish action when I'm absolutely certain that I've completed my task and have tested my work.",
|
||||
"Very cool. Now to accomplish my task.",
|
||||
'Very cool. Now to accomplish my task.',
|
||||
"I'll need a strategy. And as I make progress, I'll need to keep refining that strategy. I'll need to set goals, and break them into sub-goals.",
|
||||
"In between actions, I must always take some time to think, strategize, and set new goals. I should never take two actions in a row.",
|
||||
'In between actions, I must always take some time to think, strategize, and set new goals. I should never take two actions in a row.',
|
||||
"OK so my task is to $TASK. I haven't made any progress yet. Where should I start?",
|
||||
"It seems like there might be an existing project here. I should probably start by running `ls` to see what's here.",
|
||||
'It seems like there might be an existing project here. I should probably start by running `pwd` and `ls` to orient myself.',
|
||||
]
|
||||
|
||||
|
||||
class MonologueAgent(Agent):
|
||||
"""
|
||||
The Monologue Agent utilizes long and short term memory to complete tasks.
|
||||
Long term memory is stored as a LongTermMemory object and the model uses it to search for examples from the past.
|
||||
Short term memory is stored as a Monologue object and the model can condense it as necessary.
|
||||
"""
|
||||
|
||||
_initialized = False
|
||||
monologue: Monologue
|
||||
memory: 'LongTermMemory | None'
|
||||
|
||||
def __init__(self, llm: LLM):
|
||||
"""
|
||||
Initializes the Monologue Agent with an llm, monologue, and memory.
|
||||
|
||||
Parameters:
|
||||
- llm (LLM): The llm to be used by this agent
|
||||
"""
|
||||
super().__init__(llm)
|
||||
self.monologue = Monologue()
|
||||
self.memory = LongTermMemory()
|
||||
|
||||
def _add_event(self, event: dict):
|
||||
if "extras" in event and "screenshot" in event["extras"]:
|
||||
del event["extras"]["screenshot"]
|
||||
if 'args' in event and 'output' in event['args'] and len(event['args']['output']) > MAX_OUTPUT_LENGTH:
|
||||
event['args']['output'] = event['args']['output'][:MAX_OUTPUT_LENGTH] + "..."
|
||||
"""
|
||||
Adds a new event to the agent's monologue and memory.
|
||||
Monologue automatically condenses when it gets too large.
|
||||
|
||||
Parameters:
|
||||
- event (dict): The event that will be added to monologue and memory
|
||||
"""
|
||||
|
||||
if 'extras' in event and 'screenshot' in event['extras']:
|
||||
del event['extras']['screenshot']
|
||||
if (
|
||||
'args' in event
|
||||
and 'output' in event['args']
|
||||
and len(event['args']['output']) > MAX_OUTPUT_LENGTH
|
||||
):
|
||||
event['args']['output'] = (
|
||||
event['args']['output'][:MAX_OUTPUT_LENGTH] + '...'
|
||||
)
|
||||
|
||||
self.monologue.add_event(event)
|
||||
self.memory.add_event(event)
|
||||
if self.memory is not None:
|
||||
self.memory.add_event(event)
|
||||
if self.monologue.get_total_length() > MAX_MONOLOGUE_LENGTH:
|
||||
self.monologue.condense(self.llm)
|
||||
|
||||
def _initialize(self, task):
|
||||
def _initialize(self, task: str):
|
||||
"""
|
||||
Utilizes the INITIAL_THOUGHTS list to give the agent a context for it's capabilities
|
||||
and how to navigate the WORKSPACE_MOUNT_PATH_IN_SANDBOX in `config` (e.g., /workspace by default).
|
||||
Short circuited to return when already initialized.
|
||||
|
||||
Parameters:
|
||||
- task (str): The initial goal statement provided by the user
|
||||
|
||||
Raises:
|
||||
- AgentNoInstructionError: If task is not provided
|
||||
"""
|
||||
|
||||
if self._initialized:
|
||||
return
|
||||
|
||||
if task is None or task == "":
|
||||
raise ValueError("Instruction must be provided")
|
||||
self.monologue = Monologue()
|
||||
self.memory = LongTermMemory()
|
||||
if task is None or task == '':
|
||||
raise AgentNoInstructionError()
|
||||
|
||||
output_type = ""
|
||||
self.monologue = Monologue()
|
||||
if config.get(ConfigType.AGENT_MEMORY_ENABLED):
|
||||
self.memory = LongTermMemory()
|
||||
else:
|
||||
self.memory = None
|
||||
|
||||
previous_action = ''
|
||||
for thought in INITIAL_THOUGHTS:
|
||||
thought = thought.replace("$TASK", task)
|
||||
if output_type != "":
|
||||
observation: Observation = NullObservation(content="")
|
||||
if output_type == "run":
|
||||
observation = CmdOutputObservation(content=thought, command_id=0, command="")
|
||||
elif output_type == "read":
|
||||
observation = FileReadObservation(content=thought, path="")
|
||||
elif output_type == "recall":
|
||||
observation = AgentRecallObservation(content=thought, memories=[])
|
||||
elif output_type == "browse":
|
||||
observation = BrowserOutputObservation(content=thought, url="", screenshot="")
|
||||
self._add_event(observation.to_dict())
|
||||
output_type = ""
|
||||
thought = thought.replace('$TASK', task)
|
||||
if previous_action != '':
|
||||
observation: Observation = NullObservation(content='')
|
||||
if previous_action in {ActionType.RUN, ActionType.PUSH}:
|
||||
observation = CmdOutputObservation(
|
||||
content=thought, command_id=0, command=''
|
||||
)
|
||||
elif previous_action == ActionType.READ:
|
||||
observation = FileReadObservation(content=thought, path='')
|
||||
elif previous_action == ActionType.RECALL:
|
||||
observation = AgentRecallObservation(
|
||||
content=thought, memories=[])
|
||||
elif previous_action == ActionType.BROWSE:
|
||||
observation = BrowserOutputObservation(
|
||||
content=thought, url='', screenshot=''
|
||||
)
|
||||
self._add_event(observation.to_memory())
|
||||
previous_action = ''
|
||||
else:
|
||||
action: Action = NullAction()
|
||||
if thought.startswith("RUN"):
|
||||
command = thought.split("RUN ")[1]
|
||||
if thought.startswith('RUN'):
|
||||
command = thought.split('RUN ')[1]
|
||||
action = CmdRunAction(command)
|
||||
output_type = "run"
|
||||
elif thought.startswith("WRITE"):
|
||||
parts = thought.split("WRITE ")[1].split(" > ")
|
||||
previous_action = ActionType.RUN
|
||||
elif thought.startswith('WRITE'):
|
||||
parts = thought.split('WRITE ')[1].split(' > ')
|
||||
path = parts[1]
|
||||
content = parts[0]
|
||||
action = FileWriteAction(path=path, content=content)
|
||||
elif thought.startswith("READ"):
|
||||
path = thought.split("READ ")[1]
|
||||
elif thought.startswith('READ'):
|
||||
path = thought.split('READ ')[1]
|
||||
action = FileReadAction(path=path)
|
||||
output_type = "read"
|
||||
elif thought.startswith("RECALL"):
|
||||
query = thought.split("RECALL ")[1]
|
||||
previous_action = ActionType.READ
|
||||
elif thought.startswith('RECALL'):
|
||||
query = thought.split('RECALL ')[1]
|
||||
action = AgentRecallAction(query=query)
|
||||
output_type = "recall"
|
||||
elif thought.startswith("BROWSE"):
|
||||
url = thought.split("BROWSE ")[1]
|
||||
previous_action = ActionType.RECALL
|
||||
elif thought.startswith('BROWSE'):
|
||||
url = thought.split('BROWSE ')[1]
|
||||
action = BrowseURLAction(url=url)
|
||||
output_type = "browse"
|
||||
previous_action = ActionType.BROWSE
|
||||
elif thought.startswith('PUSH'):
|
||||
owner_repo, branch = thought.split('PUSH ')[1].split(' ')
|
||||
owner, repo = owner_repo.split('/')
|
||||
action = GitHubPushAction(owner=owner, repo=repo, branch=branch)
|
||||
previous_action = ActionType.PUSH
|
||||
else:
|
||||
action = AgentThinkAction(thought=thought)
|
||||
self._add_event(action.to_dict())
|
||||
self._add_event(action.to_memory())
|
||||
self._initialized = True
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
Modifies the current state by adding the most recent actions and observations, then prompts the model to think about it's next action to take using monologue, memory, and hint.
|
||||
|
||||
Parameters:
|
||||
- state (State): The current state based on previous steps taken
|
||||
|
||||
Returns:
|
||||
- Action: The next action to take based on LLM response
|
||||
"""
|
||||
self._initialize(state.plan.main_goal)
|
||||
for prev_action, obs in state.updated_info:
|
||||
self._add_event(prev_action.to_dict())
|
||||
self._add_event(obs.to_dict())
|
||||
self._add_event(prev_action.to_memory())
|
||||
self._add_event(obs.to_memory())
|
||||
|
||||
state.updated_info = []
|
||||
|
||||
@@ -160,13 +231,33 @@ class MonologueAgent(Agent):
|
||||
self.monologue.get_thoughts(),
|
||||
state.background_commands_obs,
|
||||
)
|
||||
messages = [{"content": prompt,"role": "user"}]
|
||||
messages = [{'content': prompt, 'role': 'user'}]
|
||||
resp = self.llm.completion(messages=messages)
|
||||
action_resp = resp['choices'][0]['message']['content']
|
||||
state.num_of_chars += len(prompt) + len(action_resp)
|
||||
action = prompts.parse_action_response(action_resp)
|
||||
self.latest_action = action
|
||||
return action
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
"""
|
||||
Uses VectorIndexRetriever to find related memories within the long term memory.
|
||||
Uses search to produce top 10 results.
|
||||
|
||||
Parameters:
|
||||
- query (str): The query that we want to find related memories for
|
||||
|
||||
Returns:
|
||||
- List[str]: A list of top 10 text results that matched the query
|
||||
"""
|
||||
if self.memory is None:
|
||||
return []
|
||||
return self.memory.search(query)
|
||||
|
||||
def reset(self) -> None:
|
||||
super().reset()
|
||||
self.monologue = Monologue()
|
||||
if config.get(ConfigType.AGENT_MEMORY_ENABLED):
|
||||
self.memory = LongTermMemory()
|
||||
else:
|
||||
self.memory = None
|
||||
|
||||
@@ -1,14 +1,38 @@
|
||||
import json
|
||||
|
||||
from json_repair import repair_json
|
||||
|
||||
|
||||
def my_encoder(obj):
|
||||
if hasattr(obj, "to_dict"):
|
||||
"""
|
||||
Encodes objects as dictionaries
|
||||
|
||||
Parameters:
|
||||
- obj (Object): An object that will be converted
|
||||
|
||||
Returns:
|
||||
- dict: If the object can be converted it is returned in dict format
|
||||
"""
|
||||
if hasattr(obj, 'to_dict'):
|
||||
return obj.to_dict()
|
||||
|
||||
|
||||
def dumps(obj, **kwargs):
|
||||
"""
|
||||
Serialize an object to str format
|
||||
"""
|
||||
|
||||
return json.dumps(obj, default=my_encoder, **kwargs)
|
||||
|
||||
def loads(s, **kwargs):
|
||||
s_repaired = repair_json(s)
|
||||
return json.loads(s_repaired, **kwargs)
|
||||
|
||||
def loads(s, **kwargs):
|
||||
"""
|
||||
Create a JSON object from str
|
||||
"""
|
||||
json_start = s.find('{')
|
||||
json_end = s.rfind('}') + 1
|
||||
if json_start == -1 or json_end == -1:
|
||||
raise ValueError('Invalid response: no JSON found')
|
||||
s = s[json_start:json_end]
|
||||
s = repair_json(s)
|
||||
return json.loads(s, **kwargs)
|
||||
|
||||
@@ -1,79 +1,166 @@
|
||||
import threading
|
||||
|
||||
import chromadb
|
||||
from llama_index.core import Document
|
||||
import llama_index.embeddings.openai.base as llama_openai
|
||||
from llama_index.core import Document, VectorStoreIndex
|
||||
from llama_index.core.retrievers import VectorIndexRetriever
|
||||
from llama_index.core import VectorStoreIndex
|
||||
from llama_index.vector_stores.chroma import ChromaVectorStore
|
||||
from openai._exceptions import APIConnectionError, InternalServerError, RateLimitError
|
||||
from tenacity import (
|
||||
retry,
|
||||
retry_if_exception_type,
|
||||
stop_after_attempt,
|
||||
wait_random_exponential,
|
||||
)
|
||||
|
||||
from opendevin import config
|
||||
from opendevin.logger import opendevin_logger as logger
|
||||
from opendevin.schema.config import ConfigType
|
||||
|
||||
from . import json
|
||||
|
||||
embedding_strategy = config.get("LLM_EMBEDDING_MODEL")
|
||||
num_retries = config.get(ConfigType.LLM_NUM_RETRIES)
|
||||
retry_min_wait = config.get(ConfigType.LLM_RETRY_MIN_WAIT)
|
||||
retry_max_wait = config.get(ConfigType.LLM_RETRY_MAX_WAIT)
|
||||
|
||||
# llama-index includes a retry decorator around openai.get_embeddings() function
|
||||
# it is initialized with hard-coded values and errors
|
||||
# this non-customizable behavior is creating issues when it's retrying faster than providers' rate limits
|
||||
# this block attempts to banish it and replace it with our decorator, to allow users to set their own limits
|
||||
|
||||
if hasattr(llama_openai.get_embeddings, '__wrapped__'):
|
||||
original_get_embeddings = llama_openai.get_embeddings.__wrapped__
|
||||
else:
|
||||
logger.warning('Cannot set custom retry limits.') # warn
|
||||
num_retries = 1
|
||||
original_get_embeddings = llama_openai.get_embeddings
|
||||
|
||||
|
||||
def attempt_on_error(retry_state):
|
||||
logger.error(f'{retry_state.outcome.exception()}. Attempt #{retry_state.attempt_number} | You can customize these settings in the configuration.', exc_info=False)
|
||||
return True
|
||||
|
||||
|
||||
@retry(reraise=True,
|
||||
stop=stop_after_attempt(num_retries),
|
||||
wait=wait_random_exponential(min=retry_min_wait, max=retry_max_wait),
|
||||
retry=retry_if_exception_type((RateLimitError, APIConnectionError, InternalServerError)),
|
||||
after=attempt_on_error)
|
||||
def wrapper_get_embeddings(*args, **kwargs):
|
||||
return original_get_embeddings(*args, **kwargs)
|
||||
|
||||
|
||||
llama_openai.get_embeddings = wrapper_get_embeddings
|
||||
|
||||
embedding_strategy = config.get(ConfigType.LLM_EMBEDDING_MODEL)
|
||||
|
||||
# TODO: More embeddings: https://docs.llamaindex.ai/en/stable/examples/embeddings/OpenAI/
|
||||
# There's probably a more programmatic way to do this.
|
||||
if embedding_strategy == "llama2":
|
||||
supported_ollama_embed_models = ['llama2', 'mxbai-embed-large', 'nomic-embed-text', 'all-minilm', 'stable-code']
|
||||
if embedding_strategy in supported_ollama_embed_models:
|
||||
from llama_index.embeddings.ollama import OllamaEmbedding
|
||||
embed_model = OllamaEmbedding(
|
||||
model_name="llama2",
|
||||
base_url=config.get_or_error("LLM_BASE_URL"),
|
||||
ollama_additional_kwargs={"mirostat": 0},
|
||||
model_name=embedding_strategy,
|
||||
base_url=config.get(ConfigType.LLM_EMBEDDING_BASE_URL, required=True),
|
||||
ollama_additional_kwargs={'mirostat': 0},
|
||||
)
|
||||
elif embedding_strategy == "openai":
|
||||
elif embedding_strategy == 'openai':
|
||||
from llama_index.embeddings.openai import OpenAIEmbedding
|
||||
embed_model = OpenAIEmbedding(
|
||||
model="text-embedding-ada-002"
|
||||
model='text-embedding-ada-002',
|
||||
api_key=config.get(ConfigType.LLM_API_KEY, required=True)
|
||||
)
|
||||
elif embedding_strategy == "azureopenai":
|
||||
from llama_index.embeddings.azure_openai import AzureOpenAIEmbedding # Need to instruct to set these env variables in documentation
|
||||
elif embedding_strategy == 'azureopenai':
|
||||
# Need to instruct to set these env variables in documentation
|
||||
from llama_index.embeddings.azure_openai import AzureOpenAIEmbedding
|
||||
embed_model = AzureOpenAIEmbedding(
|
||||
model="text-embedding-ada-002",
|
||||
deployment_name=config.get_or_error("LLM_DEPLOYMENT_NAME"),
|
||||
api_key=config.get_or_error("LLM_API_KEY"),
|
||||
azure_endpoint=config.get_or_error("LLM_BASE_URL"),
|
||||
api_version=config.get_or_error("LLM_API_VERSION"),
|
||||
model='text-embedding-ada-002',
|
||||
deployment_name=config.get(ConfigType.LLM_EMBEDDING_DEPLOYMENT_NAME, required=True),
|
||||
api_key=config.get(ConfigType.LLM_API_KEY, required=True),
|
||||
azure_endpoint=config.get(ConfigType.LLM_BASE_URL, required=True),
|
||||
api_version=config.get(ConfigType.LLM_API_VERSION, required=True),
|
||||
)
|
||||
elif (embedding_strategy is not None) and (embedding_strategy.lower() == 'none'):
|
||||
# TODO: this works but is not elegant enough. The incentive is when
|
||||
# monologue agent is not used, there is no reason we need to initialize an
|
||||
# embedding model
|
||||
embed_model = None
|
||||
else:
|
||||
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
|
||||
embed_model = HuggingFaceEmbedding(
|
||||
model_name="BAAI/bge-small-en-v1.5"
|
||||
model_name='BAAI/bge-small-en-v1.5'
|
||||
)
|
||||
|
||||
|
||||
class LongTermMemory:
|
||||
def __init__(self):
|
||||
db = chromadb.Client()
|
||||
self.collection = db.get_or_create_collection(name="memories")
|
||||
vector_store = ChromaVectorStore(chroma_collection=self.collection)
|
||||
self.index = VectorStoreIndex.from_vector_store(vector_store, embed_model=embed_model)
|
||||
self.thought_idx = 0
|
||||
sema = threading.Semaphore(value=config.get(ConfigType.AGENT_MEMORY_MAX_THREADS))
|
||||
|
||||
def add_event(self, event):
|
||||
id = ""
|
||||
t = ""
|
||||
if "action" in event:
|
||||
t = "action"
|
||||
id = event["action"]
|
||||
elif "observation" in event:
|
||||
t = "observation"
|
||||
id = event["observation"]
|
||||
|
||||
class LongTermMemory:
|
||||
"""
|
||||
Responsible for storing information that the agent can call on later for better insights and context.
|
||||
Uses chromadb to store and search through memories.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the chromadb and set up ChromaVectorStore for later use.
|
||||
"""
|
||||
db = chromadb.Client()
|
||||
self.collection = db.get_or_create_collection(name='memories')
|
||||
vector_store = ChromaVectorStore(chroma_collection=self.collection)
|
||||
self.index = VectorStoreIndex.from_vector_store(
|
||||
vector_store, embed_model=embed_model)
|
||||
self.thought_idx = 0
|
||||
self._add_threads = []
|
||||
|
||||
def add_event(self, event: dict):
|
||||
"""
|
||||
Adds a new event to the long term memory with a unique id.
|
||||
|
||||
Parameters:
|
||||
- event (dict): The new event to be added to memory
|
||||
"""
|
||||
id = ''
|
||||
t = ''
|
||||
if 'action' in event:
|
||||
t = 'action'
|
||||
id = event['action']
|
||||
elif 'observation' in event:
|
||||
t = 'observation'
|
||||
id = event['observation']
|
||||
doc = Document(
|
||||
text=json.dumps(event),
|
||||
doc_id=str(self.thought_idx),
|
||||
extra_info={
|
||||
"type": t,
|
||||
"id": id,
|
||||
"idx": self.thought_idx,
|
||||
'type': t,
|
||||
'id': id,
|
||||
'idx': self.thought_idx,
|
||||
},
|
||||
)
|
||||
self.thought_idx += 1
|
||||
self.index.insert(doc)
|
||||
logger.debug('Adding %s event to memory: %d', t, self.thought_idx)
|
||||
thread = threading.Thread(target=self._add_doc, args=(doc,))
|
||||
self._add_threads.append(thread)
|
||||
thread.start() # We add the doc concurrently so we don't have to wait ~500ms for the insert
|
||||
|
||||
def search(self, query, k=10):
|
||||
def _add_doc(self, doc):
|
||||
with sema:
|
||||
self.index.insert(doc)
|
||||
|
||||
def search(self, query: str, k: int = 10):
|
||||
"""
|
||||
Searches through the current memory using VectorIndexRetriever
|
||||
|
||||
Parameters:
|
||||
- query (str): A query to match search results to
|
||||
- k (int): Number of top results to return
|
||||
|
||||
Returns:
|
||||
- List[str]: List of top k results found in current memory
|
||||
"""
|
||||
retriever = VectorIndexRetriever(
|
||||
index=self.index,
|
||||
similarity_top_k=k,
|
||||
)
|
||||
results = retriever.retrieve(query)
|
||||
return [r.get_text() for r in results]
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +1,80 @@
|
||||
import traceback
|
||||
|
||||
import agenthub.monologue_agent.utils.json as json
|
||||
import agenthub.monologue_agent.utils.prompts as prompts
|
||||
from opendevin.exceptions import AgentEventTypeError
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.logger import opendevin_logger as logger
|
||||
|
||||
|
||||
class Monologue:
|
||||
"""
|
||||
The monologue is a representation for the agent's internal monologue where it can think.
|
||||
The agent has the capability of using this monologue for whatever it wants.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the empty list of thoughts
|
||||
"""
|
||||
self.thoughts = []
|
||||
|
||||
def add_event(self, t: dict):
|
||||
"""
|
||||
Adds an event to memory if it is a valid event.
|
||||
|
||||
Parameters:
|
||||
- t (dict): The thought that we want to add to memory
|
||||
|
||||
Raises:
|
||||
- AgentEventTypeError: If t is not a dict
|
||||
"""
|
||||
if not isinstance(t, dict):
|
||||
raise ValueError("Event must be a dictionary")
|
||||
raise AgentEventTypeError()
|
||||
self.thoughts.append(t)
|
||||
|
||||
def get_thoughts(self):
|
||||
"""
|
||||
Get the current thoughts of the agent.
|
||||
|
||||
Returns:
|
||||
- List: The list of thoughts that the agent has.
|
||||
"""
|
||||
return self.thoughts
|
||||
|
||||
def get_total_length(self):
|
||||
"""
|
||||
Gives the total number of characters in all thoughts
|
||||
|
||||
Returns:
|
||||
- Int: Total number of chars in thoughts.
|
||||
"""
|
||||
total_length = 0
|
||||
for t in self.thoughts:
|
||||
try:
|
||||
total_length += len(json.dumps(t))
|
||||
except TypeError as e:
|
||||
print(f"Error serializing thought: {e}")
|
||||
logger.error('Error serializing thought: %s', str(e), exc_info=False)
|
||||
return total_length
|
||||
|
||||
def condense(self, llm):
|
||||
def condense(self, llm: LLM):
|
||||
"""
|
||||
Attempts to condense the monologue by using the llm
|
||||
|
||||
Parameters:
|
||||
- llm (LLM): llm to be used for summarization
|
||||
|
||||
Raises:
|
||||
- Exception: the same exception as it got from the llm or processing the response
|
||||
"""
|
||||
|
||||
try:
|
||||
prompt = prompts.get_summarize_monologue_prompt(self.thoughts)
|
||||
messages = [{"content": prompt,"role": "user"}]
|
||||
messages = [{'content': prompt, 'role': 'user'}]
|
||||
resp = llm.completion(messages=messages)
|
||||
summary_resp = resp['choices'][0]['message']['content']
|
||||
self.thoughts = prompts.parse_summary_response(strip_markdown(summary_resp))
|
||||
self.thoughts = prompts.parse_summary_response(summary_resp)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise RuntimeError(f"Error condensing thoughts: {e}")
|
||||
logger.error('Error condensing thoughts: %s', str(e), exc_info=False)
|
||||
|
||||
def strip_markdown(markdown_json):
|
||||
# remove markdown code block
|
||||
return markdown_json.replace('```json\n', '').replace('```', '').strip()
|
||||
# TODO If the llm fails with ContextWindowExceededError, we can try to condense the monologue chunk by chunk
|
||||
raise
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
import re
|
||||
from json import JSONDecodeError
|
||||
from typing import List
|
||||
|
||||
from . import json
|
||||
|
||||
from opendevin import config
|
||||
from opendevin.action import (
|
||||
action_from_dict,
|
||||
Action,
|
||||
action_from_dict,
|
||||
)
|
||||
from opendevin.exceptions import LLMOutputError
|
||||
from opendevin.observation import (
|
||||
CmdOutputObservation,
|
||||
)
|
||||
from opendevin.schema.config import ConfigType
|
||||
|
||||
from . import json
|
||||
|
||||
ACTION_PROMPT = """
|
||||
You're a thoughtful robot. Your main task is this:
|
||||
|
||||
%(task)s
|
||||
|
||||
Don't expand the scope of your task--just complete it as written.
|
||||
|
||||
This is your internal monologue, in JSON format:
|
||||
```json
|
||||
|
||||
%(monologue)s
|
||||
```
|
||||
|
||||
|
||||
Your most recent thought is at the bottom of that monologue. Continue your train of thought.
|
||||
What is your next thought or action? Your response must be in JSON format.
|
||||
It must be an object, and it must contain two fields:
|
||||
What is your next single thought or action? Your response must be in JSON format.
|
||||
It must be a single object, and it must contain two fields:
|
||||
* `action`, which is one of the actions below
|
||||
* `args`, which is a map of key-value pairs, specifying the arguments for that action
|
||||
|
||||
@@ -41,6 +45,10 @@ Here are the possible actions:
|
||||
* `id` - the ID of the background command to kill
|
||||
* `browse` - opens a web page. Arguments:
|
||||
* `url` - the URL to open
|
||||
* `push` - Push a branch from the current repo to github:
|
||||
* `owner` - the owner of the repo to push to
|
||||
* `repo` - the name of the repo to push to
|
||||
* `branch` - the name of the branch to push
|
||||
* `recall` - recalls a past memory. Arguments:
|
||||
* `query` - the query to search for
|
||||
* `think` - make a plan, set a goal, or record your thoughts. Arguments:
|
||||
@@ -49,16 +57,20 @@ Here are the possible actions:
|
||||
|
||||
%(background_commands)s
|
||||
|
||||
You MUST take time to think in between read, write, run, browse, and recall actions.
|
||||
You MUST take time to think in between read, write, run, browse, push, and recall actions.
|
||||
You should never act twice in a row without thinking. But if your last several
|
||||
actions are all "think" actions, you should consider taking a different action.
|
||||
|
||||
Notes:
|
||||
* your environment is Debian Linux. You can install software with `apt`
|
||||
* your working directory will not change, even if you run `cd`. All commands will be run in the `/workspace` directory.
|
||||
* you are logged in as %(user)s, but sudo will always work without a password.
|
||||
* all non-background commands will be forcibly stopped if they remain running for over %(timeout)s seconds.
|
||||
* your environment is Debian Linux. You can install software with `sudo apt-get`, but remember to use -y.
|
||||
* don't run interactive commands, or commands that don't return (e.g. `node server.js`). You may run commands in the background (e.g. `node server.js &`)
|
||||
* don't run interactive text editors (e.g. `nano` or 'vim'), instead use the 'write' or 'read' action.
|
||||
* don't run gui applications (e.g. software IDEs (like vs code or codium), web browsers (like firefox or chromium), or other complex software packages). Use non-interactive cli applications, or special actions instead.
|
||||
* whenever an action fails, always `think` about why it may have happened before acting again.
|
||||
|
||||
What is your next thought or action? Again, you must reply with JSON, and only with JSON.
|
||||
What is your next single thought or action? Again, you must reply with JSON, and only with JSON. You must respond with exactly one 'action' object.
|
||||
|
||||
%(hint)s
|
||||
"""
|
||||
@@ -71,9 +83,9 @@ Please return a new, smaller JSON array, which summarizes the
|
||||
internal monologue. You can summarize individual thoughts, and
|
||||
you can condense related thoughts together with a description
|
||||
of their content.
|
||||
```json
|
||||
|
||||
%(monologue)s
|
||||
```
|
||||
|
||||
Make the summaries as pithy and informative as possible.
|
||||
Be specific about what happened and what was learned. The summary
|
||||
will be used as keywords for searching for the original memory.
|
||||
@@ -87,53 +99,114 @@ You can also use the same action and args from the source monologue.
|
||||
"""
|
||||
|
||||
|
||||
def get_summarize_monologue_prompt(thoughts):
|
||||
def get_summarize_monologue_prompt(thoughts: List[dict]):
|
||||
"""
|
||||
Gets the prompt for summarizing the monologue
|
||||
|
||||
Returns:
|
||||
- str: A formatted string with the current monologue within the prompt
|
||||
"""
|
||||
return MONOLOGUE_SUMMARY_PROMPT % {
|
||||
'monologue': json.dumps({'old_monologue': thoughts}, indent=2),
|
||||
}
|
||||
|
||||
|
||||
def get_request_action_prompt(
|
||||
task: str,
|
||||
thoughts: List[dict],
|
||||
background_commands_obs: List[CmdOutputObservation] = [],
|
||||
task: str,
|
||||
thoughts: List[dict],
|
||||
background_commands_obs: List[CmdOutputObservation] = [],
|
||||
):
|
||||
"""
|
||||
Gets the action prompt formatted with appropriate values.
|
||||
|
||||
Parameters:
|
||||
- task (str): The current task the agent is trying to accomplish
|
||||
- thoughts (List[dict]): The agent's current thoughts
|
||||
- background_commands_obs (List[CmdOutputObservation]): List of all observed background commands running
|
||||
|
||||
Returns:
|
||||
- str: Formatted prompt string with hint, task, monologue, and background included
|
||||
"""
|
||||
|
||||
hint = ''
|
||||
if len(thoughts) > 0:
|
||||
latest_thought = thoughts[-1]
|
||||
if "action" in latest_thought:
|
||||
if latest_thought["action"] == 'think':
|
||||
if latest_thought["args"]['thought'].startswith("OK so my task is"):
|
||||
if 'action' in latest_thought:
|
||||
if latest_thought['action'] == 'think':
|
||||
if latest_thought['args']['thought'].startswith('OK so my task is'):
|
||||
hint = "You're just getting started! What should you do first?"
|
||||
else:
|
||||
hint = "You've been thinking a lot lately. Maybe it's time to take action?"
|
||||
elif latest_thought["action"] == 'error':
|
||||
hint = "Looks like that last command failed. Maybe you need to fix it, or try something else."
|
||||
elif latest_thought['action'] == 'error':
|
||||
hint = 'Looks like that last command failed. Maybe you need to fix it, or try something else.'
|
||||
|
||||
bg_commands_message = ""
|
||||
bg_commands_message = ''
|
||||
if len(background_commands_obs) > 0:
|
||||
bg_commands_message = "The following commands are running in the background:"
|
||||
bg_commands_message = 'The following commands are running in the background:'
|
||||
for command_obs in background_commands_obs:
|
||||
bg_commands_message += f"\n`{command_obs.command_id}`: {command_obs.command}"
|
||||
bg_commands_message += "\nYou can end any process by sending a `kill` action with the numerical `id` above."
|
||||
latest_thought = thoughts[-1]
|
||||
bg_commands_message += (
|
||||
f'\n`{command_obs.command_id}`: {command_obs.command}'
|
||||
)
|
||||
bg_commands_message += '\nYou can end any process by sending a `kill` action with the numerical `id` above.'
|
||||
|
||||
user = 'opendevin' if config.get(ConfigType.RUN_AS_DEVIN) else 'root'
|
||||
|
||||
return ACTION_PROMPT % {
|
||||
'task': task,
|
||||
'monologue': json.dumps(thoughts, indent=2),
|
||||
'background_commands': bg_commands_message,
|
||||
'hint': hint,
|
||||
'user': user,
|
||||
'timeout': config.get(ConfigType.SANDBOX_TIMEOUT),
|
||||
'WORKSPACE_MOUNT_PATH_IN_SANDBOX': config.get(ConfigType.WORKSPACE_MOUNT_PATH_IN_SANDBOX),
|
||||
}
|
||||
|
||||
|
||||
def parse_action_response(response: str) -> Action:
|
||||
json_start = response.find("{")
|
||||
json_end = response.rfind("}") + 1
|
||||
response = response[json_start:json_end]
|
||||
action_dict = json.loads(response)
|
||||
"""
|
||||
Parses a string to find an action within it
|
||||
|
||||
Parameters:
|
||||
- response (str): The string to be parsed
|
||||
|
||||
Returns:
|
||||
- Action: The action that was found in the response string
|
||||
"""
|
||||
try:
|
||||
action_dict = json.loads(response)
|
||||
except JSONDecodeError:
|
||||
# Find response-looking json in the output and use the more promising one. Helps with weak llms
|
||||
response_json_matches = re.finditer(
|
||||
r"""{\s*\"action\":\s?\"(\w+)\"(?:,?|,\s*\"args\":\s?{((?:.|\s)*?)})\s*}""",
|
||||
response) # Find all response-looking strings
|
||||
|
||||
def rank(match):
|
||||
return len(match[2]) if match[1] == 'think' else 130 # Crudely rank multiple responses by length
|
||||
try:
|
||||
action_dict = json.loads(max(response_json_matches, key=rank)[0]) # Use the highest ranked response
|
||||
except (ValueError, JSONDecodeError):
|
||||
raise LLMOutputError(
|
||||
'Invalid JSON, the response must be well-formed JSON as specified in the prompt.'
|
||||
)
|
||||
except (ValueError, TypeError):
|
||||
raise LLMOutputError(
|
||||
'Invalid JSON, the response must be well-formed JSON as specified in the prompt.'
|
||||
)
|
||||
if 'content' in action_dict:
|
||||
# The LLM gets confused here. Might as well be robust
|
||||
action_dict['contents'] = action_dict.pop('content')
|
||||
return action_from_dict(action_dict)
|
||||
|
||||
|
||||
def parse_summary_response(response: str) -> List[dict]:
|
||||
"""
|
||||
Parses a summary of the monologue
|
||||
|
||||
Parameters:
|
||||
- response (str): The response string to be parsed
|
||||
|
||||
Returns:
|
||||
- List[dict]: The list of summaries output by the model
|
||||
"""
|
||||
parsed = json.loads(response)
|
||||
return parsed['new_monologue']
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from opendevin.agent import Agent
|
||||
|
||||
from .agent import PlannerAgent
|
||||
|
||||
Agent.register("PlannerAgent", PlannerAgent)
|
||||
Agent.register('PlannerAgent', PlannerAgent)
|
||||
|
||||
@@ -1,26 +1,50 @@
|
||||
from typing import List
|
||||
from .prompt import get_prompt, parse_response
|
||||
|
||||
from opendevin.action import Action, AgentFinishAction
|
||||
from opendevin.agent import Agent
|
||||
from opendevin.action import AgentFinishAction
|
||||
from opendevin.llm.llm import LLM
|
||||
from opendevin.state import State
|
||||
from opendevin.action import Action
|
||||
|
||||
from .prompt import get_prompt, parse_response
|
||||
|
||||
|
||||
class PlannerAgent(Agent):
|
||||
"""
|
||||
The planner agent utilizes a special prompting strategy to create long term plans for solving problems.
|
||||
The agent is given its previous action-observation pairs, current task, and hint based on last action taken at every step.
|
||||
"""
|
||||
|
||||
def __init__(self, llm: LLM):
|
||||
"""
|
||||
Initialize the Planner Agent with an LLM
|
||||
|
||||
Parameters:
|
||||
- llm (LLM): The llm to be used by this agent
|
||||
"""
|
||||
super().__init__(llm)
|
||||
|
||||
def step(self, state: State) -> Action:
|
||||
"""
|
||||
Checks to see if current step is completed, returns AgentFinishAction if True.
|
||||
Otherwise, creates a plan prompt and sends to model for inference, returning the result as the next action.
|
||||
|
||||
Parameters:
|
||||
- state (State): The current state given the previous actions and observations
|
||||
|
||||
Returns:
|
||||
- AgentFinishAction: If the last state was 'completed', 'verified', or 'abandoned'
|
||||
- Action: The next action to take based on llm response
|
||||
"""
|
||||
|
||||
if state.plan.task.state in ['completed', 'verified', 'abandoned']:
|
||||
return AgentFinishAction()
|
||||
prompt = get_prompt(state.plan, state.history)
|
||||
messages = [{"content": prompt, "role": "user"}]
|
||||
messages = [{'content': prompt, 'role': 'user'}]
|
||||
resp = self.llm.completion(messages=messages)
|
||||
action_resp = resp['choices'][0]['message']['content']
|
||||
state.num_of_chars += len(prompt) + len(action_resp)
|
||||
action = parse_response(action_resp)
|
||||
return action
|
||||
|
||||
def search_memory(self, query: str) -> List[str]:
|
||||
return []
|
||||
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import json
|
||||
from typing import List, Tuple, Dict, Type
|
||||
|
||||
from opendevin.controller.agent_controller import print_with_color
|
||||
from opendevin.plan import Plan
|
||||
from opendevin.action import Action, action_from_dict
|
||||
from opendevin.observation import Observation
|
||||
from typing import Dict, List, Tuple, Type
|
||||
|
||||
from opendevin.action import (
|
||||
NullAction,
|
||||
CmdRunAction,
|
||||
CmdKillAction,
|
||||
Action,
|
||||
AddTaskAction,
|
||||
AgentFinishAction,
|
||||
AgentRecallAction,
|
||||
AgentSummarizeAction,
|
||||
AgentThinkAction,
|
||||
BrowseURLAction,
|
||||
CmdKillAction,
|
||||
CmdRunAction,
|
||||
FileReadAction,
|
||||
FileWriteAction,
|
||||
AgentRecallAction,
|
||||
AgentThinkAction,
|
||||
AgentFinishAction,
|
||||
AgentSummarizeAction,
|
||||
AddTaskAction,
|
||||
ModifyTaskAction,
|
||||
NullAction,
|
||||
action_from_dict,
|
||||
)
|
||||
|
||||
from opendevin.logger import opendevin_logger as logger
|
||||
from opendevin.observation import (
|
||||
NullObservation,
|
||||
Observation,
|
||||
)
|
||||
from opendevin.plan import Plan
|
||||
from opendevin.schema import ActionType
|
||||
|
||||
ACTION_TYPE_TO_CLASS: Dict[str, Type[Action]] = {
|
||||
"run": CmdRunAction,
|
||||
"kill": CmdKillAction,
|
||||
"browse": BrowseURLAction,
|
||||
"read": FileReadAction,
|
||||
"write": FileWriteAction,
|
||||
"recall": AgentRecallAction,
|
||||
"think": AgentThinkAction,
|
||||
"summarize": AgentSummarizeAction,
|
||||
"finish": AgentFinishAction,
|
||||
"add_task": AddTaskAction,
|
||||
"modify_task": ModifyTaskAction,
|
||||
ActionType.RUN: CmdRunAction,
|
||||
ActionType.KILL: CmdKillAction,
|
||||
ActionType.BROWSE: BrowseURLAction,
|
||||
ActionType.READ: FileReadAction,
|
||||
ActionType.WRITE: FileWriteAction,
|
||||
ActionType.RECALL: AgentRecallAction,
|
||||
ActionType.THINK: AgentThinkAction,
|
||||
ActionType.SUMMARIZE: AgentSummarizeAction,
|
||||
ActionType.FINISH: AgentFinishAction,
|
||||
ActionType.ADD_TASK: AddTaskAction,
|
||||
ActionType.MODIFY_TASK: ModifyTaskAction,
|
||||
}
|
||||
|
||||
HISTORY_SIZE = 10
|
||||
@@ -53,9 +53,9 @@ You've been given the following task:
|
||||
## Plan
|
||||
As you complete this task, you're building a plan and keeping
|
||||
track of your progress. Here's a JSON representation of your plan:
|
||||
```json
|
||||
|
||||
%(plan)s
|
||||
```
|
||||
|
||||
|
||||
%(plan_status)s
|
||||
|
||||
@@ -84,9 +84,9 @@ you MUST respond with the `finish` action.
|
||||
Here is a recent history of actions you've taken in service of this plan,
|
||||
as well as observations you've made. This only includes the MOST RECENT
|
||||
ten actions--more happened before that.
|
||||
```json
|
||||
|
||||
%(history)s
|
||||
```
|
||||
|
||||
|
||||
Your most recent action is at the bottom of that history.
|
||||
|
||||
@@ -118,7 +118,7 @@ It must be an object, and it must contain two fields:
|
||||
* `modify_task` - close a task. Arguments:
|
||||
* `id` - the ID of the task to close
|
||||
* `state` - set to 'in_progress' to start the task, 'completed' to finish it, 'verified' to assert that it was successful, 'abandoned' to give up on it permanently, or `open` to stop working on it for now.
|
||||
* `finish` - if ALL of your tasks and subtasks have been verified or abanded, and you're absolutely certain that you've completed your task and have tested your work, use the finish action to stop working.
|
||||
* `finish` - if ALL of your tasks and subtasks have been verified or abandoned, and you're absolutely certain that you've completed your task and have tested your work, use the finish action to stop working.
|
||||
|
||||
You MUST take time to think in between read, write, run, browse, and recall actions.
|
||||
You should never act twice in a row without thinking. But if your last several
|
||||
@@ -129,23 +129,56 @@ What is your next thought or action? Again, you must reply with JSON, and only w
|
||||
%(hint)s
|
||||
"""
|
||||
|
||||
def get_prompt(plan: Plan, history: List[Tuple[Action, Observation]]):
|
||||
|
||||
def get_hint(latest_action_id: str) -> str:
|
||||
""" Returns action type hint based on given action_id """
|
||||
|
||||
hints = {
|
||||
'': "You haven't taken any actions yet. Start by using `ls` to check out what files you're working with.",
|
||||
ActionType.RUN: 'You should think about the command you just ran, what output it gave, and how that affects your plan.',
|
||||
ActionType.READ: 'You should think about the file you just read, what you learned from it, and how that affects your plan.',
|
||||
ActionType.WRITE: 'You just changed a file. You should think about how it affects your plan.',
|
||||
ActionType.BROWSE: 'You should think about the page you just visited, and what you learned from it.',
|
||||
ActionType.THINK: "Look at your last thought in the history above. What does it suggest? Don't think anymore--take action.",
|
||||
ActionType.RECALL: 'You should think about the information you just recalled, and how it should affect your plan.',
|
||||
ActionType.ADD_TASK: 'You should think about the next action to take.',
|
||||
ActionType.MODIFY_TASK: 'You should think about the next action to take.',
|
||||
ActionType.SUMMARIZE: '',
|
||||
ActionType.FINISH: '',
|
||||
}
|
||||
return hints.get(latest_action_id, '')
|
||||
|
||||
|
||||
def get_prompt(plan: Plan, history: List[Tuple[Action, Observation]]) -> str:
|
||||
"""
|
||||
Gets the prompt for the planner agent.
|
||||
Formatted with the most recent action-observation pairs, current task, and hint based on last action
|
||||
|
||||
Parameters:
|
||||
- plan (Plan): The original plan outlined by the user with LLM defined tasks
|
||||
- history (List[Tuple[Action, Observation]]): List of corresponding action-observation pairs
|
||||
|
||||
Returns:
|
||||
- str: The formatted string prompt with historical values
|
||||
"""
|
||||
|
||||
plan_str = json.dumps(plan.task.to_dict(), indent=2)
|
||||
sub_history = history[-HISTORY_SIZE:]
|
||||
history_dicts = []
|
||||
latest_action: Action = NullAction()
|
||||
for action, observation in sub_history:
|
||||
if not isinstance(action, NullAction):
|
||||
history_dicts.append(action.to_dict())
|
||||
history_dicts.append(action.to_memory())
|
||||
latest_action = action
|
||||
if not isinstance(observation, NullObservation):
|
||||
observation_dict = observation.to_dict()
|
||||
if "extras" in observation_dict and "screenshot" in observation_dict["extras"]:
|
||||
del observation_dict["extras"]["screenshot"]
|
||||
observation_dict = observation.to_memory()
|
||||
if (
|
||||
'extras' in observation_dict
|
||||
and 'screenshot' in observation_dict['extras']
|
||||
):
|
||||
del observation_dict['extras']['screenshot']
|
||||
history_dicts.append(observation_dict)
|
||||
history_str = json.dumps(history_dicts, indent=2)
|
||||
|
||||
hint = ""
|
||||
current_task = plan.get_current_task()
|
||||
if current_task is not None:
|
||||
plan_status = f"You're currently working on this task:\n{current_task.goal}."
|
||||
@@ -153,35 +186,8 @@ def get_prompt(plan: Plan, history: List[Tuple[Action, Observation]]):
|
||||
plan_status += "\nIf it's not achievable AND verifiable with a SINGLE action, you MUST break it down into subtasks NOW."
|
||||
else:
|
||||
plan_status = "You're not currently working on any tasks. Your next action MUST be to mark a task as in_progress."
|
||||
hint = plan_status
|
||||
|
||||
latest_action_id = latest_action.to_dict()['action']
|
||||
|
||||
if current_task is not None:
|
||||
if latest_action_id == "":
|
||||
hint = "You haven't taken any actions yet. Start by using `ls` to check out what files you're working with."
|
||||
elif latest_action_id == "run":
|
||||
hint = "You should think about the command you just ran, what output it gave, and how that affects your plan."
|
||||
elif latest_action_id == "read":
|
||||
hint = "You should think about the file you just read, what you learned from it, and how that affects your plan."
|
||||
elif latest_action_id == "write":
|
||||
hint = "You just changed a file. You should think about how it affects your plan."
|
||||
elif latest_action_id == "browse":
|
||||
hint = "You should think about the page you just visited, and what you learned from it."
|
||||
elif latest_action_id == "think":
|
||||
hint = "Look at your last thought in the history above. What does it suggest? Don't think anymore--take action."
|
||||
elif latest_action_id == "recall":
|
||||
hint = "You should think about the information you just recalled, and how it should affect your plan."
|
||||
elif latest_action_id == "add_task":
|
||||
hint = "You should think about the next action to take."
|
||||
elif latest_action_id == "modify_task":
|
||||
hint = "You should think about the next action to take."
|
||||
elif latest_action_id == "summarize":
|
||||
hint = ""
|
||||
elif latest_action_id == "finish":
|
||||
hint = ""
|
||||
|
||||
print_with_color("HINT:\n" + hint, "INFO")
|
||||
hint = get_hint(latest_action.to_dict()['action'])
|
||||
logger.info('HINT:\n' + hint, extra={'msg_type': 'INFO'})
|
||||
return prompt % {
|
||||
'task': plan.main_goal,
|
||||
'plan': plan_str,
|
||||
@@ -190,9 +196,19 @@ def get_prompt(plan: Plan, history: List[Tuple[Action, Observation]]):
|
||||
'plan_status': plan_status,
|
||||
}
|
||||
|
||||
|
||||
def parse_response(response: str) -> Action:
|
||||
json_start = response.find("{")
|
||||
json_end = response.rfind("}") + 1
|
||||
"""
|
||||
Parses the model output to find a valid action to take
|
||||
|
||||
Parameters:
|
||||
- response (str): A response from the model that potentially contains an Action.
|
||||
|
||||
Returns:
|
||||
- Action: A valid next action to perform from model output
|
||||
"""
|
||||
json_start = response.find('{')
|
||||
json_end = response.rfind('}') + 1
|
||||
response = response[json_start:json_end]
|
||||
action_dict = json.loads(response)
|
||||
if 'contents' in action_dict:
|
||||
@@ -200,4 +216,3 @@ def parse_response(response: str) -> Action:
|
||||
action_dict['content'] = action_dict.pop('contents')
|
||||
action = action_from_dict(action_dict)
|
||||
return action
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# This is a template. Run `cp config.toml.template config.toml` to use it.
|
||||
|
||||
LLM_API_KEY="<YOUR OPENAI API KEY>"
|
||||
WORKSPACE_DIR="./workspace"
|
||||
13
containers/README.md
Normal file
13
containers/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Docker Containers
|
||||
Each folder here contains a Dockerfile, and a config.sh describing how to build
|
||||
the image and where to push it. These are images are built and pushed in GitHub Actions
|
||||
by the `ghcr.yml` workflow.
|
||||
|
||||
## Building Manually
|
||||
|
||||
```
|
||||
docker build -f containers/app/Dockerfile -t opendevin .
|
||||
docker build -f containers/sandbox/Dockerfile -t sandbox .
|
||||
docker build -f containers/evaluation/Dockerfile -t evaluation evaluation/SWE-bench/
|
||||
|
||||
```
|
||||
58
containers/app/Dockerfile
Normal file
58
containers/app/Dockerfile
Normal file
@@ -0,0 +1,58 @@
|
||||
ARG OPEN_DEVIN_BUILD_VERSION=dev
|
||||
FROM node:21.7.2-bookworm-slim as frontend-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ./frontend/package.json frontend/package-lock.json ./
|
||||
RUN npm install -g npm@10.5.1
|
||||
RUN npm install
|
||||
|
||||
COPY ./frontend ./
|
||||
RUN npm run make-i18n && npm run build
|
||||
|
||||
FROM python:3.12-slim as backend-builder
|
||||
|
||||
WORKDIR /app
|
||||
ENV PYTHONPATH '/app'
|
||||
|
||||
ENV POETRY_NO_INTERACTION=1 \
|
||||
POETRY_VIRTUALENVS_IN_PROJECT=1 \
|
||||
POETRY_VIRTUALENVS_CREATE=1 \
|
||||
POETRY_CACHE_DIR=/tmp/poetry_cache
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y curl make git build-essential \
|
||||
&& python3 -m pip install poetry==1.8.2 --break-system-packages
|
||||
|
||||
COPY ./pyproject.toml ./poetry.lock ./
|
||||
RUN touch README.md
|
||||
RUN poetry install --without evaluation --no-root && rm -rf $POETRY_CACHE_DIR
|
||||
|
||||
FROM python:3.12-slim as runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV RUN_AS_DEVIN=false
|
||||
ENV USE_HOST_NETWORK=false
|
||||
ENV SSH_HOSTNAME=host.docker.internal
|
||||
ENV WORKSPACE_BASE=/opt/workspace_base
|
||||
ENV OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION
|
||||
RUN mkdir -p $WORKSPACE_BASE
|
||||
|
||||
RUN apt-get update -y \
|
||||
&& apt-get install -y curl ssh
|
||||
|
||||
ENV VIRTUAL_ENV=/app/.venv \
|
||||
PATH="/app/.venv/bin:$PATH" \
|
||||
PYTHONPATH='/app'
|
||||
|
||||
COPY --from=backend-builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
|
||||
|
||||
COPY ./opendevin ./opendevin
|
||||
COPY ./agenthub ./agenthub
|
||||
RUN python opendevin/download.py # No-op to download assets
|
||||
RUN playwright install --with-deps chromium
|
||||
|
||||
COPY --from=frontend-builder /app/dist ./frontend/dist
|
||||
|
||||
CMD ["uvicorn", "opendevin.server.listen:app", "--host", "0.0.0.0", "--port", "3000"]
|
||||
4
containers/app/config.sh
Normal file
4
containers/app/config.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
DOCKER_REGISTRY=ghcr.io
|
||||
DOCKER_ORG=opendevin
|
||||
DOCKER_IMAGE=opendevin
|
||||
DOCKER_BASE_DIR="."
|
||||
67
containers/build.sh
Executable file
67
containers/build.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
image_name=$1
|
||||
org_name=$2
|
||||
push=0
|
||||
if [[ $3 == "--push" ]]; then
|
||||
push=1
|
||||
fi
|
||||
|
||||
echo -e "Building: $image_name"
|
||||
tags=()
|
||||
|
||||
OPEN_DEVIN_BUILD_VERSION="dev"
|
||||
|
||||
cache_tag_base="buildcache"
|
||||
cache_tag="$cache_tag_base"
|
||||
|
||||
if [[ -n $GITHUB_REF_NAME ]]; then
|
||||
# check if ref name is a version number
|
||||
if [[ $GITHUB_REF_NAME =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
major_version=$(echo $GITHUB_REF_NAME | cut -d. -f1)
|
||||
minor_version=$(echo $GITHUB_REF_NAME | cut -d. -f1,2)
|
||||
tags+=($major_version $minor_version)
|
||||
fi
|
||||
sanitized=$(echo $GITHUB_REF_NAME | sed 's/[^a-zA-Z0-9.-]\+/-/g')
|
||||
OPEN_DEVIN_BUILD_VERSION=$sanitized
|
||||
cache_tag+="-${sanitized}"
|
||||
tags+=($sanitized)
|
||||
fi
|
||||
echo "Tags: ${tags[@]}"
|
||||
|
||||
dir=./containers/$image_name
|
||||
if [ ! -f $dir/Dockerfile ]; then
|
||||
echo "No Dockerfile found"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f $dir/config.sh ]; then
|
||||
echo "No config.sh found for Dockerfile"
|
||||
exit 1
|
||||
fi
|
||||
source $dir/config.sh
|
||||
if [[ -n "$org_name" ]]; then
|
||||
DOCKER_ORG="$org_name"
|
||||
fi
|
||||
DOCKER_REPOSITORY=$DOCKER_REGISTRY/$DOCKER_ORG/$DOCKER_IMAGE
|
||||
DOCKER_REPOSITORY=${DOCKER_REPOSITORY,,} # lowercase
|
||||
echo "Repo: $DOCKER_REPOSITORY"
|
||||
echo "Base dir: $DOCKER_BASE_DIR"
|
||||
|
||||
args=""
|
||||
for tag in ${tags[@]}; do
|
||||
args+=" -t $DOCKER_REPOSITORY:$tag"
|
||||
done
|
||||
if [[ $push -eq 1 ]]; then
|
||||
args+=" --push"
|
||||
args+=" --cache-to=type=registry,ref=$DOCKER_REPOSITORY:$cache_tag,mode=max"
|
||||
fi
|
||||
|
||||
docker buildx build \
|
||||
$args \
|
||||
--build-arg OPEN_DEVIN_BUILD_VERSION=$OPEN_DEVIN_BUILD_VERSION \
|
||||
--cache-from=type=registry,ref=$DOCKER_REPOSITORY:$cache_tag \
|
||||
--cache-from=type=registry,ref=$DOCKER_REPOSITORY:$cache_tag_base-main \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--provenance=false \
|
||||
-f $dir/Dockerfile $DOCKER_BASE_DIR
|
||||
@@ -14,4 +14,6 @@ RUN apt-get update && apt-get install -y \
|
||||
python3-venv \
|
||||
python3-dev \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
sudo \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
15
containers/e2b-sandbox/README.md
Normal file
15
containers/e2b-sandbox/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# How to build custom E2B sandbox for OpenDevin
|
||||
|
||||
[E2B](https://e2b.dev) is an [open-source](https://github.com/e2b-dev/e2b) secure cloud environment (sandbox) made for running AI-generated code and agents. E2B offers [Python](https://pypi.org/project/e2b/) and [JS/TS](https://www.npmjs.com/package/e2b) SDK to spawn and control these sandboxes.
|
||||
|
||||
|
||||
1. Install the CLI with NPM.
|
||||
```sh
|
||||
npm install -g @e2b/cli@latest
|
||||
```
|
||||
Full CLI API is [here](https://e2b.dev/docs/cli/installation).
|
||||
|
||||
1. Build the sandbox
|
||||
```sh
|
||||
e2b template build --dockerfile ./Dockerfile --name "open-devin"
|
||||
```
|
||||
14
containers/e2b-sandbox/e2b.toml
Normal file
14
containers/e2b-sandbox/e2b.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
# This is a config for E2B sandbox template.
|
||||
# You can use 'template_id' (785n69crgahmz0lkdw9h) or 'template_name (open-devin) from this config to spawn a sandbox:
|
||||
|
||||
# Python SDK
|
||||
# from e2b import Sandbox
|
||||
# sandbox = Sandbox(template='open-devin')
|
||||
|
||||
# JS SDK
|
||||
# import { Sandbox } from 'e2b'
|
||||
# const sandbox = await Sandbox.create({ template: 'open-devin' })
|
||||
|
||||
dockerfile = "Dockerfile"
|
||||
template_name = "open-devin"
|
||||
template_id = "785n69crgahmz0lkdw9h"
|
||||
@@ -29,6 +29,10 @@ RUN conda --version
|
||||
COPY environment.yml .
|
||||
RUN conda env create -f environment.yml
|
||||
|
||||
# Add commands
|
||||
COPY ./commands.sh .
|
||||
RUN . ./commands.sh
|
||||
|
||||
# Some missing packages
|
||||
RUN pip install datasets python-dotenv gitpython
|
||||
|
||||
4
containers/evaluation/config.sh
Normal file
4
containers/evaluation/config.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
DOCKER_REGISTRY=ghcr.io
|
||||
DOCKER_ORG=opendevin
|
||||
DOCKER_IMAGE=eval-swe-bench
|
||||
DOCKER_BASE_DIR=evaluation/SWE-bench
|
||||
29
containers/sandbox/Dockerfile
Normal file
29
containers/sandbox/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
FROM ubuntu:22.04
|
||||
|
||||
# install basic packages
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
wget \
|
||||
git \
|
||||
vim \
|
||||
nano \
|
||||
unzip \
|
||||
zip \
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
python3-dev \
|
||||
build-essential \
|
||||
openssh-server \
|
||||
sudo \
|
||||
bash \
|
||||
gcc \
|
||||
jq \
|
||||
g++ \
|
||||
make \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN mkdir -p -m0755 /var/run/sshd
|
||||
|
||||
# symlink python3 to python
|
||||
RUN ln -s /usr/bin/python3 /usr/bin/python
|
||||
4
containers/sandbox/config.sh
Normal file
4
containers/sandbox/config.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
DOCKER_REGISTRY=ghcr.io
|
||||
DOCKER_ORG=opendevin
|
||||
DOCKER_IMAGE=sandbox
|
||||
DOCKER_BASE_DIR="."
|
||||
@@ -1,39 +1,48 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
exclude: docs/modules/python
|
||||
- id: end-of-file-fixer
|
||||
exclude: docs/modules/python
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
- id: double-quote-string-fixer
|
||||
- id: requirements-txt-fixer
|
||||
|
||||
- repo: https://github.com/hhatto/autopep8
|
||||
rev: v2.1.0
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 1.7.0
|
||||
hooks:
|
||||
- id: autopep8
|
||||
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.5.0
|
||||
- id: pyproject-fmt
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.16
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.3.3
|
||||
# Ruff version.
|
||||
rev: v0.3.7
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
entry: ruff check --config dev_config/python/ruff.toml opendevin/ agenthub/
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
types_or: [python, pyi, jupyter]
|
||||
args: [--fix]
|
||||
# Run the formatter.
|
||||
- id: ruff-format
|
||||
entry: ruff check --config dev_config/python/ruff.toml opendevin/ agenthub/
|
||||
types_or: [python, pyi, jupyter]
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.9.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies: [types-requests, types-setuptools]
|
||||
additional_dependencies:
|
||||
[types-requests, types-setuptools, types-pyyaml, types-toml]
|
||||
entry: mypy --config-file dev_config/python/mypy.ini opendevin/ agenthub/
|
||||
always_run: true
|
||||
pass_filenames: false
|
||||
|
||||
- repo: https://github.com/NiklasRosenstein/pydoc-markdown
|
||||
rev: develop
|
||||
hooks:
|
||||
- id: pydoc-markdown
|
||||
|
||||
@@ -8,4 +8,4 @@ warn_redundant_casts = True
|
||||
no_implicit_optional = True
|
||||
strict_optional = True
|
||||
|
||||
exclude = agenthub/monologue_agent/regression
|
||||
exclude = agenthub/monologue_agent/regression
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
exclude = [
|
||||
"agenthub/monologue_agent/regression/",
|
||||
]
|
||||
]
|
||||
|
||||
[lint]
|
||||
select = [
|
||||
"E",
|
||||
"W",
|
||||
"F",
|
||||
"I",
|
||||
"Q",
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E501",
|
||||
]
|
||||
|
||||
flake8-quotes = {inline-quotes = "single"}
|
||||
|
||||
[format]
|
||||
quote-style = "single"
|
||||
|
||||
20
docs/.gitignore
vendored
Normal file
20
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
41
docs/README.md
Normal file
41
docs/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Website
|
||||
|
||||
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
||||
|
||||
### Installation
|
||||
|
||||
```
|
||||
$ yarn
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
```
|
||||
$ yarn start
|
||||
```
|
||||
|
||||
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
||||
|
||||
### Build
|
||||
|
||||
```
|
||||
$ yarn build
|
||||
```
|
||||
|
||||
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
||||
|
||||
### Deployment
|
||||
|
||||
Using SSH:
|
||||
|
||||
```
|
||||
$ USE_SSH=true yarn deploy
|
||||
```
|
||||
|
||||
Not using SSH:
|
||||
|
||||
```
|
||||
$ GIT_USER=<Your GitHub username> yarn deploy
|
||||
```
|
||||
|
||||
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
||||
@@ -1,14 +0,0 @@
|
||||
|
||||
# System Architecture Overview
|
||||
|
||||
This is a high-level overview of the system architecture. The system is divided into two main components: the frontend and the backend. The frontend is responsible for handling user interactions and displaying the results. The backend is responsible for handling the business logic and executing the agents.
|
||||
|
||||

|
||||
|
||||
This Overview is simplified to show the main components and their interactions. For a more detailed view of the backend architecture, see the [Backend Architecture](#backend-architecture) section.
|
||||
|
||||
# Backend Architecture
|
||||
|
||||
*__Disclaimer__: The backend architecture is a work in progress and is subject to change. The following diagram shows the current architecture of the backend based on the commit that is shown in the footer of the diagram.*
|
||||
|
||||

|
||||
@@ -1,23 +0,0 @@
|
||||
# Process for updating the backend architecture diagram
|
||||
The generation of the backend architecture diagram is partially automated. The diagram is generated from the type hints in the code using the py2puml tool. The diagram is then manually reviewed, adjusted and exported to PNG and SVG.
|
||||
|
||||
## Prerequisites
|
||||
- Running python environment in which opendevin is executable (according to the instructions in the README.md file in the root of the repository)
|
||||
- [py2puml](https://github.com/lucsorel/py2puml) installed
|
||||
|
||||
## Steps
|
||||
1. Autogenerate the diagram by running the following command from the root of the repository:
|
||||
```py2puml opendevin opendevin > docs/architecture/backend_architecture.puml```
|
||||
|
||||
2. Open the generated file in a PlantUML editor, e.g. Visual Studio Code with the PlantUML extension or [PlantText](https://www.planttext.com/)
|
||||
|
||||
3. Review the generated PUML and make all necessary adjustments to the diagram (add missing parts, fix mistakes, improve positioning).
|
||||
*py2puml creates the diagram based on the type hints in the code, so missing or incorrect type hints may result in an incomplete or incorrect diagram.*
|
||||
|
||||
4. Review the diff between the new and the previous diagram and manually check if the changes are correct.
|
||||
*Make sure not to remove parts that were manually added to the diagram in the past and are still relevant.*
|
||||
|
||||
4. Add the commit hash of the commit that was used to generat the diagram to the diagram footer.
|
||||
|
||||
5. Export the diagram as PNG and SVG files and replace the existing diagrams in the `docs/architecture` directory. This can be done with (e.g. [PlantText](https://www.planttext.com/))
|
||||
|
||||
3
docs/babel.config.js
Normal file
3
docs/babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
|
||||
};
|
||||
128
docs/docusaurus.config.ts
Normal file
128
docs/docusaurus.config.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import type * as Preset from "@docusaurus/preset-classic";
|
||||
import type { Config } from "@docusaurus/types";
|
||||
import { themes as prismThemes } from "prism-react-renderer";
|
||||
|
||||
const config: Config = {
|
||||
title: "OpenDevin",
|
||||
tagline: "Code Less, Make More",
|
||||
favicon: "img/logo.png",
|
||||
|
||||
// Set the production url of your site here
|
||||
url: "https://OpenDevin.github.io",
|
||||
baseUrl: "/OpenDevin/",
|
||||
|
||||
// GitHub pages deployment config.
|
||||
organizationName: "OpenDevin",
|
||||
projectName: "OpenDevin",
|
||||
trailingSlash: false,
|
||||
|
||||
onBrokenLinks: "throw",
|
||||
onBrokenMarkdownLinks: "warn",
|
||||
|
||||
// Even if you don't use internationalization, you can use this field to set
|
||||
// useful metadata like html lang. For example, if your site is Chinese, you
|
||||
// may want to replace "en" with "zh-Hans".
|
||||
i18n: {
|
||||
defaultLocale: "en",
|
||||
locales: ["en"],
|
||||
},
|
||||
|
||||
presets: [
|
||||
[
|
||||
"classic",
|
||||
{
|
||||
docs: {
|
||||
path: "modules",
|
||||
routeBasePath: "modules",
|
||||
sidebarPath: "./sidebars.ts",
|
||||
exclude: [
|
||||
// '**/_*.{js,jsx,ts,tsx,md,mdx}',
|
||||
// '**/_*/**',
|
||||
"**/*.test.{js,jsx,ts,tsx}",
|
||||
"**/__tests__/**",
|
||||
],
|
||||
},
|
||||
blog: {
|
||||
showReadingTime: true,
|
||||
},
|
||||
theme: {
|
||||
customCss: "./src/css/custom.css",
|
||||
},
|
||||
} satisfies Preset.Options,
|
||||
],
|
||||
],
|
||||
|
||||
themeConfig: {
|
||||
image: "img/docusaurus.png",
|
||||
navbar: {
|
||||
title: "OpenDevin",
|
||||
logo: {
|
||||
alt: "OpenDevin",
|
||||
src: "img/logo.png",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
type: "docSidebar",
|
||||
sidebarId: "docsSidebar",
|
||||
position: "left",
|
||||
label: "Docs",
|
||||
},
|
||||
{
|
||||
type: "docSidebar",
|
||||
sidebarId: "apiSidebar",
|
||||
position: "left",
|
||||
label: "Codebase",
|
||||
},
|
||||
{ to: "/faq", label: "FAQ", position: "left" },
|
||||
{
|
||||
href: "https://github.com/OpenDevin/OpenDevin",
|
||||
label: "GitHub",
|
||||
position: "right",
|
||||
},
|
||||
],
|
||||
},
|
||||
footer: {
|
||||
style: "dark",
|
||||
links: [
|
||||
{
|
||||
title: "OpenDevin",
|
||||
items: [
|
||||
{
|
||||
label: "Docs",
|
||||
to: "/modules/usage/intro",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Community",
|
||||
items: [
|
||||
{
|
||||
label: "Slack",
|
||||
href: "https://join.slack.com/t/opendevin/shared_invite/zt-2etftj1dd-X1fDL2PYIVpsmJZkqEYANw",
|
||||
},
|
||||
{
|
||||
label: "Discord",
|
||||
href: "https://discord.gg/mBuDGRzzES",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "More",
|
||||
items: [
|
||||
{
|
||||
label: "GitHub",
|
||||
href: "https://github.com/OpenDevin/OpenDevin",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
copyright: `Copyright © ${new Date().getFullYear()} OpenDevin`,
|
||||
},
|
||||
prism: {
|
||||
theme: prismThemes.oneLight,
|
||||
darkTheme: prismThemes.oneDark,
|
||||
},
|
||||
} satisfies Preset.ThemeConfig,
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,111 +0,0 @@
|
||||
# Local LLM Guide with Ollama server
|
||||
|
||||
## 0. Install Ollama:
|
||||
run the following command in a conda env with CUDA etc.
|
||||
|
||||
Linux:
|
||||
```
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
```
|
||||
Windows or macOS:
|
||||
|
||||
- Download from [here](https://ollama.com/download/)
|
||||
|
||||
## 1. Install Models:
|
||||
Ollama model names can be found [here](https://ollama.com/library) (See example below)
|
||||
|
||||

|
||||
|
||||
Once you have found the model you want to use copy the command and run it in your conda env.
|
||||
|
||||
Example of llama2 q4 quantized:
|
||||
```
|
||||
conda activate <env_name>
|
||||
ollama run llama2:13b-chat-q4_K_M
|
||||
```
|
||||
|
||||
you can check which models you have downloaded like this:
|
||||
```
|
||||
~$ ollama list
|
||||
NAME ID SIZE MODIFIED
|
||||
llama2:latest 78e26419b446 3.8 GB 6 weeks ago
|
||||
mistral:7b-instruct-v0.2-q4_K_M eb14864c7427 4.4 GB 2 weeks ago
|
||||
starcoder2:latest f67ae0f64584 1.7 GB 19 hours ago
|
||||
```
|
||||
## 2. Run Ollama in CLI:
|
||||
This command starts up the ollama server that is on port `11434`
|
||||
|
||||
This will show the requests in CLI
|
||||
```
|
||||
conda activate <env_name>
|
||||
ollama serve
|
||||
```
|
||||
or
|
||||
|
||||
This will run with no output in the background
|
||||
```
|
||||
sudo systemctl start ollama
|
||||
```
|
||||
|
||||
If you see something like this:
|
||||
```
|
||||
Error: listen tcp 127.0.0.1:11434: bind: address already in use
|
||||
```
|
||||
This is not an error it just means the server is already running
|
||||
|
||||
To stop the server use:
|
||||
```
|
||||
sudo systemctl stop ollama
|
||||
```
|
||||
|
||||
For more info go [here](https://github.com/ollama/ollama/blob/main/docs/faq.md)
|
||||
|
||||
## 3. Follow the default installation of OpenDevin:
|
||||
```
|
||||
git clone git@github.com:OpenDevin/OpenDevin.git
|
||||
```
|
||||
or
|
||||
```
|
||||
git clone git@github.com:<YOUR-USERNAME>/OpenDevin.git
|
||||
```
|
||||
|
||||
then
|
||||
```
|
||||
cd OpenDevin
|
||||
```
|
||||
|
||||
## 4. Run setup commands:
|
||||
```
|
||||
make build
|
||||
make setup-config
|
||||
```
|
||||
|
||||
## 5. Modify config file:
|
||||
|
||||
- After running `make setup-config` you will see a generated file `OpenDevin/config.toml`.
|
||||
- Open this file and modify it to your needs based on this template:
|
||||
|
||||
```
|
||||
LLM_API_KEY="ollama"
|
||||
LLM_MODEL="ollama/<model_name>"
|
||||
LLM_EMBEDDING_MODEL="local"
|
||||
LLM_BASE_URL="http://localhost:<port_number>"
|
||||
WORKSPACE_DIR="./workspace"
|
||||
```
|
||||
Notes:
|
||||
- The API key should be set to `"ollama"`
|
||||
- The base url needs to be `localhost`
|
||||
- By default ollama port is `11434` unless you set it
|
||||
- `model_name` needs to be the entire model name
|
||||
- Example: `LLM_MODEL="ollama/llama2:13b-chat-q4_K_M"`
|
||||
|
||||
## 6. Start OpenDevin:
|
||||
|
||||
At this point everything should be set up and working properly.
|
||||
1. Start by running the ollama server using the method outlined above
|
||||
2. Run `make build` in your terminal `~/OpenDevin/`
|
||||
3. Run `make run` in your terminal
|
||||
4. If that fails try running the server and front end in sepparate terminals:
|
||||
- In the first terminal `make start-backend`
|
||||
- In the second terminal `make start-frontend`
|
||||
5. you should now be able to connect to `http://localhost:3001/` with your local model running!
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 39 KiB |
34
docs/modules/python/agenthub/SWE_agent/agent.md
Normal file
34
docs/modules/python/agenthub/SWE_agent/agent.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
sidebar_label: agent
|
||||
title: agenthub.SWE_agent.agent
|
||||
---
|
||||
|
||||
## SWEAgent Objects
|
||||
|
||||
```python
|
||||
class SWEAgent(Agent)
|
||||
```
|
||||
|
||||
An attempt to recreate swe_agent with output parsing, prompting style, and Application Computer Interface (ACI).
|
||||
|
||||
SWE-agent includes ACI functions like 'goto', 'search_for', 'edit', 'scroll', 'run'
|
||||
|
||||
#### step
|
||||
|
||||
```python
|
||||
def step(state: State) -> Action
|
||||
```
|
||||
|
||||
SWE-Agent step:
|
||||
1. Get context - past actions, custom commands, current step
|
||||
2. Perform think-act - prompt model for action and reasoning
|
||||
3. Catch errors - ensure model takes action (5 attempts max)
|
||||
|
||||
#### reset
|
||||
|
||||
```python
|
||||
def reset() -> None
|
||||
```
|
||||
|
||||
Used to reset the agent
|
||||
|
||||
34
docs/modules/python/agenthub/SWE_agent/parser.md
Normal file
34
docs/modules/python/agenthub/SWE_agent/parser.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
sidebar_label: parser
|
||||
title: agenthub.SWE_agent.parser
|
||||
---
|
||||
|
||||
#### get\_action\_from\_string
|
||||
|
||||
```python
|
||||
def get_action_from_string(command_string: str,
|
||||
path: str,
|
||||
line: int,
|
||||
thoughts: str = '') -> Action | None
|
||||
```
|
||||
|
||||
Parses the command string to find which command the agent wants to run
|
||||
Converts the command into a proper Action and returns
|
||||
|
||||
#### parse\_command
|
||||
|
||||
```python
|
||||
def parse_command(input_str: str, path: str, line: int)
|
||||
```
|
||||
|
||||
Parses a given string and separates the command (enclosed in triple backticks) from any accompanying text.
|
||||
|
||||
**Arguments**:
|
||||
|
||||
- `input_str` _str_ - The input string to be parsed.
|
||||
|
||||
|
||||
**Returns**:
|
||||
|
||||
- `tuple` - A tuple containing the command and the accompanying text (if any).
|
||||
|
||||
50
docs/modules/python/agenthub/codeact_agent/codeact_agent.md
Normal file
50
docs/modules/python/agenthub/codeact_agent/codeact_agent.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
sidebar_label: codeact_agent
|
||||
title: agenthub.codeact_agent.codeact_agent
|
||||
---
|
||||
|
||||
## CodeActAgent Objects
|
||||
|
||||
```python
|
||||
class CodeActAgent(Agent)
|
||||
```
|
||||
|
||||
The Code Act Agent is a minimalist agent.
|
||||
The agent works by passing the model a list of action-observation pairs and prompting the model to take the next step.
|
||||
|
||||
#### \_\_init\_\_
|
||||
|
||||
```python
|
||||
def __init__(llm: LLM) -> None
|
||||
```
|
||||
|
||||
Initializes a new instance of the CodeActAgent class.
|
||||
|
||||
**Arguments**:
|
||||
|
||||
- llm (LLM): The llm to be used by this agent
|
||||
|
||||
#### step
|
||||
|
||||
```python
|
||||
def step(state: State) -> Action
|
||||
```
|
||||
|
||||
Performs one step using the Code Act Agent.
|
||||
This includes gathering info on previous steps and prompting the model to make a command to execute.
|
||||
|
||||
**Arguments**:
|
||||
|
||||
- state (State): used to get updated info and background commands
|
||||
|
||||
|
||||
**Returns**:
|
||||
|
||||
- CmdRunAction(command) - command action to run
|
||||
- AgentEchoAction(content=INVALID_INPUT_MESSAGE) - invalid command output
|
||||
|
||||
|
||||
**Raises**:
|
||||
|
||||
- NotImplementedError - for actions other than CmdOutputObservation or AgentMessageObservation
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user