Validate to_replace in edit_file_by_replace AgentSkill (#3073)

* Validate to_replace in edit_file_by_replace AgentSkill

* Remove redundant replace reminder prompt

* Add unit tests

* Fix prompt
This commit is contained in:
Boxuan Li
2024-07-22 21:01:35 -07:00
committed by GitHub
parent 41a8bb3cf1
commit 445f290beb
34 changed files with 85 additions and 93 deletions

View File

@@ -605,7 +605,6 @@ def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> N
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
@@ -650,12 +649,20 @@ def edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> N
if to_replace.strip() == '':
raise ValueError('`to_replace` must not be empty.')
if to_replace == new_content:
raise ValueError('`to_replace` and `new_content` must be different.')
# search for `to_replace` in the file
# if found, replace it with `new_content`
# if not found, perform a fuzzy search to find the closest match and replace it with `new_content`
with open(file_name, 'r') as file:
file_content = file.read()
if file_content.count(to_replace) > 1:
raise ValueError(
'`to_replace` appears more than once, please include enough lines to make code in `to_replace` unique.'
)
start = file_content.find(to_replace)
if start != -1:
# Convert start from index to line number

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -58,7 +58,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1
@@ -602,13 +601,13 @@ open_file("bad.txt")
----------
OBSERVATION:
[File: /workspace/bad.txt (4 lines total)]
(this is the beginning of the file)
1|This is a stupid typoo.
2|Really?
3|No mor typos!
4|Enjoy!
(this is the end of the file)
[File: /workspace/bad.txt (4 lines total)]
(this is the beginning of the file)
1|This is a stupid typoo.
2|Really?
3|No mor typos!
4|Enjoy!
(this is the end of the file)
----------
@@ -630,12 +629,12 @@ edit_file_by_replace(
----------
OBSERVATION:
[No exact match found in bad.txt for
```
This is a stupid typoo.
No mor typos!
```
]
[No exact match found in bad.txt for
```
This is a stupid typoo.
No mor typos!
```
]
----------
@@ -657,14 +656,14 @@ edit_file_by_replace(
----------
OBSERVATION:
[File: /workspace/bad.txt (4 lines total after edit)]
(this is the beginning of the file)
1|This is a stupid typo.
2|Really?
3|No mor typos!
4|Enjoy!
(this is the end of the file)
[File updated (edited at line 1). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
[File: /workspace/bad.txt (4 lines total after edit)]
(this is the beginning of the file)
1|This is a stupid typo.
2|Really?
3|No mor typos!
4|Enjoy!
(this is the end of the file)
[File updated (edited at line 1). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
----------
@@ -681,14 +680,14 @@ edit_file_by_replace(
----------
OBSERVATION:
[File: /workspace/bad.txt (4 lines total after edit)]
(this is the beginning of the file)
1|This is a stupid typo.
2|Really?
3|No more typos!
4|Enjoy!
(this is the end of the file)
[File updated (edited at line 3). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
[File: /workspace/bad.txt (4 lines total after edit)]
(this is the beginning of the file)
1|This is a stupid typo.
2|Really?
3|No more typos!
4|Enjoy!
(this is the end of the file)
[File updated (edited at line 3). Please review the changes and make sure they are correct (correct indentation, no duplicate lines, etc). Edit the file again if necessary.]
ENVIRONMENT REMINDER: You have 10 turns left to complete the task.

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -46,7 +46,6 @@ edit_file_by_replace(file_name: str, to_replace: str, new_content: str) -> None:
Edit a file. This will search for `to_replace` in the given file and replace it with `new_content`.
Every *to_replace* must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
Include enough lines to make code in `to_replace` unique. `to_replace` should NOT be empty.
`edit_file_by_replace` will only replace the *first* matching occurrences.
For example, given a file "/workspace/example.txt" with the following content:
```
line 1

View File

@@ -693,41 +693,58 @@ def test_edit_file_by_replace_multiline(tmp_path):
with io.StringIO() as buf:
with contextlib.redirect_stdout(buf):
edit_file_by_replace(
file_name=str(temp_file_path),
to_replace='Line 2',
new_content='REPLACE TEXT',
)
result = buf.getvalue()
expected = (
f'[File: {temp_file_path} (5 lines total after edit)]\n'
'(this is the beginning of the file)\n'
'1|Line 1\n'
'2|REPLACE TEXT\n'
'3|Line 2\n'
'4|Line 4\n'
'5|Line 5\n'
'(this is the end of the file)\n'
+ MSG_FILE_UPDATED.format(line_number=2)
+ '\n'
)
assert result.split('\n') == expected.split('\n')
with open(temp_file_path, 'r') as file:
lines = file.readlines()
assert len(lines) == 5
assert lines[0].rstrip() == 'Line 1'
assert lines[1].rstrip() == 'REPLACE TEXT'
assert lines[2].rstrip() == 'Line 2'
assert lines[3].rstrip() == 'Line 4'
assert lines[4].rstrip() == 'Line 5'
with pytest.raises(
ValueError,
match='`to_replace` appears more than once, please include enough lines to make code in `to_replace` unique',
):
edit_file_by_replace(
file_name=str(temp_file_path),
to_replace='Line 2',
new_content='REPLACE TEXT',
)
def test_edit_file_by_replace_toreplace_empty():
with pytest.raises(ValueError):
def test_edit_file_by_replace_no_diff(tmp_path):
temp_file_path = tmp_path / 'a.txt'
content = 'Line 1\nLine 2\nLine 2\nLine 4\nLine 5'
temp_file_path.write_text(content)
open_file(str(temp_file_path))
with io.StringIO() as buf:
with contextlib.redirect_stdout(buf):
with pytest.raises(
ValueError, match='`to_replace` and `new_content` must be different'
):
edit_file_by_replace(
file_name=str(temp_file_path),
to_replace='Line 1',
new_content='Line 1',
)
def test_edit_file_by_replace_toreplace_empty(tmp_path):
temp_file_path = tmp_path / 'a.txt'
content = 'Line 1\nLine 2\nLine 2\nLine 4\nLine 5'
temp_file_path.write_text(content)
open_file(str(temp_file_path))
with io.StringIO() as buf:
with contextlib.redirect_stdout(buf):
with pytest.raises(ValueError, match='`to_replace` must not be empty.'):
edit_file_by_replace(
file_name=str(temp_file_path),
to_replace=' ',
new_content='Line 1',
)
def test_edit_file_by_replace_unknown_file():
with pytest.raises(FileNotFoundError):
edit_file_by_replace(
str('unknown file'),
'',
'ORIGINAL TEXT',
'REPLACE TEXT',
)