auto-coder 0.1.348__py3-none-any.whl → 0.1.350__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.348.dist-info → auto_coder-0.1.350.dist-info}/METADATA +1 -1
- {auto_coder-0.1.348.dist-info → auto_coder-0.1.350.dist-info}/RECORD +35 -26
- autocoder/auto_coder_runner.py +14 -10
- autocoder/chat_auto_coder_lang.py +5 -3
- autocoder/common/model_speed_tester.py +392 -0
- autocoder/common/printer.py +7 -8
- autocoder/common/run_cmd.py +247 -0
- autocoder/common/test_run_cmd.py +110 -0
- autocoder/common/v2/agent/agentic_edit.py +61 -11
- autocoder/common/v2/agent/agentic_edit_conversation.py +9 -0
- autocoder/common/v2/agent/agentic_edit_tools/execute_command_tool_resolver.py +21 -36
- autocoder/common/v2/agent/agentic_edit_tools/list_files_tool_resolver.py +4 -7
- autocoder/common/v2/agent/agentic_edit_tools/search_files_tool_resolver.py +2 -5
- autocoder/helper/rag_doc_creator.py +141 -0
- autocoder/ignorefiles/__init__.py +4 -0
- autocoder/ignorefiles/ignore_file_utils.py +63 -0
- autocoder/ignorefiles/test_ignore_file_utils.py +91 -0
- autocoder/models.py +48 -8
- autocoder/rag/cache/byzer_storage_cache.py +10 -4
- autocoder/rag/cache/file_monitor_cache.py +27 -24
- autocoder/rag/cache/local_byzer_storage_cache.py +11 -5
- autocoder/rag/cache/local_duckdb_storage_cache.py +203 -128
- autocoder/rag/cache/simple_cache.py +56 -37
- autocoder/rag/loaders/filter_utils.py +106 -0
- autocoder/rag/loaders/image_loader.py +45 -23
- autocoder/rag/loaders/pdf_loader.py +3 -3
- autocoder/rag/loaders/test_image_loader.py +209 -0
- autocoder/rag/qa_conversation_strategy.py +3 -5
- autocoder/rag/utils.py +20 -9
- autocoder/utils/_markitdown.py +35 -0
- autocoder/version.py +1 -1
- {auto_coder-0.1.348.dist-info → auto_coder-0.1.350.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.348.dist-info → auto_coder-0.1.350.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.348.dist-info → auto_coder-0.1.350.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.348.dist-info → auto_coder-0.1.350.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
import os
|
|
3
3
|
from typing import Dict, Any, Optional
|
|
4
|
+
from autocoder.common.run_cmd import run_cmd_subprocess
|
|
4
5
|
from autocoder.common.v2.agent.agentic_edit_tools.base_tool_resolver import BaseToolResolver
|
|
5
6
|
from autocoder.common.v2.agent.agentic_edit_types import ExecuteCommandTool, ToolResult # Import ToolResult from types
|
|
6
7
|
from autocoder.common import shells
|
|
@@ -8,7 +9,8 @@ from autocoder.common.printer import Printer
|
|
|
8
9
|
from loguru import logger
|
|
9
10
|
import typing
|
|
10
11
|
from autocoder.common import AutoCoderArgs
|
|
11
|
-
|
|
12
|
+
from autocoder.events.event_manager_singleton import get_event_manager
|
|
13
|
+
from autocoder.run_context import get_run_context
|
|
12
14
|
if typing.TYPE_CHECKING:
|
|
13
15
|
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
14
16
|
|
|
@@ -39,45 +41,28 @@ class ExecuteCommandToolResolver(BaseToolResolver):
|
|
|
39
41
|
pass
|
|
40
42
|
|
|
41
43
|
printer.print_str_in_terminal(f"Executing command: {command} in {os.path.abspath(source_dir)}")
|
|
42
|
-
try:
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# Execute the command
|
|
54
|
-
process = subprocess.Popen(
|
|
55
|
-
command,
|
|
56
|
-
shell=True,
|
|
57
|
-
stdout=subprocess.PIPE,
|
|
58
|
-
stderr=subprocess.PIPE,
|
|
59
|
-
cwd=source_dir,
|
|
60
|
-
text=True,
|
|
61
|
-
encoding=shells.get_terminal_encoding(),
|
|
62
|
-
errors='replace' # Handle potential decoding errors
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
stdout, stderr = process.communicate()
|
|
66
|
-
returncode = process.returncode
|
|
44
|
+
try:
|
|
45
|
+
# 使用封装的run_cmd方法执行命令
|
|
46
|
+
if get_run_context().is_web():
|
|
47
|
+
answer = get_event_manager(
|
|
48
|
+
self.args.event_file).ask_user(prompt=f"Allow to execute the `{command}`?",options=["yes","no"])
|
|
49
|
+
if answer == "yes":
|
|
50
|
+
pass
|
|
51
|
+
else:
|
|
52
|
+
return ToolResult(success=False, message=f"Command '{command}' execution denied by user.")
|
|
53
|
+
|
|
54
|
+
exit_code, output = run_cmd_subprocess(command, verbose=True, cwd=source_dir)
|
|
67
55
|
|
|
68
56
|
logger.info(f"Command executed: {command}")
|
|
69
|
-
logger.info(f"Return Code: {
|
|
70
|
-
if
|
|
71
|
-
logger.info(f"
|
|
72
|
-
if stderr:
|
|
73
|
-
logger.info(f"stderr:\n{stderr}")
|
|
74
|
-
|
|
57
|
+
logger.info(f"Return Code: {exit_code}")
|
|
58
|
+
if output:
|
|
59
|
+
logger.info(f"Output:\n{output}")
|
|
75
60
|
|
|
76
|
-
if
|
|
77
|
-
return ToolResult(success=True, message="Command executed successfully.", content=
|
|
61
|
+
if exit_code == 0:
|
|
62
|
+
return ToolResult(success=True, message="Command executed successfully.", content=output)
|
|
78
63
|
else:
|
|
79
|
-
error_message = f"Command failed with return code {
|
|
80
|
-
return ToolResult(success=False, message=error_message, content={"
|
|
64
|
+
error_message = f"Command failed with return code {exit_code}.\nOutput:\n{output}"
|
|
65
|
+
return ToolResult(success=False, message=error_message, content={"output": output, "returncode": exit_code})
|
|
81
66
|
|
|
82
67
|
except FileNotFoundError:
|
|
83
68
|
return ToolResult(success=False, message=f"Error: The command '{command.split()[0]}' was not found. Please ensure it is installed and in the system's PATH.")
|
|
@@ -6,7 +6,7 @@ from loguru import logger
|
|
|
6
6
|
import typing
|
|
7
7
|
from autocoder.common import AutoCoderArgs
|
|
8
8
|
|
|
9
|
-
from autocoder.
|
|
9
|
+
from autocoder.ignorefiles.ignore_file_utils import should_ignore
|
|
10
10
|
|
|
11
11
|
if typing.TYPE_CHECKING:
|
|
12
12
|
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
@@ -25,9 +25,6 @@ class ListFilesToolResolver(BaseToolResolver):
|
|
|
25
25
|
absolute_source_dir = os.path.abspath(source_dir)
|
|
26
26
|
absolute_list_path = os.path.abspath(os.path.join(source_dir, list_path_str))
|
|
27
27
|
|
|
28
|
-
# Load ignore spec from .autocoderignore if exists
|
|
29
|
-
ignore_spec = load_ignore_spec(absolute_source_dir)
|
|
30
|
-
|
|
31
28
|
# Security check: Allow listing outside source_dir IF the original path is outside?
|
|
32
29
|
is_outside_source = not absolute_list_path.startswith(absolute_source_dir)
|
|
33
30
|
if is_outside_source:
|
|
@@ -59,10 +56,10 @@ class ListFilesToolResolver(BaseToolResolver):
|
|
|
59
56
|
if recursive:
|
|
60
57
|
for root, dirs, files in os.walk(base_dir):
|
|
61
58
|
# Modify dirs in-place to skip ignored dirs early
|
|
62
|
-
dirs[:] = [d for d in dirs if not should_ignore(os.path.join(root, d)
|
|
59
|
+
dirs[:] = [d for d in dirs if not should_ignore(os.path.join(root, d))]
|
|
63
60
|
for name in files:
|
|
64
61
|
full_path = os.path.join(root, name)
|
|
65
|
-
if should_ignore(full_path
|
|
62
|
+
if should_ignore(full_path):
|
|
66
63
|
continue
|
|
67
64
|
display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
|
|
68
65
|
result.add(display_path)
|
|
@@ -73,7 +70,7 @@ class ListFilesToolResolver(BaseToolResolver):
|
|
|
73
70
|
else:
|
|
74
71
|
for item in os.listdir(base_dir):
|
|
75
72
|
full_path = os.path.join(base_dir, item)
|
|
76
|
-
if should_ignore(full_path
|
|
73
|
+
if should_ignore(full_path):
|
|
77
74
|
continue
|
|
78
75
|
display_path = os.path.relpath(full_path, source_dir) if not is_outside_source else full_path
|
|
79
76
|
if os.path.isdir(full_path):
|
|
@@ -9,7 +9,7 @@ from loguru import logger
|
|
|
9
9
|
from autocoder.common import AutoCoderArgs
|
|
10
10
|
import typing
|
|
11
11
|
|
|
12
|
-
from autocoder.
|
|
12
|
+
from autocoder.ignorefiles.ignore_file_utils import should_ignore
|
|
13
13
|
|
|
14
14
|
if typing.TYPE_CHECKING:
|
|
15
15
|
from autocoder.common.v2.agent.agentic_edit import AgenticEdit
|
|
@@ -29,9 +29,6 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
29
29
|
absolute_source_dir = os.path.abspath(source_dir)
|
|
30
30
|
absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
|
|
31
31
|
|
|
32
|
-
# Load ignore spec from .autocoderignore if exists
|
|
33
|
-
ignore_spec = load_ignore_spec(absolute_source_dir)
|
|
34
|
-
|
|
35
32
|
# Security check
|
|
36
33
|
if not absolute_search_path.startswith(absolute_source_dir):
|
|
37
34
|
return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
|
|
@@ -65,7 +62,7 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
65
62
|
|
|
66
63
|
for filepath in glob.glob(search_glob_pattern, recursive=True):
|
|
67
64
|
abs_path = os.path.abspath(filepath)
|
|
68
|
-
if should_ignore(abs_path
|
|
65
|
+
if should_ignore(abs_path):
|
|
69
66
|
continue
|
|
70
67
|
|
|
71
68
|
if os.path.isfile(filepath):
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
from loguru import logger
|
|
4
|
+
|
|
5
|
+
def create_sample_files(base_dir: str):
|
|
6
|
+
"""创建示例代码文件用于演示"""
|
|
7
|
+
os.makedirs(base_dir, exist_ok=True)
|
|
8
|
+
|
|
9
|
+
calculator_content = """
|
|
10
|
+
class Calculator:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.history = []
|
|
13
|
+
|
|
14
|
+
def add(self, a: int, b: int) -> int:
|
|
15
|
+
'''加法函数'''
|
|
16
|
+
result = a + b
|
|
17
|
+
self.history.append(f"{a} + {b} = {result}")
|
|
18
|
+
return result
|
|
19
|
+
|
|
20
|
+
def subtract(self, a: int, b: int) -> int:
|
|
21
|
+
'''减法函数'''
|
|
22
|
+
result = a - b
|
|
23
|
+
self.history.append(f"{a} - {b} = {result}")
|
|
24
|
+
return result
|
|
25
|
+
|
|
26
|
+
def multiply(self, a: int, b: int) -> int:
|
|
27
|
+
'''乘法函数'''
|
|
28
|
+
result = a * b
|
|
29
|
+
self.history.append(f"{a} * {b} = {result}")
|
|
30
|
+
return result
|
|
31
|
+
|
|
32
|
+
def divide(self, a: int, b: int) -> float:
|
|
33
|
+
'''除法函数'''
|
|
34
|
+
if b == 0:
|
|
35
|
+
raise ValueError("Cannot divide by zero")
|
|
36
|
+
result = a / b
|
|
37
|
+
self.history.append(f"{a} / {b} = {result}")
|
|
38
|
+
return result
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
string_processor_content = """
|
|
42
|
+
class StringProcessor:
|
|
43
|
+
@staticmethod
|
|
44
|
+
def reverse(text: str) -> str:
|
|
45
|
+
'''反转字符串'''
|
|
46
|
+
return text[::-1]
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def capitalize(text: str) -> str:
|
|
50
|
+
'''首字母大写'''
|
|
51
|
+
return text.capitalize()
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def count_words(text: str) -> int:
|
|
55
|
+
'''计算单词数量'''
|
|
56
|
+
return len(text.split())
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def format_name(first_name: str, last_name: str) -> str:
|
|
60
|
+
'''格式化姓名'''
|
|
61
|
+
return f"{last_name}, {first_name}"
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
data_processor_content = """
|
|
65
|
+
import statistics
|
|
66
|
+
from typing import List, Dict, Any
|
|
67
|
+
|
|
68
|
+
class DataProcessor:
|
|
69
|
+
@staticmethod
|
|
70
|
+
def calculate_average(numbers: List[float]) -> float:
|
|
71
|
+
'''计算平均值'''
|
|
72
|
+
return sum(numbers) / len(numbers)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def find_median(numbers: List[float]) -> float:
|
|
76
|
+
'''计算中位数'''
|
|
77
|
+
sorted_numbers = sorted(numbers)
|
|
78
|
+
n = len(sorted_numbers)
|
|
79
|
+
if n % 2 == 0:
|
|
80
|
+
return (sorted_numbers[n//2-1] + sorted_numbers[n//2]) / 2
|
|
81
|
+
else:
|
|
82
|
+
return sorted_numbers[n//2]
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def find_mode(numbers: List[float]) -> List[float]:
|
|
86
|
+
'''查找众数'''
|
|
87
|
+
try:
|
|
88
|
+
return statistics.mode(numbers)
|
|
89
|
+
except statistics.StatisticsError:
|
|
90
|
+
# 没有唯一众数的情况
|
|
91
|
+
count_dict = {}
|
|
92
|
+
for num in numbers:
|
|
93
|
+
if num in count_dict:
|
|
94
|
+
count_dict[num] += 1
|
|
95
|
+
else:
|
|
96
|
+
count_dict[num] = 1
|
|
97
|
+
max_count = max(count_dict.values())
|
|
98
|
+
return [num for num, count in count_dict.items() if count == max_count]
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
with open(os.path.join(base_dir, "calculator.py"), "w", encoding="utf-8") as f:
|
|
102
|
+
f.write(calculator_content)
|
|
103
|
+
|
|
104
|
+
with open(os.path.join(base_dir, "string_processor.py"), "w", encoding="utf-8") as f:
|
|
105
|
+
f.write(string_processor_content)
|
|
106
|
+
|
|
107
|
+
with open(os.path.join(base_dir, "data_processor.py"), "w", encoding="utf-8") as f:
|
|
108
|
+
f.write(data_processor_content)
|
|
109
|
+
|
|
110
|
+
logger.info(f"示例代码文件已创建在: {base_dir}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def update_sample_file(base_dir: str, filename: str, content: str):
|
|
114
|
+
"""更新指定示例文件内容"""
|
|
115
|
+
file_path = os.path.join(base_dir, filename)
|
|
116
|
+
if not os.path.exists(file_path):
|
|
117
|
+
logger.warning(f"文件 {file_path} 不存在,无法更新")
|
|
118
|
+
return
|
|
119
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
120
|
+
f.write(content)
|
|
121
|
+
logger.info(f"已更新文件: {file_path}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def add_sample_file(base_dir: str, filename: str, content: str):
|
|
125
|
+
"""新增示例文件,若存在则覆盖"""
|
|
126
|
+
file_path = os.path.join(base_dir, filename)
|
|
127
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
128
|
+
f.write(content)
|
|
129
|
+
logger.info(f"已新增文件: {file_path}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def delete_sample_file(base_dir: str, filename: str):
|
|
133
|
+
"""删除指定示例文件"""
|
|
134
|
+
file_path = os.path.join(base_dir, filename)
|
|
135
|
+
try:
|
|
136
|
+
os.remove(file_path)
|
|
137
|
+
logger.info(f"已删除文件: {file_path}")
|
|
138
|
+
except FileNotFoundError:
|
|
139
|
+
logger.warning(f"文件 {file_path} 不存在,无法删除")
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"删除文件 {file_path} 失败: {e}")
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from threading import Lock
|
|
5
|
+
import pathspec
|
|
6
|
+
|
|
7
|
+
DEFAULT_EXCLUDES = [
|
|
8
|
+
'.git', '.auto-coder', 'node_modules', '.mvn', '.idea',
|
|
9
|
+
'__pycache__', '.venv', 'venv', 'dist', 'build', '.gradle',".next"
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IgnoreFileManager:
|
|
14
|
+
_instance = None
|
|
15
|
+
_lock = Lock()
|
|
16
|
+
|
|
17
|
+
def __new__(cls):
|
|
18
|
+
if not cls._instance:
|
|
19
|
+
with cls._lock:
|
|
20
|
+
if not cls._instance:
|
|
21
|
+
cls._instance = super(IgnoreFileManager, cls).__new__(cls)
|
|
22
|
+
cls._instance._initialized = False
|
|
23
|
+
return cls._instance
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
if self._initialized:
|
|
27
|
+
return
|
|
28
|
+
self._initialized = True
|
|
29
|
+
self._spec = None
|
|
30
|
+
self._load_ignore_spec()
|
|
31
|
+
|
|
32
|
+
def _load_ignore_spec(self):
|
|
33
|
+
ignore_patterns = []
|
|
34
|
+
project_root = Path(os.getcwd())
|
|
35
|
+
|
|
36
|
+
ignore_file_paths = [
|
|
37
|
+
project_root / '.autocoderignore',
|
|
38
|
+
project_root / '.auto-coder' / '.autocoderignore'
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
for ignore_file in ignore_file_paths:
|
|
42
|
+
if ignore_file.is_file():
|
|
43
|
+
with open(ignore_file, 'r', encoding='utf-8') as f:
|
|
44
|
+
ignore_patterns = f.read().splitlines()
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
# 添加默认排除目录
|
|
48
|
+
ignore_patterns.extend(DEFAULT_EXCLUDES)
|
|
49
|
+
|
|
50
|
+
self._spec = pathspec.PathSpec.from_lines('gitwildmatch', ignore_patterns)
|
|
51
|
+
|
|
52
|
+
def should_ignore(self, path: str) -> bool:
|
|
53
|
+
rel_path = os.path.relpath(path, os.getcwd())
|
|
54
|
+
# 标准化分隔符
|
|
55
|
+
rel_path = rel_path.replace(os.sep, '/')
|
|
56
|
+
return self._spec.match_file(rel_path)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# 对外提供单例
|
|
60
|
+
_ignore_manager = IgnoreFileManager()
|
|
61
|
+
|
|
62
|
+
def should_ignore(path: str) -> bool:
|
|
63
|
+
return _ignore_manager.should_ignore(path)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from src.autocoder.ignorefiles import ignore_file_utils
|
|
9
|
+
|
|
10
|
+
@pytest.fixture(autouse=True)
|
|
11
|
+
def cleanup_ignore_manager(monkeypatch):
|
|
12
|
+
"""
|
|
13
|
+
在每个测试前后清理 IgnoreFileManager 的单例状态,保证测试隔离
|
|
14
|
+
"""
|
|
15
|
+
# 备份原始实例
|
|
16
|
+
original_instance = ignore_file_utils._ignore_manager
|
|
17
|
+
# 强制重新加载忽略规则
|
|
18
|
+
def reset_ignore_manager():
|
|
19
|
+
ignore_file_utils.IgnoreFileManager._instance = None
|
|
20
|
+
return ignore_file_utils.IgnoreFileManager()
|
|
21
|
+
|
|
22
|
+
monkeypatch.setattr(ignore_file_utils, "_ignore_manager", reset_ignore_manager())
|
|
23
|
+
yield
|
|
24
|
+
# 恢复原始实例
|
|
25
|
+
ignore_file_utils._ignore_manager = original_instance
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_default_excludes(tmp_path, monkeypatch):
|
|
29
|
+
# 切换当前工作目录
|
|
30
|
+
monkeypatch.chdir(tmp_path)
|
|
31
|
+
|
|
32
|
+
# 不创建任何 .autocoderignore 文件,使用默认排除规则
|
|
33
|
+
# 创建默认排除目录
|
|
34
|
+
for dirname in ignore_file_utils.DEFAULT_EXCLUDES:
|
|
35
|
+
(tmp_path / dirname).mkdir(parents=True, exist_ok=True)
|
|
36
|
+
# 应该被忽略
|
|
37
|
+
assert ignore_file_utils.should_ignore(str(tmp_path / dirname)) is True
|
|
38
|
+
|
|
39
|
+
# 创建不会被忽略的文件
|
|
40
|
+
normal_file = tmp_path / "myfile.txt"
|
|
41
|
+
normal_file.write_text("hello")
|
|
42
|
+
assert ignore_file_utils.should_ignore(str(normal_file)) is False
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_custom_ignore_file(tmp_path, monkeypatch):
|
|
46
|
+
monkeypatch.chdir(tmp_path)
|
|
47
|
+
|
|
48
|
+
# 创建自定义忽略文件
|
|
49
|
+
ignore_file = tmp_path / ".autocoderignore"
|
|
50
|
+
ignore_file.write_text("data/**\nsecret.txt")
|
|
51
|
+
|
|
52
|
+
# 重新初始化忽略管理器以加载新规则
|
|
53
|
+
ignore_file_utils.IgnoreFileManager._instance = None
|
|
54
|
+
ignore_file_utils._ignore_manager = ignore_file_utils.IgnoreFileManager()
|
|
55
|
+
|
|
56
|
+
# 符合忽略规则的路径
|
|
57
|
+
ignored_dir = tmp_path / "data" / "subdir"
|
|
58
|
+
ignored_dir.mkdir(parents=True)
|
|
59
|
+
ignored_file = tmp_path / "secret.txt"
|
|
60
|
+
ignored_file.write_text("secret")
|
|
61
|
+
|
|
62
|
+
assert ignore_file_utils.should_ignore(str(ignored_dir)) is True
|
|
63
|
+
assert ignore_file_utils.should_ignore(str(ignored_file)) is True
|
|
64
|
+
|
|
65
|
+
# 不应被忽略的文件
|
|
66
|
+
normal_file = tmp_path / "keepme.txt"
|
|
67
|
+
normal_file.write_text("keep me")
|
|
68
|
+
assert ignore_file_utils.should_ignore(str(normal_file)) is False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_nested_ignore_file(tmp_path, monkeypatch):
|
|
72
|
+
monkeypatch.chdir(tmp_path)
|
|
73
|
+
|
|
74
|
+
# 没有根目录的.ignore,创建.auto-coder/.autocoderignore
|
|
75
|
+
nested_dir = tmp_path / ".auto-coder"
|
|
76
|
+
nested_dir.mkdir()
|
|
77
|
+
|
|
78
|
+
ignore_file = nested_dir / ".autocoderignore"
|
|
79
|
+
ignore_file.write_text("logs/**")
|
|
80
|
+
|
|
81
|
+
# 重新初始化忽略管理器以加载新规则
|
|
82
|
+
ignore_file_utils.IgnoreFileManager._instance = None
|
|
83
|
+
ignore_file_utils._ignore_manager = ignore_file_utils.IgnoreFileManager()
|
|
84
|
+
|
|
85
|
+
ignored_dir = tmp_path / "logs" / "2024"
|
|
86
|
+
ignored_dir.mkdir(parents=True)
|
|
87
|
+
assert ignore_file_utils.should_ignore(str(ignored_dir)) is True
|
|
88
|
+
|
|
89
|
+
normal_file = tmp_path / "main.py"
|
|
90
|
+
normal_file.write_text("# main")
|
|
91
|
+
assert ignore_file_utils.should_ignore(str(normal_file)) is False
|
autocoder/models.py
CHANGED
|
@@ -8,7 +8,7 @@ MODELS_JSON = os.path.expanduser("~/.auto-coder/keys/models.json")
|
|
|
8
8
|
# Default built-in models
|
|
9
9
|
default_models_list = [
|
|
10
10
|
{
|
|
11
|
-
"name": "
|
|
11
|
+
"name": "deepseek/r1",
|
|
12
12
|
"description": "DeepSeek Reasoner is for design/review",
|
|
13
13
|
"model_name": "deepseek-reasoner",
|
|
14
14
|
"model_type": "saas/openai",
|
|
@@ -21,7 +21,7 @@ default_models_list = [
|
|
|
21
21
|
"max_output_tokens": 8096
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
|
-
"name": "
|
|
24
|
+
"name": "deepseek/v3",
|
|
25
25
|
"description": "DeepSeek Chat is for coding",
|
|
26
26
|
"model_name": "deepseek-chat",
|
|
27
27
|
"model_type": "saas/openai",
|
|
@@ -34,16 +34,56 @@ default_models_list = [
|
|
|
34
34
|
"max_output_tokens": 8096
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
|
-
"name":"
|
|
38
|
-
"description": "
|
|
39
|
-
"model_name": "
|
|
37
|
+
"name": "ark/deepseek-v3-250324",
|
|
38
|
+
"description": "DeepSeek Chat is for coding",
|
|
39
|
+
"model_name": "deepseek-v3-250324",
|
|
40
40
|
"model_type": "saas/openai",
|
|
41
|
-
"base_url": "https://
|
|
41
|
+
"base_url": "https://ark.cn-beijing.volces.com/api/v3",
|
|
42
|
+
"api_key_path": "",
|
|
43
|
+
"is_reasoning": False,
|
|
44
|
+
"input_price": 2.0,
|
|
45
|
+
"output_price": 8.0,
|
|
46
|
+
"average_speed": 0.0,
|
|
47
|
+
"max_output_tokens": 8096
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "ark/deepseek-r1-250120",
|
|
51
|
+
"description": "DeepSeek Reasoner is for design/review",
|
|
52
|
+
"model_name": "deepseek-r1-250120",
|
|
53
|
+
"model_type": "saas/openai",
|
|
54
|
+
"base_url": "https://ark.cn-beijing.volces.com/api/v3",
|
|
42
55
|
"api_key_path": "",
|
|
43
56
|
"is_reasoning": True,
|
|
57
|
+
"input_price": 4.0,
|
|
58
|
+
"output_price": 16.0,
|
|
59
|
+
"average_speed": 0.0,
|
|
60
|
+
"max_output_tokens": 8096
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "openrouter/quasar-alpha",
|
|
64
|
+
"description": "",
|
|
65
|
+
"model_name": "openrouter/quasar-alpha",
|
|
66
|
+
"model_type": "saas/openai",
|
|
67
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
68
|
+
"api_key_path": "",
|
|
69
|
+
"is_reasoning": False,
|
|
70
|
+
"input_price": 0.0,
|
|
71
|
+
"output_price": 0.0,
|
|
72
|
+
"average_speed": 0.0,
|
|
73
|
+
"max_output_tokens": 8096*2
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "openrouter/google/gemini-2.5-pro-preview-03-25",
|
|
77
|
+
"description": "",
|
|
78
|
+
"model_name": "google/gemini-2.5-pro-preview-03-25",
|
|
79
|
+
"model_type": "saas/openai",
|
|
80
|
+
"base_url": "https://openrouter.ai/api/v1",
|
|
81
|
+
"api_key_path": "",
|
|
82
|
+
"is_reasoning": False,
|
|
44
83
|
"input_price": 0.0,
|
|
45
84
|
"output_price": 0.0,
|
|
46
|
-
"average_speed": 0.0
|
|
85
|
+
"average_speed": 0.0,
|
|
86
|
+
"max_output_tokens": 8096*2
|
|
47
87
|
}
|
|
48
88
|
]
|
|
49
89
|
|
|
@@ -263,7 +303,7 @@ def update_model_with_api_key(name: str, api_key: str) -> Dict:
|
|
|
263
303
|
if not found_model:
|
|
264
304
|
return None
|
|
265
305
|
|
|
266
|
-
api_key_path = name
|
|
306
|
+
api_key_path = name.replace("/", "_") # 替换 / 为 _,保证文件名合法
|
|
267
307
|
if api_key_path:
|
|
268
308
|
found_model["api_key_path"] = api_key_path
|
|
269
309
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from token import OP
|
|
1
2
|
from autocoder.rag.cache.base_cache import (
|
|
2
3
|
BaseCacheManager,
|
|
3
4
|
DeleteEvent,
|
|
@@ -31,6 +32,8 @@ from pydantic import BaseModel
|
|
|
31
32
|
from autocoder.rag.cache.cache_result_merge import CacheResultMerger, MergeStrategy
|
|
32
33
|
from .failed_files_utils import save_failed_files, load_failed_files
|
|
33
34
|
import time
|
|
35
|
+
from byzerllm import ByzerLLM, SimpleByzerLLM
|
|
36
|
+
from autocoder.utils.llms import get_llm_names
|
|
34
37
|
|
|
35
38
|
if platform.system() != "Windows":
|
|
36
39
|
import fcntl
|
|
@@ -66,8 +69,8 @@ class ByzerStorageCache(BaseCacheManager):
|
|
|
66
69
|
ignore_spec,
|
|
67
70
|
required_exts,
|
|
68
71
|
extra_params: Optional[AutoCoderArgs] = None,
|
|
69
|
-
args=None,
|
|
70
|
-
llm=None,
|
|
72
|
+
args:Optional[AutoCoderArgs]=None,
|
|
73
|
+
llm:Optional[Union[ByzerLLM,SimpleByzerLLM,str]]=None,
|
|
71
74
|
):
|
|
72
75
|
"""
|
|
73
76
|
初始化基于云端 Byzer Storage 的 RAG 缓存管理器。
|
|
@@ -78,6 +81,7 @@ class ByzerStorageCache(BaseCacheManager):
|
|
|
78
81
|
self.extra_params = extra_params
|
|
79
82
|
self.args = args
|
|
80
83
|
self.llm = llm
|
|
84
|
+
self.product_mode = self.args.product_mode
|
|
81
85
|
self.rag_build_name = extra_params.rag_build_name
|
|
82
86
|
self.storage = ByzerStorage("byzerai_store", "rag", self.rag_build_name)
|
|
83
87
|
self.queue = []
|
|
@@ -218,6 +222,8 @@ class ByzerStorageCache(BaseCacheManager):
|
|
|
218
222
|
from autocoder.rag.token_counter import initialize_tokenizer
|
|
219
223
|
|
|
220
224
|
logger.info("[BUILD CACHE] Starting parallel file processing...")
|
|
225
|
+
llm_name = get_llm_names(self.llm)[0] if self.llm else None
|
|
226
|
+
product_mode = self.product_mode
|
|
221
227
|
start_time = time.time()
|
|
222
228
|
with Pool(
|
|
223
229
|
processes=os.cpu_count(),
|
|
@@ -227,7 +233,7 @@ class ByzerStorageCache(BaseCacheManager):
|
|
|
227
233
|
target_files_to_process = []
|
|
228
234
|
for file_info in files_to_process:
|
|
229
235
|
target_files_to_process.append(self.fileinfo_to_tuple(file_info))
|
|
230
|
-
results = pool.map(process_file_in_multi_process, target_files_to_process)
|
|
236
|
+
results = pool.map(process_file_in_multi_process, target_files_to_process, llm=llm_name, product_mode=product_mode)
|
|
231
237
|
processing_time = time.time() - start_time
|
|
232
238
|
logger.info(f"[BUILD CACHE] File processing completed, time elapsed: {processing_time:.2f}s")
|
|
233
239
|
|
|
@@ -417,7 +423,7 @@ class ByzerStorageCache(BaseCacheManager):
|
|
|
417
423
|
for file_info in file_list.file_infos:
|
|
418
424
|
logger.info(f"[QUEUE PROCESSING] Processing file update: {file_info.file_path}")
|
|
419
425
|
try:
|
|
420
|
-
content = process_file_local(self.fileinfo_to_tuple(file_info))
|
|
426
|
+
content = process_file_local(self.fileinfo_to_tuple(file_info), llm=self.llm, product_mode=self.product_mode)
|
|
421
427
|
if content:
|
|
422
428
|
self.cache[file_info.file_path] = CacheItem(
|
|
423
429
|
file_path=file_info.file_path,
|