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:
Paul Gauthier 2023-10-25 15:24:03 -07:00
parent aa18d0f946
commit 15d3a5d581
2 changed files with 31 additions and 33 deletions

View file

@ -31,10 +31,10 @@ class EditBlockCoder(Coder):
continue
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.
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:
```
@ -122,7 +122,7 @@ def try_dotdotdots(whole, part, replace):
replace_pieces = re.split(dots_re, replace)
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:
# 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))
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)]
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
continue
if whole.count(part) != 1:
raise ValueError(
"No perfect matching chunk in edit block with ... or part appears more than once"
)
if whole.count(part) == 0:
raise ValueError
if whole.count(part) > 1:
raise ValueError
whole = whole.replace(part, replace, 1)
@ -301,9 +301,9 @@ def do_replace(fname, content, before_text, after_text, fence=None):
return new_content
HEAD = "<<<<<<< HEAD"
HEAD = "<<<<<<< SEARCH"
DIVIDER = "======="
UPDATED = ">>>>>>> updated"
UPDATED = ">>>>>>> REPLACE"
separators = "|".join([HEAD, DIVIDER, UPDATED])
@ -379,10 +379,10 @@ def find_original_update_blocks(content):
raise ValueError(f"{processed}\n^^^ {err}")
except IndexError:
processed = "".join(processed)
raise ValueError(f"{processed}\n^^^ Incomplete HEAD/updated block.")
raise ValueError(f"{processed}\n^^^ Incomplete SEARCH/REPLACE block.")
except Exception:
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__":

View file

@ -14,40 +14,38 @@ If the request is ambiguous, ask questions.
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.
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
some/dir/example.py
<<<<<<< HEAD
# some comment
# Func to multiply
def mul(a,b)
<<<<<<< SEARCH
# Multiplication function
def multiply(a,b)
"multiply 2 numbers"
return a*b
=======
# updated comment
# Function to add
# Addition function
def add(a,b):
>>>>>>> updated
"add 2 numbers"
return a+b
>>>>>>> REPLACE
{fence[1]}
A program will parse the edit blocks you generate and replace the `HEAD` lines with the `updated` lines.
So edit blocks must be precise and unambiguous!
The *SEARCH* section must *EXACTLY MATCH* the existing source code, character for character.
Every *edit block* must be fenced with {fence[0]}...{fence[1]} with the correct code language.
Every *edit block* must start with the full path! *NEVER* propose edit blocks for *read-only* files.
Every *SEARCH/REPLACE block* must be fenced with {fence[0]} and {fence[1]}, with the correct code language.
The `HEAD` section must be an *exact set of sequential lines* from the file!
NEVER SKIP LINES in the `HEAD` section!
NEVER ELIDE LINES AND REPLACE THEM WITH A COMMENT!
NEVER OMIT ANY WHITESPACE in the `HEAD` section!
Every *SEARCH/REPLACE block* must start with the full path!
NEVER try to *SEARCH/REPLACE* any *read-only* files.
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 an edit block with:
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 `HEAD` section
- An empty `SEARCH` section
- The new file's contents in the `updated` section
"""