From 2e54fcacbb4d28f98571c6f0576dd03fdeb01ae7 Mon Sep 17 00:00:00 2001 From: LeonOstrez Date: Fri, 1 Sep 2023 11:32:02 +0200 Subject: [PATCH 01/11] add requirements to readme --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 32fe06e..c3ea724 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,16 @@ The idea is that AI won't be able to (at least in the near future) create apps f Obviously, it still can't create any production-ready app but the general concept of how this could work is there. +# 🔌 Requirements + + +- **Python** +- **PostgreSQL** + - DB is needed for multiple reasons like continuing app development if you had to stop at any point or app crashed, going back to specific step so you can change some later steps in development, easier debugging, for future we will add functionality to update project (change some things in existing project or add new features to the project and so on)... + + # 🚦How to start using gpt-pilot? +After you have Python and PostgreSQL installed, follow these steps: 1. `git clone https://github.com/Pythagora-io/gpt-pilot.git` (clone the repo) 2. `cd gpt-pilot` 3. `python -m venv pilot-env` (create a virtual environment) @@ -43,7 +52,7 @@ Obviously, it still can't create any production-ready app but the general concep 5. `pip install -r requirements.txt` (install the dependencies) 6. `cd pilot` 7. `mv .env.example .env` (create the .env file) -8. Add your OpenAI API key and the database info to the `.env` file +8. Add your OpenAI API key and the PostgreSQL database info to the `.env` file 9. `python db_init.py` (initialize the database) 10. `python main.py` (start GPT Pilot) From 827f18a27d4f7ce185ee28eb1a53da5157211e82 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:24:46 +0200 Subject: [PATCH 02/11] Typo fix --- pilot/const/function_calls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pilot/const/function_calls.py b/pilot/const/function_calls.py index 3d39c99..e3a83c7 100644 --- a/pilot/const/function_calls.py +++ b/pilot/const/function_calls.py @@ -172,7 +172,7 @@ IMPLEMENT_TASK = { 'properties': { 'type': { 'type': 'string', - 'enum': ['command', 'code_change', 'human_invervention'], + 'enum': ['command', 'code_change', 'human_intervention'], 'description': 'Type of the development step that needs to be done to complete the entire task.', }, 'command': command_definition(), From 4ba39862370f7ef070bd7eb25ae91dc81b3103d5 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:26:41 +0200 Subject: [PATCH 03/11] Improved prompts by removing unnecessary new lines --- .../development/env_setup/cli_response.prompt | 4 +--- .../prompts/development/implement_changes.prompt | 8 ++------ pilot/prompts/development/task/breakdown.prompt | 16 ++++++++-------- .../task/request_files_for_code_changes.prompt | 9 ++------- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/pilot/prompts/development/env_setup/cli_response.prompt b/pilot/prompts/development/env_setup/cli_response.prompt index 2f596d0..6c551e3 100644 --- a/pilot/prompts/development/env_setup/cli_response.prompt +++ b/pilot/prompts/development/env_setup/cli_response.prompt @@ -1,4 +1,2 @@ Response from the CLI: -``` -{{ cli_response }} -``` \ No newline at end of file +{{ cli_response }} \ No newline at end of file diff --git a/pilot/prompts/development/implement_changes.prompt b/pilot/prompts/development/implement_changes.prompt index d5d77f1..b299425 100644 --- a/pilot/prompts/development/implement_changes.prompt +++ b/pilot/prompts/development/implement_changes.prompt @@ -1,14 +1,10 @@ -{% if files|length > 0 %} -Here is how files look now: +{% if files|length > 0 %}Here is how files look now: {% for file in files %} **{{ file.path }}** ```{# file.language #} {{ file.content }} ``` -{% endfor %} -{% endif %} - -Now, think step by step and apply the needed changes for step #{{ step_index }} - {{ step_description }}. +{% endfor %}{% endif %}Now, think step by step and apply the needed changes for step #{{ step_index }} - `{{ step_description }}`. Within the file modifications, anything needs to be written by the user, add the comment in the same line as the code that starts with `// INPUT_REQUIRED {input_description}` where `input_description` is a description of what needs to be added here by the user. Finally, you can save the modified files on the disk by calling `save_files` function. \ No newline at end of file diff --git a/pilot/prompts/development/task/breakdown.prompt b/pilot/prompts/development/task/breakdown.prompt index 4258f6e..1509b57 100644 --- a/pilot/prompts/development/task/breakdown.prompt +++ b/pilot/prompts/development/task/breakdown.prompt @@ -20,15 +20,15 @@ Here are the technologies that you need to use for this project: - {{ tech }}{% endfor %} ``` -{% if parent_task %} -You are currently working on this task: -``` -{{ array_of_objects_to_string(parent_task) }} -``` -We've broken it down to these subtasks: -```{% for subtask in sibling_tasks %} -- {{ subtask['description'] }}{% endfor %} +{% if current_task_index != 0 %} +So far, this code has been implemented +{% for file in files %} +**{{ file.path }}** +```{# file.language #} +{{ file.content }} ``` + +{% endfor %} {% endif %} Now, tell me all the code that needs to be written to implement this app and have it fully working and all commands that need to be run to implement this app. diff --git a/pilot/prompts/development/task/request_files_for_code_changes.prompt b/pilot/prompts/development/task/request_files_for_code_changes.prompt index da866ad..2b74657 100644 --- a/pilot/prompts/development/task/request_files_for_code_changes.prompt +++ b/pilot/prompts/development/task/request_files_for_code_changes.prompt @@ -1,13 +1,8 @@ {#You need to implement the current changes into a codebase: -- INSTRUCTIONS -- {{ instructions }} --- END OF INSTRUCTIONS --#} -{% if step_index != 0 %} -So far, steps {{ finished_steps }} are finished so let's do -{% else %} -Let's start with the -{% endif %} - step #{{ step_index }} - `{{ step_description }}`. +-- END OF INSTRUCTIONS -- +#}{% if step_index != 0 %}So far, steps {{ finished_steps }} are finished so let's do{% else %}Let's start with the{% endif %} step #{{ step_index }} - `{{ step_description }}`. {# I will give you each file that needs to be changed and you will implement changes from the instructions. #}To do this, you will need to see the currently implemented files so first, filter the files outlined above that are relevant for the instructions. Then, tell me files that you need to see so that you can make appropriate changes to the code. If no files are needed (eg. if you need to create a file), just return an empty array. {# From 190230a0e54a54d1c4d560ddad1468e060981da8 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:27:35 +0200 Subject: [PATCH 04/11] Changed position of saving files --- pilot/database/database.py | 8 ++++++-- pilot/helpers/AgentConvo.py | 1 - pilot/helpers/Project.py | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pilot/database/database.py b/pilot/database/database.py index eb5020c..60bb18d 100644 --- a/pilot/database/database.py +++ b/pilot/database/database.py @@ -215,9 +215,13 @@ def save_development_step(project, prompt_path, prompt_data, messages, llm_respo 'previous_step': project.checkpoints['last_development_step'], } - development_step = hash_and_save_step(DevelopmentSteps, project.args['app_id'], hash_data_args, data_fields, - "Saved Development Step") + development_step = hash_and_save_step(DevelopmentSteps, project.args['app_id'], hash_data_args, data_fields, "Saved Development Step") project.checkpoints['last_development_step'] = development_step + + + project.save_files_snapshot(development_step.id) + + return development_step diff --git a/pilot/helpers/AgentConvo.py b/pilot/helpers/AgentConvo.py index b5bac8e..c4d24fa 100644 --- a/pilot/helpers/AgentConvo.py +++ b/pilot/helpers/AgentConvo.py @@ -52,7 +52,6 @@ class AgentConvo: if self.agent.__class__.__name__ == 'Developer': development_step = save_development_step(self.agent.project, prompt_path, prompt_data, self.messages, response) self.agent.project.checkpoints['last_development_step'] = development_step - self.agent.project.save_files_snapshot(development_step.id) # TODO handle errors from OpenAI if response == {}: diff --git a/pilot/helpers/Project.py b/pilot/helpers/Project.py index 2a4283c..baa443e 100644 --- a/pilot/helpers/Project.py +++ b/pilot/helpers/Project.py @@ -150,6 +150,7 @@ class Project: development_step, created = DevelopmentSteps.get_or_create(id=development_step_id) for file in files: + print(colored(f'Saving file {file["path"] + "/" + file["name"]}', 'light_cyan')) # TODO this can be optimized so we don't go to the db each time file_in_db, created = File.get_or_create( app=self.app, From 990eb0d1823ee499249033fc8e4a23ab5c279ed4 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:29:20 +0200 Subject: [PATCH 05/11] Added back the functionality for tech lead to break down the project and the developer to code task by task --- pilot/const/common.py | 2 +- pilot/helpers/Project.py | 10 +++--- pilot/helpers/agents/Developer.py | 31 +++++++++++++------ pilot/prompts/development/plan.prompt | 8 ++++- .../prompts/development/task/breakdown.prompt | 13 ++++++-- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/pilot/const/common.py b/pilot/const/common.py index 6e39416..05269cb 100644 --- a/pilot/const/common.py +++ b/pilot/const/common.py @@ -11,8 +11,8 @@ STEPS = [ 'user_stories', 'user_tasks', 'architecture', - 'development_planning', 'environment_setup', + 'development_planning', 'coding' ] diff --git a/pilot/helpers/Project.py b/pilot/helpers/Project.py index baa443e..00baa20 100644 --- a/pilot/helpers/Project.py +++ b/pilot/helpers/Project.py @@ -60,8 +60,11 @@ class Project: self.architect = Architect(self) self.architecture = self.architect.get_architecture() - # self.tech_lead = TechLead(self) - # self.development_plan = self.tech_lead.create_development_plan() + self.developer = Developer(self) + self.developer.set_up_environment(); + + self.tech_lead = TechLead(self) + self.development_plan = self.tech_lead.create_development_plan() # TODO move to constructor eventually if 'skip_until_dev_step' in self.args: @@ -75,9 +78,6 @@ class Project: self.save_files_snapshot(self.skip_until_dev_step) # TODO END - self.developer = Developer(self) - self.developer.set_up_environment(); - self.developer.start_coding() def get_directory_tree(self, with_descriptions=False): diff --git a/pilot/helpers/agents/Developer.py b/pilot/helpers/agents/Developer.py index cb451e4..757b80a 100644 --- a/pilot/helpers/agents/Developer.py +++ b/pilot/helpers/agents/Developer.py @@ -29,13 +29,16 @@ class Developer(Agent): print(colored(f"Ok, great, now, let's start with the actual development...\n", "green", attrs=['bold'])) logger.info(f"Starting to create the actual code...") - self.implement_task() + for i, dev_task in enumerate(self.project.development_plan): + self.implement_task(i, dev_task) # DEVELOPMENT END logger.info('The app is DONE!!! Yay...you can use it now.') - def implement_task(self): + def implement_task(self, i, development_task=None): + print(colored(f'Implementing task #{i + 1}: ', 'green', attrs=['bold']) + colored(f' {development_task["description"]}\n', 'green')); + convo_dev_task = AgentConvo(self) task_description = convo_dev_task.send_message('development/task/breakdown.prompt', { "name": self.project.args['name'], @@ -46,13 +49,16 @@ class Developer(Agent): "technologies": self.project.architecture, "array_of_objects_to_string": array_of_objects_to_string, "directory_tree": self.project.get_directory_tree(True), + "current_task_index": i, + "development_tasks": self.project.development_plan, + "files": self.project.get_all_coded_files(), }) task_steps = convo_dev_task.send_message('development/parse_task.prompt', {}, IMPLEMENT_TASK) convo_dev_task.remove_last_x_messages(2) - self.execute_task(convo_dev_task, task_steps, continue_development=True) + self.execute_task(convo_dev_task, task_steps, development_task=development_task, continue_development=True) - def execute_task(self, convo, task_steps, test_command=None, reset_convo=True, test_after_code_changes=True, continue_development=False): + def execute_task(self, convo, task_steps, test_command=None, reset_convo=True, test_after_code_changes=True, continue_development=False, development_task=None): function_uuid = str(uuid.uuid4()) convo.save_branch(function_uuid) @@ -112,14 +118,21 @@ class Developer(Agent): if self.run_command.endswith('`'): self.run_command = self.run_command[:-1] - if continue_development: - self.continue_development(convo) + if development_task is not None: + convo.remove_last_x_messages(2) + detailed_user_review_goal = convo.send_message('development/define_user_review_goal.prompt', {}) - def continue_development(self, iteration_convo): + if continue_development: + continue_description = detailed_user_review_goal if detailed_user_review_goal is not None else None + self.continue_development(convo, continue_description) + + def continue_development(self, iteration_convo, continue_description=''): while True: - # TODO add description about how can the user check if the app works + user_description = ('Here is a description of what should be working: \n\n' + colored(continue_description, 'blue', attrs=['bold']) + '\n') if continue_description != '' else '' + user_description = 'Can you check if the app works please? ' + user_description + '\nIf you want to run the app, ' + colored('just type "r" and press ENTER and that will run `' + self.run_command + '`', 'yellow', attrs=['bold']) + continue_description = '' user_feedback = self.project.ask_for_human_intervention( - 'Can you check if the app works?\nIf you want to run the app, ' + colored('just type "r" and press ENTER', 'yellow', attrs=['bold']), + user_description, cbs={ 'r': lambda: run_command_until_success(self.run_command, None, iteration_convo, force=True) }) if user_feedback == 'continue': diff --git a/pilot/prompts/development/plan.prompt b/pilot/prompts/development/plan.prompt index 40cfbd2..99cf96e 100644 --- a/pilot/prompts/development/plan.prompt +++ b/pilot/prompts/development/plan.prompt @@ -34,4 +34,10 @@ Here are the technologies that you need to use for this project: {% endfor %} ``` -Now, based on the app's description, user stories and user tasks, and the technologies that you need to use, think step by step and write up the entire plan for the development. Start from the project setup and specify each step until the moment when the entire app should be fully working. For each step, write a description, a programmatic goal, and a user-review goal. \ No newline at end of file +OK, now, you need to create code to have this app fully working but before we go into the coding part, I want you to split the development process of creating this app into smaller tasks so that it is easier to debug and make the app work. Each smaller task of this project has to be a whole that can be reviewed by a developer to make sure we're on a right track to create this app completely. However, it cannot be split into tasks that are too small as well. + +Each task needs to be related only to the development of this app and nothing else - once the app is fully working, that is it. There shouldn't be a task for deployment, writing documentation, or anything that is not writing the actual code. Think task by task and create the least number of tasks that are relevant for this specific app. + +For each task, there must be a way for human developer to check if the task is done or not. Write how should the developer check if the task is done. + +Now, based on the app's description, user stories and user tasks, and the technologies that you need to use, think task by task and write up the entire plan for the development. Start from the project setup and specify each task until the moment when the entire app should be fully working. For each task, write a description and a user-review goal. \ No newline at end of file diff --git a/pilot/prompts/development/task/breakdown.prompt b/pilot/prompts/development/task/breakdown.prompt index 1509b57..d9f41a0 100644 --- a/pilot/prompts/development/task/breakdown.prompt +++ b/pilot/prompts/development/task/breakdown.prompt @@ -31,12 +31,19 @@ So far, this code has been implemented {% endfor %} {% endif %} -Now, tell me all the code that needs to be written to implement this app and have it fully working and all commands that need to be run to implement this app. +We've broken the development of this app down to these tasks: +```{% for task in development_tasks %} +- {{ task['description'] }}{% endfor %} +``` -This should be a simple version of the app so you don't need to aim to provide a production ready code but rather something that a developer can run locally and play with the implementation. Do not leave any parts of the code to be written afterwards. Make sure that all the code you provide is working and does as outlined in the description area above. +You are currently working on this task with the following description: {{ development_tasks[current_task_index]['description'] }} +After all the code is finished, a human developer will check it works this way - {{ development_tasks[current_task_index]['user_review_goal'] }} + +Now, tell me all the code that needs to be written to implement this app and have it fully working and all commands that need to be run to implement this app. !IMPORTANT! Remember, I'm currently in an empty folder where I will start writing files that you tell me. Tell me how can I test the app to see if it's working or not. You do not need to make any automated tests work. -DO NOT specify commands to create any folders or files, they will be created automatically - just specify the relative path to each file that needs to be written \ No newline at end of file +DO NOT specify commands to create any folders or files, they will be created automatically - just specify the relative path to each file that needs to be written. +Never use the port 5000 to run the app, it's reserved. From 9967df02e8bef18e3b4b8593b3f0c9c5fda50c85 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:32:52 +0200 Subject: [PATCH 06/11] HARDCODED: we don't want to create directories in any other way than by running a mkdir command --- pilot/const/function_calls.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pilot/const/function_calls.py b/pilot/const/function_calls.py index e3a83c7..4bbf730 100644 --- a/pilot/const/function_calls.py +++ b/pilot/const/function_calls.py @@ -40,7 +40,7 @@ def return_array_from_prompt(name_plural, name_singular, return_var_name): } -def command_definition(description_command=f'A single command that needs to be executed.', description_timeout=f'Timeout in milliseconds that represent the approximate time this command takes to finish. If you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.'): +def command_definition(description_command=f'A single command that needs to be executed.', description_timeout=f'Timeout in milliseconds that represent the approximate time this command takes to finish. If you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds. If you need to create a directory that doesn\'t exist and is not the root project directory, always create it by running a command `mkdir`'): return { 'type': 'object', 'description': 'Command that needs to be run to complete the current task. This should be used only if the task is of a type "command".', @@ -125,7 +125,7 @@ DEV_TASKS_BREAKDOWN = { 'description': 'List of smaller development steps that need to be done to complete the entire task.', 'items': { 'type': 'object', - 'description': 'A smaller development step that needs to be done to complete the entire task. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.', + 'description': 'A smaller development step that needs to be done to complete the entire task. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds. If you need to create a directory that doesn\'t exist and is not the root project directory, always create it by running a command `mkdir`', 'properties': { 'type': { 'type': 'string', @@ -168,7 +168,7 @@ IMPLEMENT_TASK = { 'description': 'List of smaller development steps that need to be done to complete the entire task.', 'items': { 'type': 'object', - 'description': 'A smaller development step that needs to be done to complete the entire task. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.', + 'description': 'A smaller development step that needs to be done to complete the entire task. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an If you need to create a directory that doesn\'t exist and is not the root project directory, always create it by running a command `mkdir`', 'properties': { 'type': { 'type': 'string', @@ -316,7 +316,7 @@ CODE_CHANGES = { 'description': 'List of smaller development steps that need to be done to complete the entire task.', 'items': { 'type': 'object', - 'description': 'A smaller development step that needs to be done to complete the entire task. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.', + 'description': 'A smaller development step that needs to be done to complete the entire task. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds. If you need to create a directory that doesn\'t exist and is not the root project directory, always create it by running a command `mkdir`', 'properties': { 'type': { 'type': 'string', @@ -390,7 +390,7 @@ EXECUTE_COMMANDS = { 'properties': { 'commands': { 'type': 'array', - 'description': f'List of commands that need to be executed. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.', + 'description': f'List of commands that need to be executed. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds. If you need to create a directory that doesn\'t exist and is not the root project directory, always create it by running a command `mkdir`', 'items': command_definition(f'A single command that needs to be executed.', f'Timeout in milliseconds that represent the approximate time this command takes to finish. If you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.') } }, @@ -483,7 +483,7 @@ GET_TEST_TYPE = { 'description': f'Type of a test that needs to be run. If this is just an intermediate step in getting a task done, put `no_test` as the type and we\'ll just go onto the next task without testing.', 'enum': ['automated_test', 'command_test', 'manual_test', 'no_test'] }, - 'command': command_definition('Command that needs to be run to test the changes.', 'Timeout in milliseconds that represent the approximate time this command takes to finish. If you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.'), + 'command': command_definition('Command that needs to be run to test the changes.', 'Timeout in milliseconds that represent the approximate time this command takes to finish. If you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds. If you need to create a directory that doesn\'t exist and is not the root project directory, always create it by running a command `mkdir`'), 'automated_test_description': { 'type': 'string', 'description': 'Description of an automated test that needs to be run to test the changes. This should be used only if the test type is "automated_test" and it should thoroughly describe what needs to be done to implement the automated test so that when someone looks at this test can know exactly what needs to be done to implement this automated test.', @@ -515,7 +515,7 @@ DEBUG_STEPS_BREAKDOWN = { 'description': 'List of steps that need to be done to debug the problem.', 'items': { 'type': 'object', - 'description': 'A single step that needs to be done to get closer to debugging this issue. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds.', + 'description': 'A single step that needs to be done to get closer to debugging this issue. Remember, if you need to run a command that doesnt\'t finish by itself (eg. a command to run an app), put the timeout to 3000 milliseconds. If you need to create a directory that doesn\'t exist and is not the root project directory, always create it by running a command `mkdir`', 'properties': { 'type': { 'type': 'string', From 8e3fecfb33440c6b4727f75db39b815b0b81da36 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:33:02 +0200 Subject: [PATCH 07/11] Fix --- pilot/helpers/agents/Developer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pilot/helpers/agents/Developer.py b/pilot/helpers/agents/Developer.py index 757b80a..d02f813 100644 --- a/pilot/helpers/agents/Developer.py +++ b/pilot/helpers/agents/Developer.py @@ -18,6 +18,7 @@ from helpers.cli import execute_command class Developer(Agent): def __init__(self, project): super().__init__('full_stack_developer', project) + self.run_command = None def start_coding(self): self.project.current_step = 'coding' From f8f7006e6840c6d3ba94e8268e36c109bd426877 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:33:30 +0200 Subject: [PATCH 08/11] Added description to human intervention log --- pilot/helpers/agents/Developer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pilot/helpers/agents/Developer.py b/pilot/helpers/agents/Developer.py index d02f813..534e9bf 100644 --- a/pilot/helpers/agents/Developer.py +++ b/pilot/helpers/agents/Developer.py @@ -95,7 +95,11 @@ class Developer(Agent): # TODO end elif step['type'] == 'human_intervention': - user_feedback = self.project.ask_for_human_intervention('I need human intervention:', step['human_intervention_description']) + human_intervention_description = step['human_intervention_description'] + colored('\n\nIf you want to run the app, just type "r" and press ENTER and that will run `' + self.run_command + '`', 'yellow', attrs=['bold']) if self.run_command is not None else step['human_intervention_description'] + user_feedback = self.project.ask_for_human_intervention('I need human intervention:', + human_intervention_description, + cbs={ 'r': lambda: run_command_until_success(self.run_command, None, convo, force=True) }) + if user_feedback is not None and user_feedback != 'continue': debug(convo, user_input=user_feedback, issue_description=step['human_intervention_description']) From c4a5bbf1f2571c19bb492f28cc4240876e188568 Mon Sep 17 00:00:00 2001 From: Zvonimir Sabljic Date: Sat, 2 Sep 2023 17:34:05 +0200 Subject: [PATCH 09/11] IMPORTANT: Fix for shell built-in commands - this will need to be improved down the line --- pilot/helpers/cli.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pilot/helpers/cli.py b/pilot/helpers/cli.py index 48413c6..a6aa2c3 100644 --- a/pilot/helpers/cli.py +++ b/pilot/helpers/cli.py @@ -82,6 +82,12 @@ def execute_command(project, command, timeout=None, force=False): 'If yes, just press ENTER' ) + + # TODO when a shell built-in commands (like cd or source) is executed, the output is not captured properly - this will need to be changed at some point + if "cd " in command or "source " in command: + command = "bash -c '" + command + "'" + + project.command_runs_count += 1 command_run = get_command_run_from_hash_id(project, command) if command_run is not None and project.skip_steps: From c4299dac343f69865a68bf1de75300f19d1f2e63 Mon Sep 17 00:00:00 2001 From: rajveer43 Date: Mon, 4 Sep 2023 20:10:20 +0530 Subject: [PATCH 10/11] push --- pilot/helpers/AgentConvo.py | 44 ++++++++++++++++++++++++++++ pilot/helpers/Project.py | 52 +++++++++++++++++++++++++++++++++ pilot/helpers/cli.py | 58 +++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/pilot/helpers/AgentConvo.py b/pilot/helpers/AgentConvo.py index c4d24fa..0789ec2 100644 --- a/pilot/helpers/AgentConvo.py +++ b/pilot/helpers/AgentConvo.py @@ -11,6 +11,12 @@ from const.llm import END_RESPONSE class AgentConvo: + """ + Represents a conversation with an agent. + + Args: + agent: An instance of the agent participating in the conversation. + """ def __init__(self, agent): self.messages = [] self.branches = {} @@ -22,6 +28,17 @@ class AgentConvo: self.messages.append(get_sys_message(self.agent.role)) def send_message(self, prompt_path=None, prompt_data=None, function_calls=None): + """ + Sends a message in the conversation. + + Args: + prompt_path: The path to a prompt. + prompt_data: Data associated with the prompt. + function_calls: Optional function calls to be included in the message. + + Returns: + The response from the agent. + """ # craft message self.construct_and_add_message_from_prompt(prompt_path, prompt_data) @@ -83,6 +100,17 @@ class AgentConvo: return response def continuous_conversation(self, prompt_path, prompt_data, function_calls=None): + """ + Conducts a continuous conversation with the agent. + + Args: + prompt_path: The path to a prompt. + prompt_data: Data associated with the prompt. + function_calls: Optional function calls to be included in the conversation. + + Returns: + List of accepted messages in the conversation. + """ self.log_to_user = False accepted_messages = [] response = self.send_message(prompt_path, prompt_data, function_calls) @@ -111,6 +139,16 @@ class AgentConvo: return len([msg for msg in self.messages if msg['role'] != 'system']) def postprocess_response(self, response, function_calls): + """ + Post-processes the response from the agent. + + Args: + response: The response from the agent. + function_calls: Optional function calls associated with the response. + + Returns: + The post-processed response. + """ if 'function_calls' in response and function_calls is not None: if 'send_convo' in function_calls: response['function_calls']['arguments']['convo'] = self @@ -121,6 +159,12 @@ class AgentConvo: return response def log_message(self, content): + """ + Logs a message in the conversation. + + Args: + content: The content of the message to be logged. + """ print_msg = capitalize_first_word_with_underscores(self.high_level_step) if self.log_to_user: if self.agent.project.checkpoints['last_development_step'] is not None: diff --git a/pilot/helpers/Project.py b/pilot/helpers/Project.py index 00baa20..9e605e2 100644 --- a/pilot/helpers/Project.py +++ b/pilot/helpers/Project.py @@ -21,6 +21,19 @@ from utils.files import get_parent_folder class Project: def __init__(self, args, name=None, description=None, user_stories=None, user_tasks=None, architecture=None, development_plan=None, current_step=None): + """ + Initialize a project. + + Args: + args (dict): Project arguments. + name (str, optional): Project name. Default is None. + description (str, optional): Project description. Default is None. + user_stories (list, optional): List of user stories. Default is None. + user_tasks (list, optional): List of user tasks. Default is None. + architecture (str, optional): Project architecture. Default is None. + development_plan (str, optional): Development plan. Default is None. + current_step (str, optional): Current step in the project. Default is None. + """ self.args = args self.llm_req_num = 0 self.command_runs_count = 0 @@ -52,6 +65,9 @@ class Project: # self.development_plan = development_plan def start(self): + """ + Start the project. + """ self.project_manager = ProductOwner(self) self.project_manager.get_project_description() self.user_stories = self.project_manager.get_user_stories() @@ -81,6 +97,15 @@ class Project: self.developer.start_coding() def get_directory_tree(self, with_descriptions=False): + """ + Get the directory tree of the project. + + Args: + with_descriptions (bool, optional): Whether to include descriptions. Default is False. + + Returns: + dict: The directory tree. + """ files = {} if with_descriptions and False: files = File.select().where(File.app_id == self.args['app_id']) @@ -88,15 +113,36 @@ class Project: return build_directory_tree(self.root_path + '/', ignore=IGNORE_FOLDERS, files=files, add_descriptions=False) def get_test_directory_tree(self): + """ + Get the directory tree of the tests. + + Returns: + dict: The directory tree of tests. + """ # TODO remove hardcoded path return build_directory_tree(self.root_path + '/tests', ignore=IGNORE_FOLDERS) def get_all_coded_files(self): + """ + Get all coded files in the project. + + Returns: + list: A list of coded files. + """ files = File.select().where(File.app_id == self.args['app_id']) files = self.get_files([file.path + '/' + file.name for file in files]) return files def get_files(self, files): + """ + Get file contents. + + Args: + files (list): List of file paths. + + Returns: + list: A list of files with content. + """ files_with_content = [] for file in files: # TODO this is a hack, fix it @@ -113,6 +159,12 @@ class Project: return files_with_content def save_file(self, data): + """ + Save a file. + + Args: + data (dict): File data. + """ # TODO fix this in prompts if ' ' in data['name'] or '.' not in data['name']: data['name'] = data['path'].rsplit('/', 1)[1] diff --git a/pilot/helpers/cli.py b/pilot/helpers/cli.py index a6aa2c3..d758320 100644 --- a/pilot/helpers/cli.py +++ b/pilot/helpers/cli.py @@ -24,6 +24,19 @@ def enqueue_output(out, q): out.close() def run_command(command, root_path, q_stdout, q_stderr, pid_container): + """ + Execute a command in a subprocess. + + Args: + command (str): The command to run. + root_path (str): The directory in which to run the command. + q_stdout (Queue): A queue to capture stdout. + q_stderr (Queue): A queue to capture stderr. + pid_container (list): A list to store the process ID. + + Returns: + subprocess.Popen: The subprocess object. + """ if platform.system() == 'Windows': # Check the operating system process = subprocess.Popen( command, @@ -68,6 +81,18 @@ def terminate_process(pid): pass def execute_command(project, command, timeout=None, force=False): + """ + Execute a command and capture its output. + + Args: + project: The project associated with the command. + command (str): The command to run. + timeout (int, optional): The maximum execution time in milliseconds. Default is None. + force (bool, optional): Whether to execute the command without confirmation. Default is False. + + Returns: + str: The command output. + """ if timeout is not None: if timeout < 1000: timeout *= 1000 @@ -210,12 +235,33 @@ def build_directory_tree(path, prefix="", ignore=None, is_last=False, files=None return output def execute_command_and_check_cli_response(command, timeout, convo): + """ + Execute a command and check its CLI response. + + Args: + command (str): The command to run. + timeout (int): The maximum execution time in milliseconds. + convo (AgentConvo): The conversation object. + + Returns: + tuple: A tuple containing the CLI response and the agent's response. + """ cli_response = execute_command(convo.agent.project, command, timeout) response = convo.send_message('dev_ops/ran_command.prompt', { 'cli_response': cli_response, 'command': command }) return cli_response, response def run_command_until_success(command, timeout, convo, additional_message=None, force=False): + """ + Run a command until it succeeds or reaches a timeout. + + Args: + command (str): The command to run. + timeout (int): The maximum execution time in milliseconds. + convo (AgentConvo): The conversation object. + additional_message (str, optional): Additional message to include in the response. + force (bool, optional): Whether to execute the command without confirmation. Default is False. + """ cli_response = execute_command(convo.agent.project, command, timeout, force) response = convo.send_message('dev_ops/ran_command.prompt', {'cli_response': cli_response, 'command': command, 'additional_message': additional_message}) @@ -230,6 +276,18 @@ def run_command_until_success(command, timeout, convo, additional_message=None, def debug(convo, command=None, user_input=None, issue_description=None): + """ + Debug a conversation. + + Args: + convo (AgentConvo): The conversation object. + command (dict, optional): The command to debug. Default is None. + user_input (str, optional): User input for debugging. Default is None. + issue_description (str, optional): Description of the issue to debug. Default is None. + + Returns: + bool: True if debugging was successful, False otherwise. + """ function_uuid = str(uuid.uuid4()) convo.save_branch(function_uuid) success = False From af6a972cba580e76c69c5746882ad0bcc406941a Mon Sep 17 00:00:00 2001 From: Dani Acosta Date: Tue, 5 Sep 2023 00:15:21 +0200 Subject: [PATCH 11/11] Add OPENAI_MODEL env var Adds a env variable OPENAI_MODEL to be able to use different models to GPT-4 --- pilot/utils/llm_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pilot/utils/llm_connection.py b/pilot/utils/llm_connection.py index c191d8e..58f4b6a 100644 --- a/pilot/utils/llm_connection.py +++ b/pilot/utils/llm_connection.py @@ -94,7 +94,7 @@ def create_gpt_chat_completion(messages: List[dict], req_type, min_tokens=MIN_TO raise ValueError(f'Too many tokens in messages: {tokens_in_messages}. Please try a different test.') gpt_data = { - 'model': 'gpt-4', + 'model': os.getenv('OPENAI_MODEL', 'gpt-4'), 'n': 1, 'max_tokens': min(4096, MAX_GPT_MODEL_TOKENS - tokens_in_messages), 'temperature': 1,