Skip to content

Commit e90ac46

Browse files
committed
Merge branch 'main' of github.com:AntonOsika/gpt-engineer
* 'main' of github.com:AntonOsika/gpt-engineer: Mark test as failed because it requires OpenAI API access currently `black` Create test_ai.py fix to_files execute_workspace -> gen_entrypoint; execute_entrypoint Ignore my-new-project/ Added CODE_OF_CONDUCT.md to the .github directory (AntonOsika#147) make pre commit pass in the whole codebase (AntonOsika#149) Create ci.yaml Fix linting Add support for directory paths in filenames and improve code splitting - Enforce an explicit markdown code block format - Add a token to split the output to clearly detect when the code blocks start - Save all non-code output to a `README.md` file - Update RegEx to extract and strip text more reliably and clean up the output - Update the identify prompts appropriately Enhance philosophy to include supporting documents - Create instructions for running/compiling the project - Create any package manager files Generate instructions for all platforms - Update prompt to create instructions for all 3 major OS platforms - Fix small typo Add support for directory creation and binary files - Use the `Path` module instead of `os` - Add ability to create any amount of missing directories for a given file - Add ability to save both text and binary files to save images (or other file types) later Add cleanup & move `projects` to their own directory - Add optional argument to clean and delete the working directories of the project before running the prompt - Add `.gitignore` entry to ignore all possible projects - Update readme
2 parents 4a212d9 + d3d1c9e commit e90ac46

File tree

17 files changed

+352
-114
lines changed

17 files changed

+352
-114
lines changed

.github/CODE_OF_CONDUCT.md

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Contributor Covenant Code of Conduct
2+
3+
## Our Pledge
4+
5+
We as members, contributors, and leaders pledge to make participation in our
6+
community a harassment-free experience for everyone, regardless of age, body
7+
size, visible or invisible disability, ethnicity, sex characteristics, gender
8+
identity or expression, level of experience, education, socio-economic status,
9+
nationality, personal appearance, race, caste, color, religion, or sexual
10+
identity and orientation.
11+
12+
We pledge to act and interact in ways that contribute to an open, welcoming,
13+
diverse, inclusive, and healthy community.
14+
15+
## Our Standards
16+
17+
Examples of behavior that contributes to a positive environment for our
18+
community include:
19+
20+
* Demonstrating empathy and kindness toward other people
21+
* Being respectful of differing opinions, viewpoints, and experiences
22+
* Giving and gracefully accepting constructive feedback
23+
* Accepting responsibility and apologizing to those affected by our mistakes,
24+
and learning from the experience
25+
* Focusing on what is best not just for us as individuals, but for the overall
26+
community
27+
28+
Examples of unacceptable behavior include:
29+
30+
* The use of sexualized language or imagery, and sexual attention or advances of
31+
any kind
32+
* Trolling, insulting or derogatory comments, and personal or political attacks
33+
* Public or private harassment
34+
* Publishing others' private information, such as a physical or email address,
35+
without their explicit permission
36+
* Other conduct which could reasonably be considered inappropriate in a
37+
professional setting
38+
39+
## Enforcement Responsibilities
40+
41+
Community leaders are responsible for clarifying and enforcing our standards of
42+
acceptable behavior and will take appropriate and fair corrective action in
43+
response to any behavior that they deem inappropriate, threatening, offensive,
44+
or harmful.
45+
46+
Community leaders have the right and responsibility to remove, edit, or reject
47+
comments, commits, code, wiki edits, issues, and other contributions that are
48+
not aligned to this Code of Conduct, and will communicate reasons for moderation
49+
decisions when appropriate.
50+
51+
## Scope
52+
53+
This Code of Conduct applies within all community spaces, and also applies when
54+
an individual is officially representing the community in public spaces.
55+
Examples of representing our community include using an official e-mail address,
56+
posting using an official social media account, or acting as an appointed
57+
representative at an online or offline event.
58+
59+
## Enforcement
60+
61+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
62+
reported to the community leaders responsible for enforcement at
63+
anton.osika@gmail.com.
64+
All complaints will be reviewed and investigated promptly and fairly.
65+
66+
All community leaders are obligated to respect the privacy and security of reporters of incidents.
67+
68+
## Enforcement Guidelines
69+
70+
Community leaders will follow these Community Impact Guidelines in determining
71+
the consequences for any action they deem in violation of this Code of Conduct:
72+
73+
### 1. Correction
74+
75+
**Community Impact**: Use of inappropriate language or other behavior deemed
76+
unprofessional or unwelcome in the community.
77+
78+
**Consequence**: A private, written warning from community leaders, providing
79+
clarity around the nature of the violation and an explanation of why the
80+
behavior was inappropriate. A public apology may be requested.
81+
82+
### 2. Warning
83+
84+
**Community Impact**: A violation through a single incident or series of
85+
actions.
86+
87+
**Consequence**: A warning with consequences for continued behavior. No
88+
interaction with the people involved, including unsolicited interaction with
89+
those enforcing the Code of Conduct, for a specified period of time. This
90+
includes avoiding interactions in community spaces as well as external channels
91+
like social media. Violating these terms may lead to a temporary or permanent
92+
ban.
93+
94+
### 3. Temporary Ban
95+
96+
**Community Impact**: A serious violation of community standards, including
97+
sustained inappropriate behavior.
98+
99+
**Consequence**: A temporary ban from any sort of interaction or public
100+
communication with the community for a specified period of time. No public or
101+
private interaction with the people involved, including unsolicited interaction
102+
with those enforcing the Code of Conduct, is allowed during this period.
103+
Violating these terms may lead to a permanent ban.
104+
105+
### 4. Permanent Ban
106+
107+
**Community Impact**: Demonstrating a pattern of violation of community
108+
standards, including sustained inappropriate behavior, harassment of an
109+
individual, or aggression toward or disparagement of classes of individuals.
110+
111+
**Consequence**: A permanent ban from any sort of public interaction within the
112+
community.
113+
114+
## Attribution
115+
116+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
117+
version 2.1, available at
118+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
119+
120+
Community Impact Guidelines were inspired by
121+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
122+
123+
For answers to common questions about this code of conduct, see the FAQ at
124+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
125+
[https://www.contributor-covenant.org/translations][translations].
126+
127+
[homepage]: https://www.contributor-covenant.org
128+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
129+
[Mozilla CoC]: https://github.com/mozilla/diversity
130+
[FAQ]: https://www.contributor-covenant.org/faq
131+
[translations]: https://www.contributor-covenant.org/translations

.github/workflows/ci.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
on:
2+
pull_request:
3+
branches:
4+
- main
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version:
15+
- "3.8"
16+
- "3.9"
17+
- "3.10"
18+
steps:
19+
- uses: actions/checkout@v3
20+
21+
- uses: actions/setup-python@v4
22+
with:
23+
python-version: ${{ matrix.python-version }}
24+
cache: pip
25+
26+
- name: Install package
27+
run: pip install -e .
28+
29+
- name: Install test runner
30+
run: pip install pytest pytest-cov
31+
32+
- name: Run unit tests
33+
run: pytest --cov=gpt_engineer

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ archive
3838
# any log file
3939
*log.txt
4040
todo
41+
42+
# Ignore GPT Engineer files
43+
projects
44+
my-new-project/

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ GPT Engineer is made to be easy to adapt, extend, and make your agent learn how
2222
- `export OPENAI_API_KEY=[your api key]` with a key that has GPT4 access
2323

2424
**Run**:
25-
- Create a new empty folder with a `main_prompt` file (or copy the example folder `cp -r example/ my-new-project`)
25+
- Create a new empty folder with a `main_prompt` file in the `projects` folder (or copy the example folder `cp -r projects/example/ projects/my-new-project`)
2626
- Fill in the `main_prompt` in your new folder
2727
- Run `python -m gpt_engineer.main my-new-project`
28+
- Optionally pass in `true` to delete the working files before running
2829

2930
**Results**:
30-
- Check the generated files in my-new-project/workspace
31+
- Check the generated files in projects/my-new-project/workspace
3132

3233
### Limitations
3334
Implementing additional chain of thought prompting, e.g. [Reflexion](https://github.com/noahshinn024/reflexion), should be able to make it more reliable and not miss requested functionality in the main prompt.

gpt_engineer/ai.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ def __init__(self, **kwargs):
88
try:
99
openai.Model.retrieve("gpt-4")
1010
except openai.error.InvalidRequestError:
11-
print("Model gpt-4 not available for provided api key reverting "
12-
"to gpt-3.5.turbo. Sign up for the gpt-4 wait list here: "
13-
"https://openai.com/waitlist/gpt-4-api")
14-
self.kwargs['model'] = "gpt-3.5-turbo"
11+
print(
12+
"Model gpt-4 not available for provided api key reverting "
13+
"to gpt-3.5.turbo. Sign up for the gpt-4 wait list here: "
14+
"https://openai.com/waitlist/gpt-4-api"
15+
)
16+
self.kwargs["model"] = "gpt-3.5-turbo"
1517

1618
def start(self, system, user):
1719
messages = [
@@ -26,10 +28,10 @@ def fsystem(self, msg):
2628

2729
def fuser(self, msg):
2830
return {"role": "user", "content": msg}
31+
2932
def fassistant(self, msg):
3033
return {"role": "assistant", "content": msg}
3134

32-
3335
def next(self, messages: list[dict[str, str]], prompt=None):
3436
if prompt:
3537
messages = messages + [{"role": "user", "content": prompt}]

gpt_engineer/chat_to_files.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,42 @@
11
import re
2-
from typing import List, Tuple
3-
from gpt_engineer.db import DB
42

53

6-
def parse_chat(chat) -> List[Tuple[str, str]]:
7-
# Get all ``` blocks
8-
regex = r"```(.*?)```"
4+
def parse_chat(chat): # -> List[Tuple[str, str]]:
5+
# Split the chat into sections by the "*CODEBLOCKSBELOW*" token
6+
split_chat = chat.split("*CODEBLOCKSBELOW*")
97

10-
matches = re.finditer(regex, chat, re.DOTALL)
8+
# Check if the "*CODEBLOCKSBELOW*" token was found
9+
is_token_found = len(split_chat) > 1
10+
11+
# If the "*CODEBLOCKSBELOW*" token is found, use the first part as README
12+
# and second part as code blocks. Otherwise, treat README as optional and
13+
# proceed with empty README and the entire chat as code blocks
14+
readme = split_chat[0].strip() if is_token_found else "No readme"
15+
code_blocks = split_chat[1] if is_token_found else chat
16+
17+
# Get all ``` blocks and preceding filenames
18+
regex = r"(\S+?)\n```\S+\n(.+?)```"
19+
matches = re.finditer(regex, code_blocks, re.DOTALL)
1120

1221
files = []
1322
for match in matches:
14-
path = match.group(1).split("\n")[0]
23+
# Strip the filename of any non-allowed characters and convert / to \
24+
path = re.sub(r'[<>"|?*]', "", match.group(1))
25+
1526
# Get the code
16-
code = match.group(1).split("\n")[1:]
17-
code = "\n".join(code)
27+
code = match.group(2)
28+
1829
# Add the file to the list
1930
files.append((path, code))
2031

32+
# Add README to the list
33+
files.append(("README.txt", readme))
34+
35+
# Return the files
2136
return files
2237

2338

24-
def to_files(chat: str, workspace: DB):
39+
def to_files(chat, workspace):
2540
workspace["all_output.txt"] = chat
2641

2742
files = parse_chat(chat)

gpt_engineer/db.py

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,51 @@
11
from dataclasses import dataclass
2-
import os
32
from pathlib import Path
43

54

5+
# This class represents a simple database that stores its data as files in a directory.
6+
# It supports both text and binary files, and can handle directory structures.
67
class DB:
7-
"""A simple key-value store, where keys are filenames and values are file contents."""
8-
98
def __init__(self, path):
9+
# Convert the path string to a Path object and get its absolute path.
1010
self.path = Path(path).absolute()
11-
os.makedirs(self.path, exist_ok=True)
11+
12+
# Create the directory if it doesn't exist.
13+
self.path.mkdir(parents=True, exist_ok=True)
1214

1315
def __getitem__(self, key):
14-
with open(self.path / key, encoding='utf-8') as f:
15-
return f.read()
16+
# Combine the database directory with the provided file path.
17+
full_path = self.path / key
18+
19+
# Check if the file exists before trying to open it.
20+
if full_path.is_file():
21+
# Open the file in text mode and return its content.
22+
with full_path.open("r") as f:
23+
return f.read()
24+
else:
25+
# If the file doesn't exist, raise an error.
26+
raise FileNotFoundError(f"No such file: '{full_path}'")
1627

1728
def __setitem__(self, key, val):
18-
Path(self.path / key).absolute().parent.mkdir(parents=True, exist_ok=True)
29+
# Combine the database directory with the provided file path.
30+
full_path = self.path / key
1931

20-
with open(self.path / key, 'w', encoding='utf-8') as f:
21-
f.write(val)
32+
# Create the directory tree if it doesn't exist.
33+
full_path.parent.mkdir(parents=True, exist_ok=True)
2234

23-
def __contains__(self, key):
24-
return (self.path / key).exists()
35+
# Write the data to the file. If val is a string, it's written as text.
36+
# If val is bytes, it's written as binary data.
37+
if isinstance(val, str):
38+
full_path.write_text(val)
39+
elif isinstance(val, bytes):
40+
full_path.write_bytes(val)
41+
else:
42+
# If val is neither a string nor bytes, raise an error.
43+
raise TypeError("val must be either a str or bytes")
2544

2645

46+
# dataclass for all dbs:
2747
@dataclass
2848
class DBs:
29-
"""A dataclass for all dbs"""
30-
3149
memory: DB
3250
logs: DB
3351
identity: DB

gpt_engineer/main.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
1-
import os
21
import json
2+
import os
33
import pathlib
4+
import shutil
5+
46
import typer
57

6-
from gpt_engineer.chat_to_files import to_files
78
from gpt_engineer.ai import AI
8-
from gpt_engineer.steps import STEPS
99
from gpt_engineer.db import DB, DBs
10-
10+
from gpt_engineer.steps import STEPS
1111

1212
app = typer.Typer()
1313

1414

1515
@app.command()
1616
def chat(
17-
project_path: str = typer.Argument(str(pathlib.Path(os.path.curdir) / "example"), help="path"),
17+
project_path: str = typer.Argument("example", help="path"),
18+
delete_existing: str = typer.Argument(None, help="delete existing files"),
1819
run_prefix: str = typer.Option(
1920
"",
20-
help="run prefix, if you want to run multiple variants of the same project and later compare them",
21+
help=(
22+
"run prefix, if you want to run multiple variants of the same project and "
23+
"later compare them",
24+
),
2125
),
2226
model: str = "gpt-4",
2327
temperature: float = 0.1,
2428
steps_config: str = "default",
2529
):
2630
app_dir = pathlib.Path(os.path.curdir)
27-
input_path = project_path
28-
memory_path = pathlib.Path(project_path) / (run_prefix + "memory")
29-
workspace_path = pathlib.Path(project_path) / (run_prefix + "workspace")
31+
input_path = pathlib.Path(app_dir / "projects" / project_path)
32+
memory_path = input_path / (run_prefix + "memory")
33+
workspace_path = input_path / (run_prefix + "workspace")
34+
35+
if delete_existing == "true":
36+
# Delete files and subdirectories in paths
37+
shutil.rmtree(memory_path, ignore_errors=True)
38+
shutil.rmtree(workspace_path, ignore_errors=True)
3039

3140
ai = AI(
3241
model=model,
@@ -45,5 +54,6 @@ def chat(
4554
messages = step(ai, dbs)
4655
dbs.logs[step.__name__] = json.dumps(messages)
4756

57+
4858
if __name__ == "__main__":
4959
app()

0 commit comments

Comments
 (0)