diff --git a/euclid/const/function_calls.py b/euclid/const/function_calls.py index 619ab73..3d39c99 100644 --- a/euclid/const/function_calls.py +++ b/euclid/const/function_calls.py @@ -43,6 +43,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.'): 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".', 'properties': { 'command': { 'type': 'string', @@ -154,6 +155,64 @@ DEV_TASKS_BREAKDOWN = { }, } +IMPLEMENT_TASK = { + 'definitions': [ + { + 'name': 'parse_development_task', + 'description': 'Breaks down the development task into smaller steps that need to be done to implement the entire task.', + 'parameters': { + 'type': 'object', + "properties": { + "tasks": { + 'type': 'array', + '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.', + 'properties': { + 'type': { + 'type': 'string', + 'enum': ['command', 'code_change', 'human_invervention'], + 'description': 'Type of the development step that needs to be done to complete the entire task.', + }, + 'command': command_definition(), + 'code_change': { + 'type': 'object', + 'description': 'A code change that needs to be implemented. This should be used only if the task is of a type "code_change".', + 'properties': { + 'name': { + 'type': 'string', + 'description': 'Name of the file that needs to be implemented.', + }, + 'path': { + 'type': 'string', + 'description': 'Full path of the file with the file name that needs to be implemented.', + }, + 'content': { + 'type': 'string', + 'description': 'Full content of the file that needs to be implemented.', + }, + }, + 'required': ['name', 'path', 'content'], + }, + 'human_intervention_description': { + 'type': 'string', + 'description': 'Description of a step in debugging this issue when there is a human intervention needed. This should be used only if the task is of a type "human_intervention".', + }, + }, + 'required': ['type'], + } + } + }, + "required": ['tasks'], + }, + }, + ], + 'functions': { + 'parse_development_task': lambda tasks: tasks + }, +} + DEV_STEPS = { 'definitions': [ { diff --git a/euclid/database/database.py b/euclid/database/database.py index ecd5b4f..5f6c1bf 100644 --- a/euclid/database/database.py +++ b/euclid/database/database.py @@ -154,10 +154,9 @@ def get_progress_steps(app_id, step=None): return steps -def get_db_model_from_hash_id(data_to_hash, model, app_id, previous_step): - hash_id = hash_data(data_to_hash) +def get_db_model_from_hash_id(model, app_id, previous_step): try: - db_row = model.get((model.hash_id == hash_id) & (model.app == app_id) & (model.previous_step == previous_step)) + db_row = model.get((model.app == app_id) & (model.previous_step == previous_step)) except DoesNotExist: return None return db_row @@ -217,7 +216,7 @@ def get_development_step_from_hash_id(project, prompt_path, prompt_data, llm_req 'prompt_data': {} if prompt_data is None else {k: v for k, v in prompt_data.items() if k not in PROMPT_DATA_TO_IGNORE}, 'llm_req_num': llm_req_num } - development_step = get_db_model_from_hash_id(data_to_hash, DevelopmentSteps, project.args['app_id'], project.checkpoints['last_development_step']) + development_step = get_db_model_from_hash_id(DevelopmentSteps, project.args['app_id'], project.checkpoints['last_development_step']) return development_step def save_command_run(project, command, cli_response): @@ -240,7 +239,7 @@ def get_command_run_from_hash_id(project, command): 'command': command, 'command_runs_count': project.command_runs_count } - command_run = get_db_model_from_hash_id(data_to_hash, CommandRuns, project.args['app_id'], project.checkpoints['last_command_run']) + command_run = get_db_model_from_hash_id(CommandRuns, project.args['app_id'], project.checkpoints['last_command_run']) return command_run def save_user_input(project, query, user_input): @@ -262,7 +261,7 @@ def get_user_input_from_hash_id(project, query): 'query': query, 'user_inputs_count': project.user_inputs_count } - user_input = get_db_model_from_hash_id(data_to_hash, UserInputs, project.args['app_id'], project.checkpoints['last_user_input']) + user_input = get_db_model_from_hash_id(UserInputs, project.args['app_id'], project.checkpoints['last_user_input']) return user_input def delete_all_subsequent_steps(project): diff --git a/euclid/helpers/AgentConvo.py b/euclid/helpers/AgentConvo.py index ee2609f..e9640a7 100644 --- a/euclid/helpers/AgentConvo.py +++ b/euclid/helpers/AgentConvo.py @@ -23,9 +23,7 @@ class AgentConvo: def send_message(self, prompt_path=None, prompt_data=None, function_calls=None): # craft message - if prompt_path is not None and prompt_data is not None: - prompt = get_prompt(prompt_path, prompt_data) - self.messages.append({"role": "user", "content": prompt}) + self.construct_and_add_message_from_prompt(prompt_path, prompt_data) if function_calls is not None and 'function_calls' in function_calls: self.messages[-1]['content'] += '\nMAKE SURE THAT YOU RESPOND WITH A CORRECT JSON FORMAT!!!' @@ -134,4 +132,9 @@ class AgentConvo: process.communicate(content.replace('{{messages}}', str(self.messages)).encode('utf-8')) def remove_last_x_messages(self, x): - self.messages = self.messages[:-x] \ No newline at end of file + self.messages = self.messages[:-x] + + def construct_and_add_message_from_prompt(self, prompt_path, prompt_data): + if prompt_path is not None and prompt_data is not None: + prompt = get_prompt(prompt_path, prompt_data) + self.messages.append({"role": "user", "content": prompt}) diff --git a/euclid/helpers/Project.py b/euclid/helpers/Project.py index fee995e..4a3ef1c 100644 --- a/euclid/helpers/Project.py +++ b/euclid/helpers/Project.py @@ -66,7 +66,7 @@ class Project: if 'skip_until_dev_step' in self.args: self.skip_until_dev_step = self.args['skip_until_dev_step'] if self.args['skip_until_dev_step'] == '0': - clear_directory(self.root_path, IGNORE_FOLDERS) + clear_directory(self.root_path) delete_all_app_development_data(self.args['app_id']) self.skip_steps = False else: @@ -117,18 +117,18 @@ class Project: data['path'], data['full_path'] = self.get_full_file_path(data['path'], data['name']) update_file(data['full_path'], data['content']) - file_in_db, created = File.get_or_create( - app=self.app, - name=data['name'], - path=data['path'], - full_path=data['full_path'], - ) + (File.insert(app=self.app, path=data['path'], name=data['name'], full_path=data['full_path']) + .on_conflict( + conflict_target=[File.app, File.name, File.path], + preserve=[], + update={ 'name': data['name'], 'path': data['path'], 'full_path': data['full_path'] }) + .execute()) def get_full_file_path(self, file_path, file_name): file_path = file_path.replace('./', '', 1).rstrip(file_name) - if not file_path.endswith('/'): - file_path = file_path + '/' + if file_path.endswith('/'): + file_path = file_path.rstrip('/') if file_name.startswith('/'): file_name = file_name[1:] @@ -136,7 +136,7 @@ class Project: if not file_path.startswith('/'): file_path = '/' + file_path - return (file_path, self.root_path + file_path + file_name) + return (file_path, self.root_path + file_path + '/' + file_name) def save_files_snapshot(self, development_step_id): files = get_files_content(self.root_path, ignore=IGNORE_FOLDERS) @@ -166,25 +166,29 @@ class Project: clear_directory(self.root_path, IGNORE_FOLDERS) for file_snapshot in file_snapshots: - full_path = self.root_path + file_snapshot.file.path + '/' + file_snapshot.file.name - # Ensure directory exists - os.makedirs(os.path.dirname(full_path), exist_ok=True) - - # Write/overwrite the file with its content - with open(full_path, 'w', encoding='utf-8') as f: - f.write(file_snapshot.content) + update_file(file_snapshot.file.full_path, file_snapshot.content); def delete_all_steps_except_current_branch(self): delete_unconnected_steps_from(self.checkpoints['last_development_step'], 'previous_step') delete_unconnected_steps_from(self.checkpoints['last_command_run'], 'previous_step') delete_unconnected_steps_from(self.checkpoints['last_user_input'], 'previous_step') - def ask_for_human_intervention(self, message, description): + def ask_for_human_intervention(self, message, description=None): print(colored(message, "yellow")) - print(description) + if description is not None: + print(description) answer = '' while answer != 'continue': answer = styled_text( self, 'Once you are ready, type "continue" to continue.', ) + + if answer != '' and answer != 'continue': + confirmation = styled_text( + self, + 'Do you want me to debug this by your instructions? If you mistyped and just want to continue, type "continue" and if you want me to debug this, just press ENTER', + ) + if confirmation == '': + print(colored('Ok, just a second.', "yellow")) + return answer diff --git a/euclid/helpers/agents/CodeMonkey.py b/euclid/helpers/agents/CodeMonkey.py index 0cdfca8..9442778 100644 --- a/euclid/helpers/agents/CodeMonkey.py +++ b/euclid/helpers/agents/CodeMonkey.py @@ -22,10 +22,12 @@ class CodeMonkey(Agent): changes = convo.send_message('development/implement_changes.prompt', { - "instructions": code_changes_description, + "step_description": code_changes_description, + "step_index": step_index, "directory_tree": self.project.get_directory_tree(True), "files": self.project.get_files(files_needed), }, IMPLEMENT_CHANGES) + convo.remove_last_x_messages(1) for file_data in changes: self.project.save_file(file_data) diff --git a/euclid/helpers/agents/Developer.py b/euclid/helpers/agents/Developer.py index b2c96f4..d12e1d9 100644 --- a/euclid/helpers/agents/Developer.py +++ b/euclid/helpers/agents/Developer.py @@ -1,6 +1,7 @@ import json import uuid from termcolor import colored +from utils.questionary import styled_text from helpers.files import update_file from utils.utils import step_already_finished from helpers.agents.CodeMonkey import CodeMonkey @@ -8,9 +9,9 @@ from logger.logger import logger from helpers.Agent import Agent from helpers.AgentConvo import AgentConvo from utils.utils import execute_step, array_of_objects_to_string, generate_app_data -from helpers.cli import build_directory_tree, run_command_until_success, execute_command_and_check_cli_response -from const.function_calls import FILTER_OS_TECHNOLOGIES, DEVELOPMENT_PLAN, EXECUTE_COMMANDS, DEV_STEPS, GET_TEST_TYPE, DEV_TASKS_BREAKDOWN -from database.database import save_progress, get_progress_steps +from helpers.cli import build_directory_tree, run_command_until_success, execute_command_and_check_cli_response, debug +from const.function_calls import FILTER_OS_TECHNOLOGIES, DEVELOPMENT_PLAN, EXECUTE_COMMANDS, GET_TEST_TYPE, DEV_TASKS_BREAKDOWN, IMPLEMENT_TASK +from database.database import save_progress, get_progress_steps, save_file_description from utils.utils import get_os_info from helpers.cli import execute_command @@ -25,21 +26,22 @@ class Developer(Agent): print(colored(f"Ok, great, now, let's start with the actual development...\n", "green")) logger.info(f"Starting to create the actual code...") - for i, dev_task in enumerate(self.project.development_plan): - self.implement_task(self.project.development_plan, i) + self.implement_task() # DEVELOPMENT END logger.info('The app is DONE!!! Yay...you can use it now.') - def implement_task(self, sibling_tasks, current_task_index, parent_task=None): + def implement_task(self): print(colored('-------------------------', 'green', attrs=['bold'])) - print(colored(f"Implementing task {current_task_index + 1}...\n", "green", attrs=['bold'])) - print(colored(sibling_tasks[current_task_index]['description'], 'green', attrs=['bold'])) + # print(colored(f"Implementing task {current_task_index + 1}...\n", "green", attrs=['bold'])) + print(colored(f"Implementing task...\n", "green", attrs=['bold'])) + # print(colored(sibling_tasks[current_task_index]['description'], 'green', attrs=['bold'])) + # print(colored(task_explanation, 'green', attrs=['bold'])) print(colored('-------------------------', 'green', attrs=['bold'])) convo_dev_task = AgentConvo(self) - task_steps = convo_dev_task.send_message('development/task/breakdown.prompt', { + task_description = convo_dev_task.send_message('development/task/breakdown.prompt', { "name": self.project.args['name'], "app_summary": self.project.project_description, "clarification": [], @@ -48,11 +50,13 @@ 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": current_task_index, - "sibling_tasks": sibling_tasks, - "parent_task": parent_task, - }, DEV_TASKS_BREAKDOWN) + # "current_task_index": current_task_index, + # "sibling_tasks": sibling_tasks, + # "parent_task": parent_task, + }) + 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) def execute_task(self, convo, task_steps, test_command=None, reset_convo=True, test_after_code_changes=True): @@ -64,32 +68,71 @@ class Developer(Agent): convo.load_branch(function_uuid) if step['type'] == 'command': - run_command_until_success(step['command']['command'], step['command']['timeout'], convo) + additional_message = 'Let\'s start with the step #0:\n\n' if i == 0 else f'So far, steps { ", ".join(f"#{j}" for j in range(i)) } are finished so let\'s do step #{i + 1} now.\n\n' + run_command_until_success(step['command']['command'], step['command']['timeout'], convo, additional_message=additional_message) - elif step['type'] == 'code_change': + elif step['type'] == 'code_change' and 'code_change_description' in step: + # TODO this should be refactored so it always uses the same function call print(f'Implementing code changes for `{step["code_change_description"]}`') code_monkey = CodeMonkey(self.project, self) updated_convo = code_monkey.implement_code_changes(convo, step['code_change_description'], i) if test_after_code_changes: self.test_code_changes(code_monkey, updated_convo) + elif step['type'] == 'code_change': + self.project.save_file(step['code_change']) + # self.project.save_file(step if 'code_change' not in step else step['code_change']) + elif step['type'] == 'human_intervention': - self.project.ask_for_human_intervention('I need your help! Can you try debugging this yourself and let me take over afterwards? Here are the details about the issue:', step['human_intervention_description']) - - if test_command is not None and step.get('check_if_fixed'): + user_feedback = self.project.ask_for_human_intervention('I need your help! Can you try debugging this yourself and let me take over afterwards? Here are the details about the issue:', step['human_intervention_description']) + if user_feedback is not None: + debug(convo, user_input=user_feedback, issue_description=step['human_intervention_description']) + + if test_command is not None and ('check_if_fixed' not in step or step['check_if_fixed']): should_rerun_command = convo.send_message('dev_ops/should_rerun_command.prompt', test_command) if should_rerun_command == 'NO': return True elif should_rerun_command == 'YES': - response = execute_command_and_check_cli_response(test_command['command'], test_command['timeout'], convo) - if response == 'NEEDS_DEBUGGING': + cli_response, llm_response = execute_command_and_check_cli_response(test_command['command'], test_command['timeout'], convo) + if llm_response == 'NEEDS_DEBUGGING': print(colored(f'Got incorrect CLI response:', 'red')) - print(response) + print(cli_response) print(colored('-------------------', 'red')) - if response == 'DONE': + if llm_response == 'DONE': return True + self.continue_development() + + def continue_development(self): + while True: + user_feedback = self.project.ask_for_human_intervention('Can you check if all this works?') + + if user_feedback == 'DONE': + return True + + if user_feedback is not None: + iteration_convo = AgentConvo(self) + iteration_convo.send_message('development/iteration.prompt', { + "name": self.project.args['name'], + "app_summary": self.project.project_description, + "clarification": [], + "user_stories": self.project.user_stories, + "user_tasks": self.project.user_tasks, + "technologies": self.project.architecture, + "array_of_objects_to_string": array_of_objects_to_string, + "directory_tree": self.project.get_directory_tree(True), + "files": self.project.get_all_coded_files(), + "user_input": user_feedback, + }) + + # debug(iteration_convo, user_input=user_feedback) + + task_steps = iteration_convo.send_message('development/parse_task.prompt', {}, IMPLEMENT_TASK) + iteration_convo.remove_last_x_messages(2) + self.execute_task(iteration_convo, task_steps) + + def set_up_environment(self): self.project.current_step = 'environment_setup' self.convo_os_specific_tech = AgentConvo(self) @@ -100,6 +143,10 @@ class Developer(Agent): step_already_finished(self.project.args, step) return + user_input = '' + while user_input != 'DONE': + user_input = styled_text(self.project, 'Please set up your local environment so that the technologies above can be utilized. When you\'re done, write "DONE"', 'yellow') + return # ENVIRONMENT SETUP print(colored(f"Setting up the environment...\n", "green")) logger.info(f"Setting up the environment...") @@ -109,7 +156,8 @@ class Developer(Agent): { "name": self.project.args['name'], "os_info": os_info, "technologies": self.project.architecture }, FILTER_OS_TECHNOLOGIES) for technology in os_specific_techologies: - llm_response = self.convo_os_specific_tech.send_message('development/env_setup/install_next_technology.prompt', + # TODO move the functions definisions to function_calls.py + cli_response, llm_response = self.convo_os_specific_tech.send_message('development/env_setup/install_next_technology.prompt', { 'technology': technology}, { 'definitions': [{ 'name': 'execute_command', diff --git a/euclid/helpers/cli.py b/euclid/helpers/cli.py index 207b3e7..adf9f78 100644 --- a/euclid/helpers/cli.py +++ b/euclid/helpers/cli.py @@ -154,41 +154,55 @@ def execute_command_and_check_cli_response(command, timeout, convo): 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 response + return cli_response, response -def run_command_until_success(command, timeout, convo): +def run_command_until_success(command, timeout, convo, additional_message=None): cli_response = execute_command(convo.agent.project, command, timeout) response = convo.send_message('dev_ops/ran_command.prompt', - {'cli_response': cli_response, 'command': command}) - command_successfully_executed = response == 'DONE' + {'cli_response': cli_response, 'command': command, 'additional_message': additional_message}) - function_uuid = str(uuid.uuid4()) - convo.save_branch(function_uuid) - for i in range(MAX_COMMAND_DEBUG_TRIES): - if command_successfully_executed: - break - - convo.load_branch(function_uuid) + if response != 'DONE': print(colored(f'Got incorrect CLI response:', 'red')) print(cli_response) print(colored('-------------------', 'red')) + + debug(convo, {'command': command, 'timeout': timeout}) + + + +def debug(convo, command=None, user_input=None, issue_description=None): + function_uuid = str(uuid.uuid4()) + convo.save_branch(function_uuid) + success = False + + for i in range(MAX_COMMAND_DEBUG_TRIES): + if success: + break + + convo.load_branch(function_uuid) + debugging_plan = convo.send_message('dev_ops/debug.prompt', - { 'command': command, 'debugging_try_num': i }, + { 'command': command['command'] if command is not None else None, 'user_input': user_input, 'issue_description': issue_description }, DEBUG_STEPS_BREAKDOWN) # TODO refactor to nicely get the developer agent - command_successfully_executed = convo.agent.project.developer.execute_task( + success = convo.agent.project.developer.execute_task( convo, debugging_plan, - {'command': command, 'timeout': timeout}, + command, False, False) - if not command_successfully_executed: + if not success: # TODO explain better how should the user approach debugging # we can copy the entire convo to clipboard so they can paste it in the playground - convo.agent.project.ask_for_human_intervention( - 'It seems like I cannot debug this problem by myself. Can you please help me and try debugging it yourself?', + user_input = convo.agent.project.ask_for_human_intervention( + 'It seems like I cannot debug this problem by myself. Can you please help me and try debugging it yourself?' if user_input is None else f'Can you check this again:\n{issue_description}?', command ) + + if user_input == 'continue': + success = True + + return success diff --git a/euclid/helpers/files.py b/euclid/helpers/files.py index b97676f..24f2d75 100644 --- a/euclid/helpers/files.py +++ b/euclid/helpers/files.py @@ -28,10 +28,11 @@ def get_files_content(directory, ignore=[]): with open(path, 'r', encoding='utf-8', errors='ignore') as f: file_content = f.read() - file_name = path.replace(directory + '/', '') + file_name = os.path.basename(path) + relative_path = path.replace(directory, '').replace('/' + file_name, '') return_array.append({ 'name': file_name, - 'path': '/' + file.replace(file_name, ''), + 'path': relative_path, 'content': file_content, 'full_path': path, }) diff --git a/euclid/prompts/architecture/technologies.prompt b/euclid/prompts/architecture/technologies.prompt index a2dbe02..4d7af79 100644 --- a/euclid/prompts/architecture/technologies.prompt +++ b/euclid/prompts/architecture/technologies.prompt @@ -27,4 +27,6 @@ Here are user tasks that specify what users need to do to interact with "{{ name {% endfor %} ``` -Now, based on the app's description, user stories and user tasks, think step by step and write up all technologies that will be used by your development team to create the app "{{ name }}". Do not write any explanations behind your choices but only a list of technologies that will be used. \ No newline at end of file +Now, based on the app's description, user stories and user tasks, think step by step and write up all technologies that will be used by your development team to create the app "{{ name }}". Do not write any explanations behind your choices but only a list of technologies that will be used. + +You do not need to list any technologies related to automated tests like Jest, Cypress, Mocha, Selenium, etc. \ No newline at end of file diff --git a/euclid/prompts/dev_ops/debug.prompt b/euclid/prompts/dev_ops/debug.prompt index 3bf8ff3..30de7cf 100644 --- a/euclid/prompts/dev_ops/debug.prompt +++ b/euclid/prompts/dev_ops/debug.prompt @@ -1,7 +1,14 @@ -Ok, we need to debug this issue so we can execute `{{ command }}` successfully. I want you to debug this issue by yourself and I will give you 2 functions that you can use - `run_command` and `implement_code_changes`. +{% if issue_description %} +You wanted me to check this - `{{ issue_description }}` but there was a problem{% else %}Ok, we need to debug this issue{% endif %}{% if command %} and we need to be able to execute `{{ command }}` successfully. {% else %}. Here is a brief explanation of what's happening: +``` +{{ user_input }} +``` +{% endif %}I want you to debug this issue by yourself and I will give you 2 functions that you can use - `run_command` and `implement_code_changes`. `run_command` function will run a command on the machine and will return the CLI output to you so you can see what to do next. `implement_code_changes` function will change the code where you just need to thoroughly describe what needs to be implmemented, I will implement the requested changes and let you know. -After this, you need to deside what to do next. You can rerun the command `{{ command }}` to check if the problem is fixed or run another command with `run_command` or change more code with `implement_code_changes`. \ No newline at end of file +Return a list of steps that are needed to debug this issue. By the time we execute the last step, the issue should be fixed completely. Also, make sure that at least the last step has `check_if_fixed` set to TRUE. + +{# After this, you need to decide what to do next. You can rerun the command `{{ command }}` to check if the problem is fixed or run another command with `run_command` or change more code with `implement_code_changes`. #} diff --git a/euclid/prompts/development/task/breakdown.prompt b/euclid/prompts/development/task/breakdown.prompt index bba56ae..c92fa06 100644 --- a/euclid/prompts/development/task/breakdown.prompt +++ b/euclid/prompts/development/task/breakdown.prompt @@ -4,15 +4,7 @@ Here is a high level description of "{{ name }}": ``` {{ app_summary }} ``` -{# -Here are some additional questions and answers to clarify the apps description: -``` -{% for clarification in clarifications %} -Q: {{ clarification.question }} -A: {{ clarification.answer }} -{% endfor %} -``` -#} + Here are user stories that specify how users use "{{ name }}": ```{% for story in user_stories %} - {{ story }}{% endfor %} @@ -39,50 +31,8 @@ We've broken it down to these subtasks: ``` {% endif %} -{% if current_task_index != 0 %} -So far, these tasks are done: -``` -{% for task in sibling_tasks[0:current_task_index] %} -#{{ loop.index }} {{ task['description'] }} -{% endfor %} -``` -so let's the next task which is this: -{% else %} -Let's start with the task #0: -{% endif %} -``` -{{ array_of_objects_to_string(sibling_tasks[current_task_index]) }} -``` -{# -Think step by step about what needs to be done to complete this task. -{% if sibling_tasks[current_task_index]['type'] == 'COMMAND' %} -Respond with all commands that need to be run to fulfill this step. -{% elif sibling_tasks[current_task_index]['type'] == 'CODE_CHANGE' %} -First, you need to know the code that's currently written so that you can appropriately write new or update the existing code. Here are all the file that are written so far in a file tree format: -``` -{{ directory_tree }} -``` +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. -You can get the list of files by calling `get_files` function. -{% else %} -#} +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. -Here are all the file that are written so far in a file tree format: -``` -{{ directory_tree }} -``` - -First, just make a list of steps we need to do to fulfill this task. It should be in a JSON array. Every step must NOT contain both a command that needs to be run and the code that needs to be changed. It can be either command (or multiple commands) that need to be run or a change in the code. -{# -Each step must start with a keyword `command` in case the step consists of commands that need to be run or `code_change` in case it consists of changes in the code. After the keyword, write a description of what will be done in that step. Do not write what needs to be done for each step but only list them in an array. -#} -If a step involves changing the code, it must include code changes for only one single file. In case you need to change multiple files, split it into multiple steps where each involves changes for a single file. - -You can also request a human intervention if needed. For example, if you need an API key to some service, you should make one task for human to get an API key and put it where you think is appropriate. Just make sure that you describe the task is enough details so that a person reading it can be completely sure what to do. - -You have all the technologies installed on the machine (not dependencies but the technologies like databases, etc.) and you have a folder set up for this project. All commands that you specify will be ran within that folder. - -Also, keep in mind that you also need to write test (or tests) that will programmatically verify that your task is complete. -{# -{% endif %} -#} \ No newline at end of file +Remember, I'm currently in an empty folder where I will start writing files that you tell me. Also, 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. \ No newline at end of file diff --git a/euclid/prompts/development/task/next_step.prompt b/euclid/prompts/development/task/next_step.prompt index 8b47680..28d97ab 100644 --- a/euclid/prompts/development/task/next_step.prompt +++ b/euclid/prompts/development/task/next_step.prompt @@ -9,11 +9,11 @@ This step by step about what needs to be done to fulfill this step. {% if step_type == 'COMMAND' %} Respond with all commands that need to be run to fulfill this step. In case this is a complex step that can't be completed by only running commands (maybe it requires some code changes or user review), respond with an array with only one item `COMPLEX`. Like this - `[{"command": "COMPLEX", "timeout": 0}]` {% elif step_type == 'CODE_CHANGE' %} -First, you need to know the code that's currently written so that you can appropriately write new or update the existing code. Here are all the file that are written so far in a file tree format: +First, you need to know the code that's currently written so that you can appropriately write new or update the existing code. {# Here are all the file that are written so far in a file tree format: ``` {{ directory_tree }} ``` - +#} Respond with a list of files that you need to see before you can write the code for the current step. This list needs to be in a JSON array where each item is a file name. Do not respond with anything other than the mentioned JSON array. {% endif %} \ No newline at end of file diff --git a/euclid/prompts/development/task/request_files_for_code_changes.prompt b/euclid/prompts/development/task/request_files_for_code_changes.prompt index df8e4df..da866ad 100644 --- a/euclid/prompts/development/task/request_files_for_code_changes.prompt +++ b/euclid/prompts/development/task/request_files_for_code_changes.prompt @@ -9,11 +9,11 @@ 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. - +{# 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. +{# Here is the current folder tree: ``` {{ directory_tree }} ``` - +#} Remember, ask for files relative to the project root. For example, if you need a file with path `{project_root}/models/model.py`, you need to request the file `models/model.py`. \ No newline at end of file diff --git a/euclid/prompts/system_messages/full_stack_developer.prompt b/euclid/prompts/system_messages/full_stack_developer.prompt index 055ce13..cc5d7a7 100644 --- a/euclid/prompts/system_messages/full_stack_developer.prompt +++ b/euclid/prompts/system_messages/full_stack_developer.prompt @@ -1 +1 @@ -You are a full stack software developer who works in a software development agency. You write very modular code and you practice TDD (test driven development) whenever is suitable to use it. Your job is to implement tasks that your tech lead assigns you. Each task has a description of what needs to be implemented, a programmatic goal that will determine if a task can be marked as done from a programmatic perspective (this is basically a blueprint for an automated test that is run before you send the task for a review to your tech lead) and user-review goal that will determine if a task is done or not but from a user perspective since it will be reviewed by a human. \ No newline at end of file +{#You are a full stack software developer who works in a software development agency. You write very modular code and you practice TDD (test driven development) whenever is suitable to use it. Your job is to implement tasks that your tech lead assigns you. Each task has a description of what needs to be implemented, a programmatic goal that will determine if a task can be marked as done from a programmatic perspective (this is basically a blueprint for an automated test that is run before you send the task for a review to your tech lead) and user-review goal that will determine if a task is done or not but from a user perspective since it will be reviewed by a human.#} \ No newline at end of file diff --git a/euclid/utils/llm_connection.py b/euclid/utils/llm_connection.py index 8c38faf..c35b2c0 100644 --- a/euclid/utils/llm_connection.py +++ b/euclid/utils/llm_connection.py @@ -11,7 +11,8 @@ from jinja2 import Environment, FileSystemLoader from const.llm import MIN_TOKENS_FOR_GPT_RESPONSE, MAX_GPT_MODEL_TOKENS, MAX_QUESTIONS, END_RESPONSE from logger.logger import logger from termcolor import colored -from utils.utils import get_prompt_components, fix_json_newlines +from utils.utils import get_prompt_components, fix_json +from utils.spinner import spinner_start, spinner_stop def connect_to_llm(): @@ -96,6 +97,10 @@ def create_gpt_chat_completion(messages: List[dict], req_type, min_tokens=MIN_TO 'model': 'gpt-4', 'n': 1, 'max_tokens': min(4096, MAX_GPT_MODEL_TOKENS - tokens_in_messages), + 'temperature': 1, + 'top_p': 1, + 'presence_penalty': 0, + 'frequency_penalty': 0, 'messages': messages, 'stream': True } @@ -200,15 +205,17 @@ def stream_gpt_completion(data, req_type): continue try: - json_line = load_data_to_json(line) + json_line = json.loads(line) if 'error' in json_line: logger.error(f'Error in LLM response: {json_line}') raise ValueError(f'Error in LLM response: {json_line["error"]["message"]}') + if json_line['choices'][0]['finish_reason'] == 'function_call': function_calls['arguments'] = load_data_to_json(function_calls['arguments']) return return_result({'function_calls': function_calls}, lines_printed); json_line = json_line['choices'][0]['delta'] + except json.JSONDecodeError: logger.error(f'Unable to decode line: {line}') continue # skip to the next line @@ -250,4 +257,4 @@ def postprocessing(gpt_response, req_type): def load_data_to_json(string): - return json.loads(fix_json_newlines(string)) + return json.loads(fix_json(string)) diff --git a/euclid/utils/utils.py b/euclid/utils/utils.py index 6fea50d..771dafb 100644 --- a/euclid/utils/utils.py +++ b/euclid/utils/utils.py @@ -145,6 +145,12 @@ def replace_functions(obj): else: return obj +def fix_json(s): + s = s.replace('True', 'true') + s = s.replace('False', 'false') + # s = s.replace('`', '"') + return fix_json_newlines(s) + def fix_json_newlines(s): pattern = r'("(?:\\\\n|\\.|[^"\\])*")'