auto-coder 0.1.74__tar.gz → 0.1.76__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {auto-coder-0.1.74 → auto-coder-0.1.76}/PKG-INFO +1 -1
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/PKG-INFO +1 -1
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/SOURCES.txt +2 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/auto_coder.py +41 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/command_args.py +4 -0
- auto-coder-0.1.76/src/autocoder/common/code_auto_generate_editblock.py +413 -0
- auto-coder-0.1.76/src/autocoder/common/code_auto_merge_editblock.py +171 -0
- auto-coder-0.1.76/src/autocoder/dispacher/actions/action.py +342 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/copilot.py +190 -138
- auto-coder-0.1.76/src/autocoder/dispacher/actions/plugins/action_regex_project.py +94 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/plugins/action_translate.py +116 -71
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/lang.py +1 -0
- auto-coder-0.1.76/src/autocoder/version.py +1 -0
- auto-coder-0.1.74/src/autocoder/dispacher/actions/action.py +0 -248
- auto-coder-0.1.74/src/autocoder/dispacher/actions/plugins/action_regex_project.py +0 -68
- auto-coder-0.1.74/src/autocoder/version.py +0 -1
- {auto-coder-0.1.74 → auto-coder-0.1.76}/README.md +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/setup.cfg +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/setup.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/dependency_links.txt +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/entry_points.txt +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/requires.txt +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/top_level.txt +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/agent/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/agent/coder.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/JupyterClient.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/ShellClient.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/anything2images.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/audio.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/cleaner.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_execute.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_generate.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_generate_diff.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_generate_strict_diff.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_merge.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_merge_diff.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_merge_strict_diff.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/command_templates.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/const.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/git_utils.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/image_to_page.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/llm_rerank.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/screenshots.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/search.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/search_replace.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/types.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/db/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/db/store.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/plugins/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/index/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/index/for_command.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/index/index.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/pyproject/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/api_server.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/llm_wrapper.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/simple_rag.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/types.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/regexproject/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/suffixproject/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/tsproject/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/__init__.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/llm_client_interceptors.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/multi_turn.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/print_table.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/rest.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_code_auto_merge.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_image_to_page.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_init_command.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_llm_rerank.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_shell_client.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_simple_rag.py +0 -0
- {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_tsproject.py +0 -0
|
@@ -22,9 +22,11 @@ src/autocoder/common/cleaner.py
|
|
|
22
22
|
src/autocoder/common/code_auto_execute.py
|
|
23
23
|
src/autocoder/common/code_auto_generate.py
|
|
24
24
|
src/autocoder/common/code_auto_generate_diff.py
|
|
25
|
+
src/autocoder/common/code_auto_generate_editblock.py
|
|
25
26
|
src/autocoder/common/code_auto_generate_strict_diff.py
|
|
26
27
|
src/autocoder/common/code_auto_merge.py
|
|
27
28
|
src/autocoder/common/code_auto_merge_diff.py
|
|
29
|
+
src/autocoder/common/code_auto_merge_editblock.py
|
|
28
30
|
src/autocoder/common/code_auto_merge_strict_diff.py
|
|
29
31
|
src/autocoder/common/command_templates.py
|
|
30
32
|
src/autocoder/common/const.py
|
|
@@ -20,6 +20,8 @@ from byzerllm.apps.byzer_storage.env import get_latest_byzer_retrieval_lib
|
|
|
20
20
|
from autocoder.command_args import parse_args
|
|
21
21
|
from autocoder.rag.api_server import serve,ServerArgs
|
|
22
22
|
from loguru import logger
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
23
25
|
|
|
24
26
|
def resolve_include_path(base_path, include_path):
|
|
25
27
|
if include_path.startswith('.') or include_path.startswith('..'):
|
|
@@ -117,6 +119,45 @@ def main():
|
|
|
117
119
|
gen_screenshots(args.urls, args.output)
|
|
118
120
|
print(f"Successfully captured screenshot of {args.urls} and saved to {args.output}")
|
|
119
121
|
return
|
|
122
|
+
|
|
123
|
+
if raw_args.command == "next":
|
|
124
|
+
actions_dir = os.path.join(os.getcwd(), "actions")
|
|
125
|
+
if not os.path.exists(actions_dir):
|
|
126
|
+
print("Current directory does not have an actions directory")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
action_files = [f for f in os.listdir(actions_dir) if f[:3].isdigit() and f.endswith(".yml") and f[:3] != "101"]
|
|
130
|
+
if not action_files:
|
|
131
|
+
max_seq = 0
|
|
132
|
+
else:
|
|
133
|
+
seqs = [int(f[:3]) for f in action_files]
|
|
134
|
+
max_seq = max(seqs)
|
|
135
|
+
|
|
136
|
+
new_seq = str(max_seq + 1).zfill(3)
|
|
137
|
+
prev_files = [f for f in action_files if int(f[:3]) < int(new_seq)]
|
|
138
|
+
|
|
139
|
+
if not prev_files:
|
|
140
|
+
new_file = os.path.join(actions_dir, f"{new_seq}_{raw_args.name}.yml")
|
|
141
|
+
with open(new_file, "w") as f:
|
|
142
|
+
pass
|
|
143
|
+
else:
|
|
144
|
+
prev_file = sorted(prev_files)[-1] # 取序号最大的文件
|
|
145
|
+
with open(os.path.join(actions_dir, prev_file), "r") as f:
|
|
146
|
+
content = f.read()
|
|
147
|
+
new_file = os.path.join(actions_dir, f"{new_seq}_{raw_args.name}.yml")
|
|
148
|
+
with open(new_file, "w") as f:
|
|
149
|
+
f.write(content)
|
|
150
|
+
|
|
151
|
+
print(f"Successfully created new action file: {new_file}")
|
|
152
|
+
|
|
153
|
+
if os.environ.get("TERMINAL_EMULATOR") == "JetBrains-JediTerm":
|
|
154
|
+
subprocess.run(["idea", new_file])
|
|
155
|
+
else:
|
|
156
|
+
if shutil.which("code"):
|
|
157
|
+
subprocess.run(["code", "-r", new_file])
|
|
158
|
+
elif shutil.which("idea"):
|
|
159
|
+
subprocess.run(["idea", new_file])
|
|
160
|
+
return
|
|
120
161
|
|
|
121
162
|
if args.model:
|
|
122
163
|
|
|
@@ -160,6 +160,10 @@ def parse_args() -> AutoCoderArgs:
|
|
|
160
160
|
screenshot_parser.add_argument("--output", required=True, help=desc["screenshot_output"])
|
|
161
161
|
screenshot_parser.add_argument("--source_dir", default=".", help="")
|
|
162
162
|
|
|
163
|
+
next_parser = subparsers.add_parser("next", help="Create a new action file based on the previous one")
|
|
164
|
+
next_parser.add_argument("name", help="Name for the new action file")
|
|
165
|
+
next_parser.add_argument("--source_dir", default=".", help="")
|
|
166
|
+
|
|
163
167
|
doc2html_parser = subparsers.add_parser("doc2html", help="Convert word/pdf document to html")
|
|
164
168
|
doc2html_parser.add_argument("--file", default="", help=desc["file"])
|
|
165
169
|
doc2html_parser.add_argument("--model", default="", help=desc["model"])
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
from typing import List, Dict, Tuple
|
|
2
|
+
from autocoder.common.types import Mode
|
|
3
|
+
from autocoder.common import AutoCoderArgs
|
|
4
|
+
import byzerllm
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CodeAutoGenerateEditBlock:
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
llm: byzerllm.ByzerLLM,
|
|
11
|
+
args: AutoCoderArgs,
|
|
12
|
+
action=None,
|
|
13
|
+
fence_0: str = "```",
|
|
14
|
+
fence_1: str = "```",
|
|
15
|
+
) -> None:
|
|
16
|
+
self.llm = llm
|
|
17
|
+
self.args = args
|
|
18
|
+
self.action = action
|
|
19
|
+
self.fence_0 = fence_0
|
|
20
|
+
self.fence_1 = fence_1
|
|
21
|
+
if not self.llm:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"Please provide a valid model instance to use for code generation."
|
|
24
|
+
)
|
|
25
|
+
if self.llm.get_sub_client("code_model"):
|
|
26
|
+
self.llm = self.llm.get_sub_client("code_model")
|
|
27
|
+
|
|
28
|
+
@byzerllm.prompt(llm=lambda self: self.llm)
|
|
29
|
+
def auto_implement_function(self, instruction: str, content: str) -> str:
|
|
30
|
+
"""
|
|
31
|
+
下面是一些文件路径以及每个文件对应的源码:
|
|
32
|
+
|
|
33
|
+
{{ content }}
|
|
34
|
+
|
|
35
|
+
请参考上面的内容,重新实现所有文件下方法体标记了如下内容的方法:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
raise NotImplementedError("This function should be implemented by the model.")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
{{ instruction }}
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
@byzerllm.prompt(llm=lambda self: self.llm)
|
|
46
|
+
def multi_round_instruction(self, instruction: str, content: str) -> str:
|
|
47
|
+
"""
|
|
48
|
+
如果你需要生成代码,对于每个需要更改的文件,你需要按 *SEARCH/REPLACE block* 的格式进行生成。
|
|
49
|
+
|
|
50
|
+
# *SEARCH/REPLACE block* Rules:
|
|
51
|
+
|
|
52
|
+
Every *SEARCH/REPLACE block* must use this format:
|
|
53
|
+
1. The opening fence and code language, eg: {{ fence_0 }}python
|
|
54
|
+
2. The file path alone on a line, starting with "##File:" and verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
|
55
|
+
3. The start of search block: <<<<<<< SEARCH
|
|
56
|
+
4. A contiguous chunk of lines to search for in the existing source code
|
|
57
|
+
5. The dividing line: =======
|
|
58
|
+
6. The lines to replace into the source code
|
|
59
|
+
7. The end of the replace block: >>>>>>> REPLACE
|
|
60
|
+
8. The closing fence: {{ fence_1 }}
|
|
61
|
+
|
|
62
|
+
Every *SEARCH* section must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
|
|
63
|
+
|
|
64
|
+
*SEARCH/REPLACE* blocks will replace *all* matching occurrences.
|
|
65
|
+
Include enough lines to make the SEARCH blocks unique.
|
|
66
|
+
|
|
67
|
+
Include *ALL* the code being searched and replaced!
|
|
68
|
+
|
|
69
|
+
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.
|
|
70
|
+
|
|
71
|
+
If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
|
72
|
+
- A new file path, including dir name if needed
|
|
73
|
+
- An empty `SEARCH` section
|
|
74
|
+
- The new file's contents in the `REPLACE` section
|
|
75
|
+
|
|
76
|
+
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
|
77
|
+
|
|
78
|
+
下面我们来看一个例子:
|
|
79
|
+
|
|
80
|
+
当前项目目录结构:
|
|
81
|
+
1. 项目根目录: /tmp/projects/mathweb
|
|
82
|
+
2. 项目子目录/文件列表(类似tree 命令输出)
|
|
83
|
+
flask/
|
|
84
|
+
app.py
|
|
85
|
+
templates/
|
|
86
|
+
index.html
|
|
87
|
+
static/
|
|
88
|
+
style.css
|
|
89
|
+
|
|
90
|
+
用户需求: Change get_factorial() to use math.factorial
|
|
91
|
+
|
|
92
|
+
回答: To make this change we need to modify `/tmp/projects/mathweb/flask/app.py` to:
|
|
93
|
+
|
|
94
|
+
1. Import the math package.
|
|
95
|
+
2. Remove the existing factorial() function.
|
|
96
|
+
3. Update get_factorial() to call math.factorial instead.
|
|
97
|
+
|
|
98
|
+
Here are the *SEARCH/REPLACE* blocks:
|
|
99
|
+
|
|
100
|
+
{{ fence_0 }}python
|
|
101
|
+
##File: /tmp/projects/mathweb/flask/app.py
|
|
102
|
+
<<<<<<< SEARCH
|
|
103
|
+
from flask import Flask
|
|
104
|
+
=======
|
|
105
|
+
import math
|
|
106
|
+
from flask import Flask
|
|
107
|
+
>>>>>>> REPLACE
|
|
108
|
+
{{ fence_1 }}
|
|
109
|
+
|
|
110
|
+
{{ fence_0 }}python
|
|
111
|
+
##File: /tmp/projects/mathweb/flask/app.py
|
|
112
|
+
<<<<<<< SEARCH
|
|
113
|
+
def factorial(n):
|
|
114
|
+
"compute factorial"
|
|
115
|
+
|
|
116
|
+
if n == 0:
|
|
117
|
+
return 1
|
|
118
|
+
else:
|
|
119
|
+
return n * factorial(n-1)
|
|
120
|
+
|
|
121
|
+
=======
|
|
122
|
+
>>>>>>> REPLACE
|
|
123
|
+
{{ fence_1 }}
|
|
124
|
+
|
|
125
|
+
{{ fence_0 }}python
|
|
126
|
+
##File: /tmp/projects/mathweb/flask/app.py
|
|
127
|
+
<<<<<<< SEARCH
|
|
128
|
+
return str(factorial(n))
|
|
129
|
+
=======
|
|
130
|
+
return str(math.factorial(n))
|
|
131
|
+
>>>>>>> REPLACE
|
|
132
|
+
{{ fence_1 }}
|
|
133
|
+
|
|
134
|
+
用户需求: Refactor hello() into its own file.
|
|
135
|
+
|
|
136
|
+
回答:To make this change we need to modify `main.py` and make a new file `hello.py`:
|
|
137
|
+
|
|
138
|
+
1. Make a new hello.py file with hello() in it.
|
|
139
|
+
2. Remove hello() from main.py and replace it with an import.
|
|
140
|
+
|
|
141
|
+
Here are the *SEARCH/REPLACE* blocks:
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
{{ fence_0 }}python
|
|
145
|
+
##File: /tmp/projects/mathweb/hello.py
|
|
146
|
+
<<<<<<< SEARCH
|
|
147
|
+
=======
|
|
148
|
+
def hello():
|
|
149
|
+
"print a greeting"
|
|
150
|
+
|
|
151
|
+
print("hello")
|
|
152
|
+
>>>>>>> REPLACE
|
|
153
|
+
{{ fence_1 }}
|
|
154
|
+
|
|
155
|
+
{{ fence_0 }}python
|
|
156
|
+
##File: /tmp/projects/mathweb/main.py
|
|
157
|
+
<<<<<<< SEARCH
|
|
158
|
+
def hello():
|
|
159
|
+
"print a greeting"
|
|
160
|
+
|
|
161
|
+
print("hello")
|
|
162
|
+
=======
|
|
163
|
+
from hello import hello
|
|
164
|
+
>>>>>>> REPLACE
|
|
165
|
+
{{ fence_1 }}
|
|
166
|
+
|
|
167
|
+
现在让我们开始一个新的任务:
|
|
168
|
+
|
|
169
|
+
{%- if structure %}
|
|
170
|
+
{{ structure }}
|
|
171
|
+
{%- endif %}
|
|
172
|
+
|
|
173
|
+
{%- if content %}
|
|
174
|
+
下面是一些文件路径以及每个文件对应的源码:
|
|
175
|
+
|
|
176
|
+
{{ content }}
|
|
177
|
+
{%- endif %}
|
|
178
|
+
|
|
179
|
+
下面是用户的需求:
|
|
180
|
+
|
|
181
|
+
{{ instruction }}
|
|
182
|
+
|
|
183
|
+
每次生成一个文件的*SEARCH/REPLACE* blocks,然后询问我是否继续,当我回复继续,继续生成下一个文件的*SEARCH/REPLACE* blocks。当没有后续任务时,请回复 "__完成__" 或者 "__EOF__"。
|
|
184
|
+
"""
|
|
185
|
+
return {
|
|
186
|
+
"structure": (
|
|
187
|
+
self.action.pp.get_tree_like_directory_structure()
|
|
188
|
+
if self.action
|
|
189
|
+
else ""
|
|
190
|
+
),
|
|
191
|
+
"fence_0": self.fence_0,
|
|
192
|
+
"fence_1": self.fence_1,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@byzerllm.prompt(llm=lambda self: self.llm)
|
|
196
|
+
def single_round_instruction(self, instruction: str, content: str) -> str:
|
|
197
|
+
"""
|
|
198
|
+
如果你需要生成代码,对于每个需要更改的文件,你需要按 *SEARCH/REPLACE block* 的格式进行生成。
|
|
199
|
+
|
|
200
|
+
# *SEARCH/REPLACE block* Rules:
|
|
201
|
+
|
|
202
|
+
Every *SEARCH/REPLACE block* must use this format:
|
|
203
|
+
1. The opening fence and code language, eg: {{ fence_0 }}python
|
|
204
|
+
2. The file path alone on a line, starting with "##File:" and verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
|
|
205
|
+
3. The start of search block: <<<<<<< SEARCH
|
|
206
|
+
4. A contiguous chunk of lines to search for in the existing source code
|
|
207
|
+
5. The dividing line: =======
|
|
208
|
+
6. The lines to replace into the source code
|
|
209
|
+
7. The end of the replace block: >>>>>>> REPLACE
|
|
210
|
+
8. The closing fence: {{ fence_1 }}
|
|
211
|
+
|
|
212
|
+
Every *SEARCH* section must *EXACTLY MATCH* the existing source code, character for character, including all comments, docstrings, etc.
|
|
213
|
+
|
|
214
|
+
*SEARCH/REPLACE* blocks will replace *all* matching occurrences.
|
|
215
|
+
Include enough lines to make the SEARCH blocks unique.
|
|
216
|
+
|
|
217
|
+
Include *ALL* the code being searched and replaced!
|
|
218
|
+
|
|
219
|
+
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.
|
|
220
|
+
|
|
221
|
+
If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
|
|
222
|
+
- A new file path, including dir name if needed
|
|
223
|
+
- An empty `SEARCH` section
|
|
224
|
+
- The new file's contents in the `REPLACE` section
|
|
225
|
+
|
|
226
|
+
ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
|
|
227
|
+
|
|
228
|
+
下面我们来看一个例子:
|
|
229
|
+
|
|
230
|
+
当前项目目录结构:
|
|
231
|
+
1. 项目根目录: /tmp/projects/mathweb
|
|
232
|
+
2. 项目子目录/文件列表(类似tree 命令输出)
|
|
233
|
+
flask/
|
|
234
|
+
app.py
|
|
235
|
+
templates/
|
|
236
|
+
index.html
|
|
237
|
+
static/
|
|
238
|
+
style.css
|
|
239
|
+
|
|
240
|
+
用户需求: Change get_factorial() to use math.factorial
|
|
241
|
+
|
|
242
|
+
回答: To make this change we need to modify `/tmp/projects/mathweb/flask/app.py` to:
|
|
243
|
+
|
|
244
|
+
1. Import the math package.
|
|
245
|
+
2. Remove the existing factorial() function.
|
|
246
|
+
3. Update get_factorial() to call math.factorial instead.
|
|
247
|
+
|
|
248
|
+
Here are the *SEARCH/REPLACE* blocks:
|
|
249
|
+
|
|
250
|
+
{{ fence_0 }}python
|
|
251
|
+
##File: /tmp/projects/mathweb/flask/app.py
|
|
252
|
+
<<<<<<< SEARCH
|
|
253
|
+
from flask import Flask
|
|
254
|
+
=======
|
|
255
|
+
import math
|
|
256
|
+
from flask import Flask
|
|
257
|
+
>>>>>>> REPLACE
|
|
258
|
+
{{ fence_1 }}
|
|
259
|
+
|
|
260
|
+
{{ fence_0 }}python
|
|
261
|
+
##File: /tmp/projects/mathweb/flask/app.py
|
|
262
|
+
<<<<<<< SEARCH
|
|
263
|
+
def factorial(n):
|
|
264
|
+
"compute factorial"
|
|
265
|
+
|
|
266
|
+
if n == 0:
|
|
267
|
+
return 1
|
|
268
|
+
else:
|
|
269
|
+
return n * factorial(n-1)
|
|
270
|
+
|
|
271
|
+
=======
|
|
272
|
+
>>>>>>> REPLACE
|
|
273
|
+
{{ fence_1 }}
|
|
274
|
+
|
|
275
|
+
{{ fence_0 }}python
|
|
276
|
+
##File: /tmp/projects/mathweb/flask/app.py
|
|
277
|
+
<<<<<<< SEARCH
|
|
278
|
+
return str(factorial(n))
|
|
279
|
+
=======
|
|
280
|
+
return str(math.factorial(n))
|
|
281
|
+
>>>>>>> REPLACE
|
|
282
|
+
{{ fence_1 }}
|
|
283
|
+
|
|
284
|
+
用户需求: Refactor hello() into its own file.
|
|
285
|
+
|
|
286
|
+
回答:To make this change we need to modify `main.py` and make a new file `hello.py`:
|
|
287
|
+
|
|
288
|
+
1. Make a new hello.py file with hello() in it.
|
|
289
|
+
2. Remove hello() from main.py and replace it with an import.
|
|
290
|
+
|
|
291
|
+
Here are the *SEARCH/REPLACE* blocks:
|
|
292
|
+
|
|
293
|
+
{{ fence_0 }}python
|
|
294
|
+
##File: /tmp/projects/mathweb/hello.py
|
|
295
|
+
<<<<<<< SEARCH
|
|
296
|
+
=======
|
|
297
|
+
def hello():
|
|
298
|
+
"print a greeting"
|
|
299
|
+
|
|
300
|
+
print("hello")
|
|
301
|
+
>>>>>>> REPLACE
|
|
302
|
+
{{ fence_1 }}
|
|
303
|
+
|
|
304
|
+
{{ fence_0 }}python
|
|
305
|
+
##File: /tmp/projects/mathweb/main.py
|
|
306
|
+
<<<<<<< SEARCH
|
|
307
|
+
def hello():
|
|
308
|
+
"print a greeting"
|
|
309
|
+
|
|
310
|
+
print("hello")
|
|
311
|
+
=======
|
|
312
|
+
from hello import hello
|
|
313
|
+
>>>>>>> REPLACE
|
|
314
|
+
{{ fence_1 }}
|
|
315
|
+
|
|
316
|
+
现在让我们开始一个新的任务:
|
|
317
|
+
|
|
318
|
+
{%- if structure %}
|
|
319
|
+
{{ structure }}
|
|
320
|
+
{%- endif %}
|
|
321
|
+
|
|
322
|
+
{%- if content %}
|
|
323
|
+
下面是一些文件路径以及每个文件对应的源码:
|
|
324
|
+
|
|
325
|
+
{{ content }}
|
|
326
|
+
{%- endif %}
|
|
327
|
+
|
|
328
|
+
下面是用户的需求:
|
|
329
|
+
|
|
330
|
+
{{ instruction }}
|
|
331
|
+
|
|
332
|
+
"""
|
|
333
|
+
return {
|
|
334
|
+
"structure": (
|
|
335
|
+
self.action.pp.get_tree_like_directory_structure()
|
|
336
|
+
if self.action
|
|
337
|
+
else ""
|
|
338
|
+
),
|
|
339
|
+
"fence_0": self.fence_0,
|
|
340
|
+
"fence_1": self.fence_1,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
def single_round_run(
|
|
344
|
+
self, query: str, source_content: str
|
|
345
|
+
) -> Tuple[str, Dict[str, str]]:
|
|
346
|
+
llm_config = {"human_as_model": self.args.human_as_model}
|
|
347
|
+
|
|
348
|
+
if self.args.template == "common":
|
|
349
|
+
init_prompt = self.single_round_instruction.prompt(
|
|
350
|
+
instruction=query, content=source_content
|
|
351
|
+
)
|
|
352
|
+
elif self.args.template == "auto_implement":
|
|
353
|
+
init_prompt = self.auto_implement_function.prompt(
|
|
354
|
+
instruction=query, content=source_content
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
with open(self.args.target_file, "w") as file:
|
|
358
|
+
file.write(init_prompt)
|
|
359
|
+
|
|
360
|
+
conversations = [{"role": "user", "content": init_prompt}]
|
|
361
|
+
|
|
362
|
+
t = self.llm.chat_oai(conversations=conversations, llm_config=llm_config)
|
|
363
|
+
conversations.append({"role": "assistant", "content": t[0].output})
|
|
364
|
+
return [t[0].output], conversations
|
|
365
|
+
|
|
366
|
+
def multi_round_run(
|
|
367
|
+
self, query: str, source_content: str, max_steps: int = 10
|
|
368
|
+
) -> Tuple[List[str], List[Dict[str, str]]]:
|
|
369
|
+
llm_config = {"human_as_model": self.args.human_as_model}
|
|
370
|
+
result = []
|
|
371
|
+
|
|
372
|
+
if self.args.template == "common":
|
|
373
|
+
init_prompt = self.multi_round_instruction.prompt(
|
|
374
|
+
instruction=query, content=source_content
|
|
375
|
+
)
|
|
376
|
+
elif self.args.template == "auto_implement":
|
|
377
|
+
init_prompt = self.auto_implement_function.prompt(
|
|
378
|
+
instruction=query, content=source_content
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
conversations = [{"role": "user", "content": init_prompt}]
|
|
382
|
+
|
|
383
|
+
with open(self.args.target_file, "w") as file:
|
|
384
|
+
file.write(init_prompt)
|
|
385
|
+
|
|
386
|
+
t = self.llm.chat_oai(conversations=conversations, llm_config=llm_config)
|
|
387
|
+
|
|
388
|
+
result.append(t[0].output)
|
|
389
|
+
|
|
390
|
+
conversations.append({"role": "assistant", "content": t[0].output})
|
|
391
|
+
|
|
392
|
+
if "__完成__" in t[0].output:
|
|
393
|
+
return result, conversations
|
|
394
|
+
|
|
395
|
+
current_step = 0
|
|
396
|
+
|
|
397
|
+
while current_step < max_steps:
|
|
398
|
+
|
|
399
|
+
conversations.append({"role": "user", "content": "继续"})
|
|
400
|
+
|
|
401
|
+
with open(self.args.target_file, "w") as file:
|
|
402
|
+
file.write("继续")
|
|
403
|
+
|
|
404
|
+
t = self.llm.chat_oai(conversations=conversations, llm_config=llm_config)
|
|
405
|
+
|
|
406
|
+
result.append(t[0].output)
|
|
407
|
+
conversations.append({"role": "assistant", "content": t[0].output})
|
|
408
|
+
current_step += 1
|
|
409
|
+
|
|
410
|
+
if "__完成__" in t[0].output or "__EOF__" in t[0].output:
|
|
411
|
+
return result, conversations
|
|
412
|
+
|
|
413
|
+
return result, conversations
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from byzerllm.utils.client import code_utils
|
|
3
|
+
from autocoder.common import AutoCoderArgs, git_utils
|
|
4
|
+
from typing import List
|
|
5
|
+
import pydantic
|
|
6
|
+
import byzerllm
|
|
7
|
+
from loguru import logger
|
|
8
|
+
import hashlib
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PathAndCode(pydantic.BaseModel):
|
|
12
|
+
path: str
|
|
13
|
+
content: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CodeAutoMergeEditBlock:
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
llm: byzerllm.ByzerLLM,
|
|
20
|
+
args: AutoCoderArgs,
|
|
21
|
+
fence_0: str = "```",
|
|
22
|
+
fence_1: str = "```",
|
|
23
|
+
):
|
|
24
|
+
self.llm = llm
|
|
25
|
+
self.args = args
|
|
26
|
+
self.fence_0 = fence_0
|
|
27
|
+
self.fence_1 = fence_1
|
|
28
|
+
|
|
29
|
+
def parse_whole_text(self, text: str) -> List[PathAndCode]:
|
|
30
|
+
lines = text.split("\n")
|
|
31
|
+
lines_len = len(lines)
|
|
32
|
+
start_marker_count = 0
|
|
33
|
+
inline_start_marker_count = 0
|
|
34
|
+
block = []
|
|
35
|
+
path_and_code_list = []
|
|
36
|
+
|
|
37
|
+
def guard(index):
|
|
38
|
+
return index + 1 < lines_len
|
|
39
|
+
|
|
40
|
+
def start_marker(line, index):
|
|
41
|
+
return (
|
|
42
|
+
line.startswith(self.fence_0)
|
|
43
|
+
and guard(index)
|
|
44
|
+
and lines[index + 1].startswith("##File:")
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def inline_start_marker(line, index):
|
|
48
|
+
return line.startswith(self.fence_0) and line.strip() != self.fence_0
|
|
49
|
+
|
|
50
|
+
def end_marker(line, index):
|
|
51
|
+
return line.startswith(self.fence_1) and line.strip() == self.fence_1
|
|
52
|
+
|
|
53
|
+
for index, line in enumerate(lines):
|
|
54
|
+
if start_marker(line, index) and start_marker_count == 0:
|
|
55
|
+
start_marker_count += 1
|
|
56
|
+
elif (
|
|
57
|
+
start_marker(line, index) or inline_start_marker(line, index)
|
|
58
|
+
) and start_marker_count > 0:
|
|
59
|
+
inline_start_marker_count += 1
|
|
60
|
+
block.append(line)
|
|
61
|
+
elif (
|
|
62
|
+
end_marker(line, index)
|
|
63
|
+
and start_marker_count == 1
|
|
64
|
+
and inline_start_marker_count == 0
|
|
65
|
+
):
|
|
66
|
+
start_marker_count -= 1
|
|
67
|
+
if block:
|
|
68
|
+
path = block[0].split(":", 1)[1].strip()
|
|
69
|
+
content = "\n".join(block[1:])
|
|
70
|
+
block = []
|
|
71
|
+
path_and_code_list.append(PathAndCode(path=path, content=content))
|
|
72
|
+
elif end_marker(line, index) and inline_start_marker_count > 0:
|
|
73
|
+
inline_start_marker_count -= 1
|
|
74
|
+
block.append(line)
|
|
75
|
+
elif start_marker_count > 0:
|
|
76
|
+
block.append(line)
|
|
77
|
+
|
|
78
|
+
return path_and_code_list
|
|
79
|
+
|
|
80
|
+
@byzerllm.prompt()
|
|
81
|
+
def git_require_msg(self, source_dir: str, error: str) -> str:
|
|
82
|
+
"""
|
|
83
|
+
auto_merge only works for git repositories.
|
|
84
|
+
|
|
85
|
+
Try to use git init in the source directory.
|
|
86
|
+
|
|
87
|
+
```shell
|
|
88
|
+
cd {{ source_dir }}
|
|
89
|
+
git init .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then try to run auto-coder again.
|
|
93
|
+
Error: {{ error }}
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def get_edits(self,content: str):
|
|
97
|
+
edits = self.parse_whole_text(content)
|
|
98
|
+
HEAD = "<<<<<<< SEARCH"
|
|
99
|
+
DIVIDER = "======="
|
|
100
|
+
UPDATED = ">>>>>>> REPLACE"
|
|
101
|
+
result = []
|
|
102
|
+
for edit in edits:
|
|
103
|
+
heads = []
|
|
104
|
+
updates = []
|
|
105
|
+
c = edit.content
|
|
106
|
+
in_head = False
|
|
107
|
+
in_updated = False
|
|
108
|
+
for line in c.splitlines():
|
|
109
|
+
if line.strip()==HEAD:
|
|
110
|
+
in_head = True
|
|
111
|
+
continue
|
|
112
|
+
if line.strip()==DIVIDER:
|
|
113
|
+
in_head= False
|
|
114
|
+
in_updated = True
|
|
115
|
+
continue
|
|
116
|
+
if line.strip()==UPDATED:
|
|
117
|
+
in_head = False
|
|
118
|
+
in_updated = False
|
|
119
|
+
continue
|
|
120
|
+
if in_head:
|
|
121
|
+
heads.append(line)
|
|
122
|
+
if in_updated:
|
|
123
|
+
updates.append(line)
|
|
124
|
+
result.append((edit.path,"\n".join(heads),"\n".join(updates)))
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
def merge_code(self, content: str, force_skip_git: bool = False):
|
|
128
|
+
file_content = open(self.args.file).read()
|
|
129
|
+
md5 = hashlib.md5(file_content.encode("utf-8")).hexdigest()
|
|
130
|
+
# get the file name
|
|
131
|
+
file_name = os.path.basename(self.args.file)
|
|
132
|
+
|
|
133
|
+
if not force_skip_git:
|
|
134
|
+
try:
|
|
135
|
+
git_utils.commit_changes(
|
|
136
|
+
self.args.source_dir, f"auto_coder_pre_{file_name}_{md5}"
|
|
137
|
+
)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(
|
|
140
|
+
self.git_require_msg(source_dir=self.args.source_dir, error=str(e))
|
|
141
|
+
)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
codes = self.get_edits(content)
|
|
145
|
+
updated_files = []
|
|
146
|
+
for block in codes:
|
|
147
|
+
file_path = block[0]
|
|
148
|
+
updated_files.append(file_path)
|
|
149
|
+
head = block[1]
|
|
150
|
+
update = block[2]
|
|
151
|
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
|
152
|
+
if not os.path.exists(file_path):
|
|
153
|
+
with open(file_path, "w") as f:
|
|
154
|
+
logger.info(f"Upsert path: {file_path}")
|
|
155
|
+
f.write(update)
|
|
156
|
+
continue
|
|
157
|
+
with open(file_path, "r") as f:
|
|
158
|
+
existing_content = f.read()
|
|
159
|
+
logger.info(f'''in:\n {file_path}
|
|
160
|
+
replace:\n{head}
|
|
161
|
+
with:\n{update}''')
|
|
162
|
+
existing_content = existing_content.replace(head,update,1)
|
|
163
|
+
logger.info(f"Upsert Result: {existing_content}")
|
|
164
|
+
with open(file_path, "w") as f:
|
|
165
|
+
f.write(existing_content)
|
|
166
|
+
|
|
167
|
+
logger.info(f"Merged {len(set(updated_files))} files into the project.")
|
|
168
|
+
if not force_skip_git:
|
|
169
|
+
git_utils.commit_changes(
|
|
170
|
+
self.args.source_dir, f"auto_coder_{file_name}_{md5}"
|
|
171
|
+
)
|