mirror of
https://github.com/Aider-AI/aider.git
synced 2025-05-30 17:24:59 +00:00
Switch from "edit block" to "search/replace block"
Succeeded in tricky task in the grep-ast codebase: - checkout ed714ffe58734 / tricky-search-and-replace-state - "read and parse .gitignore once, not each time we recurse `enumerate_files`" - was having a lot of trouble creating a head/updated block that matched the actual source code - new search/replace block does much better Benchmark had *best* try 1 result and *lowest* num_error_outputs ever seen on gpt-4-0613. Low num_error_outputs means it's less likely to elide/... code in the before block (original/search). ──────────── tmp.benchmarks/2023-10-25-22-03-19--search-and-replace-and-think ───────────── test-cases: 133 model: gpt-4 edit_format: diff commit_hash: c9c2ddb num_error_outputs: 6 num_user_asks: 0 num_exhausted_context_windows 0 test_timeouts: 2 50.4% correct after try 0 66.2% correct after try 1
This commit is contained in:
parent
aa18d0f946
commit
15d3a5d581
2 changed files with 31 additions and 33 deletions
|
@ -31,10 +31,10 @@ class EditBlockCoder(Coder):
|
||||||
continue
|
continue
|
||||||
raise ValueError(f"""InvalidEditBlock: edit failed!
|
raise ValueError(f"""InvalidEditBlock: edit failed!
|
||||||
|
|
||||||
{path} does not contain the *exact sequence* of HEAD lines you specified.
|
{path} does not contain the *exact chunk* of SEARCH lines you specified.
|
||||||
Try again.
|
Try again.
|
||||||
DO NOT skip blank lines, comments, docstrings, etc!
|
DO NOT skip blank lines, comments, docstrings, etc!
|
||||||
The HEAD block needs to be EXACTLY the same as the lines in {path} with nothing missing!
|
The SEARCH block needs to be EXACTLY the same as the lines in {path} with nothing missing!
|
||||||
|
|
||||||
{path} does not contain these {len(original.splitlines())} exact lines in a row:
|
{path} does not contain these {len(original.splitlines())} exact lines in a row:
|
||||||
```
|
```
|
||||||
|
@ -122,7 +122,7 @@ def try_dotdotdots(whole, part, replace):
|
||||||
replace_pieces = re.split(dots_re, replace)
|
replace_pieces = re.split(dots_re, replace)
|
||||||
|
|
||||||
if len(part_pieces) != len(replace_pieces):
|
if len(part_pieces) != len(replace_pieces):
|
||||||
raise ValueError("Unpaired ... in edit block")
|
raise ValueError("Unpaired ... in SEARCH/REPLACE block")
|
||||||
|
|
||||||
if len(part_pieces) == 1:
|
if len(part_pieces) == 1:
|
||||||
# no dots in this edit block, just return None
|
# no dots in this edit block, just return None
|
||||||
|
@ -132,7 +132,7 @@ def try_dotdotdots(whole, part, replace):
|
||||||
all_dots_match = all(part_pieces[i] == replace_pieces[i] for i in range(1, len(part_pieces), 2))
|
all_dots_match = all(part_pieces[i] == replace_pieces[i] for i in range(1, len(part_pieces), 2))
|
||||||
|
|
||||||
if not all_dots_match:
|
if not all_dots_match:
|
||||||
raise ValueError("Unmatched ... in edit block")
|
raise ValueError("Unmatched ... in SEARCH/REPLACE block")
|
||||||
|
|
||||||
part_pieces = [part_pieces[i] for i in range(0, len(part_pieces), 2)]
|
part_pieces = [part_pieces[i] for i in range(0, len(part_pieces), 2)]
|
||||||
replace_pieces = [replace_pieces[i] for i in range(0, len(replace_pieces), 2)]
|
replace_pieces = [replace_pieces[i] for i in range(0, len(replace_pieces), 2)]
|
||||||
|
@ -148,10 +148,10 @@ def try_dotdotdots(whole, part, replace):
|
||||||
whole += replace
|
whole += replace
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if whole.count(part) != 1:
|
if whole.count(part) == 0:
|
||||||
raise ValueError(
|
raise ValueError
|
||||||
"No perfect matching chunk in edit block with ... or part appears more than once"
|
if whole.count(part) > 1:
|
||||||
)
|
raise ValueError
|
||||||
|
|
||||||
whole = whole.replace(part, replace, 1)
|
whole = whole.replace(part, replace, 1)
|
||||||
|
|
||||||
|
@ -301,9 +301,9 @@ def do_replace(fname, content, before_text, after_text, fence=None):
|
||||||
return new_content
|
return new_content
|
||||||
|
|
||||||
|
|
||||||
HEAD = "<<<<<<< HEAD"
|
HEAD = "<<<<<<< SEARCH"
|
||||||
DIVIDER = "======="
|
DIVIDER = "======="
|
||||||
UPDATED = ">>>>>>> updated"
|
UPDATED = ">>>>>>> REPLACE"
|
||||||
|
|
||||||
separators = "|".join([HEAD, DIVIDER, UPDATED])
|
separators = "|".join([HEAD, DIVIDER, UPDATED])
|
||||||
|
|
||||||
|
@ -379,10 +379,10 @@ def find_original_update_blocks(content):
|
||||||
raise ValueError(f"{processed}\n^^^ {err}")
|
raise ValueError(f"{processed}\n^^^ {err}")
|
||||||
except IndexError:
|
except IndexError:
|
||||||
processed = "".join(processed)
|
processed = "".join(processed)
|
||||||
raise ValueError(f"{processed}\n^^^ Incomplete HEAD/updated block.")
|
raise ValueError(f"{processed}\n^^^ Incomplete SEARCH/REPLACE block.")
|
||||||
except Exception:
|
except Exception:
|
||||||
processed = "".join(processed)
|
processed = "".join(processed)
|
||||||
raise ValueError(f"{processed}\n^^^ Error parsing HEAD/updated block.")
|
raise ValueError(f"{processed}\n^^^ Error parsing SEARCH/REPLACE block.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -14,40 +14,38 @@ If the request is ambiguous, ask questions.
|
||||||
Once you understand the request you MUST:
|
Once you understand the request you MUST:
|
||||||
1. List the files you need to modify. *NEVER* suggest changes to a *read-only* file. Instead, 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.
|
1. List the files you need to modify. *NEVER* suggest changes to a *read-only* file. Instead, 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.
|
||||||
2. Think step-by-step and explain the needed changes.
|
2. Think step-by-step and explain the needed changes.
|
||||||
3. Describe each change with an *edit block* per the example below.
|
3. Describe each change with a *SEARCH/REPLACE block* per the example below.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
system_reminder = """You MUST format EVERY code change with an *edit block* like this:
|
system_reminder = """You MUST use a *SEARCH/REPLACE block* to modify the source file:
|
||||||
|
|
||||||
{fence[0]}python
|
{fence[0]}python
|
||||||
some/dir/example.py
|
some/dir/example.py
|
||||||
<<<<<<< HEAD
|
<<<<<<< SEARCH
|
||||||
# some comment
|
# Multiplication function
|
||||||
# Func to multiply
|
def multiply(a,b)
|
||||||
def mul(a,b)
|
"multiply 2 numbers"
|
||||||
|
|
||||||
|
return a*b
|
||||||
=======
|
=======
|
||||||
# updated comment
|
# Addition function
|
||||||
# Function to add
|
|
||||||
def add(a,b):
|
def add(a,b):
|
||||||
>>>>>>> updated
|
"add 2 numbers"
|
||||||
|
|
||||||
|
return a+b
|
||||||
|
>>>>>>> REPLACE
|
||||||
{fence[1]}
|
{fence[1]}
|
||||||
|
|
||||||
A program will parse the edit blocks you generate and replace the `HEAD` lines with the `updated` lines.
|
The *SEARCH* section must *EXACTLY MATCH* the existing source code, character for character.
|
||||||
So edit blocks must be precise and unambiguous!
|
|
||||||
|
|
||||||
Every *edit block* must be fenced with {fence[0]}...{fence[1]} with the correct code language.
|
Every *SEARCH/REPLACE block* must be fenced with {fence[0]} and {fence[1]}, with the correct code language.
|
||||||
Every *edit block* must start with the full path! *NEVER* propose edit blocks for *read-only* files.
|
|
||||||
|
|
||||||
The `HEAD` section must be an *exact set of sequential lines* from the file!
|
Every *SEARCH/REPLACE block* must start with the full path!
|
||||||
NEVER SKIP LINES in the `HEAD` section!
|
NEVER try to *SEARCH/REPLACE* any *read-only* files.
|
||||||
NEVER ELIDE LINES AND REPLACE THEM WITH A COMMENT!
|
|
||||||
NEVER OMIT ANY WHITESPACE in the `HEAD` section!
|
|
||||||
|
|
||||||
Edits to different parts of a file each need their own *edit block*, including the full path.
|
If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
||||||
|
|
||||||
If you want to put code in a new file, use an edit block with:
|
|
||||||
- A new file path, including dir name if needed
|
- A new file path, including dir name if needed
|
||||||
- An empty `HEAD` section
|
- An empty `SEARCH` section
|
||||||
- The new file's contents in the `updated` section
|
- The new file's contents in the `updated` section
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue