auto-coder 0.1.374__py3-none-any.whl → 0.1.376__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.374.dist-info → auto_coder-0.1.376.dist-info}/METADATA +2 -2
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/RECORD +27 -57
- autocoder/agent/base_agentic/base_agent.py +202 -52
- autocoder/agent/base_agentic/default_tools.py +38 -6
- autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +83 -43
- autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +88 -25
- autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +171 -62
- autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +101 -56
- autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +5 -0
- autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +5 -0
- autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +145 -32
- autocoder/auto_coder_rag.py +80 -11
- autocoder/models.py +2 -2
- autocoder/rag/agentic_rag.py +217 -0
- autocoder/rag/cache/local_duckdb_storage_cache.py +63 -33
- autocoder/rag/conversation_to_queries.py +37 -5
- autocoder/rag/long_context_rag.py +161 -41
- autocoder/rag/tools/__init__.py +10 -0
- autocoder/rag/tools/recall_tool.py +163 -0
- autocoder/rag/tools/search_tool.py +126 -0
- autocoder/rag/types.py +36 -0
- autocoder/utils/_markitdown.py +59 -13
- autocoder/version.py +1 -1
- autocoder/agent/agentic_edit.py +0 -833
- autocoder/agent/agentic_edit_tools/__init__.py +0 -28
- autocoder/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +0 -32
- autocoder/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +0 -29
- autocoder/agent/agentic_edit_tools/base_tool_resolver.py +0 -29
- autocoder/agent/agentic_edit_tools/execute_command_tool_resolver.py +0 -84
- autocoder/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +0 -75
- autocoder/agent/agentic_edit_tools/list_files_tool_resolver.py +0 -62
- autocoder/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +0 -30
- autocoder/agent/agentic_edit_tools/read_file_tool_resolver.py +0 -36
- autocoder/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +0 -95
- autocoder/agent/agentic_edit_tools/search_files_tool_resolver.py +0 -70
- autocoder/agent/agentic_edit_tools/use_mcp_tool_resolver.py +0 -55
- autocoder/agent/agentic_edit_tools/write_to_file_tool_resolver.py +0 -98
- autocoder/agent/agentic_edit_types.py +0 -124
- autocoder/auto_coder_lang.py +0 -60
- autocoder/auto_coder_rag_client_mcp.py +0 -170
- autocoder/auto_coder_rag_mcp.py +0 -193
- autocoder/common/llm_rerank.py +0 -84
- autocoder/common/model_speed_test.py +0 -392
- autocoder/common/v2/agent/agentic_edit_conversation.py +0 -188
- autocoder/common/v2/agent/ignore_utils.py +0 -50
- autocoder/dispacher/actions/plugins/action_translate.py +0 -214
- autocoder/ignorefiles/__init__.py +0 -4
- autocoder/ignorefiles/ignore_file_utils.py +0 -63
- autocoder/ignorefiles/test_ignore_file_utils.py +0 -91
- autocoder/linters/code_linter.py +0 -588
- autocoder/rag/loaders/test_image_loader.py +0 -209
- autocoder/rag/raw_rag.py +0 -96
- autocoder/rag/simple_directory_reader.py +0 -646
- autocoder/rag/simple_rag.py +0 -404
- autocoder/regex_project/__init__.py +0 -162
- autocoder/utils/coder.py +0 -125
- autocoder/utils/tests.py +0 -37
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
3
|
import glob
|
|
4
|
-
from typing import Dict, Any, Optional, List
|
|
4
|
+
from typing import Dict, Any, Optional, List, Union
|
|
5
5
|
from autocoder.agent.base_agentic.tools.base_tool_resolver import BaseToolResolver
|
|
6
6
|
from autocoder.agent.base_agentic.types import SearchFilesTool, ToolResult # Import ToolResult from types
|
|
7
7
|
from loguru import logger
|
|
@@ -20,14 +20,54 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
20
20
|
self.tool: SearchFilesTool = tool
|
|
21
21
|
self.shadow_manager = self.agent.shadow_manager if self.agent else None
|
|
22
22
|
|
|
23
|
-
def
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
def search_in_dir(self, base_dir: str, regex_pattern: str, file_pattern: str, source_dir: str, is_shadow: bool = False, compiled_regex: Optional[re.Pattern] = None) -> List[Dict[str, Any]]:
|
|
24
|
+
"""Helper function to search in a directory"""
|
|
25
|
+
search_results = []
|
|
26
|
+
search_glob_pattern = os.path.join(base_dir, "**", file_pattern)
|
|
27
|
+
|
|
28
|
+
logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{base_dir}' (shadow: {is_shadow}) with ignore rules applied.")
|
|
29
|
+
|
|
30
|
+
if compiled_regex is None:
|
|
31
|
+
compiled_regex = re.compile(regex_pattern)
|
|
32
|
+
|
|
33
|
+
for filepath in glob.glob(search_glob_pattern, recursive=True):
|
|
34
|
+
abs_path = os.path.abspath(filepath)
|
|
35
|
+
if should_ignore(abs_path):
|
|
36
|
+
continue
|
|
30
37
|
|
|
38
|
+
if os.path.isfile(filepath):
|
|
39
|
+
try:
|
|
40
|
+
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
|
|
41
|
+
lines = f.readlines()
|
|
42
|
+
for i, line in enumerate(lines):
|
|
43
|
+
if compiled_regex.search(line):
|
|
44
|
+
context_start = max(0, i - 2)
|
|
45
|
+
context_end = min(len(lines), i + 3)
|
|
46
|
+
context = "".join([f"{j+1}: {lines[j]}" for j in range(context_start, context_end)])
|
|
47
|
+
|
|
48
|
+
if is_shadow and self.shadow_manager:
|
|
49
|
+
try:
|
|
50
|
+
abs_project_path = self.shadow_manager.from_shadow_path(filepath)
|
|
51
|
+
relative_path = os.path.relpath(abs_project_path, source_dir)
|
|
52
|
+
except Exception:
|
|
53
|
+
relative_path = os.path.relpath(filepath, source_dir)
|
|
54
|
+
else:
|
|
55
|
+
relative_path = os.path.relpath(filepath, source_dir)
|
|
56
|
+
|
|
57
|
+
search_results.append({
|
|
58
|
+
"path": relative_path,
|
|
59
|
+
"line_number": i + 1,
|
|
60
|
+
"match_line": line.strip(),
|
|
61
|
+
"context": context.strip()
|
|
62
|
+
})
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.warning(f"Could not read or process file {filepath}: {e}")
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
return search_results
|
|
68
|
+
|
|
69
|
+
def search_files_with_shadow(self, search_path_str: str, regex_pattern: str, file_pattern: str, source_dir: str, absolute_source_dir: str, absolute_search_path: str) -> Union[ToolResult, List[Dict[str, Any]]]:
|
|
70
|
+
"""Search files using shadow manager for path translation"""
|
|
31
71
|
# Security check
|
|
32
72
|
if not absolute_search_path.startswith(absolute_source_dir):
|
|
33
73
|
return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
|
|
@@ -54,58 +94,15 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
54
94
|
try:
|
|
55
95
|
compiled_regex = re.compile(regex_pattern)
|
|
56
96
|
|
|
57
|
-
# Helper function to search in a directory
|
|
58
|
-
def search_in_dir(base_dir, is_shadow=False):
|
|
59
|
-
search_results = []
|
|
60
|
-
search_glob_pattern = os.path.join(base_dir, "**", file_pattern)
|
|
61
|
-
|
|
62
|
-
logger.info(f"Searching for regex '{regex_pattern}' in files matching '{file_pattern}' under '{base_dir}' (shadow: {is_shadow}) with ignore rules applied.")
|
|
63
|
-
|
|
64
|
-
for filepath in glob.glob(search_glob_pattern, recursive=True):
|
|
65
|
-
abs_path = os.path.abspath(filepath)
|
|
66
|
-
if should_ignore(abs_path):
|
|
67
|
-
continue
|
|
68
|
-
|
|
69
|
-
if os.path.isfile(filepath):
|
|
70
|
-
try:
|
|
71
|
-
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
|
|
72
|
-
lines = f.readlines()
|
|
73
|
-
for i, line in enumerate(lines):
|
|
74
|
-
if compiled_regex.search(line):
|
|
75
|
-
context_start = max(0, i - 2)
|
|
76
|
-
context_end = min(len(lines), i + 3)
|
|
77
|
-
context = "".join([f"{j+1}: {lines[j]}" for j in range(context_start, context_end)])
|
|
78
|
-
|
|
79
|
-
if is_shadow and self.shadow_manager:
|
|
80
|
-
try:
|
|
81
|
-
abs_project_path = self.shadow_manager.from_shadow_path(filepath)
|
|
82
|
-
relative_path = os.path.relpath(abs_project_path, source_dir)
|
|
83
|
-
except Exception:
|
|
84
|
-
relative_path = os.path.relpath(filepath, source_dir)
|
|
85
|
-
else:
|
|
86
|
-
relative_path = os.path.relpath(filepath, source_dir)
|
|
87
|
-
|
|
88
|
-
search_results.append({
|
|
89
|
-
"path": relative_path,
|
|
90
|
-
"line_number": i + 1,
|
|
91
|
-
"match_line": line.strip(),
|
|
92
|
-
"context": context.strip()
|
|
93
|
-
})
|
|
94
|
-
except Exception as e:
|
|
95
|
-
logger.warning(f"Could not read or process file {filepath}: {e}")
|
|
96
|
-
continue
|
|
97
|
-
|
|
98
|
-
return search_results
|
|
99
|
-
|
|
100
97
|
# Search in both directories and merge results
|
|
101
98
|
shadow_results = []
|
|
102
99
|
source_results = []
|
|
103
100
|
|
|
104
101
|
if shadow_exists:
|
|
105
|
-
shadow_results = search_in_dir(shadow_dir_path, is_shadow=True)
|
|
102
|
+
shadow_results = self.search_in_dir(shadow_dir_path, regex_pattern, file_pattern, source_dir, is_shadow=True, compiled_regex=compiled_regex)
|
|
106
103
|
|
|
107
104
|
if os.path.exists(absolute_search_path) and os.path.isdir(absolute_search_path):
|
|
108
|
-
source_results = search_in_dir(absolute_search_path, is_shadow=False)
|
|
105
|
+
source_results = self.search_in_dir(absolute_search_path, regex_pattern, file_pattern, source_dir, is_shadow=False, compiled_regex=compiled_regex)
|
|
109
106
|
|
|
110
107
|
# Merge results, prioritizing shadow results
|
|
111
108
|
# Create a dictionary for quick lookup
|
|
@@ -122,9 +119,34 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
122
119
|
# Convert back to list
|
|
123
120
|
merged_results = list(results_dict.values())
|
|
124
121
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
return merged_results
|
|
123
|
+
|
|
124
|
+
except re.error as e:
|
|
125
|
+
logger.error(f"Invalid regex pattern '{regex_pattern}': {e}")
|
|
126
|
+
return ToolResult(success=False, message=f"Invalid regex pattern: {e}")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.error(f"Error during file search: {str(e)}")
|
|
129
|
+
return ToolResult(success=False, message=f"An unexpected error occurred during search: {str(e)}")
|
|
130
|
+
|
|
131
|
+
def search_files_normal(self, search_path_str: str, regex_pattern: str, file_pattern: str, source_dir: str, absolute_source_dir: str, absolute_search_path: str) -> Union[ToolResult, List[Dict[str, Any]]]:
|
|
132
|
+
"""Search files directly without using shadow manager"""
|
|
133
|
+
# Security check
|
|
134
|
+
if not absolute_search_path.startswith(absolute_source_dir):
|
|
135
|
+
return ToolResult(success=False, message=f"Error: Access denied. Attempted to search outside the project directory: {search_path_str}")
|
|
136
|
+
|
|
137
|
+
# Validate that the directory exists
|
|
138
|
+
if not os.path.exists(absolute_search_path):
|
|
139
|
+
return ToolResult(success=False, message=f"Error: Search path not found: {search_path_str}")
|
|
140
|
+
if not os.path.isdir(absolute_search_path):
|
|
141
|
+
return ToolResult(success=False, message=f"Error: Search path is not a directory: {search_path_str}")
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
compiled_regex = re.compile(regex_pattern)
|
|
145
|
+
|
|
146
|
+
# Search in the directory
|
|
147
|
+
search_results = self.search_in_dir(absolute_search_path, regex_pattern, file_pattern, source_dir, is_shadow=False, compiled_regex=compiled_regex)
|
|
148
|
+
|
|
149
|
+
return search_results
|
|
128
150
|
|
|
129
151
|
except re.error as e:
|
|
130
152
|
logger.error(f"Invalid regex pattern '{regex_pattern}': {e}")
|
|
@@ -132,3 +154,26 @@ class SearchFilesToolResolver(BaseToolResolver):
|
|
|
132
154
|
except Exception as e:
|
|
133
155
|
logger.error(f"Error during file search: {str(e)}")
|
|
134
156
|
return ToolResult(success=False, message=f"An unexpected error occurred during search: {str(e)}")
|
|
157
|
+
|
|
158
|
+
def resolve(self) -> ToolResult:
|
|
159
|
+
"""Resolve the search files tool by calling the appropriate implementation"""
|
|
160
|
+
search_path_str = self.tool.path
|
|
161
|
+
regex_pattern = self.tool.regex
|
|
162
|
+
file_pattern = self.tool.file_pattern or "*"
|
|
163
|
+
source_dir = self.args.source_dir or "."
|
|
164
|
+
absolute_source_dir = os.path.abspath(source_dir)
|
|
165
|
+
absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
|
|
166
|
+
|
|
167
|
+
# Choose the appropriate implementation based on whether shadow_manager is available
|
|
168
|
+
if self.shadow_manager:
|
|
169
|
+
result = self.search_files_with_shadow(search_path_str, regex_pattern, file_pattern, source_dir, absolute_source_dir, absolute_search_path)
|
|
170
|
+
else:
|
|
171
|
+
result = self.search_files_normal(search_path_str, regex_pattern, file_pattern, source_dir, absolute_source_dir, absolute_search_path)
|
|
172
|
+
|
|
173
|
+
# Handle the case where the implementation returns a list instead of a ToolResult
|
|
174
|
+
if isinstance(result, list):
|
|
175
|
+
message = f"Search completed. Found {len(result)} matches."
|
|
176
|
+
logger.info(message)
|
|
177
|
+
return ToolResult(success=True, message=message, content=result)
|
|
178
|
+
else:
|
|
179
|
+
return result
|
|
@@ -2,10 +2,15 @@ from typing import Optional, Dict, Any
|
|
|
2
2
|
import os
|
|
3
3
|
from loguru import logger
|
|
4
4
|
from datetime import datetime
|
|
5
|
+
import typing
|
|
5
6
|
|
|
6
7
|
from ..types import TalkToGroupTool, ToolResult
|
|
7
8
|
from ..tools.base_tool_resolver import BaseToolResolver
|
|
8
9
|
from ..agent_hub import AgentHub, Group
|
|
10
|
+
from autocoder.common import AutoCoderArgs
|
|
11
|
+
|
|
12
|
+
if typing.TYPE_CHECKING:
|
|
13
|
+
from ..base_agent import BaseAgent
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class TalkToGroupToolResolver(BaseToolResolver):
|
|
@@ -2,10 +2,15 @@ from typing import Optional, Dict, Any
|
|
|
2
2
|
import os
|
|
3
3
|
from loguru import logger
|
|
4
4
|
from datetime import datetime
|
|
5
|
+
import typing
|
|
5
6
|
|
|
6
7
|
from ..types import TalkToTool, ToolResult
|
|
7
8
|
from ..tools.base_tool_resolver import BaseToolResolver
|
|
8
9
|
from ..agent_hub import AgentHub
|
|
10
|
+
from autocoder.common import AutoCoderArgs
|
|
11
|
+
|
|
12
|
+
if typing.TYPE_CHECKING:
|
|
13
|
+
from ..base_agent import BaseAgent
|
|
9
14
|
|
|
10
15
|
|
|
11
16
|
class TalkToToolResolver(BaseToolResolver):
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import Dict, Any, Optional
|
|
2
|
+
from typing import Dict, Any, Optional,List
|
|
3
3
|
from autocoder.agent.base_agentic.types import WriteToFileTool, ToolResult # Import ToolResult from types
|
|
4
4
|
from autocoder.agent.base_agentic.tools.base_tool_resolver import BaseToolResolver
|
|
5
5
|
from loguru import logger
|
|
6
6
|
from autocoder.common import AutoCoderArgs
|
|
7
|
+
from autocoder.common.file_checkpoint.models import FileChange as CheckpointFileChange
|
|
8
|
+
from autocoder.common.file_checkpoint.manager import FileChangeManager as CheckpointFileChangeManager
|
|
9
|
+
from autocoder.linters.models import IssueSeverity, FileLintResult
|
|
7
10
|
import typing
|
|
8
11
|
|
|
9
12
|
if typing.TYPE_CHECKING:
|
|
@@ -13,9 +16,148 @@ class WriteToFileToolResolver(BaseToolResolver):
|
|
|
13
16
|
def __init__(self, agent: Optional['BaseAgent'], tool: WriteToFileTool, args: AutoCoderArgs):
|
|
14
17
|
super().__init__(agent, tool, args)
|
|
15
18
|
self.tool: WriteToFileTool = tool # For type hinting
|
|
19
|
+
self.args = args
|
|
16
20
|
self.shadow_manager = self.agent.shadow_manager if self.agent else None
|
|
21
|
+
self.shadow_linter = self.agent.shadow_linter if self.agent else None
|
|
22
|
+
|
|
23
|
+
def _filter_lint_issues(self, lint_result:FileLintResult, levels: List[IssueSeverity] = [IssueSeverity.ERROR, IssueSeverity.WARNING]):
|
|
24
|
+
"""
|
|
25
|
+
过滤 lint 结果,只保留指定级别的问题
|
|
26
|
+
|
|
27
|
+
参数:
|
|
28
|
+
lint_result: 单个文件的 lint 结果对象
|
|
29
|
+
levels: 要保留的问题级别列表,默认保留 ERROR 和 WARNING 级别
|
|
30
|
+
|
|
31
|
+
返回:
|
|
32
|
+
过滤后的 lint 结果对象(原对象的副本)
|
|
33
|
+
"""
|
|
34
|
+
if not lint_result or not lint_result.issues:
|
|
35
|
+
return lint_result
|
|
36
|
+
|
|
37
|
+
# 创建一个新的 issues 列表,只包含指定级别的问题
|
|
38
|
+
filtered_issues = []
|
|
39
|
+
for issue in lint_result.issues:
|
|
40
|
+
if issue.severity in levels:
|
|
41
|
+
filtered_issues.append(issue)
|
|
42
|
+
|
|
43
|
+
# 更新 lint_result 的副本
|
|
44
|
+
filtered_result = lint_result
|
|
45
|
+
filtered_result.issues = filtered_issues
|
|
46
|
+
|
|
47
|
+
# 更新计数
|
|
48
|
+
filtered_result.error_count = sum(1 for issue in filtered_issues if issue.severity == IssueSeverity.ERROR)
|
|
49
|
+
filtered_result.warning_count = sum(1 for issue in filtered_issues if issue.severity == IssueSeverity.WARNING)
|
|
50
|
+
filtered_result.info_count = sum(1 for issue in filtered_issues if issue.severity == IssueSeverity.INFO)
|
|
51
|
+
|
|
52
|
+
return filtered_result
|
|
53
|
+
|
|
54
|
+
def _format_lint_issues(self, lint_result:FileLintResult):
|
|
55
|
+
"""
|
|
56
|
+
将 lint 结果格式化为可读的文本格式
|
|
57
|
+
|
|
58
|
+
参数:
|
|
59
|
+
lint_result: 单个文件的 lint 结果对象
|
|
60
|
+
|
|
61
|
+
返回:
|
|
62
|
+
str: 格式化的问题描述
|
|
63
|
+
"""
|
|
64
|
+
formatted_issues = []
|
|
65
|
+
|
|
66
|
+
for issue in lint_result.issues:
|
|
67
|
+
severity = "错误" if issue.severity.value == 3 else "警告" if issue.severity.value == 2 else "信息"
|
|
68
|
+
line_info = f"第{issue.position.line}行"
|
|
69
|
+
if issue.position.column:
|
|
70
|
+
line_info += f", 第{issue.position.column}列"
|
|
71
|
+
|
|
72
|
+
formatted_issues.append(
|
|
73
|
+
f" - [{severity}] {line_info}: {issue.message} (规则: {issue.code})"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return "\n".join(formatted_issues)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def write_file_normal(self, file_path: str, content: str, source_dir: str, abs_project_dir: str, abs_file_path: str) -> ToolResult:
|
|
80
|
+
"""Write file directly without using shadow manager"""
|
|
81
|
+
try:
|
|
82
|
+
os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
|
|
83
|
+
|
|
84
|
+
if self.agent:
|
|
85
|
+
rel_path = os.path.relpath(abs_file_path, abs_project_dir)
|
|
86
|
+
self.agent.record_file_change(rel_path, "added", diff=None, content=content)
|
|
87
|
+
|
|
88
|
+
if self.agent and self.agent.checkpoint_manager:
|
|
89
|
+
changes = {
|
|
90
|
+
file_path: CheckpointFileChange(
|
|
91
|
+
file_path=file_path,
|
|
92
|
+
content=content,
|
|
93
|
+
is_deletion=False,
|
|
94
|
+
is_new=True
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
change_group_id = self.args.event_file
|
|
98
|
+
|
|
99
|
+
self.agent.checkpoint_manager.apply_changes_with_conversation(
|
|
100
|
+
changes=changes,
|
|
101
|
+
conversations=self.agent.current_conversations,
|
|
102
|
+
change_group_id=change_group_id,
|
|
103
|
+
metadata={"event_file": self.args.event_file}
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
with open(abs_file_path, 'w', encoding='utf-8') as f:
|
|
107
|
+
f.write(content)
|
|
108
|
+
logger.info(f"Successfully wrote to file: {file_path}")
|
|
109
|
+
|
|
110
|
+
# 新增:执行代码质量检查
|
|
111
|
+
lint_results = None
|
|
112
|
+
lint_message = ""
|
|
113
|
+
formatted_issues = ""
|
|
114
|
+
has_lint_issues = False
|
|
115
|
+
|
|
116
|
+
# 检查是否启用了Lint功能
|
|
117
|
+
enable_lint = self.args.enable_auto_fix_lint
|
|
118
|
+
|
|
119
|
+
if enable_lint:
|
|
120
|
+
try:
|
|
121
|
+
if self.agent.linter:
|
|
122
|
+
lint_results = self.agent.linter.lint_file(file_path)
|
|
123
|
+
if lint_results and lint_results.issues:
|
|
124
|
+
# 过滤 lint 结果,只保留 ERROR 和 WARNING 级别的问题
|
|
125
|
+
filtered_results = self._filter_lint_issues(lint_results)
|
|
126
|
+
if filtered_results.issues:
|
|
127
|
+
has_lint_issues = True
|
|
128
|
+
# 格式化 lint 问题
|
|
129
|
+
formatted_issues = self._format_lint_issues(filtered_results)
|
|
130
|
+
lint_message = f"\n\n代码质量检查发现 {len(filtered_results.issues)} 个问题"
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.error(f"Lint 检查失败: {str(e)}")
|
|
133
|
+
lint_message = "\n\n尝试进行代码质量检查时出错。"
|
|
134
|
+
else:
|
|
135
|
+
logger.info("代码质量检查已禁用")
|
|
136
|
+
|
|
137
|
+
# 构建包含 lint 结果的返回消息
|
|
138
|
+
message = f"{file_path}"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# 附加 lint 结果到返回内容
|
|
142
|
+
result_content = {
|
|
143
|
+
"content": content,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
# 只有在启用Lint时才添加Lint结果
|
|
147
|
+
if enable_lint:
|
|
148
|
+
message = message + "\n" + lint_message
|
|
149
|
+
result_content["lint_results"] = {
|
|
150
|
+
"has_issues": has_lint_issues,
|
|
151
|
+
"issues": formatted_issues if has_lint_issues else None
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return ToolResult(success=True, message=message, content=result_content)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"Error writing to file '{file_path}': {str(e)}")
|
|
157
|
+
return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
|
|
17
158
|
|
|
18
159
|
def resolve(self) -> ToolResult:
|
|
160
|
+
"""Resolve the write file tool by calling the appropriate implementation"""
|
|
19
161
|
file_path = self.tool.path
|
|
20
162
|
content = self.tool.content
|
|
21
163
|
source_dir = self.args.source_dir or "."
|
|
@@ -25,34 +167,5 @@ class WriteToFileToolResolver(BaseToolResolver):
|
|
|
25
167
|
# Security check: ensure the path is within the source directory
|
|
26
168
|
if not abs_file_path.startswith(abs_project_dir):
|
|
27
169
|
return ToolResult(success=False, message=f"Error: Access denied. Attempted to write file outside the project directory: {file_path}")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if self.shadow_manager:
|
|
31
|
-
shadow_path = self.shadow_manager.to_shadow_path(abs_file_path)
|
|
32
|
-
# Ensure shadow directory exists
|
|
33
|
-
os.makedirs(os.path.dirname(shadow_path), exist_ok=True)
|
|
34
|
-
with open(shadow_path, 'w', encoding='utf-8') as f:
|
|
35
|
-
f.write(content)
|
|
36
|
-
logger.info(f"[Shadow] Successfully wrote shadow file: {shadow_path}")
|
|
37
|
-
|
|
38
|
-
# 回调AgenticEdit,记录变更
|
|
39
|
-
if self.agent:
|
|
40
|
-
rel_path = os.path.relpath(abs_file_path, abs_project_dir)
|
|
41
|
-
self.agent.record_file_change(rel_path, "added", diff=None, content=content)
|
|
42
|
-
|
|
43
|
-
return ToolResult(success=True, message=f"Successfully wrote to file (shadow): {file_path}", content=content)
|
|
44
|
-
else:
|
|
45
|
-
# No shadow manager fallback to original file
|
|
46
|
-
os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
|
|
47
|
-
with open(abs_file_path, 'w', encoding='utf-8') as f:
|
|
48
|
-
f.write(content)
|
|
49
|
-
logger.info(f"Successfully wrote to file: {file_path}")
|
|
50
|
-
|
|
51
|
-
if self.agent:
|
|
52
|
-
rel_path = os.path.relpath(abs_file_path, abs_project_dir)
|
|
53
|
-
self.agent.record_file_change(rel_path, "added", diff=None, content=content)
|
|
54
|
-
|
|
55
|
-
return ToolResult(success=True, message=f"Successfully wrote to file: {file_path}", content=content)
|
|
56
|
-
except Exception as e:
|
|
57
|
-
logger.error(f"Error writing to file '{file_path}': {str(e)}")
|
|
58
|
-
return ToolResult(success=False, message=f"An error occurred while writing to the file: {str(e)}")
|
|
170
|
+
|
|
171
|
+
return self.write_file_normal(file_path, content, source_dir, abs_project_dir, abs_file_path)
|
autocoder/auto_coder_rag.py
CHANGED
|
@@ -6,6 +6,8 @@ from typing import Optional, List
|
|
|
6
6
|
import byzerllm
|
|
7
7
|
from autocoder.rag.api_server import serve, ServerArgs
|
|
8
8
|
from autocoder.rag.rag_entry import RAGFactory
|
|
9
|
+
from autocoder.rag.agentic_rag import AgenticRAG
|
|
10
|
+
from autocoder.rag.long_context_rag import LongContextRAG
|
|
9
11
|
from autocoder.rag.llm_wrapper import LLWrapper
|
|
10
12
|
from autocoder.common import AutoCoderArgs
|
|
11
13
|
from autocoder.lang import lang_desc
|
|
@@ -32,6 +34,7 @@ from autocoder.rag.utils import process_file_local
|
|
|
32
34
|
import pkg_resources
|
|
33
35
|
from autocoder.rag.token_counter import TokenCounter
|
|
34
36
|
from autocoder.rag.types import RAGServiceInfo
|
|
37
|
+
from autocoder.version import __version__
|
|
35
38
|
|
|
36
39
|
if platform.system() == "Windows":
|
|
37
40
|
from colorama import init
|
|
@@ -167,6 +170,17 @@ def initialize_system(args):
|
|
|
167
170
|
|
|
168
171
|
|
|
169
172
|
def main(input_args: Optional[List[str]] = None):
|
|
173
|
+
print(
|
|
174
|
+
f"""
|
|
175
|
+
\033[1;32m
|
|
176
|
+
_ _ __ __ _ _ _ _____ _____ _______ ____ _ ____
|
|
177
|
+
| | | | | \/ | | \ | | / \|_ _|_ _\ \ / / ____| | _ \ / \ / ___|
|
|
178
|
+
| | | | | |\/| |_____| \| | / _ \ | | | | \ \ / /| _| | |_) | / _ \| | _
|
|
179
|
+
| |___| |___| | | |_____| |\ |/ ___ \| | | | \ V / | |___ | _ < / ___ \ |_| |
|
|
180
|
+
|_____|_____|_| |_| |_| \_/_/ \_\_| |___| \_/ |_____| |_| \_\/_/ \_\____|
|
|
181
|
+
v{__version__}
|
|
182
|
+
\033[0m"""
|
|
183
|
+
)
|
|
170
184
|
|
|
171
185
|
try:
|
|
172
186
|
tokenizer_path = pkg_resources.resource_filename(
|
|
@@ -301,6 +315,7 @@ def main(input_args: Optional[List[str]] = None):
|
|
|
301
315
|
help="Document directory path, also used as the root directory for serving static files"
|
|
302
316
|
)
|
|
303
317
|
serve_parser.add_argument("--enable_local_image_host", action="store_true", help=" enable local image host for local Chat app")
|
|
318
|
+
serve_parser.add_argument("--agentic", action="store_true", help="使用 AgenticRAG 而不是 LongContextRAG")
|
|
304
319
|
serve_parser.add_argument("--tokenizer_path", default=tokenizer_path, help="")
|
|
305
320
|
serve_parser.add_argument(
|
|
306
321
|
"--collections", default="", help="Collection name for indexing"
|
|
@@ -432,6 +447,18 @@ def main(input_args: Optional[List[str]] = None):
|
|
|
432
447
|
help="The model used for embedding documents",
|
|
433
448
|
)
|
|
434
449
|
|
|
450
|
+
serve_parser.add_argument(
|
|
451
|
+
"--agentic_model",
|
|
452
|
+
default="",
|
|
453
|
+
help="The model used for agentic operations",
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
serve_parser.add_argument(
|
|
457
|
+
"--context_prune_model",
|
|
458
|
+
default="",
|
|
459
|
+
help="The model used for context pruning",
|
|
460
|
+
)
|
|
461
|
+
|
|
435
462
|
# Benchmark command
|
|
436
463
|
benchmark_parser = subparsers.add_parser(
|
|
437
464
|
"benchmark", help="Benchmark LLM client performance"
|
|
@@ -622,6 +649,18 @@ def main(input_args: Optional[List[str]] = None):
|
|
|
622
649
|
emb_model.skip_nontext_check = True
|
|
623
650
|
llm.setup_sub_client("emb_model", emb_model)
|
|
624
651
|
|
|
652
|
+
if args.agentic_model:
|
|
653
|
+
agentic_model = byzerllm.ByzerLLM()
|
|
654
|
+
agentic_model.setup_default_model_name(args.agentic_model)
|
|
655
|
+
agentic_model.skip_nontext_check = True
|
|
656
|
+
llm.setup_sub_client("agentic_model", agentic_model)
|
|
657
|
+
|
|
658
|
+
if args.context_prune_model:
|
|
659
|
+
context_prune_model = byzerllm.ByzerLLM()
|
|
660
|
+
context_prune_model.setup_default_model_name(args.context_prune_model)
|
|
661
|
+
context_prune_model.skip_nontext_check = True
|
|
662
|
+
llm.setup_sub_client("context_prune_model", context_prune_model)
|
|
663
|
+
|
|
625
664
|
# 当启用hybrid_index时,检查必要的组件
|
|
626
665
|
if auto_coder_args.enable_hybrid_index:
|
|
627
666
|
if not args.emb_model and not llm.is_model_exist("emb"):
|
|
@@ -698,7 +737,7 @@ def main(input_args: Optional[List[str]] = None):
|
|
|
698
737
|
"saas.max_output_tokens": model_info.get("max_output_tokens", 8096)
|
|
699
738
|
}
|
|
700
739
|
)
|
|
701
|
-
llm.setup_sub_client("qa_model", qa_model)
|
|
740
|
+
llm.setup_sub_client("qa_model", qa_model)
|
|
702
741
|
|
|
703
742
|
if args.emb_model:
|
|
704
743
|
model_info = models_module.get_model_by_name(args.emb_model)
|
|
@@ -717,22 +756,52 @@ def main(input_args: Optional[List[str]] = None):
|
|
|
717
756
|
)
|
|
718
757
|
llm.setup_sub_client("emb_model", emb_model)
|
|
719
758
|
|
|
759
|
+
if args.agentic_model:
|
|
760
|
+
model_info = models_module.get_model_by_name(args.agentic_model)
|
|
761
|
+
agentic_model = byzerllm.SimpleByzerLLM(default_model_name=args.agentic_model)
|
|
762
|
+
agentic_model.deploy(
|
|
763
|
+
model_path="",
|
|
764
|
+
pretrained_model_type=model_info["model_type"],
|
|
765
|
+
udf_name=args.agentic_model,
|
|
766
|
+
infer_params={
|
|
767
|
+
"saas.base_url": model_info["base_url"],
|
|
768
|
+
"saas.api_key": model_info["api_key"],
|
|
769
|
+
"saas.model": model_info["model_name"],
|
|
770
|
+
"saas.is_reasoning": model_info["is_reasoning"],
|
|
771
|
+
"saas.max_output_tokens": model_info.get("max_output_tokens", 8096)
|
|
772
|
+
}
|
|
773
|
+
)
|
|
774
|
+
llm.setup_sub_client("agentic_model", agentic_model)
|
|
775
|
+
|
|
776
|
+
if args.context_prune_model:
|
|
777
|
+
model_info = models_module.get_model_by_name(args.context_prune_model)
|
|
778
|
+
context_prune_model = byzerllm.SimpleByzerLLM(default_model_name=args.context_prune_model)
|
|
779
|
+
context_prune_model.deploy(
|
|
780
|
+
model_path="",
|
|
781
|
+
pretrained_model_type=model_info["model_type"],
|
|
782
|
+
udf_name=args.context_prune_model,
|
|
783
|
+
infer_params={
|
|
784
|
+
"saas.base_url": model_info["base_url"],
|
|
785
|
+
"saas.api_key": model_info["api_key"],
|
|
786
|
+
"saas.model": model_info["model_name"],
|
|
787
|
+
"saas.is_reasoning": model_info["is_reasoning"],
|
|
788
|
+
"saas.max_output_tokens": model_info.get("max_output_tokens", 8096)
|
|
789
|
+
}
|
|
790
|
+
)
|
|
791
|
+
llm.setup_sub_client("context_prune_model", context_prune_model)
|
|
792
|
+
|
|
720
793
|
if args.enable_hybrid_index:
|
|
721
794
|
if not args.emb_model:
|
|
722
795
|
raise Exception("When enable_hybrid_index is true, an 'emb' model must be specified")
|
|
723
796
|
|
|
724
|
-
if server_args.doc_dir:
|
|
725
|
-
auto_coder_args.rag_type = "simple"
|
|
797
|
+
if server_args.doc_dir:
|
|
726
798
|
auto_coder_args.rag_build_name = generate_unique_name_from_path(server_args.doc_dir)
|
|
727
|
-
|
|
728
|
-
llm=llm,
|
|
729
|
-
|
|
730
|
-
path=server_args.doc_dir,
|
|
731
|
-
tokenizer_path=server_args.tokenizer_path,
|
|
732
|
-
)
|
|
799
|
+
if args.agentic:
|
|
800
|
+
rag = AgenticRAG(llm=llm, args=auto_coder_args, path=server_args.doc_dir, tokenizer_path=server_args.tokenizer_path)
|
|
801
|
+
else:
|
|
802
|
+
rag = LongContextRAG(llm=llm, args=auto_coder_args, path=server_args.doc_dir, tokenizer_path=server_args.tokenizer_path)
|
|
733
803
|
else:
|
|
734
|
-
|
|
735
|
-
rag = RAGFactory.get_rag(llm=llm, args=auto_coder_args, path="")
|
|
804
|
+
raise Exception("doc_dir is required")
|
|
736
805
|
|
|
737
806
|
llm_wrapper = LLWrapper(llm=llm, rag=rag)
|
|
738
807
|
# Save service info
|
autocoder/models.py
CHANGED
|
@@ -165,8 +165,8 @@ def load_models() -> List[Dict]:
|
|
|
165
165
|
if model.get("api_key_path",""):
|
|
166
166
|
api_key_file = os.path.join(api_key_dir, model["api_key_path"])
|
|
167
167
|
if os.path.exists(api_key_file):
|
|
168
|
-
with open(api_key_file, "r",encoding="utf-8") as f:
|
|
169
|
-
model["api_key"] = f.read()
|
|
168
|
+
with open(api_key_file, "r", encoding="utf-8") as f:
|
|
169
|
+
model["api_key"] = f.read().strip()
|
|
170
170
|
return target_models
|
|
171
171
|
|
|
172
172
|
def save_models(models: List[Dict]) -> None:
|