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.

Files changed (61) hide show
  1. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/METADATA +2 -2
  2. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/RECORD +27 -57
  3. autocoder/agent/base_agentic/base_agent.py +202 -52
  4. autocoder/agent/base_agentic/default_tools.py +38 -6
  5. autocoder/agent/base_agentic/tools/list_files_tool_resolver.py +83 -43
  6. autocoder/agent/base_agentic/tools/read_file_tool_resolver.py +88 -25
  7. autocoder/agent/base_agentic/tools/replace_in_file_tool_resolver.py +171 -62
  8. autocoder/agent/base_agentic/tools/search_files_tool_resolver.py +101 -56
  9. autocoder/agent/base_agentic/tools/talk_to_group_tool_resolver.py +5 -0
  10. autocoder/agent/base_agentic/tools/talk_to_tool_resolver.py +5 -0
  11. autocoder/agent/base_agentic/tools/write_to_file_tool_resolver.py +145 -32
  12. autocoder/auto_coder_rag.py +80 -11
  13. autocoder/models.py +2 -2
  14. autocoder/rag/agentic_rag.py +217 -0
  15. autocoder/rag/cache/local_duckdb_storage_cache.py +63 -33
  16. autocoder/rag/conversation_to_queries.py +37 -5
  17. autocoder/rag/long_context_rag.py +161 -41
  18. autocoder/rag/tools/__init__.py +10 -0
  19. autocoder/rag/tools/recall_tool.py +163 -0
  20. autocoder/rag/tools/search_tool.py +126 -0
  21. autocoder/rag/types.py +36 -0
  22. autocoder/utils/_markitdown.py +59 -13
  23. autocoder/version.py +1 -1
  24. autocoder/agent/agentic_edit.py +0 -833
  25. autocoder/agent/agentic_edit_tools/__init__.py +0 -28
  26. autocoder/agent/agentic_edit_tools/ask_followup_question_tool_resolver.py +0 -32
  27. autocoder/agent/agentic_edit_tools/attempt_completion_tool_resolver.py +0 -29
  28. autocoder/agent/agentic_edit_tools/base_tool_resolver.py +0 -29
  29. autocoder/agent/agentic_edit_tools/execute_command_tool_resolver.py +0 -84
  30. autocoder/agent/agentic_edit_tools/list_code_definition_names_tool_resolver.py +0 -75
  31. autocoder/agent/agentic_edit_tools/list_files_tool_resolver.py +0 -62
  32. autocoder/agent/agentic_edit_tools/plan_mode_respond_tool_resolver.py +0 -30
  33. autocoder/agent/agentic_edit_tools/read_file_tool_resolver.py +0 -36
  34. autocoder/agent/agentic_edit_tools/replace_in_file_tool_resolver.py +0 -95
  35. autocoder/agent/agentic_edit_tools/search_files_tool_resolver.py +0 -70
  36. autocoder/agent/agentic_edit_tools/use_mcp_tool_resolver.py +0 -55
  37. autocoder/agent/agentic_edit_tools/write_to_file_tool_resolver.py +0 -98
  38. autocoder/agent/agentic_edit_types.py +0 -124
  39. autocoder/auto_coder_lang.py +0 -60
  40. autocoder/auto_coder_rag_client_mcp.py +0 -170
  41. autocoder/auto_coder_rag_mcp.py +0 -193
  42. autocoder/common/llm_rerank.py +0 -84
  43. autocoder/common/model_speed_test.py +0 -392
  44. autocoder/common/v2/agent/agentic_edit_conversation.py +0 -188
  45. autocoder/common/v2/agent/ignore_utils.py +0 -50
  46. autocoder/dispacher/actions/plugins/action_translate.py +0 -214
  47. autocoder/ignorefiles/__init__.py +0 -4
  48. autocoder/ignorefiles/ignore_file_utils.py +0 -63
  49. autocoder/ignorefiles/test_ignore_file_utils.py +0 -91
  50. autocoder/linters/code_linter.py +0 -588
  51. autocoder/rag/loaders/test_image_loader.py +0 -209
  52. autocoder/rag/raw_rag.py +0 -96
  53. autocoder/rag/simple_directory_reader.py +0 -646
  54. autocoder/rag/simple_rag.py +0 -404
  55. autocoder/regex_project/__init__.py +0 -162
  56. autocoder/utils/coder.py +0 -125
  57. autocoder/utils/tests.py +0 -37
  58. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/LICENSE +0 -0
  59. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/WHEEL +0 -0
  60. {auto_coder-0.1.374.dist-info → auto_coder-0.1.376.dist-info}/entry_points.txt +0 -0
  61. {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 resolve(self) -> ToolResult:
24
- search_path_str = self.tool.path
25
- regex_pattern = self.tool.regex
26
- file_pattern = self.tool.file_pattern or "*"
27
- source_dir = self.args.source_dir or "."
28
- absolute_source_dir = os.path.abspath(source_dir)
29
- absolute_search_path = os.path.abspath(os.path.join(source_dir, search_path_str))
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
- message = f"Search completed. Found {len(merged_results)} matches."
126
- logger.info(message)
127
- return ToolResult(success=True, message=message, content=merged_results)
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
- try:
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)
@@ -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
- rag = RAGFactory.get_rag(
728
- llm=llm,
729
- args=auto_coder_args,
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
- auto_coder_args.rag_build_name = generate_unique_name_from_path("")
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: