auto-coder 0.1.298__py3-none-any.whl → 0.1.300__py3-none-any.whl
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.
Potentially problematic release.
This version of auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.298.dist-info → auto_coder-0.1.300.dist-info}/METADATA +2 -2
- {auto_coder-0.1.298.dist-info → auto_coder-0.1.300.dist-info}/RECORD +23 -21
- autocoder/agent/auto_learn_from_commit.py +125 -59
- autocoder/agent/auto_review_commit.py +106 -16
- autocoder/auto_coder.py +65 -66
- autocoder/auto_coder_runner.py +23 -40
- autocoder/command_parser.py +280 -0
- autocoder/commands/auto_command.py +112 -33
- autocoder/commands/tools.py +170 -10
- autocoder/common/__init__.py +5 -1
- autocoder/common/action_yml_file_manager.py +367 -0
- autocoder/common/auto_coder_lang.py +8 -2
- autocoder/common/auto_configure.py +6 -0
- autocoder/common/command_completer.py +8 -1
- autocoder/common/memory_manager.py +5 -1
- autocoder/index/entry.py +17 -0
- autocoder/rag/cache/local_duckdb_storage_cache.py +111 -17
- autocoder/utils/__init__.py +13 -9
- autocoder/version.py +1 -1
- {auto_coder-0.1.298.dist-info → auto_coder-0.1.300.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.298.dist-info → auto_coder-0.1.300.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.298.dist-info → auto_coder-0.1.300.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.298.dist-info → auto_coder-0.1.300.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
import hashlib
|
|
4
|
+
import git
|
|
5
|
+
from typing import List, Dict, Tuple, Optional, Union, Any
|
|
6
|
+
from loguru import logger
|
|
7
|
+
from autocoder.common.printer import Printer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ActionYmlFileManager:
|
|
11
|
+
"""
|
|
12
|
+
Actions 目录文件操作工具类,用于抽象和管理 actions 目录下的 YAML 文件操作。
|
|
13
|
+
|
|
14
|
+
主要功能包括:
|
|
15
|
+
- 获取最新的 YAML 文件
|
|
16
|
+
- 按序号排序 YAML 文件
|
|
17
|
+
- 创建新的 YAML 文件
|
|
18
|
+
- 更新 YAML 文件内容
|
|
19
|
+
- 处理 commit 消息与 YAML 文件的关联
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, source_dir: str):
|
|
23
|
+
"""
|
|
24
|
+
初始化 ActionYmlFileManager
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
source_dir: 项目根目录
|
|
28
|
+
"""
|
|
29
|
+
self.source_dir = source_dir
|
|
30
|
+
self.actions_dir = os.path.join(source_dir, "actions")
|
|
31
|
+
self.printer = Printer()
|
|
32
|
+
|
|
33
|
+
def ensure_actions_dir(self) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
确保 actions 目录存在
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
bool: 如果目录存在或创建成功返回 True,否则返回 False
|
|
39
|
+
"""
|
|
40
|
+
if not os.path.exists(self.actions_dir):
|
|
41
|
+
try:
|
|
42
|
+
os.makedirs(self.actions_dir, exist_ok=True)
|
|
43
|
+
return True
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error(f"Failed to create actions directory: {e}")
|
|
46
|
+
return False
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
def get_action_files(self, filter_prefix: Optional[str] = None) -> List[str]:
|
|
50
|
+
"""
|
|
51
|
+
获取所有符合条件的 YAML 文件名
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
filter_prefix: 可选的文件名前缀过滤
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List[str]: 符合条件的文件名列表
|
|
58
|
+
"""
|
|
59
|
+
if not os.path.exists(self.actions_dir):
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
action_files = [
|
|
63
|
+
f for f in os.listdir(self.actions_dir)
|
|
64
|
+
if f[:3].isdigit() and "_" in f and f.endswith('.yml')
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
if filter_prefix:
|
|
68
|
+
action_files = [f for f in action_files if f.startswith(filter_prefix)]
|
|
69
|
+
|
|
70
|
+
return action_files
|
|
71
|
+
|
|
72
|
+
def get_sequence_number(self, file_name: str) -> int:
|
|
73
|
+
"""
|
|
74
|
+
从文件名中提取序号
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
file_name: YAML 文件名
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
int: 文件序号
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
return int(file_name.split("_")[0])
|
|
84
|
+
except (ValueError, IndexError):
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
def get_latest_action_file(self, filter_prefix: Optional[str] = None) -> Optional[str]:
|
|
88
|
+
"""
|
|
89
|
+
获取最新的 action 文件名(序号最大的)
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
filter_prefix: 可选的文件名前缀过滤
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Optional[str]: 最新文件名,如果没有则返回 None
|
|
96
|
+
"""
|
|
97
|
+
action_files = self.get_action_files(filter_prefix)
|
|
98
|
+
|
|
99
|
+
if not action_files:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
# 按序号排序
|
|
103
|
+
sorted_files = sorted(action_files, key=self.get_sequence_number, reverse=True)
|
|
104
|
+
return sorted_files[0] if sorted_files else None
|
|
105
|
+
|
|
106
|
+
def get_next_sequence_number(self) -> int:
|
|
107
|
+
"""
|
|
108
|
+
获取下一个序号
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
int: 下一个序号
|
|
112
|
+
"""
|
|
113
|
+
action_files = self.get_action_files()
|
|
114
|
+
|
|
115
|
+
if not action_files:
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
seqs = [self.get_sequence_number(f) for f in action_files]
|
|
119
|
+
return max(seqs) + 1
|
|
120
|
+
|
|
121
|
+
def create_next_action_file(self, name: str, content: Optional[str] = None,
|
|
122
|
+
from_yaml: Optional[str] = None) -> Optional[str]:
|
|
123
|
+
"""
|
|
124
|
+
创建下一个序号的 action 文件
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
name: 文件基本名称(不含序号和扩展名)
|
|
128
|
+
content: 可选的文件内容
|
|
129
|
+
from_yaml: 可选的基于某个已有 YAML 文件(指定前缀)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
Optional[str]: 创建的文件路径,如果创建失败返回 None
|
|
133
|
+
"""
|
|
134
|
+
if not self.ensure_actions_dir():
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
next_seq = str(self.get_next_sequence_number()).zfill(12)
|
|
138
|
+
new_file_name = f"{next_seq}_{name}.yml"
|
|
139
|
+
new_file_path = os.path.join(self.actions_dir, new_file_name)
|
|
140
|
+
|
|
141
|
+
if from_yaml:
|
|
142
|
+
from_files = [f for f in self.get_action_files() if f.startswith(from_yaml)]
|
|
143
|
+
if from_files:
|
|
144
|
+
from_file = from_files[0] # 取第一个匹配的文件
|
|
145
|
+
try:
|
|
146
|
+
with open(os.path.join(self.actions_dir, from_file), "r", encoding="utf-8") as f:
|
|
147
|
+
content = f.read()
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(f"Failed to read from yaml file: {e}")
|
|
150
|
+
return None
|
|
151
|
+
else:
|
|
152
|
+
logger.error(f"No YAML file found matching prefix: {from_yaml}")
|
|
153
|
+
return None
|
|
154
|
+
else:
|
|
155
|
+
# 如果没有指定内容和基础文件,则尝试复制最新的文件内容
|
|
156
|
+
if content is None:
|
|
157
|
+
latest_file = self.get_latest_action_file()
|
|
158
|
+
if latest_file:
|
|
159
|
+
try:
|
|
160
|
+
with open(os.path.join(self.actions_dir, latest_file), "r", encoding="utf-8") as f:
|
|
161
|
+
content = f.read()
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Failed to read latest yaml file: {e}")
|
|
164
|
+
content = ""
|
|
165
|
+
else:
|
|
166
|
+
content = ""
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
with open(new_file_path, "w", encoding="utf-8") as f:
|
|
170
|
+
f.write(content or "")
|
|
171
|
+
return new_file_path
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logger.error(f"Failed to create new action file: {e}")
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
def load_yaml_content(self, file_name: str) -> Dict:
|
|
177
|
+
"""
|
|
178
|
+
加载 YAML 文件内容
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
file_name: YAML 文件名(仅文件名,不含路径)
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Dict: YAML 内容,如果加载失败返回空字典
|
|
185
|
+
"""
|
|
186
|
+
yaml_path = os.path.join(self.actions_dir, file_name)
|
|
187
|
+
|
|
188
|
+
try:
|
|
189
|
+
with open(yaml_path, 'r', encoding='utf-8') as f:
|
|
190
|
+
content = yaml.safe_load(f) or {}
|
|
191
|
+
return content
|
|
192
|
+
except Exception as e:
|
|
193
|
+
self.printer.print_in_terminal("yaml_load_error", style="red",
|
|
194
|
+
yaml_file=yaml_path, error=str(e))
|
|
195
|
+
return {}
|
|
196
|
+
|
|
197
|
+
def save_yaml_content(self, file_name: str, content: Dict) -> bool:
|
|
198
|
+
"""
|
|
199
|
+
保存 YAML 文件内容
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
file_name: YAML 文件名(仅文件名,不含路径)
|
|
203
|
+
content: 要保存的内容
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
bool: 保存成功返回 True,否则返回 False
|
|
207
|
+
"""
|
|
208
|
+
yaml_path = os.path.join(self.actions_dir, file_name)
|
|
209
|
+
|
|
210
|
+
try:
|
|
211
|
+
with open(yaml_path, 'w', encoding='utf-8') as f:
|
|
212
|
+
yaml.dump(content, f, allow_unicode=True, default_flow_style=False)
|
|
213
|
+
self.printer.print_in_terminal("yaml_update_success", style="green", yaml_file=yaml_path)
|
|
214
|
+
return True
|
|
215
|
+
except Exception as e:
|
|
216
|
+
self.printer.print_in_terminal("yaml_save_error", style="red",
|
|
217
|
+
yaml_file=yaml_path, error=str(e))
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
def update_yaml_field(self, file_name: str, field: str, value: Any) -> bool:
|
|
221
|
+
"""
|
|
222
|
+
更新 YAML 文件中的特定字段
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
file_name: YAML 文件名(仅文件名,不含路径)
|
|
226
|
+
field: 要更新的字段名
|
|
227
|
+
value: 字段值
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
bool: 更新成功返回 True,否则返回 False
|
|
231
|
+
"""
|
|
232
|
+
yaml_content = self.load_yaml_content(file_name)
|
|
233
|
+
yaml_content[field] = value
|
|
234
|
+
return self.save_yaml_content(file_name, yaml_content)
|
|
235
|
+
|
|
236
|
+
def get_commit_id_from_file(self, file_name: str) -> Optional[str]:
|
|
237
|
+
"""
|
|
238
|
+
从文件内容计算 commit ID
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
file_name: YAML 文件名(仅文件名,不含路径)
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Optional[str]: commit ID,如果计算失败返回 None
|
|
245
|
+
"""
|
|
246
|
+
yaml_path = os.path.join(self.actions_dir, file_name)
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
with open(yaml_path, 'r', encoding='utf-8') as f:
|
|
250
|
+
yaml_content = f.read()
|
|
251
|
+
file_md5 = hashlib.md5(yaml_content.encode("utf-8")).hexdigest()
|
|
252
|
+
return f"auto_coder_{file_name}_{file_md5}"
|
|
253
|
+
except Exception as e:
|
|
254
|
+
logger.error(f"Failed to calculate commit ID: {e}")
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
def get_file_name_from_commit_id(self, commit_id: str) -> Optional[str]:
|
|
258
|
+
"""
|
|
259
|
+
从 commit ID 中提取文件名
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
commit_id: commit ID
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Optional[str]: 文件名,如果提取失败返回 None
|
|
266
|
+
"""
|
|
267
|
+
if not commit_id.startswith("auto_coder_"):
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
# auto_coder_000000001926_chat_action.yml_88614d5bd4046a068786c252fbc39c13
|
|
272
|
+
parts = commit_id.split("_")
|
|
273
|
+
# 去掉第一部分 "auto_coder_" 和最后一部分 hash 值
|
|
274
|
+
if len(parts) >= 3:
|
|
275
|
+
file_name_parts = parts[1:-1]
|
|
276
|
+
return "_".join(file_name_parts)
|
|
277
|
+
return None
|
|
278
|
+
except Exception:
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
def get_commit_changes(self, file_name: Optional[str] = None) -> List[Tuple[str, List[str], Dict[str, Tuple[str, str]]]]:
|
|
282
|
+
"""
|
|
283
|
+
获取与特定文件相关的 commit 变更
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
file_name: 可选的 YAML 文件名(仅文件名,不含路径)
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
List[Tuple[str, List[str], Dict[str, Tuple[str, str]]]]: 变更信息列表
|
|
290
|
+
"""
|
|
291
|
+
if not file_name:
|
|
292
|
+
file_name = self.get_latest_action_file()
|
|
293
|
+
if not file_name:
|
|
294
|
+
self.printer.print_in_terminal("no_latest_commit", style="red")
|
|
295
|
+
return []
|
|
296
|
+
|
|
297
|
+
yaml_content = self.load_yaml_content(file_name)
|
|
298
|
+
query = yaml_content.get('query', '')
|
|
299
|
+
urls = yaml_content.get('urls', [])
|
|
300
|
+
|
|
301
|
+
commit_id = self.get_commit_id_from_file(file_name)
|
|
302
|
+
if not commit_id:
|
|
303
|
+
return [(query, urls, {})]
|
|
304
|
+
|
|
305
|
+
changes = {}
|
|
306
|
+
try:
|
|
307
|
+
repo = git.Repo(self.source_dir)
|
|
308
|
+
for commit in repo.iter_commits():
|
|
309
|
+
if commit_id in commit.message:
|
|
310
|
+
if commit.parents:
|
|
311
|
+
parent = commit.parents[0]
|
|
312
|
+
# 获取所有文件的前后内容
|
|
313
|
+
for diff_item in parent.diff(commit):
|
|
314
|
+
file_path = diff_item.a_path if diff_item.a_path else diff_item.b_path
|
|
315
|
+
|
|
316
|
+
# 获取变更前内容
|
|
317
|
+
before_content = None
|
|
318
|
+
try:
|
|
319
|
+
if diff_item.a_blob:
|
|
320
|
+
before_content = repo.git.show(f"{parent.hexsha}:{file_path}")
|
|
321
|
+
except git.exc.GitCommandError:
|
|
322
|
+
pass # 文件可能是新增的
|
|
323
|
+
|
|
324
|
+
# 获取变更后内容
|
|
325
|
+
after_content = None
|
|
326
|
+
try:
|
|
327
|
+
if diff_item.b_blob:
|
|
328
|
+
after_content = repo.git.show(f"{commit.hexsha}:{file_path}")
|
|
329
|
+
except git.exc.GitCommandError:
|
|
330
|
+
pass # 文件可能被删除
|
|
331
|
+
|
|
332
|
+
changes[file_path] = (before_content, after_content)
|
|
333
|
+
break
|
|
334
|
+
except git.exc.GitCommandError as e:
|
|
335
|
+
self.printer.print_in_terminal("git_command_error", style="red", error=str(e))
|
|
336
|
+
except Exception as e:
|
|
337
|
+
self.printer.print_in_terminal("get_commit_changes_error", style="red", error=str(e))
|
|
338
|
+
|
|
339
|
+
return [(query, urls, changes)]
|
|
340
|
+
|
|
341
|
+
def parse_history_tasks(self, limit: int = 5) -> List[Dict]:
|
|
342
|
+
"""
|
|
343
|
+
解析历史任务信息
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
limit: 最多解析的文件数量
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
List[Dict]: 每个字典包含一个历史任务的信息
|
|
350
|
+
"""
|
|
351
|
+
action_files = self.get_action_files()
|
|
352
|
+
|
|
353
|
+
if not action_files:
|
|
354
|
+
return []
|
|
355
|
+
|
|
356
|
+
# 按序号排序
|
|
357
|
+
sorted_files = sorted(action_files, key=self.get_sequence_number, reverse=True)
|
|
358
|
+
limited_files = sorted_files[:limit]
|
|
359
|
+
|
|
360
|
+
history_tasks = []
|
|
361
|
+
for file_name in limited_files:
|
|
362
|
+
yaml_content = self.load_yaml_content(file_name)
|
|
363
|
+
if yaml_content:
|
|
364
|
+
yaml_content['file_name'] = file_name
|
|
365
|
+
history_tasks.append(yaml_content)
|
|
366
|
+
|
|
367
|
+
return history_tasks
|
|
@@ -47,7 +47,7 @@ MESSAGES = {
|
|
|
47
47
|
"model_not_found": "Model {{model_name}} not found",
|
|
48
48
|
"generating_shell_script": "Generating Shell Script",
|
|
49
49
|
"new_session_started": "New session started. Previous chat history has been archived.",
|
|
50
|
-
"memory_save_success": "✅ Saved to your memory",
|
|
50
|
+
"memory_save_success": "✅ Saved to your memory(path: {{path}})",
|
|
51
51
|
"file_decode_error": "Failed to decode file: {{file_path}}. Tried encodings: {{encodings}}",
|
|
52
52
|
"file_write_error": "Failed to write file: {{file_path}}. Error: {{error}}",
|
|
53
53
|
"yaml_load_error": "Error loading yaml file {{yaml_file}}: {{error}}",
|
|
@@ -194,6 +194,9 @@ MESSAGES = {
|
|
|
194
194
|
"super_big_filter_title": "{{ model_name }} is analyzing how to filter extremely large context...",
|
|
195
195
|
"mcp_server_info_error": "Error getting MCP server info: {{ error }}",
|
|
196
196
|
"mcp_server_info_title": "Connected MCP Server Info",
|
|
197
|
+
"no_commit_file_name": "Cannot get the file name of the commit_id in the actions directory: {{commit_id}}",
|
|
198
|
+
"yaml_update_success": "✅ Successfully updated YAML file: {{yaml_file}} with how_to_reproduce field",
|
|
199
|
+
"yaml_save_error": "❌ Error saving YAML file {{yaml_file}}: {{error}}",
|
|
197
200
|
},
|
|
198
201
|
"zh": {
|
|
199
202
|
"file_sliding_window_processing": "文件 {{ file_path }} 过大 ({{ tokens }} tokens),正在使用滑动窗口处理...",
|
|
@@ -257,7 +260,7 @@ MESSAGES = {
|
|
|
257
260
|
"model_not_found": "未找到模型: {{model_name}}",
|
|
258
261
|
"generating_shell_script": "正在生成 Shell 脚本",
|
|
259
262
|
"new_session_started": "新会话已开始。之前的聊天历史已存档。",
|
|
260
|
-
"memory_save_success": "✅ 已保存到您的记忆中",
|
|
263
|
+
"memory_save_success": "✅ 已保存到您的记忆中(路径: {{path}})",
|
|
261
264
|
"file_decode_error": "无法解码文件: {{file_path}}。尝试的编码: {{encodings}}",
|
|
262
265
|
"file_write_error": "无法写入文件: {{file_path}}. 错误: {{error}}",
|
|
263
266
|
"yaml_load_error": "加载YAML文件出错 {{yaml_file}}: {{error}}",
|
|
@@ -385,6 +388,9 @@ MESSAGES = {
|
|
|
385
388
|
"super_big_filter_title": "{{ model_name }} 正在分析如何过滤极大规模上下文...",
|
|
386
389
|
"mcp_server_info_error": "获取MCP服务器信息时出错: {{ error }}",
|
|
387
390
|
"mcp_server_info_title": "已连接的MCP服务器信息",
|
|
391
|
+
"no_commit_file_name": "无法获取commit_id关联的actions 目录下的文件名: {{commit_id}}",
|
|
392
|
+
"yaml_update_success": "✅ 成功更新YAML文件: {{yaml_file}},添加了how_to_reproduce字段",
|
|
393
|
+
"yaml_save_error": "❌ 保存YAML文件出错 {{yaml_file}}: {{error}}",
|
|
388
394
|
}}
|
|
389
395
|
|
|
390
396
|
|
|
@@ -157,6 +157,12 @@ def config_readme() -> str:
|
|
|
157
157
|
3. 代码文件后缀名列表(比如.java,.py,.go,.js,.ts),多个按逗号分割
|
|
158
158
|
|
|
159
159
|
推荐使用 3 选项,因为项目类型通常为多种后缀名混合。
|
|
160
|
+
|
|
161
|
+
## include_project_structure: 是否包含项目结构
|
|
162
|
+
是否包含项目结构。推荐设置为 true。默认为true,但是项目结构也可能很大,如果项目结构很大,那么可以设置为 false。
|
|
163
|
+
|
|
164
|
+
## conversation_prune_safe_zone_tokens: 对话剪枝安全区token数量
|
|
165
|
+
在对话剪枝时,会根据对话的token数量,如果token数量超过该值,那么会剪枝掉一部分对话。
|
|
160
166
|
"""
|
|
161
167
|
|
|
162
168
|
class ConfigAutoTuner:
|
|
@@ -16,7 +16,14 @@ COMMANDS = {
|
|
|
16
16
|
"/sd": {},
|
|
17
17
|
},
|
|
18
18
|
"/coding": {"/apply": {}, "/next": {}},
|
|
19
|
-
"/chat": {"/new": {},
|
|
19
|
+
"/chat": {"/new": {},
|
|
20
|
+
"/save": {},
|
|
21
|
+
"/copy":{},
|
|
22
|
+
"/mcp": {},
|
|
23
|
+
"/rag": {},
|
|
24
|
+
"/review": {},
|
|
25
|
+
"/learn": {},
|
|
26
|
+
"/no_context": {}},
|
|
20
27
|
"/mcp": {
|
|
21
28
|
"/add": "",
|
|
22
29
|
"/remove": "",
|
|
@@ -40,7 +40,11 @@ def save_to_memory_file(ask_conversation,query:str,response:str):
|
|
|
40
40
|
|
|
41
41
|
# Save memory
|
|
42
42
|
with open(memory_file, 'w') as f:
|
|
43
|
-
json.dump(memory_data, f, ensure_ascii=False, indent=2)
|
|
43
|
+
json.dump(memory_data, f, ensure_ascii=False, indent=2)
|
|
44
|
+
|
|
45
|
+
get_global_memory_file_paths()
|
|
46
|
+
tmp_dir = os.path.join(memory_dir, ".tmp")
|
|
47
|
+
return tmp_dir
|
|
44
48
|
|
|
45
49
|
def load_from_memory_file() -> List[MemoryEntry]:
|
|
46
50
|
"""Load memory data from file and return as list of MemoryEntry objects"""
|
autocoder/index/entry.py
CHANGED
|
@@ -25,10 +25,13 @@ from autocoder.index.index import IndexManager
|
|
|
25
25
|
from loguru import logger
|
|
26
26
|
from autocoder.common import SourceCodeList
|
|
27
27
|
from autocoder.common.context_pruner import PruneContext
|
|
28
|
+
from autocoder.common.action_yml_file_manager import ActionYmlFileManager
|
|
28
29
|
|
|
29
30
|
def build_index_and_filter_files(
|
|
30
31
|
llm, args: AutoCoderArgs, sources: List[SourceCode]
|
|
31
32
|
) -> SourceCodeList:
|
|
33
|
+
|
|
34
|
+
action_yml_file_manager = ActionYmlFileManager(args.source_dir)
|
|
32
35
|
# Initialize timing and statistics
|
|
33
36
|
total_start_time = time.monotonic()
|
|
34
37
|
stats = {
|
|
@@ -369,5 +372,19 @@ def build_index_and_filter_files(
|
|
|
369
372
|
})
|
|
370
373
|
)
|
|
371
374
|
)
|
|
375
|
+
|
|
376
|
+
if args.file:
|
|
377
|
+
action_file_name = os.path.basename(args.file)
|
|
378
|
+
dynamic_urls = []
|
|
379
|
+
|
|
380
|
+
for file in source_code_list.sources:
|
|
381
|
+
dynamic_urls.append(file.module_name)
|
|
382
|
+
|
|
383
|
+
args.dynamic_urls = dynamic_urls
|
|
372
384
|
|
|
385
|
+
update_yaml_success = action_yml_file_manager.update_yaml_field(action_file_name, "dynamic_urls", args.dynamic_urls)
|
|
386
|
+
if not update_yaml_success:
|
|
387
|
+
printer = Printer()
|
|
388
|
+
printer.print_in_terminal("yaml_save_error", style="red", yaml_file=action_file_name)
|
|
389
|
+
|
|
373
390
|
return source_code_list
|
|
@@ -652,40 +652,68 @@ class LocalDuckDBStorageCache(BaseCacheManager):
|
|
|
652
652
|
|
|
653
653
|
return all_files
|
|
654
654
|
|
|
655
|
-
def
|
|
656
|
-
"""
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
655
|
+
def _get_single_cache(self, query: str, options: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
656
|
+
"""
|
|
657
|
+
使用单个查询检索缓存文档
|
|
658
|
+
|
|
659
|
+
参数:
|
|
660
|
+
query: 查询字符串
|
|
661
|
+
options: 包含查询选项的字典
|
|
662
|
+
|
|
663
|
+
返回:
|
|
664
|
+
包含文档信息的字典列表,每个字典包含_id、file_path、mtime和score字段
|
|
665
|
+
"""
|
|
664
666
|
logger.info(f"正在使用向量搜索检索数据, 你的问题: {query}")
|
|
665
|
-
total_tokens = 0
|
|
666
667
|
results = []
|
|
667
668
|
|
|
668
669
|
# Add vector search if enabled
|
|
669
670
|
if options.get("enable_vector_search", True):
|
|
670
671
|
# 返回值包含 [(_id, file_path, mtime, score,),]
|
|
671
|
-
# results = self.storage.vector_search(query, similarity_value=0.7, similarity_top_k=200)
|
|
672
672
|
search_results = self.storage.vector_search(
|
|
673
673
|
query,
|
|
674
674
|
similarity_value=self.extra_params.rag_duckdb_query_similarity,
|
|
675
675
|
similarity_top_k=self.extra_params.rag_duckdb_query_top_k,
|
|
676
676
|
query_dim=self.extra_params.rag_duckdb_vector_dim
|
|
677
677
|
)
|
|
678
|
-
|
|
679
|
-
|
|
678
|
+
|
|
679
|
+
# Convert tuples to dictionaries for the merger
|
|
680
|
+
for _id, file_path, mtime, score in search_results:
|
|
681
|
+
results.append({
|
|
682
|
+
"_id": _id,
|
|
683
|
+
"file_path": file_path,
|
|
684
|
+
"mtime": mtime,
|
|
685
|
+
"score": score
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
logger.info(f"查询 '{query}' 返回 {len(results)} 条记录")
|
|
689
|
+
return results
|
|
690
|
+
|
|
691
|
+
def _process_search_results(self, results: List[Dict[str, Any]]) -> Dict[str, Dict]:
|
|
692
|
+
"""
|
|
693
|
+
处理搜索结果,提取文件路径并构建结果字典
|
|
694
|
+
|
|
695
|
+
参数:
|
|
696
|
+
results: 搜索结果列表,每项包含文档信息的字典
|
|
697
|
+
|
|
698
|
+
返回:
|
|
699
|
+
匹配文档的字典,键为文件路径,值为文件内容
|
|
700
|
+
|
|
701
|
+
说明:
|
|
702
|
+
该方法会根据查询结果从缓存中提取文件内容,并记录累计token数,
|
|
703
|
+
当累计token数超过max_output_tokens时,将停止处理并返回已处理的结果。
|
|
704
|
+
"""
|
|
705
|
+
# 记录被处理的总tokens数
|
|
706
|
+
total_tokens = 0
|
|
707
|
+
|
|
680
708
|
# Group results by file_path and reconstruct documents while preserving order
|
|
681
709
|
# 这里还可以有排序优化,综合考虑一篇内容出现的次数以及排序位置
|
|
682
710
|
file_paths = []
|
|
683
711
|
seen = set()
|
|
684
712
|
for result in results:
|
|
685
|
-
|
|
686
|
-
if
|
|
687
|
-
seen.add(
|
|
688
|
-
file_paths.append(
|
|
713
|
+
file_path = result["file_path"]
|
|
714
|
+
if file_path not in seen:
|
|
715
|
+
seen.add(file_path)
|
|
716
|
+
file_paths.append(file_path)
|
|
689
717
|
|
|
690
718
|
# 从缓存中获取文件内容
|
|
691
719
|
result = {}
|
|
@@ -706,3 +734,69 @@ class LocalDuckDBStorageCache(BaseCacheManager):
|
|
|
706
734
|
f"累计tokens: {total_tokens}, "
|
|
707
735
|
f"经过向量搜索共检索出 {len(result.keys())} 个文档, 共 {len(self.cache.keys())} 个文档")
|
|
708
736
|
return result
|
|
737
|
+
|
|
738
|
+
def get_cache(self, options: Optional[Dict[str, Any]] = None) -> Dict[str, Dict]:
|
|
739
|
+
"""
|
|
740
|
+
获取缓存中的文档信息
|
|
741
|
+
|
|
742
|
+
参数:
|
|
743
|
+
options: 包含查询参数的字典,可以包含以下键:
|
|
744
|
+
- queries: 查询列表,可以是单个查询或多个查询
|
|
745
|
+
- enable_vector_search: 是否启用向量搜索,默认为True
|
|
746
|
+
- merge_strategy: 多查询时的合并策略,默认为WEIGHTED_RANK
|
|
747
|
+
- max_results: 最大结果数,默认为None表示不限制
|
|
748
|
+
|
|
749
|
+
返回:
|
|
750
|
+
匹配文档的字典,键为文件路径,值为文件内容
|
|
751
|
+
"""
|
|
752
|
+
self.trigger_update() # 检查更新
|
|
753
|
+
|
|
754
|
+
if options is None or "queries" not in options:
|
|
755
|
+
return {file_path: self.cache[file_path].model_dump() for file_path in self.cache}
|
|
756
|
+
|
|
757
|
+
queries = options.get("queries", [])
|
|
758
|
+
|
|
759
|
+
# 如果没有查询或只有一个查询,使用原来的方法
|
|
760
|
+
if not queries:
|
|
761
|
+
return {file_path: self.cache[file_path].model_dump() for file_path in self.cache}
|
|
762
|
+
elif len(queries) == 1:
|
|
763
|
+
results = self._get_single_cache(queries[0], options)
|
|
764
|
+
return self._process_search_results(results)
|
|
765
|
+
|
|
766
|
+
# 导入合并策略
|
|
767
|
+
from autocoder.rag.cache.cache_result_merge import CacheResultMerger, MergeStrategy
|
|
768
|
+
|
|
769
|
+
# 获取合并策略
|
|
770
|
+
merge_strategy_name = options.get("merge_strategy", MergeStrategy.WEIGHTED_RANK.value)
|
|
771
|
+
try:
|
|
772
|
+
merge_strategy = MergeStrategy(merge_strategy_name)
|
|
773
|
+
except ValueError:
|
|
774
|
+
logger.warning(f"未知的合并策略: {merge_strategy_name}, 使用默认策略 WEIGHTED_RANK")
|
|
775
|
+
merge_strategy = MergeStrategy.WEIGHTED_RANK
|
|
776
|
+
|
|
777
|
+
# 限制最大结果数
|
|
778
|
+
max_results = options.get("max_results", None)
|
|
779
|
+
merger = CacheResultMerger(max_results=max_results)
|
|
780
|
+
|
|
781
|
+
# 并发处理多个查询
|
|
782
|
+
logger.info(f"处理多查询请求,查询数量: {len(queries)}, 合并策略: {merge_strategy}")
|
|
783
|
+
query_results = []
|
|
784
|
+
with ThreadPoolExecutor(max_workers=min(len(queries), 10)) as executor:
|
|
785
|
+
future_to_query = {executor.submit(self._get_single_cache, query, options): query for query in queries}
|
|
786
|
+
for future in as_completed(future_to_query):
|
|
787
|
+
query = future_to_query[future]
|
|
788
|
+
try:
|
|
789
|
+
query_result = future.result()
|
|
790
|
+
logger.info(f"查询 '{query}' 返回 {len(query_result)} 条记录")
|
|
791
|
+
query_results.append((query, query_result))
|
|
792
|
+
except Exception as e:
|
|
793
|
+
logger.error(f"处理查询 '{query}' 时出错: {str(e)}")
|
|
794
|
+
|
|
795
|
+
logger.info(f"所有查询共返回 {sum(len(r) for _, r in query_results)} 条记录")
|
|
796
|
+
|
|
797
|
+
# 使用策略合并结果
|
|
798
|
+
merged_results = merger.merge(query_results, strategy=merge_strategy)
|
|
799
|
+
logger.info(f"合并后的结果共 {len(merged_results)} 条记录")
|
|
800
|
+
|
|
801
|
+
# 处理合并后的结果
|
|
802
|
+
return self._process_search_results(merged_results)
|
autocoder/utils/__init__.py
CHANGED
|
@@ -3,17 +3,21 @@ from typing import Optional
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import shutil
|
|
5
5
|
from loguru import logger
|
|
6
|
+
from autocoder.common.action_yml_file_manager import ActionYmlFileManager
|
|
6
7
|
|
|
7
8
|
def get_last_yaml_file(actions_dir:str)->Optional[str]:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
"""
|
|
10
|
+
获取最新的 YAML 文件
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
actions_dir: actions 目录路径
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Optional[str]: 最新的 YAML 文件名,如果没有则返回 None
|
|
17
|
+
"""
|
|
18
|
+
# 兼容已有代码,创建临时 ActionYmlFileManager
|
|
19
|
+
action_manager = ActionYmlFileManager(os.path.dirname(actions_dir))
|
|
20
|
+
return action_manager.get_latest_action_file()
|
|
17
21
|
|
|
18
22
|
def open_yaml_file_in_editor(new_file:str):
|
|
19
23
|
try:
|
autocoder/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.1.
|
|
1
|
+
__version__ = "0.1.300"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|