diff --git a/aider/coders/ask_prompts.py b/aider/coders/ask_prompts.py index 84935a3ce..91ee8a6af 100644 --- a/aider/coders/ask_prompts.py +++ b/aider/coders/ask_prompts.py @@ -20,6 +20,10 @@ Always reply in the same language as the change request. Other messages in the chat may contain outdated versions of the files' contents. """ # noqa: E501 + files_content_assistant_reply = ( + "Ok, I will use that as the true, current contents of the files." + ) + files_no_full_files = "I am not sharing the full contents of any files with you yet." files_no_full_files_with_repo_map = "" diff --git a/aider/coders/base_coder.py b/aider/coders/base_coder.py index 23cc0f7ec..0e1a6eaa8 100755 --- a/aider/coders/base_coder.py +++ b/aider/coders/base_coder.py @@ -648,7 +648,7 @@ class Coder: if self.abs_fnames: files_content = self.gpt_prompts.files_content_prefix files_content += self.get_files_content() - files_reply = "Ok, any changes I propose will be to those files." + files_reply = self.gpt_prompts.files_content_assistant_reply elif self.get_repo_map() and self.gpt_prompts.files_no_full_files_with_repo_map: files_content = self.gpt_prompts.files_no_full_files_with_repo_map files_reply = self.gpt_prompts.files_no_full_files_with_repo_map_reply diff --git a/aider/coders/base_prompts.py b/aider/coders/base_prompts.py index 7e07a4f54..c431c7520 100644 --- a/aider/coders/base_prompts.py +++ b/aider/coders/base_prompts.py @@ -22,6 +22,8 @@ You always COMPLETELY IMPLEMENT the needed code! Any other messages in the chat may contain outdated versions of the files' contents. """ # noqa: E501 + files_content_assistant_reply = "Ok, any changes I propose will be to those files." + files_no_full_files = "I am not sharing any files that you can edit yet." files_no_full_files_with_repo_map = """Don't try and edit any existing code without asking me to add the files to the chat! diff --git a/aider/coders/editblock_prompts.py b/aider/coders/editblock_prompts.py index 0e8431f16..4d3502521 100644 --- a/aider/coders/editblock_prompts.py +++ b/aider/coders/editblock_prompts.py @@ -4,9 +4,27 @@ from .base_prompts import CoderPrompts class EditBlockPrompts(CoderPrompts): - main_system = """Act as an expert software developer who edits source code. + main_system = """Act as an expert software developer. +Always use best practices when coding. +Respect and use existing conventions, libraries, etc that are already present in the code base. {lazy_prompt} -Describe each change with a *SEARCH/REPLACE block* per the examples below. +Take requests for changes to the supplied code. +If the request is ambiguous, ask questions. + +Always reply to the user in the same language they are using. + +Once you understand the request you MUST: + +1. Decide if you need to propose *SEARCH/REPLACE* edits to any files that haven't been added to the chat. You can create new files without asking! + +But if you need to propose edits to existing files not already added to the chat, you *MUST* tell the user their full path names and ask them to *add the files to the chat*. +End your reply and wait for their approval. +You can keep asking if you then decide you need to edit more files. + +2. Think step-by-step and explain the needed changes in a few short sentences. + +3. Describe each change with a *SEARCH/REPLACE block* per the examples below. + All changes to files must use this *SEARCH/REPLACE block* format. ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*! {shell_cmd_prompt} @@ -42,7 +60,14 @@ Keep in mind these details about the user's platform and environment: ), dict( role="assistant", - content=""" + content="""To make this change we need to modify `mathweb/flask/app.py` to: + +1. Import the math package. +2. Remove the existing factorial() function. +3. Update get_factorial() to call math.factorial instead. + +Here are the *SEARCH/REPLACE* blocks: + mathweb/flask/app.py {fence[0]}python <<<<<<< SEARCH @@ -84,7 +109,13 @@ mathweb/flask/app.py ), dict( role="assistant", - content=""" + content="""To make this change we need to modify `main.py` and make a new file `hello.py`: + +1. Make a new hello.py file with hello() in it. +2. Remove hello() from main.py and replace it with an import. + +Here are the *SEARCH/REPLACE* blocks: + hello.py {fence[0]}python <<<<<<< SEARCH diff --git a/aider/coders/junior_editblock_coder.py b/aider/coders/junior_editblock_coder.py new file mode 100644 index 000000000..5d4658244 --- /dev/null +++ b/aider/coders/junior_editblock_coder.py @@ -0,0 +1,7 @@ +from .base_coder import Coder +from .junior_editblock_prompts import JuniorEditBlockPrompts + + +class JuniorEditBlockCoder(EditBlockCoder): + edit_format = "junior-diff" + gpt_prompts = JuniorEditBlockPrompts() diff --git a/aider/coders/junior_editblock_prompts.py b/aider/coders/junior_editblock_prompts.py new file mode 100644 index 000000000..0e8431f16 --- /dev/null +++ b/aider/coders/junior_editblock_prompts.py @@ -0,0 +1,166 @@ +# flake8: noqa: E501 + +from .base_prompts import CoderPrompts + + +class EditBlockPrompts(CoderPrompts): + main_system = """Act as an expert software developer who edits source code. +{lazy_prompt} +Describe each change with a *SEARCH/REPLACE block* per the examples below. +All changes to files must use this *SEARCH/REPLACE block* format. +ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*! +{shell_cmd_prompt} +""" + + shell_cmd_prompt = """ +4. *Concisely* suggest any shell commands the user might want to run in ```bash blocks. + +Just suggest shell commands this way, not example code. +Only suggest complete shell commands that area ready to execute, without placeholders. +Only suggest at most a few shell commands at a time, not more than 1-3. + +Use the appropriate shell based on the user's system info: +{platform} +Examples of when to suggest shell commands: + +- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content. +- If you changed a CLI program, suggest the command to run it to see the new behavior. +- If you added a test, suggest how to run it with the testing tool used by the project. +- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations. +- If your code changes add new dependencies, suggest the command to install them. +- Etc. +""" + + no_shell_cmd_prompt = """ +Keep in mind these details about the user's platform and environment: +{platform} +""" + example_messages = [ + dict( + role="user", + content="Change get_factorial() to use math.factorial", + ), + dict( + role="assistant", + content=""" +mathweb/flask/app.py +{fence[0]}python +<<<<<<< SEARCH +from flask import Flask +======= +import math +from flask import Flask +>>>>>>> REPLACE +{fence[1]} + +mathweb/flask/app.py +{fence[0]}python +<<<<<<< SEARCH +def factorial(n): + "compute factorial" + + if n == 0: + return 1 + else: + return n * factorial(n-1) + +======= +>>>>>>> REPLACE +{fence[1]} + +mathweb/flask/app.py +{fence[0]}python +<<<<<<< SEARCH + return str(factorial(n)) +======= + return str(math.factorial(n)) +>>>>>>> REPLACE +{fence[1]} +""", + ), + dict( + role="user", + content="Refactor hello() into its own file.", + ), + dict( + role="assistant", + content=""" +hello.py +{fence[0]}python +<<<<<<< SEARCH +======= +def hello(): + "print a greeting" + + print("hello") +>>>>>>> REPLACE +{fence[1]} + +main.py +{fence[0]}python +<<<<<<< SEARCH +def hello(): + "print a greeting" + + print("hello") +======= +from hello import hello +>>>>>>> REPLACE +{fence[1]} +""", + ), + ] + + system_reminder = """# *SEARCH/REPLACE block* Rules: + +Every *SEARCH/REPLACE block* must use this format: +1. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc. +2. The opening fence and code language, eg: {fence[0]}python +3. The start of search block: <<<<<<< SEARCH +4. A contiguous chunk of lines to search for in the existing source code +5. The dividing line: ======= +6. The lines to replace into the source code +7. The end of the replace block: >>>>>>> REPLACE +8. The closing fence: {fence[1]} + +Use the *FULL* file path, as shown to you by the user. + +Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc. +If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup. + +*SEARCH/REPLACE* blocks will replace *all* matching occurrences. +Include enough lines to make the SEARCH blocks uniquely match the lines to change. + +Keep *SEARCH/REPLACE* blocks concise. +Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file. +Include just the changing lines, and a few surrounding lines if needed for uniqueness. +Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks. + +Only create *SEARCH/REPLACE* blocks for files that the user has added to the chat! + +To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location. + +Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file. + +If you want to put code in a new file, use a *SEARCH/REPLACE block* with: +- A new file path, including dir name if needed +- An empty `SEARCH` section +- The new file's contents in the `REPLACE` section + +To rename files which have been added to the chat, use shell commands at the end of your response. + +{lazy_prompt} +ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*! +{shell_cmd_reminder} +""" + + shell_cmd_reminder = """ +Examples of when to suggest shell commands: + +- If you changed a self-contained html file, suggest an OS-appropriate command to open a browser to view it to see the updated content. +- If you changed a CLI program, suggest the command to run it to see the new behavior. +- If you added a test, suggest how to run it with the testing tool used by the project. +- Suggest OS-appropriate commands to delete or rename files/directories, or other file system operations. +- If your code changes add new dependencies, suggest the command to install them. +- Etc. +""" diff --git a/aider/coders/junior_whole_coder.py b/aider/coders/junior_whole_coder.py new file mode 100644 index 000000000..ed350cf3c --- /dev/null +++ b/aider/coders/junior_whole_coder.py @@ -0,0 +1,7 @@ +from .base_coder import Coder +from .junior_whole_prompts import JuniorWholePrompts + + +class JuniorWholeCoder(WholeCoder): + edit_format = "junior-whole" + gpt_prompts = JuniorWholePrompts() diff --git a/aider/coders/junior_whole_prompts.py b/aider/coders/junior_whole_prompts.py new file mode 100644 index 000000000..c124ad22f --- /dev/null +++ b/aider/coders/junior_whole_prompts.py @@ -0,0 +1,55 @@ +# flake8: noqa: E501 + +from .base_prompts import CoderPrompts + + +class WholeFilePrompts(CoderPrompts): + main_system = """Act as an expert software developer and make changes to source code. +{lazy_prompt} +Output a copy of each file that needs changes. +""" + + example_messages = [ + dict( + role="user", + content="Change the greeting to be more casual", + ), + dict( + role="assistant", + content=""" +show_greeting.py +{fence[0]} +import sys + +def greeting(name): + print(f"Hey {{name}}") + +if __name__ == '__main__': + greeting(sys.argv[1]) +{fence[1]} +""", + ), + ] + + system_reminder = """You MUST return the entire content of the updated file. +You MUST use this *file listing* format: + +path/to/filename.js +{fence[0]} +// entire file content ... +// ... goes in between +{fence[1]} + +Every *file listing* MUST use this format: +- First line: the filename with any originally provided path +- Second line: opening {fence[0]} +- ... entire content of the file ... +- Final line: closing {fence[1]} + +*NEVER* skip, omit or elide content from a *file listing* using "..." or by adding comments like "... rest of code..."! +Create a new file you MUST return a *file listing* which includes an appropriate filename, including any appropriate path. + +{lazy_prompt} +""" + + redacted_edit_message = "No changes are needed." diff --git a/aider/coders/senior_coder.py b/aider/coders/senior_coder.py new file mode 100644 index 000000000..bd2384c64 --- /dev/null +++ b/aider/coders/senior_coder.py @@ -0,0 +1,7 @@ +from .base_coder import Coder +from .senior_prompts import SeniorPrompts + + +class SeniorCoder(AskCoder): + edit_format = "senior" + gpt_prompts = SeniorPrompts() diff --git a/aider/coders/senior_prompts.py b/aider/coders/senior_prompts.py new file mode 100644 index 000000000..91ee8a6af --- /dev/null +++ b/aider/coders/senior_prompts.py @@ -0,0 +1,37 @@ +# flake8: noqa: E501 + +from .base_prompts import CoderPrompts + + +class AskPrompts(CoderPrompts): + main_system = """Act as an expert senior engineer and provide direction to your junior engineer. +Study the change request and the current code. +Describe how to modify the code to complete the request. +Explain all needed code changes clearly and completely, but concisely. +The junior engineer will rely solely on your instructions, so make them unambiguous and complete. + +Always reply in the same language as the change request. +""" + + example_messages = [] + + files_content_prefix = """I have *added these files to the chat* so you see all of their contents. +*Trust this message as the true contents of the files!* +Other messages in the chat may contain outdated versions of the files' contents. +""" # noqa: E501 + + files_content_assistant_reply = ( + "Ok, I will use that as the true, current contents of the files." + ) + + files_no_full_files = "I am not sharing the full contents of any files with you yet." + + files_no_full_files_with_repo_map = "" + files_no_full_files_with_repo_map_reply = "" + + repo_content_prefix = """I am working with you on code in a git repository. +Here are summaries of some files present in my git repo. +If you need to see the full contents of any files to answer my questions, ask me to *add them to the chat*. +""" + + system_reminder = "" diff --git a/aider/coders/wholefile_prompts.py b/aider/coders/wholefile_prompts.py index c124ad22f..66eac016a 100644 --- a/aider/coders/wholefile_prompts.py +++ b/aider/coders/wholefile_prompts.py @@ -4,9 +4,17 @@ from .base_prompts import CoderPrompts class WholeFilePrompts(CoderPrompts): - main_system = """Act as an expert software developer and make changes to source code. + main_system = """Act as an expert software developer. +Take requests for changes to the supplied code. +If the request is ambiguous, ask questions. + +Always reply to the user in the same language they are using. + {lazy_prompt} -Output a copy of each file that needs changes. +Once you understand the request you MUST: +1. Determine if any code changes are needed. +2. Explain any needed changes. +3. If changes are needed, output a copy of each file that needs changes. """ example_messages = [ @@ -16,7 +24,10 @@ Output a copy of each file that needs changes. ), dict( role="assistant", - content=""" + content="""Ok, I will: + +1. Switch the greeting text from "Hello" to "Hey". + show_greeting.py {fence[0]} import sys @@ -31,7 +42,7 @@ if __name__ == '__main__': ), ] - system_reminder = """You MUST return the entire content of the updated file. + system_reminder = """To suggest changes to a file you MUST return the entire content of the updated file. You MUST use this *file listing* format: path/to/filename.js @@ -46,6 +57,7 @@ Every *file listing* MUST use this format: - ... entire content of the file ... - Final line: closing {fence[1]} +To suggest changes to a file you MUST return a *file listing* that contains the entire content of the file. *NEVER* skip, omit or elide content from a *file listing* using "..." or by adding comments like "... rest of code..."! Create a new file you MUST return a *file listing* which includes an appropriate filename, including any appropriate path. diff --git a/benchmark/benchmark.py b/benchmark/benchmark.py index 6459ba356..45e14ab79 100755 --- a/benchmark/benchmark.py +++ b/benchmark/benchmark.py @@ -550,15 +550,16 @@ def run_test_real( ) # senior_model = models.Model("o1-mini") - senior_model = models.Model("o1-preview") + # senior_model = models.Model("o1-preview") # senior_model = models.Model("gpt-4o") - # senior_model = models.Model("openrouter/anthropic/claude-3.5-sonnet") + senior_model = models.Model("openrouter/anthropic/claude-3.5-sonnet") # senior_model = models.Model("openrouter/deepseek/deepseek-chat") + # junior_model = models.Model("gemini/gemini-1.5-flash-8b-exp-0924") # junior_model = models.Model("gpt-4o") # junior_model = models.Model("openrouter/anthropic/claude-3.5-sonnet") - junior_model = models.Model("openrouter/deepseek/deepseek-chat") - # junior_model = models.Model("openrouter/anthropic/claude-3-haiku-20240307") + # junior_model = models.Model("openrouter/deepseek/deepseek-chat") + junior_model = models.Model("openrouter/anthropic/claude-3-haiku-20240307") # junior_model = models.Model("gpt-4o") # junior_model = models.Model("gpt-4o-mini") # junior_model = models.Model("openrouter/meta-llama/llama-3.1-8b-instruct")