Skip to content

Commit 67a721d

Browse files
authored
Merge pull request #695 from Pythagora-io/development
Development
2 parents a968a66 + 3612d5a commit 67a721d

22 files changed

+155
-80
lines changed

pilot/.env.example

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# OPENAI or AZURE or OPENROUTER
22
ENDPOINT=OPENAI
33

4-
OPENAI_ENDPOINT=https://api.openai.com/v1/chat/completions
4+
# OPENAI_ENDPOINT=https://api.openai.com/v1/chat/completions
5+
OPENAI_ENDPOINT=
56
OPENAI_API_KEY=
67

78
AZURE_API_KEY=

pilot/const/common.py

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
'.next',
3030
'.DS_Store',
3131
'__pycache__',
32+
"site-packages",
3233
'node_modules',
3334
'package-lock.json',
3435
'venv',

pilot/const/ipc.py

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
'loopTrigger': 'loopTrigger', # Trigger loop feedback popup in extension
1717
'progress': 'progress', # Progress bar for extension only
1818
'projectStats': 'projectStats', # Project stats for extension only
19+
'keyExpired': 'keyExpired', # (Free trial) key expired message - for extension only
1920
}
2021

2122
LOCAL_IGNORE_MESSAGE_TYPES = [
@@ -31,4 +32,5 @@
3132
'loopTrigger',
3233
'progress',
3334
'projectStats',
35+
'keyExpired',
3436
]

pilot/const/messages.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
CHECK_AND_CONTINUE = 'Is everything working? Let me know if something needs to be changed for this task or type "continue" to proceed.'
22
WHEN_USER_DONE = 'Once you have completed, enter "continue"'
3-
AFFIRMATIVE_ANSWERS = ['', 'y', 'yes', 'ok', 'okay', 'sure', 'absolutely', 'indeed', 'correct', 'affirmative', 'Use GPT Pilot\'s code']
4-
NEGATIVE_ANSWERS = ['n', 'no', 'skip', 'negative', 'not now', 'cancel', 'decline', 'stop', 'Keep my changes']
3+
AFFIRMATIVE_ANSWERS = ['', 'y', 'yes', 'ok', 'okay', 'sure', 'absolutely', 'indeed', 'correct', 'affirmative']
4+
NEGATIVE_ANSWERS = ['n', 'no', 'skip', 'negative', 'not now', 'cancel', 'decline', 'stop']
55
STUCK_IN_LOOP = 'I\'m stuck in loop'
66
NONE_OF_THESE = 'none of these'
77
MAX_PROJECT_NAME_LENGTH = 50

pilot/const/telemetry.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
LARGE_REQUEST_THRESHOLD = 50000 # tokens
22
SLOW_REQUEST_THRESHOLD = 300 # seconds
3-
LOOP_THRESHOLD = 20 # number of steps in task to be considered a loop
3+
LOOP_THRESHOLD = 3 # number of iterations in task to be considered a loop

pilot/database/database.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def save_command_run(project, command, cli_response, done_or_error_response, exi
369369
unique_data = {
370370
'app': project.args['app_id'],
371371
'previous_step': project.checkpoints['last_command_run'],
372-
'high_level_step': project.current_step,
372+
'high_level_step': str(project.checkpoints['last_development_step']['id']) if project.checkpoints['last_development_step'] else None,
373373
}
374374

375375
data_fields = {
@@ -391,7 +391,7 @@ def save_user_input(project, query, user_input, hint):
391391
unique_data = {
392392
'app': project.args['app_id'],
393393
'previous_step': project.checkpoints['last_user_input'],
394-
'high_level_step': project.current_step,
394+
'high_level_step': str(project.checkpoints['last_development_step']['id']) if project.checkpoints['last_development_step'] else None,
395395
}
396396
data_fields = {
397397
'query': query,

pilot/helpers/AgentConvo.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def send_message(self, prompt_path=None, prompt_data=None, function_calls: Funct
6868
function_calls=function_calls, prompt_data=prompt_data,
6969
temperature=self.temperature)
7070
except TokenLimitError as e:
71-
save_development_step(self.agent.project, prompt_path, prompt_data, self.messages, '', str(e))
71+
save_development_step(self.agent.project, prompt_path, prompt_data, self.messages, {"text": ""}, str(e))
7272
raise e
7373

7474
# TODO: move this code to Developer agent - https://github.com/Pythagora-io/gpt-pilot/issues/91#issuecomment-1751964079

pilot/helpers/Project.py

+46-19
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
from utils.telemetry import telemetry
3636
from utils.task import Task
37-
from utils.utils import remove_lines_with_string, is_extension_old_version
37+
from utils.utils import remove_lines_with_string
3838

3939

4040
class Project:
@@ -58,8 +58,6 @@ def __init__(
5858
current_step (str, optional): Current step in the project. Default is None.
5959
"""
6060
self.args = args
61-
# TODO remove everything related to is_extension_old_version once new version is released and everybody has to update core
62-
self.is_extension_old_version = is_extension_old_version(args)
6361
self.llm_req_num = 0
6462
self.command_runs_count = 0
6563
self.user_inputs_count = 0
@@ -98,12 +96,13 @@ def __init__(
9896
File.update_paths()
9997

10098
# start loading of project (since backwards compatibility)
101-
self.should_overwrite_files = False
99+
self.should_overwrite_files = None
102100
self.last_detailed_user_review_goal = None
103101
self.last_iteration = None
104102
self.tasks_to_load = []
105103
self.features_to_load = []
106104
self.dev_steps_to_load = []
105+
self.run_command = None
107106
# end loading of project
108107

109108
def set_root_path(self, root_path: str):
@@ -118,22 +117,41 @@ def setup_loading(self):
118117
return
119118

120119
self.skip_steps = True
121-
should_overwrite_files = None
122-
while should_overwrite_files is None or should_overwrite_files.lower() not in AFFIRMATIVE_ANSWERS + NEGATIVE_ANSWERS:
123-
print('Use GPT Pilot\'s code/Keep my changes', type='buttons-only')
124-
should_overwrite_files = styled_text(
120+
while self.should_overwrite_files is None:
121+
changes_made_question = f'Did you make any changes to "{self.args["name"]}" project files since last time you used Pythagora?'
122+
print(changes_made_question, type='ipc', category='pythagora')
123+
print('yes/no', type='buttons-only')
124+
# must use styled_text() instead of ask_user() here to avoid finish_loading() call
125+
changes_made = styled_text(
125126
self,
126-
"Can GPT Pilot overwrite code changes you made since last running GPT Pilot?",
127-
ignore_user_input_count=True
127+
changes_made_question,
128+
ignore_user_input_count=True,
128129
)
129130

130-
logger.info('should_overwrite_files: %s', should_overwrite_files)
131-
if should_overwrite_files in NEGATIVE_ANSWERS:
132-
self.should_overwrite_files = False
133-
break
134-
elif should_overwrite_files in AFFIRMATIVE_ANSWERS:
131+
# if there were no changes just load files from db
132+
if changes_made.lower() in NEGATIVE_ANSWERS:
135133
self.should_overwrite_files = True
136134
break
135+
# otherwise ask user if they want to use those changes
136+
elif changes_made.lower() in AFFIRMATIVE_ANSWERS:
137+
use_changes_question = 'Do you want to use those changes you made?'
138+
use_changes_msg = 'yes'
139+
dont_use_changes_msg = 'no, restore last pythagora state'
140+
print(use_changes_question, type='ipc', category='pythagora')
141+
print(f'{use_changes_msg}/{dont_use_changes_msg}', type='buttons-only')
142+
print(f'"{dont_use_changes_msg}" means Pythagora will restore (overwrite) all files to last stored state.\n'
143+
f'"{use_changes_msg}" means Pythagora will continue working on project using current state of files.', type='hint')
144+
use_changes = styled_text(
145+
self,
146+
use_changes_question,
147+
ignore_user_input_count=True
148+
)
149+
150+
logger.info('Use changes: %s', use_changes)
151+
if use_changes.lower() in NEGATIVE_ANSWERS + [dont_use_changes_msg]:
152+
self.should_overwrite_files = True
153+
elif use_changes.lower() in AFFIRMATIVE_ANSWERS + [use_changes_msg]:
154+
self.should_overwrite_files = False
137155

138156
load_step_before_coding = ('step' in self.args and
139157
self.args['step'] is not None and
@@ -155,6 +173,9 @@ def setup_loading(self):
155173
self.checkpoints['last_development_step'] = self.dev_steps_to_load[-1]
156174
self.tasks_to_load = [el for el in self.dev_steps_to_load if 'breakdown.prompt' in el.get('prompt_path', '')]
157175
self.features_to_load = [el for el in self.dev_steps_to_load if 'feature_plan.prompt' in el.get('prompt_path', '')]
176+
self.run_command = next((el for el in reversed(self.dev_steps_to_load) if 'get_run_command.prompt' in el.get('prompt_path', '')), None)
177+
if self.run_command is not None:
178+
self.run_command = json.loads(self.run_command['llm_response']['text'])['command']
158179

159180
def start(self):
160181
"""
@@ -216,11 +237,13 @@ def finish(self):
216237
self.previous_features = get_features_by_app_id(self.args['app_id'])
217238
if not self.skip_steps:
218239
print('', type='verbose', category='pythagora')
240+
if self.run_command and self.check_ipc():
241+
print(self.run_command, type='run_command')
219242
feature_description = ask_user(self, "Project is finished! Do you want to add any features or changes? "
220243
"If yes, describe it here and if no, just press ENTER",
221244
require_some_input=False)
222245

223-
if feature_description == '':
246+
if feature_description == '' or feature_description == 'continue':
224247
return
225248

226249
print('', type='verbose', category='agent:tech-lead')
@@ -391,13 +414,14 @@ def save_file(self, data):
391414
inputs_required = self.find_input_required_lines(data['content'])
392415
for line_number, line_content in inputs_required:
393416
user_input = None
417+
print('', type='verbose', category='human-intervention')
394418
print(color_yellow_bold(f'Input required on line {line_number}:\n{line_content}') + '\n')
395419
while user_input is None or user_input.lower() not in AFFIRMATIVE_ANSWERS + ['continue']:
396420
print({'path': full_path, 'line': line_number}, type='openFile')
397421
print('continue', type='buttons-only')
398422
user_input = ask_user(
399423
self,
400-
f'Please open the file {data["path"]} on the line {line_number} and add the required input. Once you\'re done, type "y" to continue.',
424+
f'Please open the file {data["path"]} on the line {line_number} and add the required input. Please, also remove "// INPUT_REQUIRED" comment and once you\'re done, press "continue".',
401425
require_some_input=False,
402426
ignore_user_input_count=True
403427
)
@@ -458,7 +482,7 @@ def normalize_path(path: str) -> Tuple[str, str]:
458482
# - /pilot -> /pilot/
459483
# - \pilot\server.js -> \pilot\server.js
460484
# - \pilot -> \pilot\
461-
KNOWN_FILES = ["makefile", "dockerfile", "procfile", "readme", "license"] # known exceptions that break the heuristic
485+
KNOWN_FILES = ["makefile", "dockerfile", "procfile", "readme", "license", "podfile"] # known exceptions that break the heuristic
462486
KNOWN_DIRS = [] # known exceptions that break the heuristic
463487
base = os.path.basename(path)
464488
if (
@@ -543,7 +567,10 @@ def restore_files(self, development_step_id):
543567

544568
clear_directory(self.root_path, ignore=self.files)
545569
for file_snapshot in file_snapshots:
546-
update_file(file_snapshot.file.full_path, file_snapshot.content, project=self)
570+
try:
571+
update_file(file_snapshot.file.full_path, file_snapshot.content, project=self)
572+
except (PermissionError, NotADirectoryError) as err: # noqa
573+
print(f"Error restoring file {file_snapshot.file.full_path}: {err}")
547574
if file_snapshot.file.full_path not in self.files:
548575
self.files.append(file_snapshot.file.full_path)
549576

pilot/helpers/agents/CodeMonkey.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,12 @@ def implement_code_changes(
8585
files = self.project.get_all_coded_files()
8686
file_name, file_content = self.get_original_file(code_change_description, step, files)
8787

88+
print('', type='verbose', category='agent:code-monkey')
89+
8890
if file_content:
89-
print(f'Updating existing file {file_name}')
91+
print(f'Updating existing file {file_name}:')
9092
else:
91-
print(f'Creating new file {file_name}')
93+
print(f'Creating new file {file_name}:')
9294

9395
# Get the new version of the file
9496
content = self.replace_complete_file(
@@ -103,13 +105,15 @@ def implement_code_changes(
103105
# There are no changes or there was problem talking with the LLM, we're done here
104106
break
105107

108+
print('Sending code for review...', type='verbose', category='agent:code-monkey')
106109
print('', type='verbose', category='agent:reviewer')
107110
content, rework_feedback = self.review_change(convo, code_change_description, file_name, file_content, content)
108-
print('', type='verbose', category='agent:code-monkey')
111+
print('Review finished. Continuing...', type='verbose', category='agent:code-monkey')
109112
if not rework_feedback:
110113
# No rework needed, we're done here
111114
break
112115

116+
print('', type='verbose', category='agent:code-monkey')
113117
content = convo.send_message('development/review_feedback.prompt', {
114118
"content": content,
115119
"original_content": file_content,
@@ -258,7 +262,7 @@ def review_change(
258262
else:
259263
# The reviewer failed to review all the hunks in 3 attempts, let's just use all the new content
260264
convo.remove_last_x_messages(messages_to_remove)
261-
return new_content
265+
return new_content, None
262266

263267
convo.remove_last_x_messages(messages_to_remove)
264268

pilot/helpers/agents/Developer.py

+21-8
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,13 @@ def __init__(self, project):
4545
self.debugger = Debugger(self)
4646

4747
def start_coding(self, task_source):
48-
print('', type='verbose', category='agent:developer')
48+
print('Starting development...', type='verbose', category='agent:developer')
4949
if not self.project.finished:
5050
self.project.current_step = 'coding'
5151
update_app_status(self.project.args['app_id'], self.project.current_step)
5252

5353
# DEVELOPMENT
5454
if not self.project.skip_steps:
55-
print(color_green_bold("🚀 Now for the actual development...\n"))
5655
logger.info("Starting to create the actual code...")
5756

5857
total_tasks = len(self.project.development_plan)
@@ -123,7 +122,8 @@ def implement_task(self, i, task_source, development_task=None):
123122
:param task_source: The source of the task, one of: 'app', 'feature', 'debugger', 'iteration'.
124123
:param development_task: The task to implement.
125124
"""
126-
print(color_green_bold(f'Implementing task #{i + 1}: ') + color_green(f' {development_task["description"]}\n'), category='agent:developer')
125+
print(color_green_bold(f'Implementing task #{i + 1}: ') + color_green(f' {development_task["description"]}\n'), category='pythagora')
126+
print(f'Starting task #{i + 1} implementation...', type='verbose', category='agent:developer')
127127
self.project.dot_pilot_gpt.chat_log_folder(i + 1)
128128

129129
convo_dev_task = AgentConvo(self)
@@ -391,6 +391,8 @@ def get_run_command(self, convo):
391391
elif single_match:
392392
self.run_command = single_match.group(1).strip()
393393

394+
self.project.run_command = self.run_command
395+
394396
def task_postprocessing(self, convo, development_task, continue_development, task_result, last_branch_name):
395397
if self.project.last_detailed_user_review_goal is None:
396398
self.get_run_command(convo)
@@ -636,7 +638,7 @@ def continue_development(self, iteration_convo, last_branch_name, continue_descr
636638
return_cli_response=True, is_root_task=True)},
637639
convo=iteration_convo,
638640
is_root_task=True,
639-
add_loop_button=iteration_count > 3,
641+
add_loop_button=iteration_count > 2,
640642
category='human-test')
641643

642644
logger.info('response: %s', response)
@@ -648,7 +650,7 @@ def continue_development(self, iteration_convo, last_branch_name, continue_descr
648650

649651
if user_feedback is not None:
650652
print('', type='verbose', category='agent:troubleshooter')
651-
user_feedback = self.bug_report_generator(user_feedback)
653+
self.project.current_task.inc('iterations')
652654
stuck_in_loop = user_feedback.startswith(STUCK_IN_LOOP)
653655
if stuck_in_loop:
654656
# Remove the STUCK_IN_LOOP prefix from the user feedback
@@ -666,6 +668,8 @@ def continue_development(self, iteration_convo, last_branch_name, continue_descr
666668
tried_alternative_solutions_to_current_issue.append(description_of_tried_solutions)
667669

668670
tried_alternative_solutions_to_current_issue.append(next_solution_to_try)
671+
else:
672+
user_feedback = self.bug_report_generator(user_feedback, user_description)
669673

670674
print_task_progress(1, 1, development_task['description'], 'troubleshooting', 'in_progress')
671675
iteration_convo = AgentConvo(self)
@@ -711,11 +715,12 @@ def continue_development(self, iteration_convo, last_branch_name, continue_descr
711715
self.execute_task(iteration_convo, task_steps, is_root_task=True, task_source='troubleshooting')
712716
print_task_progress(1, 1, development_task['description'], 'troubleshooting', 'done')
713717

714-
def bug_report_generator(self, user_feedback):
718+
def bug_report_generator(self, user_feedback, task_review_description):
715719
"""
716720
Generate a bug report from the user feedback.
717721
718722
:param user_feedback: The user feedback.
723+
:param task_review_description: The task review description.
719724
:return: The bug report.
720725
"""
721726
bug_report_convo = AgentConvo(self)
@@ -727,6 +732,7 @@ def bug_report_generator(self, user_feedback):
727732
"user_feedback": user_feedback,
728733
"app_summary": self.project.project_description,
729734
"files": self.project.get_all_coded_files(),
735+
"task_review_description": task_review_description,
730736
"questions_and_answers": questions_and_answers,
731737
}, GET_BUG_REPORT_MISSING_DATA)
732738

@@ -760,6 +766,7 @@ def bug_report_generator(self, user_feedback):
760766
bug_report_summary_convo = AgentConvo(self)
761767
user_feedback = bug_report_summary_convo.send_message('development/bug_report_summary.prompt', {
762768
"app_summary": self.project.project_description,
769+
"task_review_description": task_review_description,
763770
"user_feedback": user_feedback,
764771
"questions_and_answers": questions_and_answers,
765772
})
@@ -771,7 +778,7 @@ def review_task(self):
771778
Review all task changes and refactor big files.
772779
:return: bool - True if the task changes passed review, False if not
773780
"""
774-
print('', type='verbose', category='agent:reviewer')
781+
print('Starting review of all changes made in this task...', type='verbose', category='agent:reviewer')
775782
self.review_count += 1
776783
review_result = self.review_code_changes()
777784
refactoring_done = self.refactor_code()
@@ -861,7 +868,13 @@ def set_up_environment(self):
861868
dep_text = dependency['name']
862869

863870
logger.info('Checking %s', dependency)
864-
llm_response = self.check_system_dependency(dependency)
871+
try:
872+
llm_response = self.check_system_dependency(dependency)
873+
except Exception as err:
874+
# This catches weird errors like people removing or renaming the workspace
875+
# folder while we're trying to run system commands. Since these commands don't
876+
# care about folders they run in, we don't want to crash just because of that.
877+
llm_response = str(err)
865878

866879
if llm_response == 'DONE':
867880
print(color_green_bold(f"✅ {dep_text} is available."))

0 commit comments

Comments
 (0)