jensinjames
commited on
Commit
•
66214f3
1
Parent(s):
ed21180
Upload 9 files
Browse files- __init__.py +0 -0
- ai.py +63 -0
- chat_to_files.py +42 -0
- ci.yaml +32 -0
- db.py +43 -0
- main.py +65 -0
- pre-commit.yaml +14 -0
- release.yaml +52 -0
- steps.py +278 -0
__init__.py
ADDED
File without changes
|
ai.py
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from __future__ import annotations
|
2 |
+
|
3 |
+
import logging
|
4 |
+
|
5 |
+
import openai
|
6 |
+
|
7 |
+
logger = logging.getLogger(__name__)
|
8 |
+
|
9 |
+
|
10 |
+
class AI:
|
11 |
+
def __init__(self, model="gpt-4", temperature=0.1):
|
12 |
+
self.temperature = temperature
|
13 |
+
|
14 |
+
try:
|
15 |
+
openai.Model.retrieve(model)
|
16 |
+
self.model = model
|
17 |
+
except openai.InvalidRequestError:
|
18 |
+
print(
|
19 |
+
f"Model {model} not available for provided API key. Reverting "
|
20 |
+
"to gpt-3.5-turbo. Sign up for the GPT-4 wait list here: "
|
21 |
+
"https://openai.com/waitlist/gpt-4-api"
|
22 |
+
)
|
23 |
+
self.model = "gpt-3.5-turbo"
|
24 |
+
|
25 |
+
def start(self, system, user):
|
26 |
+
messages = [
|
27 |
+
{"role": "system", "content": system},
|
28 |
+
{"role": "user", "content": user},
|
29 |
+
]
|
30 |
+
|
31 |
+
return self.next(messages)
|
32 |
+
|
33 |
+
def fsystem(self, msg):
|
34 |
+
return {"role": "system", "content": msg}
|
35 |
+
|
36 |
+
def fuser(self, msg):
|
37 |
+
return {"role": "user", "content": msg}
|
38 |
+
|
39 |
+
def fassistant(self, msg):
|
40 |
+
return {"role": "assistant", "content": msg}
|
41 |
+
|
42 |
+
def next(self, messages: list[dict[str, str]], prompt=None):
|
43 |
+
if prompt:
|
44 |
+
messages += [{"role": "user", "content": prompt}]
|
45 |
+
|
46 |
+
logger.debug(f"Creating a new chat completion: {messages}")
|
47 |
+
response = openai.ChatCompletion.create(
|
48 |
+
messages=messages,
|
49 |
+
stream=True,
|
50 |
+
model=self.model,
|
51 |
+
temperature=self.temperature,
|
52 |
+
)
|
53 |
+
|
54 |
+
chat = []
|
55 |
+
for chunk in response:
|
56 |
+
delta = chunk["choices"][0]["delta"]
|
57 |
+
msg = delta.get("content", "")
|
58 |
+
print(msg, end="")
|
59 |
+
chat.append(msg)
|
60 |
+
print()
|
61 |
+
messages += [{"role": "assistant", "content": "".join(chat)}]
|
62 |
+
logger.debug(f"Chat completion finished: {messages}")
|
63 |
+
return messages
|
chat_to_files.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
|
4 |
+
def parse_chat(chat): # -> List[Tuple[str, str]]:
|
5 |
+
# Get all ``` blocks and preceding filenames
|
6 |
+
regex = r"(\S+)\n\s*```[^\n]*\n(.+?)```"
|
7 |
+
matches = re.finditer(regex, chat, re.DOTALL)
|
8 |
+
|
9 |
+
files = []
|
10 |
+
for match in matches:
|
11 |
+
# Strip the filename of any non-allowed characters and convert / to \
|
12 |
+
path = re.sub(r'[<>"|?*]', "", match.group(1))
|
13 |
+
|
14 |
+
# Remove leading and trailing brackets
|
15 |
+
path = re.sub(r"^\[(.*)\]$", r"\1", path)
|
16 |
+
|
17 |
+
# Remove leading and trailing backticks
|
18 |
+
path = re.sub(r"^`(.*)`$", r"\1", path)
|
19 |
+
|
20 |
+
# Remove trailing ]
|
21 |
+
path = re.sub(r"\]$", "", path)
|
22 |
+
|
23 |
+
# Get the code
|
24 |
+
code = match.group(2)
|
25 |
+
|
26 |
+
# Add the file to the list
|
27 |
+
files.append((path, code))
|
28 |
+
|
29 |
+
# Get all the text before the first ``` block
|
30 |
+
readme = chat.split("```")[0]
|
31 |
+
files.append(("README.md", readme))
|
32 |
+
|
33 |
+
# Return the files
|
34 |
+
return files
|
35 |
+
|
36 |
+
|
37 |
+
def to_files(chat, workspace):
|
38 |
+
workspace["all_output.txt"] = chat
|
39 |
+
|
40 |
+
files = parse_chat(chat)
|
41 |
+
for file_name, file_content in files:
|
42 |
+
workspace[file_name] = file_content
|
ci.yaml
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Pytest Execution
|
2 |
+
on:
|
3 |
+
pull_request:
|
4 |
+
branches:
|
5 |
+
- main
|
6 |
+
push:
|
7 |
+
branches:
|
8 |
+
- main
|
9 |
+
|
10 |
+
jobs:
|
11 |
+
test:
|
12 |
+
runs-on: ubuntu-latest
|
13 |
+
strategy:
|
14 |
+
matrix:
|
15 |
+
python-version:
|
16 |
+
- "3.10"
|
17 |
+
steps:
|
18 |
+
- uses: actions/checkout@v3
|
19 |
+
|
20 |
+
- uses: actions/setup-python@v4
|
21 |
+
with:
|
22 |
+
python-version: ${{ matrix.python-version }}
|
23 |
+
cache: pip
|
24 |
+
|
25 |
+
- name: Install package
|
26 |
+
run: pip install -e .
|
27 |
+
|
28 |
+
- name: Install test runner
|
29 |
+
run: pip install pytest pytest-cov
|
30 |
+
|
31 |
+
- name: Run unit tests
|
32 |
+
run: pytest --cov=gpt_engineer
|
db.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from dataclasses import dataclass
|
2 |
+
from pathlib import Path
|
3 |
+
|
4 |
+
|
5 |
+
# This class represents a simple database that stores its data as files in a directory.
|
6 |
+
class DB:
|
7 |
+
"""A simple key-value store, where keys are filenames and values are file contents."""
|
8 |
+
|
9 |
+
def __init__(self, path):
|
10 |
+
self.path = Path(path).absolute()
|
11 |
+
|
12 |
+
self.path.mkdir(parents=True, exist_ok=True)
|
13 |
+
|
14 |
+
def __contains__(self, key):
|
15 |
+
return (self.path / key).is_file()
|
16 |
+
|
17 |
+
def __getitem__(self, key):
|
18 |
+
full_path = self.path / key
|
19 |
+
|
20 |
+
if not full_path.is_file():
|
21 |
+
raise KeyError(key)
|
22 |
+
with full_path.open("r", encoding="utf-8") as f:
|
23 |
+
return f.read()
|
24 |
+
|
25 |
+
def __setitem__(self, key, val):
|
26 |
+
full_path = self.path / key
|
27 |
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
28 |
+
|
29 |
+
if isinstance(val, str):
|
30 |
+
full_path.write_text(val, encoding="utf-8")
|
31 |
+
else:
|
32 |
+
# If val is neither a string nor bytes, raise an error.
|
33 |
+
raise TypeError("val must be either a str or bytes")
|
34 |
+
|
35 |
+
|
36 |
+
# dataclass for all dbs:
|
37 |
+
@dataclass
|
38 |
+
class DBs:
|
39 |
+
memory: DB
|
40 |
+
logs: DB
|
41 |
+
preprompts: DB
|
42 |
+
input: DB
|
43 |
+
workspace: DB
|
main.py
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import logging
|
3 |
+
import shutil
|
4 |
+
|
5 |
+
from pathlib import Path
|
6 |
+
|
7 |
+
import typer
|
8 |
+
|
9 |
+
from gpt_engineer import steps
|
10 |
+
from gpt_engineer.ai import AI
|
11 |
+
from gpt_engineer.db import DB, DBs
|
12 |
+
from gpt_engineer.steps import STEPS
|
13 |
+
|
14 |
+
app = typer.Typer()
|
15 |
+
|
16 |
+
|
17 |
+
@app.command()
|
18 |
+
def main(
|
19 |
+
project_path: str = typer.Argument("example", help="path"),
|
20 |
+
delete_existing: bool = typer.Argument(False, help="delete existing files"),
|
21 |
+
model: str = "gpt-4",
|
22 |
+
temperature: float = 0.1,
|
23 |
+
steps_config: steps.Config = typer.Option(
|
24 |
+
steps.Config.DEFAULT, "--steps", "-s", help="decide which steps to run"
|
25 |
+
),
|
26 |
+
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
27 |
+
run_prefix: str = typer.Option(
|
28 |
+
"",
|
29 |
+
help=(
|
30 |
+
"run prefix, if you want to run multiple variants of the same project and "
|
31 |
+
"later compare them"
|
32 |
+
),
|
33 |
+
),
|
34 |
+
):
|
35 |
+
logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO)
|
36 |
+
|
37 |
+
input_path = Path(project_path).absolute()
|
38 |
+
memory_path = input_path / f"{run_prefix}memory"
|
39 |
+
workspace_path = input_path / f"{run_prefix}workspace"
|
40 |
+
|
41 |
+
if delete_existing:
|
42 |
+
# Delete files and subdirectories in paths
|
43 |
+
shutil.rmtree(memory_path, ignore_errors=True)
|
44 |
+
shutil.rmtree(workspace_path, ignore_errors=True)
|
45 |
+
|
46 |
+
ai = AI(
|
47 |
+
model=model,
|
48 |
+
temperature=temperature,
|
49 |
+
)
|
50 |
+
|
51 |
+
dbs = DBs(
|
52 |
+
memory=DB(memory_path),
|
53 |
+
logs=DB(memory_path / "logs"),
|
54 |
+
input=DB(input_path),
|
55 |
+
workspace=DB(workspace_path),
|
56 |
+
preprompts=DB(Path(__file__).parent / "preprompts"),
|
57 |
+
)
|
58 |
+
|
59 |
+
for step in STEPS[steps_config]:
|
60 |
+
messages = step(ai, dbs)
|
61 |
+
dbs.logs[step.__name__] = json.dumps(messages)
|
62 |
+
|
63 |
+
|
64 |
+
if __name__ == "__main__":
|
65 |
+
app()
|
pre-commit.yaml
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: pre-commit
|
2 |
+
|
3 |
+
on:
|
4 |
+
pull_request:
|
5 |
+
push:
|
6 |
+
branches: [main]
|
7 |
+
|
8 |
+
jobs:
|
9 |
+
pre-commit:
|
10 |
+
runs-on: ubuntu-latest
|
11 |
+
steps:
|
12 |
+
- uses: actions/checkout@v3
|
13 |
+
- uses: actions/setup-python@v4
|
14 |
+
- uses: pre-commit/[email protected]
|
release.yaml
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Build and publish Python packages to PyPI
|
2 |
+
|
3 |
+
on:
|
4 |
+
workflow_dispatch:
|
5 |
+
release:
|
6 |
+
types:
|
7 |
+
- published
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
build:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
strategy:
|
13 |
+
matrix:
|
14 |
+
python-version:
|
15 |
+
- "3.10"
|
16 |
+
steps:
|
17 |
+
- uses: actions/checkout@v3
|
18 |
+
|
19 |
+
- uses: actions/setup-python@v4
|
20 |
+
with:
|
21 |
+
python-version: ${{ matrix.python-version }}
|
22 |
+
cache: pip
|
23 |
+
|
24 |
+
- name: Install build tool
|
25 |
+
run: pip install build
|
26 |
+
|
27 |
+
- name: Build package
|
28 |
+
run: python -m build
|
29 |
+
|
30 |
+
- name: Upload package as build artifact
|
31 |
+
uses: actions/upload-artifact@v3
|
32 |
+
with:
|
33 |
+
name: package
|
34 |
+
path: dist/
|
35 |
+
|
36 |
+
publish:
|
37 |
+
runs-on: ubuntu-latest
|
38 |
+
needs: build
|
39 |
+
environment:
|
40 |
+
name: pypi
|
41 |
+
url: https://pypi.org/p/gpt-engineer
|
42 |
+
permissions:
|
43 |
+
id-token: write
|
44 |
+
steps:
|
45 |
+
- name: Collect packages to release
|
46 |
+
uses: actions/download-artifact@v3
|
47 |
+
with:
|
48 |
+
name: package
|
49 |
+
path: dist/
|
50 |
+
|
51 |
+
- name: Publish packages to PyPI
|
52 |
+
uses: pypa/gh-action-pypi-publish@release/v1
|
steps.py
ADDED
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import re
|
3 |
+
import subprocess
|
4 |
+
|
5 |
+
from enum import Enum
|
6 |
+
from typing import Callable, TypeVar
|
7 |
+
|
8 |
+
from gpt_engineer.ai import AI
|
9 |
+
from gpt_engineer.chat_to_files import to_files
|
10 |
+
from gpt_engineer.db import DBs
|
11 |
+
|
12 |
+
|
13 |
+
def setup_sys_prompt(dbs):
|
14 |
+
return (
|
15 |
+
dbs.preprompts["generate"] + "\nUseful to know:\n" + dbs.preprompts["philosophy"]
|
16 |
+
)
|
17 |
+
|
18 |
+
|
19 |
+
Step = TypeVar("Step", bound=Callable[[AI, DBs], list[dict]])
|
20 |
+
|
21 |
+
|
22 |
+
def simple_gen(ai: AI, dbs: DBs):
|
23 |
+
"""Run the AI on the main prompt and save the results"""
|
24 |
+
messages = ai.start(
|
25 |
+
setup_sys_prompt(dbs),
|
26 |
+
dbs.input["main_prompt"],
|
27 |
+
)
|
28 |
+
to_files(messages[-1]["content"], dbs.workspace)
|
29 |
+
return messages
|
30 |
+
|
31 |
+
|
32 |
+
def clarify(ai: AI, dbs: DBs):
|
33 |
+
"""
|
34 |
+
Ask the user if they want to clarify anything and save the results to the workspace
|
35 |
+
"""
|
36 |
+
messages = [ai.fsystem(dbs.preprompts["qa"])]
|
37 |
+
user = dbs.input["main_prompt"]
|
38 |
+
while True:
|
39 |
+
messages = ai.next(messages, user)
|
40 |
+
|
41 |
+
if messages[-1]["content"].strip().lower().startswith("no"):
|
42 |
+
break
|
43 |
+
|
44 |
+
print()
|
45 |
+
user = input('(answer in text, or "c" to move on)\n')
|
46 |
+
print()
|
47 |
+
|
48 |
+
if not user or user == "c":
|
49 |
+
break
|
50 |
+
|
51 |
+
user += (
|
52 |
+
"\n\n"
|
53 |
+
"Is anything else unclear? If yes, only answer in the form:\n"
|
54 |
+
"{remaining unclear areas} remaining questions.\n"
|
55 |
+
"{Next question}\n"
|
56 |
+
'If everything is sufficiently clear, only answer "no".'
|
57 |
+
)
|
58 |
+
|
59 |
+
print()
|
60 |
+
return messages
|
61 |
+
|
62 |
+
|
63 |
+
def gen_spec(ai: AI, dbs: DBs):
|
64 |
+
"""
|
65 |
+
Generate a spec from the main prompt + clarifications and save the results to
|
66 |
+
the workspace
|
67 |
+
"""
|
68 |
+
messages = [
|
69 |
+
ai.fsystem(setup_sys_prompt(dbs)),
|
70 |
+
ai.fsystem(f"Instructions: {dbs.input['main_prompt']}"),
|
71 |
+
]
|
72 |
+
|
73 |
+
messages = ai.next(messages, dbs.preprompts["spec"])
|
74 |
+
|
75 |
+
dbs.memory["specification"] = messages[-1]["content"]
|
76 |
+
|
77 |
+
return messages
|
78 |
+
|
79 |
+
|
80 |
+
def respec(ai: AI, dbs: DBs):
|
81 |
+
messages = json.loads(dbs.logs[gen_spec.__name__])
|
82 |
+
messages += [ai.fsystem(dbs.preprompts["respec"])]
|
83 |
+
|
84 |
+
messages = ai.next(messages)
|
85 |
+
messages = ai.next(
|
86 |
+
messages,
|
87 |
+
(
|
88 |
+
"Based on the conversation so far, please reiterate the specification for "
|
89 |
+
"the program. "
|
90 |
+
"If there are things that can be improved, please incorporate the "
|
91 |
+
"improvements. "
|
92 |
+
"If you are satisfied with the specification, just write out the "
|
93 |
+
"specification word by word again."
|
94 |
+
),
|
95 |
+
)
|
96 |
+
|
97 |
+
dbs.memory["specification"] = messages[-1]["content"]
|
98 |
+
return messages
|
99 |
+
|
100 |
+
|
101 |
+
def gen_unit_tests(ai: AI, dbs: DBs):
|
102 |
+
"""
|
103 |
+
Generate unit tests based on the specification, that should work.
|
104 |
+
"""
|
105 |
+
messages = [
|
106 |
+
ai.fsystem(setup_sys_prompt(dbs)),
|
107 |
+
ai.fuser(f"Instructions: {dbs.input['main_prompt']}"),
|
108 |
+
ai.fuser(f"Specification:\n\n{dbs.memory['specification']}"),
|
109 |
+
]
|
110 |
+
|
111 |
+
messages = ai.next(messages, dbs.preprompts["unit_tests"])
|
112 |
+
|
113 |
+
dbs.memory["unit_tests"] = messages[-1]["content"]
|
114 |
+
to_files(dbs.memory["unit_tests"], dbs.workspace)
|
115 |
+
|
116 |
+
return messages
|
117 |
+
|
118 |
+
|
119 |
+
def gen_clarified_code(ai: AI, dbs: DBs):
|
120 |
+
# get the messages from previous step
|
121 |
+
|
122 |
+
messages = json.loads(dbs.logs[clarify.__name__])
|
123 |
+
|
124 |
+
messages = [
|
125 |
+
ai.fsystem(setup_sys_prompt(dbs)),
|
126 |
+
] + messages[1:]
|
127 |
+
messages = ai.next(messages, dbs.preprompts["use_qa"])
|
128 |
+
|
129 |
+
to_files(messages[-1]["content"], dbs.workspace)
|
130 |
+
return messages
|
131 |
+
|
132 |
+
|
133 |
+
def gen_code(ai: AI, dbs: DBs):
|
134 |
+
# get the messages from previous step
|
135 |
+
|
136 |
+
messages = [
|
137 |
+
ai.fsystem(setup_sys_prompt(dbs)),
|
138 |
+
ai.fuser(f"Instructions: {dbs.input['main_prompt']}"),
|
139 |
+
ai.fuser(f"Specification:\n\n{dbs.memory['specification']}"),
|
140 |
+
ai.fuser(f"Unit tests:\n\n{dbs.memory['unit_tests']}"),
|
141 |
+
]
|
142 |
+
messages = ai.next(messages, dbs.preprompts["use_qa"])
|
143 |
+
to_files(messages[-1]["content"], dbs.workspace)
|
144 |
+
return messages
|
145 |
+
|
146 |
+
|
147 |
+
def execute_entrypoint(ai, dbs):
|
148 |
+
command = dbs.workspace["run.sh"]
|
149 |
+
|
150 |
+
print("Do you want to execute this code?")
|
151 |
+
print()
|
152 |
+
print(command)
|
153 |
+
print()
|
154 |
+
print('If yes, press enter. Otherwise, type "no"')
|
155 |
+
print()
|
156 |
+
if input() not in ["", "y", "yes"]:
|
157 |
+
print("Ok, not executing the code.")
|
158 |
+
return []
|
159 |
+
print("Executing the code...")
|
160 |
+
print(
|
161 |
+
"\033[92m" # green color
|
162 |
+
+ "Note: If it does not work as expected, please consider running the code'"
|
163 |
+
+ " in another way than above."
|
164 |
+
+ "\033[0m"
|
165 |
+
)
|
166 |
+
print()
|
167 |
+
subprocess.run("bash run.sh", shell=True, cwd=dbs.workspace.path)
|
168 |
+
return []
|
169 |
+
|
170 |
+
|
171 |
+
def gen_entrypoint(ai, dbs):
|
172 |
+
messages = ai.start(
|
173 |
+
system=(
|
174 |
+
"You will get information about a codebase that is currently on disk in "
|
175 |
+
"the current folder.\n"
|
176 |
+
"From this you will answer with code blocks that includes all the necessary "
|
177 |
+
"unix terminal commands to "
|
178 |
+
"a) install dependencies "
|
179 |
+
"b) run all necessary parts of the codebase (in parallell if necessary).\n"
|
180 |
+
"Do not install globally. Do not use sudo.\n"
|
181 |
+
"Do not explain the code, just give the commands.\n"
|
182 |
+
"Do not use placeholders, use example values (like . for a folder argument) "
|
183 |
+
"if necessary.\n"
|
184 |
+
),
|
185 |
+
user="Information about the codebase:\n\n" + dbs.workspace["all_output.txt"],
|
186 |
+
)
|
187 |
+
print()
|
188 |
+
|
189 |
+
regex = r"```\S*\n(.+?)```"
|
190 |
+
matches = re.finditer(regex, messages[-1]["content"], re.DOTALL)
|
191 |
+
dbs.workspace["run.sh"] = "\n".join(match.group(1) for match in matches)
|
192 |
+
return messages
|
193 |
+
|
194 |
+
|
195 |
+
def use_feedback(ai: AI, dbs: DBs):
|
196 |
+
messages = [
|
197 |
+
ai.fsystem(setup_sys_prompt(dbs)),
|
198 |
+
ai.fuser(f"Instructions: {dbs.input['main_prompt']}"),
|
199 |
+
ai.fassistant(dbs.workspace["all_output.txt"]),
|
200 |
+
ai.fsystem(dbs.preprompts["use_feedback"]),
|
201 |
+
]
|
202 |
+
messages = ai.next(messages, dbs.input["feedback"])
|
203 |
+
to_files(messages[-1]["content"], dbs.workspace)
|
204 |
+
return messages
|
205 |
+
|
206 |
+
|
207 |
+
def fix_code(ai: AI, dbs: DBs):
|
208 |
+
code_output = json.loads(dbs.logs[gen_code.__name__])[-1]["content"]
|
209 |
+
messages = [
|
210 |
+
ai.fsystem(setup_sys_prompt(dbs)),
|
211 |
+
ai.fuser(f"Instructions: {dbs.input['main_prompt']}"),
|
212 |
+
ai.fuser(code_output),
|
213 |
+
ai.fsystem(dbs.preprompts["fix_code"]),
|
214 |
+
]
|
215 |
+
messages = ai.next(messages, "Please fix any errors in the code above.")
|
216 |
+
to_files(messages[-1]["content"], dbs.workspace)
|
217 |
+
return messages
|
218 |
+
|
219 |
+
|
220 |
+
class Config(str, Enum):
|
221 |
+
DEFAULT = "default"
|
222 |
+
BENCHMARK = "benchmark"
|
223 |
+
SIMPLE = "simple"
|
224 |
+
TDD = "tdd"
|
225 |
+
TDD_PLUS = "tdd+"
|
226 |
+
CLARIFY = "clarify"
|
227 |
+
RESPEC = "respec"
|
228 |
+
EXECUTE_ONLY = "execute_only"
|
229 |
+
USE_FEEDBACK = "use_feedback"
|
230 |
+
|
231 |
+
|
232 |
+
# Different configs of what steps to run
|
233 |
+
STEPS = {
|
234 |
+
Config.DEFAULT: [
|
235 |
+
clarify,
|
236 |
+
gen_clarified_code,
|
237 |
+
gen_entrypoint,
|
238 |
+
execute_entrypoint,
|
239 |
+
],
|
240 |
+
Config.BENCHMARK: [simple_gen, gen_entrypoint],
|
241 |
+
Config.SIMPLE: [simple_gen, gen_entrypoint, execute_entrypoint],
|
242 |
+
Config.TDD: [
|
243 |
+
gen_spec,
|
244 |
+
gen_unit_tests,
|
245 |
+
gen_code,
|
246 |
+
gen_entrypoint,
|
247 |
+
execute_entrypoint,
|
248 |
+
],
|
249 |
+
Config.TDD_PLUS: [
|
250 |
+
gen_spec,
|
251 |
+
gen_unit_tests,
|
252 |
+
gen_code,
|
253 |
+
fix_code,
|
254 |
+
gen_entrypoint,
|
255 |
+
execute_entrypoint,
|
256 |
+
],
|
257 |
+
Config.CLARIFY: [
|
258 |
+
clarify,
|
259 |
+
gen_clarified_code,
|
260 |
+
gen_entrypoint,
|
261 |
+
execute_entrypoint,
|
262 |
+
],
|
263 |
+
Config.RESPEC: [
|
264 |
+
gen_spec,
|
265 |
+
respec,
|
266 |
+
gen_unit_tests,
|
267 |
+
gen_code,
|
268 |
+
fix_code,
|
269 |
+
gen_entrypoint,
|
270 |
+
execute_entrypoint,
|
271 |
+
],
|
272 |
+
Config.USE_FEEDBACK: [use_feedback, gen_entrypoint, execute_entrypoint],
|
273 |
+
Config.EXECUTE_ONLY: [gen_entrypoint, execute_entrypoint],
|
274 |
+
}
|
275 |
+
|
276 |
+
# Future steps that can be added:
|
277 |
+
# run_tests_and_fix_files
|
278 |
+
# execute_entrypoint_and_fix_files_if_needed
|