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.
Files changed (77) hide show
  1. {auto-coder-0.1.74 → auto-coder-0.1.76}/PKG-INFO +1 -1
  2. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/PKG-INFO +1 -1
  3. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/SOURCES.txt +2 -0
  4. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/auto_coder.py +41 -0
  5. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/command_args.py +4 -0
  6. auto-coder-0.1.76/src/autocoder/common/code_auto_generate_editblock.py +413 -0
  7. auto-coder-0.1.76/src/autocoder/common/code_auto_merge_editblock.py +171 -0
  8. auto-coder-0.1.76/src/autocoder/dispacher/actions/action.py +342 -0
  9. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/copilot.py +190 -138
  10. auto-coder-0.1.76/src/autocoder/dispacher/actions/plugins/action_regex_project.py +94 -0
  11. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/plugins/action_translate.py +116 -71
  12. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/lang.py +1 -0
  13. auto-coder-0.1.76/src/autocoder/version.py +1 -0
  14. auto-coder-0.1.74/src/autocoder/dispacher/actions/action.py +0 -248
  15. auto-coder-0.1.74/src/autocoder/dispacher/actions/plugins/action_regex_project.py +0 -68
  16. auto-coder-0.1.74/src/autocoder/version.py +0 -1
  17. {auto-coder-0.1.74 → auto-coder-0.1.76}/README.md +0 -0
  18. {auto-coder-0.1.74 → auto-coder-0.1.76}/setup.cfg +0 -0
  19. {auto-coder-0.1.74 → auto-coder-0.1.76}/setup.py +0 -0
  20. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/dependency_links.txt +0 -0
  21. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/entry_points.txt +0 -0
  22. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/requires.txt +0 -0
  23. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/auto_coder.egg-info/top_level.txt +0 -0
  24. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/__init__.py +0 -0
  25. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/agent/__init__.py +0 -0
  26. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/agent/coder.py +0 -0
  27. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/JupyterClient.py +0 -0
  28. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/ShellClient.py +0 -0
  29. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/__init__.py +0 -0
  30. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/anything2images.py +0 -0
  31. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/audio.py +0 -0
  32. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/cleaner.py +0 -0
  33. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_execute.py +0 -0
  34. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_generate.py +0 -0
  35. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_generate_diff.py +0 -0
  36. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_generate_strict_diff.py +0 -0
  37. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_merge.py +0 -0
  38. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_merge_diff.py +0 -0
  39. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/code_auto_merge_strict_diff.py +0 -0
  40. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/command_templates.py +0 -0
  41. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/const.py +0 -0
  42. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/git_utils.py +0 -0
  43. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/image_to_page.py +0 -0
  44. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/llm_rerank.py +0 -0
  45. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/screenshots.py +0 -0
  46. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/search.py +0 -0
  47. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/search_replace.py +0 -0
  48. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/common/types.py +0 -0
  49. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/db/__init__.py +0 -0
  50. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/db/store.py +0 -0
  51. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/__init__.py +0 -0
  52. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/__init__.py +0 -0
  53. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/dispacher/actions/plugins/__init__.py +0 -0
  54. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/index/__init__.py +0 -0
  55. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/index/for_command.py +0 -0
  56. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/index/index.py +0 -0
  57. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/pyproject/__init__.py +0 -0
  58. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/__init__.py +0 -0
  59. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/api_server.py +0 -0
  60. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/llm_wrapper.py +0 -0
  61. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/simple_rag.py +0 -0
  62. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/rag/types.py +0 -0
  63. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/regexproject/__init__.py +0 -0
  64. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/suffixproject/__init__.py +0 -0
  65. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/tsproject/__init__.py +0 -0
  66. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/__init__.py +0 -0
  67. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/llm_client_interceptors.py +0 -0
  68. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/multi_turn.py +0 -0
  69. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/print_table.py +0 -0
  70. {auto-coder-0.1.74 → auto-coder-0.1.76}/src/autocoder/utils/rest.py +0 -0
  71. {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_code_auto_merge.py +0 -0
  72. {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_image_to_page.py +0 -0
  73. {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_init_command.py +0 -0
  74. {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_llm_rerank.py +0 -0
  75. {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_shell_client.py +0 -0
  76. {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_simple_rag.py +0 -0
  77. {auto-coder-0.1.74 → auto-coder-0.1.76}/tests/test_tsproject.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-coder
3
- Version: 0.1.74
3
+ Version: 0.1.76
4
4
  Summary: AutoCoder: AutoCoder
5
5
  Author: allwefantasy
6
6
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: auto-coder
3
- Version: 0.1.74
3
+ Version: 0.1.76
4
4
  Summary: AutoCoder: AutoCoder
5
5
  Author: allwefantasy
6
6
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
@@ -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
+ )