Skip to content

Commit 9faae97

Browse files
authored
Merge pull request #1191 from jtoberling/main
Usability improvements
2 parents 3ca1435 + 7fa07e7 commit 9faae97

File tree

9 files changed

+140
-20
lines changed

9 files changed

+140
-20
lines changed

gpt_engineer/applications/cli/cli_agent.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def with_default_config(
109109
improve_fn: ImproveType = improve_fn,
110110
process_code_fn: CodeProcessor = execute_entrypoint,
111111
preprompts_holder: PrepromptsHolder = None,
112+
diff_timeout=3,
112113
):
113114
"""
114115
Creates a new instance of CliAgent with default configurations for memory, execution environment,
@@ -186,6 +187,7 @@ def improve(
186187
files_dict: FilesDict,
187188
prompt: Prompt,
188189
execution_command: Optional[str] = None,
190+
diff_timeout=3,
189191
) -> FilesDict:
190192
"""
191193
Improves an existing piece of code using the AI and step bundle based on the provided prompt.
@@ -206,7 +208,12 @@ def improve(
206208
"""
207209

208210
files_dict = self.improve_fn(
209-
self.ai, prompt, files_dict, self.memory, self.preprompts_holder
211+
self.ai,
212+
prompt,
213+
files_dict,
214+
self.memory,
215+
self.preprompts_holder,
216+
diff_timeout=diff_timeout,
210217
)
211218
# entrypoint = gen_entrypoint(
212219
# self.ai, prompt, files_dict, self.memory, self.preprompts_holder

gpt_engineer/applications/cli/file_selector.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def __init__(self, project_path: Union[str, Path]):
7676
self.metadata_db = DiskMemory(metadata_path(self.project_path))
7777
self.toml_path = self.metadata_db.path / self.FILE_LIST_NAME
7878

79-
def ask_for_files(self) -> tuple[FilesDict, bool]:
79+
def ask_for_files(self, skip_file_selection=False) -> tuple[FilesDict, bool]:
8080
"""
8181
Prompts the user to select files for context improvement.
8282
@@ -89,8 +89,9 @@ def ask_for_files(self) -> tuple[FilesDict, bool]:
8989
A dictionary with file paths as keys and file contents as values.
9090
"""
9191

92-
if os.getenv("GPTE_TEST_MODE"):
92+
if os.getenv("GPTE_TEST_MODE") or skip_file_selection:
9393
# In test mode, retrieve files from a predefined TOML configuration
94+
# also get from toml if skip_file_selector is active
9495
assert self.FILE_LIST_NAME in self.metadata_db
9596
selected_files = self.get_files_from_toml(self.project_path, self.toml_path)
9697
else:
@@ -412,7 +413,7 @@ def get_current_files(self, project_path: Union[str, Path]) -> List[str]:
412413
if is_git_repo(project_path) and "projects" not in project_path.parts:
413414
all_files = filter_by_gitignore(project_path, all_files)
414415

415-
return all_files
416+
return sorted(all_files, key=lambda x: Path(x).as_posix())
416417

417418

418419
class DisplayablePath(object):

gpt_engineer/applications/cli/main.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,12 @@ def main(
357357
"--use_cache",
358358
help="Speeds up computations and saves tokens when running the same prompt multiple times by caching the LLM response.",
359359
),
360+
skip_file_selection: bool = typer.Option(
361+
False,
362+
"--skip-file-selection",
363+
"-s",
364+
help="Skip interactive file selection in improve mode and use the generated TOML file directly.",
365+
),
360366
no_execution: bool = typer.Option(
361367
False,
362368
"--no_execution",
@@ -367,6 +373,11 @@ def main(
367373
"--sysinfo",
368374
help="Output system information for debugging",
369375
),
376+
diff_timeout: int = typer.Option(
377+
3,
378+
"--diff_timeout",
379+
help="Diff regexp timeout. Default: 3. Increase if regexp search timeouts.",
380+
),
370381
):
371382
"""
372383
The main entry point for the CLI tool that generates or improves a project.
@@ -405,6 +416,8 @@ def main(
405416
Speeds up computations and saves tokens when running the same prompt multiple times by caching the LLM response.
406417
verbose : bool
407418
Flag indicating whether to enable verbose logging.
419+
skip_file_selection: bool
420+
Skip interactive file selection in improve mode and use the generated TOML file directly
408421
no_execution: bool
409422
Run setup but to not call LLM or write any code. For testing purposes.
410423
sysinfo: bool
@@ -501,13 +514,17 @@ def main(
501514
files = FileStore(project_path)
502515
if not no_execution:
503516
if improve_mode:
504-
files_dict_before, is_linting = FileSelector(project_path).ask_for_files()
517+
files_dict_before, is_linting = FileSelector(project_path).ask_for_files(
518+
skip_file_selection=skip_file_selection
519+
)
505520

506521
# lint the code
507522
if is_linting:
508523
files_dict_before = files.linting(files_dict_before)
509524

510-
files_dict = handle_improve_mode(prompt, agent, memory, files_dict_before)
525+
files_dict = handle_improve_mode(
526+
prompt, agent, memory, files_dict_before, diff_timeout=diff_timeout
527+
)
511528
if not files_dict or files_dict_before == files_dict:
512529
print(
513530
f"No changes applied. Could you please upload the debug_log_file.txt in {memory.path}/logs folder in a github issue?"

gpt_engineer/core/chat_to_files.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def apply_diffs(diffs: Dict[str, Diff], files: FilesDict) -> FilesDict:
120120
return files
121121

122122

123-
def parse_diffs(diff_string: str) -> dict:
123+
def parse_diffs(diff_string: str, diff_timeout=3) -> dict:
124124
"""
125125
Parses a diff string in the unified git diff format.
126126
@@ -138,7 +138,7 @@ def parse_diffs(diff_string: str) -> dict:
138138

139139
diffs = {}
140140
try:
141-
for block in diff_block_pattern.finditer(diff_string, timeout=1):
141+
for block in diff_block_pattern.finditer(diff_string, timeout=diff_timeout):
142142
diff_block = block.group()
143143

144144
# Parse individual diff blocks and update the diffs dictionary

gpt_engineer/core/default/steps.py

+13-10
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ def improve_fn(
274274
files_dict: FilesDict,
275275
memory: BaseMemory,
276276
preprompts_holder: PrepromptsHolder,
277+
diff_timeout=3,
277278
) -> FilesDict:
278279
"""
279280
Improves the code based on user input and returns the updated files.
@@ -308,14 +309,16 @@ def improve_fn(
308309
DEBUG_LOG_FILE,
309310
"UPLOADED FILES:\n" + files_dict.to_log() + "\nPROMPT:\n" + prompt.text,
310311
)
311-
return _improve_loop(ai, files_dict, memory, messages)
312+
return _improve_loop(ai, files_dict, memory, messages, diff_timeout=diff_timeout)
312313

313314

314315
def _improve_loop(
315-
ai: AI, files_dict: FilesDict, memory: BaseMemory, messages: List
316+
ai: AI, files_dict: FilesDict, memory: BaseMemory, messages: List, diff_timeout=3
316317
) -> FilesDict:
317318
messages = ai.next(messages, step_name=curr_fn())
318-
files_dict, errors = salvage_correct_hunks(messages, files_dict, memory)
319+
files_dict, errors = salvage_correct_hunks(
320+
messages, files_dict, memory, diff_timeout=diff_timeout
321+
)
319322

320323
retries = 0
321324
while errors and retries < MAX_EDIT_REFINEMENT_STEPS:
@@ -327,21 +330,21 @@ def _improve_loop(
327330
)
328331
)
329332
messages = ai.next(messages, step_name=curr_fn())
330-
files_dict, errors = salvage_correct_hunks(messages, files_dict, memory)
333+
files_dict, errors = salvage_correct_hunks(
334+
messages, files_dict, memory, diff_timeout
335+
)
331336
retries += 1
332337

333338
return files_dict
334339

335340

336341
def salvage_correct_hunks(
337-
messages: List,
338-
files_dict: FilesDict,
339-
memory: BaseMemory,
342+
messages: List, files_dict: FilesDict, memory: BaseMemory, diff_timeout=3
340343
) -> tuple[FilesDict, List[str]]:
341344
error_messages = []
342345
ai_response = messages[-1].content.strip()
343346

344-
diffs = parse_diffs(ai_response)
347+
diffs = parse_diffs(ai_response, diff_timeout=diff_timeout)
345348
# validate and correct diffs
346349

347350
for _, diff in diffs.items():
@@ -370,13 +373,13 @@ def flush(self):
370373
file.flush()
371374

372375

373-
def handle_improve_mode(prompt, agent, memory, files_dict):
376+
def handle_improve_mode(prompt, agent, memory, files_dict, diff_timeout=3):
374377
captured_output = io.StringIO()
375378
old_stdout = sys.stdout
376379
sys.stdout = Tee(sys.stdout, captured_output)
377380

378381
try:
379-
files_dict = agent.improve(files_dict, prompt)
382+
files_dict = agent.improve(files_dict, prompt, diff_timeout=diff_timeout)
380383
except Exception as e:
381384
print(
382385
f"Error while improving the project: {e}\nCould you please upload the debug_log_file.txt in {memory.path}/logs folder to github?\nFULL STACK TRACE:\n"

gpt_engineer/tools/custom_steps.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def self_heal(
4444
prompt: Prompt = None,
4545
preprompts_holder: PrepromptsHolder = None,
4646
memory: BaseMemory = None,
47+
diff_timeout=3,
4748
) -> FilesDict:
4849
"""
4950
Attempts to execute the code from the entrypoint and if it fails, sends the error output back to the AI with instructions to fix.
@@ -111,7 +112,7 @@ def self_heal(
111112
f"A program with this specification was requested:\n{prompt}\n, but running it produced the following output:\n{stdout_full}\n and the following errors:\n{stderr_full}. Please change it so that it fulfills the requirements."
112113
)
113114
files_dict = improve_fn(
114-
ai, new_prompt, files_dict, memory, preprompts_holder
115+
ai, new_prompt, files_dict, memory, preprompts_holder, diff_timeout
115116
)
116117
else:
117118
break

gpt_engineer/tools/supported_languages.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@
4646
"extensions": [".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx"],
4747
"tree_sitter_name": "cpp",
4848
},
49-
{"name": "C", "extensions": [".c", ".h"], "tree_sitter_name": "c"}
49+
{"name": "C", "extensions": [".c", ".h"], "tree_sitter_name": "c"},
50+
{"name": "Markdown", "extensions": [".md"], "tree_sitter_name": "md"},
51+
{"name": "Arduino C", "extensions": [".ino"], "tree_sitter_name": "ino"}
5052
# ---- the following are not supported by the current code chunker implementation ----
5153
# {
5254
# "name": "Swift",

tests/applications/cli/test_main.py

+30
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,36 @@ def test_improve_existing_project(self, tmp_path, monkeypatch):
9292
)
9393
args()
9494

95+
# Runs gpt-engineer with improve mode and improves an existing project in the specified path, with skip_file_selection
96+
def test_improve_existing_project_skip_file_selection(self, tmp_path, monkeypatch):
97+
p = tmp_path / "projects/example"
98+
p.mkdir(parents=True)
99+
(p / "prompt").write_text(prompt_text)
100+
args = DefaultArgumentsMain(
101+
str(p),
102+
improve_mode=True,
103+
llm_via_clipboard=True,
104+
no_execution=True,
105+
skip_file_selection=True,
106+
)
107+
args()
108+
assert args.skip_file_selection, "Skip_file_selection not set"
109+
110+
# Runs gpt-engineer with improve mode and improves an existing project in the specified path, with skip_file_selection
111+
def test_improve_existing_project_diff_timeout(self, tmp_path, monkeypatch):
112+
p = tmp_path / "projects/example"
113+
p.mkdir(parents=True)
114+
(p / "prompt").write_text(prompt_text)
115+
args = DefaultArgumentsMain(
116+
str(p),
117+
improve_mode=True,
118+
llm_via_clipboard=True,
119+
no_execution=True,
120+
diff_timeout=99,
121+
)
122+
args()
123+
assert args.diff_timeout == 99, "Diff timeout not set"
124+
95125
# def improve_generator():
96126
# yield "y"
97127
# while True:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import os
2+
3+
from pathlib import Path
4+
from typing import List, Union
5+
6+
from gpt_engineer.applications.cli.file_selector import FileSelector
7+
8+
editorcalled = False
9+
10+
11+
def set_editor_called(
12+
self, input_path: Union[str, Path], init: bool = True
13+
) -> List[str]:
14+
global editorcalled
15+
editorcalled = True
16+
return []
17+
18+
19+
def set_file_selector_tmpproject(tmp_path):
20+
project_path = tmp_path / "project/"
21+
os.mkdir(project_path)
22+
os.mkdir(project_path / "x")
23+
os.mkdir(project_path / "a")
24+
25+
gpteng_path = project_path / ".gpteng"
26+
os.mkdir(gpteng_path)
27+
28+
with open(gpteng_path / "file_selection.toml", "w") as file:
29+
file.write("[files]\n")
30+
file.write(' "x/xxtest.py" = "selected"\n')
31+
file.write(' "a/aatest.py" = "selected"\n')
32+
33+
with open(project_path / "x/xxtest.py", "w") as file:
34+
file.write('print("Hello")')
35+
36+
with open(project_path / "a/aatest.py", "w") as file:
37+
file.write('print("Hello")')
38+
39+
return project_path
40+
41+
42+
def test_file_selector_enhancement_skip_file_selector(tmp_path):
43+
project_path = set_file_selector_tmpproject(tmp_path)
44+
fileSelector = FileSelector(project_path=project_path)
45+
fileSelector.editor_file_selector = set_editor_called
46+
fileSelector.ask_for_files(skip_file_selection=True)
47+
48+
assert editorcalled is False, "FileSelector.skip_file_selector is not working"
49+
50+
51+
def test_file_selector_enhancement_sort(tmp_path):
52+
project_path = set_file_selector_tmpproject(tmp_path)
53+
fileSelector = FileSelector(project_path=project_path)
54+
55+
sortedFiles = fileSelector.get_current_files(project_path)
56+
assert sortedFiles == [
57+
"a/aatest.py",
58+
"x/xxtest.py",
59+
], "FileSelector.get_current_files is unsorted!"

0 commit comments

Comments
 (0)