面向开发者的代码检查系统架构、扩展和定制指南
┌─────────────────────────────────────────────────────────┐
│ Chat Auto Coder │
│ (主应用) │
└────────────────────┬────────────────────────────────────┘
│
│ 加载插件
▼
┌─────────────────────────────────────────────────────────┐
│ CodeCheckerPlugin │
│ (命令注册和调度) │
│ - 注册 /check 命令 │
│ - 解析参数 │
│ - 调用检查器 │
└────────────────────┬────────────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ /check │ │ /check │ │ /check │
│ /file │ │ /folder │ │ /resume │
└─────┬────┘ └─────┬────┘ └─────┬────┘
│ │ │
└─────────────┼──────────────┘
▼
┌───────────────────────┐
│ CodeChecker │
│ (核心检查逻辑) │
│ - 协调各模块 │
│ - 调用 LLM │
│ - 结果处理 │
└───────────┬───────────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Rules │ │ File │ │Progress │
│ Loader │ │Processor │ │ Tracker │
└──────────┘ └──────────┘ └──────────┘
│ │ │
└───────────┼───────────┘
▼
┌───────────────────────┐
│ Report Generator │
│ (生成检查报告) │
└───────────────────────┘
| 模块 | 文件 | 职责 | 依赖 |
|---|---|---|---|
| 插件 | code_checker_plugin.py |
命令注册、参数解析 | CodeChecker |
| 核心检查器 | core.py |
检查逻辑、LLM 调用 | 所有模块 |
| 规则加载器 | rules_loader.py |
规则文件解析 | types.py |
| 文件处理器 | file_processor.py |
文件扫描、分块 | types.py |
| 进度跟踪器 | progress_tracker.py |
进度持久化 | types.py |
| 报告生成器 | report_generator.py |
报告生成 | types.py |
| 类型定义 | types.py |
数据模型 | pydantic |
1. 用户输入命令
↓
2. Plugin 解析参数
↓
3. CodeChecker.check_file() 或 check_files()
↓
4. RulesLoader 加载规则
↓
5. FileProcessor 扫描/分块文件
↓
6. CodeChecker 调用 LLM 检查
↓
7. 解析 LLM 返回的 JSON
↓
8. ReportGenerator 生成报告
↓
9. ProgressTracker 保存进度
↓
10. 返回结果给用户
位置:autocoder/checker/types.py
核心类型:
class Severity(str, Enum):
ERROR = "error" # 错误:必须修复
WARNING = "warning" # 警告:建议修复
INFO = "info" # 提示:可选修复class Rule(BaseModel):
id: str # 规则ID,如 "backend_001"
category: str # 规则类别,如 "代码结构"
title: str # 规则标题
description: str # 规则描述
severity: Severity # 严重程度
enabled: bool = True # 是否启用
examples: Optional[str] # 示例代码class Issue(BaseModel):
rule_id: str # 违反的规则ID
severity: Severity # 严重程度
line_start: int # 问题起始行号
line_end: int # 问题结束行号
description: str # 问题描述
suggestion: str # 修复建议
code_snippet: str # 问题代码片段class FileCheckResult(BaseModel):
file_path: str # 文件路径
check_time: str # 检查时间
issues: List[Issue] # 问题列表
error_count: int # 错误数量
warning_count: int # 警告数量
info_count: int # 提示数量
status: str # "success" | "failed" | "skipped"
error_message: Optional[str] # 错误信息(如果失败)class CheckState(BaseModel):
check_id: str # 检查ID
start_time: str # 开始时间
config: Dict[str, Any] # 配置参数
total_files: List[str] # 总文件列表
completed_files: List[str] # 已完成文件
remaining_files: List[str] # 剩余文件扩展建议:
- 添加新的严重程度级别(如 CRITICAL)
- 添加规则分组(RuleGroup)
- 添加检查统计信息(CheckStatistics)
位置:autocoder/checker/rules_loader.py
核心功能:
- 加载 Markdown 格式的规则文件
- 解析规则配置
- 根据文件类型选择适用规则
- 规则缓存优化
关键方法:
def load_rules(self, rule_type: str) -> List[Rule]:
"""
加载指定类型的规则
Args:
rule_type: 规则类型(backend/frontend)
Returns:
规则列表
"""
# 1. 检查缓存
if rule_type in self._rule_cache:
return self._rule_cache[rule_type]
# 2. 加载规则文件
rule_file = os.path.join(self.rules_dir, f"{rule_type}_rules.md")
rules = self._parse_markdown_rules(rule_file)
# 3. 应用配置
rules = self._apply_config(rules, rule_type)
# 4. 缓存结果
self._rule_cache[rule_type] = rules
return rulesdef _parse_markdown_rules(self, file_path: str) -> List[Rule]:
"""
解析 Markdown 格式的规则文件
规则格式:
### 规则ID: backend_001
**标题**: 规则标题
**严重程度**: warning
**描述**: 规则描述
"""
rules = []
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 按 "### 规则ID:" 分割
sections = re.split(r'### 规则ID:\s*(\w+)', content)
current_category = ""
for i in range(1, len(sections), 2):
rule_id = sections[i].strip()
rule_content = sections[i+1]
# 提取字段
title = self._extract_field(rule_content, "标题")
severity = self._extract_field(rule_content, "严重程度")
description = self._extract_field(rule_content, "描述")
# 提取示例
examples = self._extract_examples(rule_content)
rules.append(Rule(
id=rule_id,
category=current_category,
title=title,
description=description,
severity=Severity(severity),
examples=examples
))
return rules扩展建议:
- 支持 YAML/JSON 格式的规则文件
- 支持规则继承和覆盖
- 支持规则优先级
- 支持动态规则(从数据库加载)
位置:autocoder/checker/file_processor.py
核心功能:
- 文件扫描和过滤
- 大文件分块
- 文件类型检测
关键方法:
def scan_files(self, path: str, filters: FileFilters) -> List[str]:
"""
扫描目录,返回符合条件的文件列表
Args:
path: 扫描路径
filters: 文件过滤器(扩展名、忽略模式)
Returns:
文件路径列表
"""
files = []
for root, dirs, filenames in os.walk(path):
# 过滤忽略的目录
dirs[:] = [d for d in dirs if not self._should_ignore(d, filters.ignored)]
for filename in filenames:
file_path = os.path.join(root, filename)
# 检查扩展名
if filters.extensions:
if not any(file_path.endswith(ext) for ext in filters.extensions):
continue
# 检查忽略模式
if self._should_ignore(file_path, filters.ignored):
continue
# 检查是否可检查
if self.is_checkable(file_path):
files.append(file_path)
return filesdef chunk_file(self, file_path: str) -> List[CodeChunk]:
"""
将大文件分块,确保每块不超过 token 限制
策略:
1. 为每行添加行号
2. 计算总 token 数
3. 如果超过限制,按 token 分块
4. 块之间有重叠,避免边界问题
"""
with open(file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 添加行号
numbered_lines = [f"{i+1} {line}" for i, line in enumerate(lines)]
# 计算 tokens
total_tokens = count_tokens('\n'.join(numbered_lines))
# 不需要分块
if total_tokens <= self.chunk_size:
return [CodeChunk(
content='\n'.join(numbered_lines),
start_line=1,
end_line=len(lines),
chunk_index=0,
total_chunks=1
)]
# 需要分块
chunks = []
current_line = 0
chunk_index = 0
while current_line < len(numbered_lines):
# 计算当前 chunk 的结束行
end_line = self._calculate_chunk_end(
numbered_lines,
current_line,
self.chunk_size
)
# 创建 chunk
chunk_content = '\n'.join(numbered_lines[current_line:end_line])
chunks.append(CodeChunk(
content=chunk_content,
start_line=current_line + 1,
end_line=end_line,
chunk_index=chunk_index,
total_chunks=0 # 稍后更新
))
# 移动到下一个 chunk(考虑重叠)
current_line = end_line - self.overlap
chunk_index += 1
# 更新总 chunk 数
for chunk in chunks:
chunk.total_chunks = len(chunks)
return chunks扩展建议:
- 支持智能分块(按函数/类边界)
- 支持增量检查(只检查修改的文件)
- 支持 Git diff 集成
- 支持文件内容缓存
位置:autocoder/checker/core.py
核心功能:
- 协调各模块完成检查
- 调用 LLM 进行代码检查
- 处理检查结果
- 管理并发执行
关键方法:
def check_file(self, file_path: str) -> FileCheckResult:
"""
检查单个文件
流程:
1. 加载适用规则
2. 读取文件并分块
3. 检查每个 chunk
4. 合并结果
5. 返回检查结果
"""
try:
start_time = datetime.now()
# 1. 获取适用规则
rules = self.rules_loader.get_applicable_rules(file_path)
if not rules:
return FileCheckResult(
file_path=file_path,
check_time=start_time.isoformat(),
issues=[],
status="skipped"
)
# 2. 分块处理
chunks = self.file_processor.chunk_file(file_path)
logger.info(f"文件 {file_path} 被分为 {len(chunks)} 个 chunks")
# 3. 检查每个 chunk
all_issues = []
for chunk in chunks:
issues = self.check_code_chunk(chunk.content, rules)
all_issues.extend(issues)
# 4. 合并重复问题
merged_issues = self._merge_duplicate_issues(all_issues)
# 5. 统计
error_count = sum(1 for i in merged_issues if i.severity == Severity.ERROR)
warning_count = sum(1 for i in merged_issues if i.severity == Severity.WARNING)
info_count = sum(1 for i in merged_issues if i.severity == Severity.INFO)
return FileCheckResult(
file_path=file_path,
check_time=datetime.now().isoformat(),
issues=merged_issues,
error_count=error_count,
warning_count=warning_count,
info_count=info_count,
status="success"
)
except Exception as e:
logger.error(f"检查文件 {file_path} 失败: {e}")
return FileCheckResult(
file_path=file_path,
check_time=datetime.now().isoformat(),
issues=[],
error_count=0,
warning_count=0,
info_count=0,
status="failed",
error_message=str(e)
)@byzerllm.prompt()
def check_code_prompt(self, code_with_lines: str, rules: str) -> str:
"""
你是一个代码审查专家。请根据提供的规则检查代码,找出不符合规范的地方。
## 检查规则
{{ rules }}
## 待检查代码(带行号)
```
{{ code_with_lines }}
```
## 输出要求
请仔细检查代码,对于每个发现的问题:
1. 准确定位问题的起始和结束行号
2. 引用违反的规则ID
3. 描述问题
4. 提供修复建议
以 JSON 数组格式输出,每个问题包含:
- rule_id: 违反的规则ID
- severity: 严重程度 (error/warning/info)
- line_start: 问题起始行号(整数)
- line_end: 问题结束行号(整数)
- description: 问题描述
- suggestion: 修复建议
如果没有发现问题,返回空数组 []
示例输出:
```json
[
{
"rule_id": "backend_006",
"severity": "warning",
"line_start": 15,
"line_end": 32,
"description": "发现复杂的 if-else 嵌套",
"suggestion": "建议将内层逻辑抽取为独立方法"
}
]
```
"""
def check_code_chunk(self, code: str, rules: List[Rule]) -> List[Issue]:
"""调用 LLM 检查代码块"""
# 格式化规则
rules_text = self._format_rules(rules)
# 调用 LLM
response = self.llm.chat_oai(
conversations=[{
"role": "user",
"content": self.check_code_prompt(code, rules_text)
}]
)[0].output
# 解析 JSON
issues = self._parse_llm_response(response)
return issuesdef check_files_concurrent(
self, files: List[str], max_workers: int = 5
) -> Generator[FileCheckResult, None, None]:
"""
并发检查多个文件
使用 ThreadPoolExecutor 实现并发检查,提高大型项目的检查速度。
使用生成器模式按完成顺序实时返回结果,适合与进度条配合使用。
Args:
files: 文件列表
max_workers: 最大并发数
Yields:
FileCheckResult: 按完成顺序返回检查结果
"""
logger.info(f"开始并发检查 {len(files)} 个文件 (workers={max_workers})")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务
future_to_file = {
executor.submit(self.check_file, file_path): file_path
for file_path in files
}
# 按完成顺序返回结果
for future in as_completed(future_to_file):
file_path = future_to_file[future]
try:
result = future.result()
yield result
except Exception as exc:
logger.error(f"文件 {file_path} 检查失败: {exc}")
yield FileCheckResult(
file_path=file_path,
check_time=datetime.now().isoformat(),
issues=[],
error_count=0,
warning_count=0,
info_count=0,
status="failed",
error_message=str(exc)
)扩展建议:
- 支持自定义 Prompt 模板
- 支持多模型对比检查
- 支持增量检查优化
- 支持检查结果缓存
位置:autocoder/checker/progress_tracker.py
核心功能:
- 跟踪检查进度
- 持久化检查状态
- 支持中断恢复
关键方法:
def start_check(self, files: List[str], config: Dict[str, Any]) -> str:
"""
开始新的检查,返回 check_id
Args:
files: 文件列表
config: 检查配置
Returns:
check_id: 格式为 {project}_{timestamp}
"""
# 生成 check_id
project_name = os.path.basename(os.getcwd())
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
check_id = f"{project_name}_{timestamp}"
# 创建检查状态
state = CheckState(
check_id=check_id,
start_time=datetime.now().isoformat(),
config=config,
total_files=files,
completed_files=[],
remaining_files=files
)
# 保存状态
self.save_state(check_id, state)
return check_iddef mark_completed(self, check_id: str, file_path: str) -> None:
"""标记文件已完成检查"""
# 加载状态
state = self.load_state(check_id)
# 更新状态
if file_path in state.remaining_files:
state.remaining_files.remove(file_path)
if file_path not in state.completed_files:
state.completed_files.append(file_path)
# 保存状态
self.save_state(check_id, state)def get_remaining_files(self, check_id: str) -> List[str]:
"""获取待检查的文件列表"""
state = self.load_state(check_id)
return state.remaining_files if state else []扩展建议:
- 支持检查历史管理
- 支持检查统计分析
- 支持多用户检查隔离
- 支持分布式检查协调
位置:autocoder/checker/report_generator.py
核心功能:
- 生成 JSON 格式报告
- 生成 Markdown 格式报告
- 生成汇总统计
关键方法:
def generate_file_report(self, result: FileCheckResult, report_dir: str) -> None:
"""生成单个文件的检查报告"""
# 创建报告目录
files_dir = os.path.join(report_dir, "files")
os.makedirs(files_dir, exist_ok=True)
# 生成文件名(替换路径分隔符)
safe_filename = result.file_path.replace("/", "_").replace("\\", "_")
# 生成 JSON 报告
json_path = os.path.join(files_dir, f"{safe_filename}.json")
with open(json_path, 'w', encoding='utf-8') as f:
json.dump(result.dict(), f, indent=2, ensure_ascii=False)
# 生成 Markdown 报告
md_path = os.path.join(files_dir, f"{safe_filename}.md")
md_content = self._generate_file_markdown(result)
with open(md_path, 'w', encoding='utf-8') as f:
f.write(md_content)def generate_summary_report(
self, results: List[FileCheckResult], report_dir: str
) -> None:
"""生成汇总报告"""
# 统计信息
total_files = len(results)
total_issues = sum(len(r.issues) for r in results)
total_errors = sum(r.error_count for r in results)
total_warnings = sum(r.warning_count for r in results)
total_infos = sum(r.info_count for r in results)
# 问题最多的文件
top_files = sorted(
results,
key=lambda r: len(r.issues),
reverse=True
)[:10]
# 按规则分类
rule_counts = {}
for result in results:
for issue in result.issues:
rule_counts[issue.rule_id] = rule_counts.get(issue.rule_id, 0) + 1
# 生成汇总数据
summary = {
"check_id": os.path.basename(report_dir),
"total_files": total_files,
"total_issues": total_issues,
"error_count": total_errors,
"warning_count": total_warnings,
"info_count": total_infos,
"top_files": [
{"file": f.file_path, "issues": len(f.issues)}
for f in top_files
],
"rule_distribution": rule_counts
}
# 保存 JSON
with open(os.path.join(report_dir, "summary.json"), 'w') as f:
json.dump(summary, f, indent=2, ensure_ascii=False)
# 保存 Markdown
md_content = self._generate_summary_markdown(summary, results)
with open(os.path.join(report_dir, "summary.md"), 'w') as f:
f.write(md_content)扩展建议:
- 支持 HTML 报告
- 支持 PDF 导出
- 支持趋势分析(多次检查对比)
- 支持自定义报告模板
位置:autocoder/plugins/code_checker_plugin.py
核心功能:
- 注册
/check命令及子命令 - 提供命令补全
- 参数解析和验证
- 调用核心检查逻辑
插件结构:
class CodeCheckerPlugin(Plugin):
name = "code_checker"
description = "代码规范检查插件"
version = "1.0.0"
def initialize(self) -> bool:
"""初始化插件"""
# 初始化核心组件
self.checker = CodeChecker(self.llm, self.args)
self.report_generator = ReportGenerator()
self.progress_tracker = ProgressTracker()
return True
def get_commands(self):
"""注册命令"""
return {
"check": (self.handle_check, "代码检查命令"),
}
def get_completions(self):
"""提供命令补全"""
return {
"/check": ["/file", "/folder", "/resume", "/report"],
"/check /folder": ["/path", "/ext", "/ignore", "/workers"],
}命令处理:
def handle_check(self, args: str):
"""处理 /check 命令"""
parts = args.strip().split(maxsplit=1)
if not parts:
print(self._show_help())
return
subcommand = parts[0]
if subcommand == "/file":
self._check_file(parts[1] if len(parts) > 1 else "")
elif subcommand == "/folder":
self._check_folder(parts[1] if len(parts) > 1 else "")
elif subcommand == "/resume":
self._resume_check(parts[1] if len(parts) > 1 else "")
else:
print(f"未知子命令: {subcommand}")
print(self._show_help())扩展插件:
-
创建新插件:
class MyCheckerPlugin(CodeCheckerPlugin): name = "my_checker" def initialize(self): super().initialize() # 自定义初始化 return True def get_commands(self): commands = super().get_commands() # 添加新命令 commands["mycheck"] = (self.handle_mycheck, "我的检查") return commands
-
注册插件: 在
autocoder/plugins/__init__.py中注册
规则文件使用 Markdown 格式,便于阅读和维护。
文件位置:
- 后端规则:
rules/backend_rules.md - 前端规则:
rules/frontend_rules.md
规则格式:
## 规则类别名称
### 规则ID: backend_xxx
**标题**: 规则标题
**严重程度**: warning
**描述**: 规则的详细描述,说明为什么需要这个规则
**错误示例**:
```python
# 错误的代码示例
def bad_example():
# 违反规则的代码
pass正确示例:
# 正确的代码示例
def good_example():
# 符合规则的代码
pass
### 添加新规则步骤
1. **编辑规则文件**
```bash
vim rules/backend_rules.md
-
添加规则
## 性能优化 ### 规则ID: backend_100 **标题**: 避免在循环中进行数据库查询 **严重程度**: error **描述**: 在循环中进行数据库查询会导致 N+1 问题,严重影响性能。应该使用批量查询或 JOIN 优化。 **错误示例**: ```python def get_user_posts(user_ids): posts = [] for user_id in user_ids: # 每次循环都查询数据库 user_posts = db.query(Post).filter(Post.user_id == user_id).all() posts.extend(user_posts) return posts
正确示例:
def get_user_posts(user_ids): # 批量查询 posts = db.query(Post).filter(Post.user_id.in_(user_ids)).all() return posts
-
更新规则配置(可选)
如果需要特殊配置,编辑
rules/rules_config.json:{ "rule_sets": { "backend": { "enabled": true, "disabled_rules": [] // 如需禁用某规则,添加到这里 } } } -
测试规则
# 启动 chat_auto_coder python -m autocoder.chat_auto_coder # 测试新规则 /check /file test_file.py
-
验证结果
检查生成的报告,确认新规则正确生效
-
规则ID规范
- 后端:
backend_001-backend_999 - 前端:
frontend_001-frontend_999 - 按类别分段(如 001-099 代码结构,100-199 性能优化)
- 后端:
-
严重程度选择
error:必须修复的问题(安全、致命bug)warning:建议修复的问题(性能、规范)info:提示性信息(优化建议)
-
描述编写
- 说明"为什么"而不只是"是什么"
- 提供具体的修复建议
- 包含正反示例
-
示例代码
- 使用实际场景的代码
- 示例简洁明了
- 突出关键问题
假设要添加 SQL 检查:
步骤 1:创建规则文件
# 创建 SQL 规则文件
vim rules/sql_rules.md步骤 2:编写规则
## SQL 规范
### 规则ID: sql_001
**标题**: 避免使用 SELECT *
**严重程度**: warning
**描述**: SELECT * 会查询所有字段,影响性能,且在表结构变更时可能导致问题
**错误示例**:
```sql
SELECT * FROM users WHERE id = 1;正确示例:
SELECT id, name, email FROM users WHERE id = 1;
**步骤 3:更新配置**
编辑 `rules/rules_config.json`:
```json
{
"rule_sets": {
"backend": { ... },
"frontend": { ... },
"sql": {
"enabled": true,
"file_patterns": ["**/*.sql"],
"severity_threshold": "warning",
"disabled_rules": []
}
}
}
步骤 4:更新 RulesLoader
在 autocoder/checker/rules_loader.py 中添加:
def get_applicable_rules(self, file_path: str) -> List[Rule]:
"""根据文件路径获取适用的规则"""
ext = os.path.splitext(file_path)[1].lower()
# 现有映射
if ext == ".py":
return self.load_rules("backend")
elif ext in [".js", ".jsx", ".ts", ".tsx", ".vue"]:
return self.load_rules("frontend")
elif ext == ".sql": # 新增
return self.load_rules("sql")
return []假设要添加 HTML 报告:
步骤 1:创建报告生成方法
在 autocoder/checker/report_generator.py 中添加:
def generate_html_report(
self, results: List[FileCheckResult], report_dir: str
) -> None:
"""生成 HTML 格式报告"""
# HTML 模板
html_template = """
<!DOCTYPE html>
<html>
<head>
<title>代码检查报告</title>
<style>
/* CSS 样式 */
</style>
</head>
<body>
<h1>代码检查报告</h1>
{content}
</body>
</html>
"""
# 生成内容
content = self._generate_html_content(results)
# 保存文件
html_path = os.path.join(report_dir, "report.html")
with open(html_path, 'w', encoding='utf-8') as f:
f.write(html_template.format(content=content))步骤 2:调用生成方法
在插件中调用:
def _check_folder(self, args: str):
# ... 检查逻辑 ...
# 生成报告
self.report_generator.generate_summary_report(results, report_dir)
self.report_generator.generate_html_report(results, report_dir) # 新增假设要集成 Pylint:
步骤 1:创建工具包装器
# autocoder/checker/tools/pylint_wrapper.py
import subprocess
import json
from typing import List
from ..types import Issue, Severity
class PylintWrapper:
def check(self, file_path: str) -> List[Issue]:
"""使用 Pylint 检查文件"""
# 运行 Pylint
result = subprocess.run(
["pylint", "--output-format=json", file_path],
capture_output=True,
text=True
)
# 解析结果
pylint_issues = json.loads(result.stdout)
# 转换为 Issue 对象
issues = []
for item in pylint_issues:
issues.append(Issue(
rule_id=f"pylint_{item['message-id']}",
severity=self._map_severity(item['type']),
line_start=item['line'],
line_end=item['line'],
description=item['message'],
suggestion=item.get('hint', ''),
code_snippet=''
))
return issues
def _map_severity(self, pylint_type: str) -> Severity:
"""映射 Pylint 严重程度"""
mapping = {
'error': Severity.ERROR,
'warning': Severity.WARNING,
'convention': Severity.INFO,
'refactor': Severity.INFO,
}
return mapping.get(pylint_type, Severity.INFO)步骤 2:集成到检查流程
在 core.py 中:
def check_file(self, file_path: str) -> FileCheckResult:
"""检查单个文件"""
# LLM 检查
llm_issues = self._check_with_llm(file_path)
# Pylint 检查(可选)
if self.enable_pylint and file_path.endswith('.py'):
pylint = PylintWrapper()
pylint_issues = pylint.check(file_path)
llm_issues.extend(pylint_issues)
# 合并结果
merged_issues = self._merge_duplicate_issues(llm_issues)
# ... 返回结果class CodeChecker:
def __init__(self, llm: byzerllm.ByzerLLM, args: AutoCoderArgs)
def check_file(self, file_path: str) -> FileCheckResult
"""检查单个文件"""
def check_files_concurrent(
self, files: List[str], max_workers: int = 5
) -> Generator[FileCheckResult, None, None]
"""并发检查多个文件"""
def check_code_chunk(
self, code: str, rules: List[Rule]
) -> List[Issue]
"""检查代码块"""class RulesLoader:
def __init__(self, rules_dir: str = "rules")
def load_rules(self, rule_type: str) -> List[Rule]
"""加载指定类型的规则"""
def get_applicable_rules(self, file_path: str) -> List[Rule]
"""根据文件路径获取适用的规则"""
def reload_rules(self) -> None
"""重新加载所有规则"""class FileProcessor:
def __init__(self, chunk_size: int = 4000, overlap: int = 200)
def scan_files(self, path: str, filters: FileFilters) -> List[str]
"""扫描目录,返回符合条件的文件列表"""
def chunk_file(self, file_path: str) -> List[CodeChunk]
"""将大文件分块"""
def is_checkable(self, file_path: str) -> bool
"""判断文件是否可检查"""class ProgressTracker:
def __init__(self, state_dir: str = ".auto-coder/codecheck/progress")
def start_check(self, files: List[str], config: Dict) -> str
"""开始新的检查,返回 check_id"""
def mark_completed(self, check_id: str, file_path: str) -> None
"""标记文件已完成检查"""
def get_remaining_files(self, check_id: str) -> List[str]
"""获取待检查的文件列表"""
def save_state(self, check_id: str, state: CheckState) -> None
"""保存检查状态"""
def load_state(self, check_id: str) -> Optional[CheckState]
"""加载检查状态"""class ReportGenerator:
def __init__(self, output_dir: str = "codecheck")
def generate_file_report(
self, result: FileCheckResult, report_dir: str
) -> None
"""生成单个文件的检查报告"""
def generate_summary_report(
self, results: List[FileCheckResult], report_dir: str
) -> None
"""生成汇总报告"""测试文件位置:tests/checker/
测试示例:
# tests/checker/test_rules_loader.py
import pytest
from autocoder.checker.rules_loader import RulesLoader
from autocoder.checker.types import Rule, Severity
def test_load_backend_rules():
"""测试加载后端规则"""
loader = RulesLoader()
rules = loader.load_rules("backend")
assert len(rules) > 0
assert all(isinstance(r, Rule) for r in rules)
assert all(r.id.startswith("backend_") for r in rules)
def test_get_applicable_rules_for_python():
"""测试 Python 文件规则选择"""
loader = RulesLoader()
rules = loader.get_applicable_rules("test.py")
assert len(rules) > 0
assert all(r.id.startswith("backend_") for r in rules)
def test_rule_caching():
"""测试规则缓存"""
loader = RulesLoader()
rules1 = loader.load_rules("backend")
rules2 = loader.load_rules("backend")
# 应该返回同一个对象(缓存)
assert rules1 is rules2运行测试:
# 运行所有检查器测试
pytest tests/checker/ -v
# 运行特定测试文件
pytest tests/checker/test_rules_loader.py -v
# 查看覆盖率
pytest tests/checker/ --cov=autocoder/checker --cov-report=html# tests/checker/test_integration.py
def test_full_check_workflow():
"""测试完整的检查流程"""
# 1. 创建临时测试文件
test_file = "test_code.py"
with open(test_file, 'w') as f:
f.write("""
def test_function():
if True:
if True:
if True:
if True:
pass # 嵌套过深
""")
try:
# 2. 初始化检查器
checker = CodeChecker(mock_llm, mock_args)
# 3. 执行检查
result = checker.check_file(test_file)
# 4. 验证结果
assert result.status == "success"
assert len(result.issues) > 0
# 5. 验证报告生成
report_gen = ReportGenerator()
report_gen.generate_file_report(result, "test_reports")
assert os.path.exists("test_reports/files/test_code_py.json")
assert os.path.exists("test_reports/files/test_code_py.md")
finally:
# 清理
os.remove(test_file)
shutil.rmtree("test_reports")from unittest.mock import Mock, patch
def test_check_with_mock_llm():
"""使用 Mock LLM 测试"""
# Mock LLM 返回
mock_llm = Mock()
mock_llm.chat_oai.return_value = [Mock(output="""
```json
[
{
"rule_id": "backend_001",
"severity": "error",
"line_start": 10,
"line_end": 15,
"description": "测试问题",
"suggestion": "测试建议"
}
]
```
""")]
# 执行检查
checker = CodeChecker(mock_llm, mock_args)
result = checker.check_file("test.py")
# 验证
assert result.status == "success"
assert len(result.issues) == 1
assert result.issues[0].rule_id == "backend_001"# 使用 LRU 缓存
from functools import lru_cache
class RulesLoader:
@lru_cache(maxsize=10)
def load_rules(self, rule_type: str) -> List[Rule]:
# 加载逻辑
pass# 动态调整并发数
def get_optimal_workers(file_count: int) -> int:
if file_count < 10:
return 2
elif file_count < 50:
return 5
else:
return 10# 只检查修改的文件
def get_changed_files() -> List[str]:
result = subprocess.run(
["git", "diff", "--name-only", "HEAD"],
capture_output=True,
text=True
)
return result.stdout.strip().split('\n')A: 启用日志记录:
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("autocoder.checker")
# 查看 LLM 请求和响应
logger.debug(f"LLM Request: {prompt}")
logger.debug(f"LLM Response: {response}")A:
- 增加并发数
- 减少规则数量
- 使用更快的模型
- 实现结果缓存
A:
- 优化 Prompt 模板
- 添加更多示例到规则
- 使用更强的模型
- 结合静态分析工具
欢迎贡献代码和改进建议!
- Fork 项目
- 创建特性分支
- 编写代码和测试
- 提交 Pull Request
- 遵循 PEP 8
- 添加类型注解
- 编写文档字符串
- 添加单元测试
问题描述:
用户在使用 /check /folder 命令时遇到错误:
CodeChecker 初始化失败: Failed to create LLM instance for models: deepseek/deepseek-chat
- Model 'deepseek/deepseek-chat' not found
问题原因:
在 code_checker_plugin.py 的 _ensure_checker() 方法中(第103行),代码硬编码了一个默认模型:
model_name = conf.get("model", "deepseek/deepseek-chat")当配置中没有 "model" 字段时,会使用这个不存在的默认模型,导致初始化失败。
修复方案:
-
智能模型选择:按优先级选择模型
- 优先使用
chat_model(chat 模式专用模型) - 其次使用
model(通用模型) - 最后从 LLMManager 中自动选择第一个有 API key 的可用模型
- 优先使用
-
友好的错误提示:
- 当完全没有可用模型时,提供清晰的配置指导
- 当模型无法初始化时,提示可能的原因和解决方法
修改文件:
autocoder/plugins/code_checker_plugin.py(第82-139行)
修改内容:
def _ensure_checker(self):
# ... 省略部分代码 ...
# 智能获取模型配置
# 1. 优先使用 chat_model(chat 模式专用)
# 2. 其次使用 model(通用模型)
# 3. 最后尝试获取第一个可用模型
model_name = conf.get("chat_model") or conf.get("model")
if not model_name:
# 如果配置中没有模型,尝试从 LLMManager 获取第一个有 API key 的模型
llm_manager = LLMManager()
all_models = llm_manager.get_all_models()
# 查找第一个有 API key 的模型
for name, model in all_models.items():
if llm_manager.has_key(name):
model_name = name
logger.info(f"[{self.name}] 配置中未指定模型,自动选择: {model_name}")
break
if not model_name:
raise RuntimeError(
"未配置模型,且未找到可用的模型\n"
"请使用以下方式之一配置模型:\n"
"1. /models /add <model_name> <api_key> - 添加并激活模型\n"
"2. /config model <model_name> - 设置当前使用的模型"
)
# ... 省略后续代码 ...测试验证:
- 场景1:配置中有
chat_model→ 使用chat_model - 场景2:配置中只有
model→ 使用model - 场景3:配置中都没有,但有已激活的模型 → 自动选择第一个有 API key 的模型
- 场景4:没有任何可用模型 → 显示友好的错误提示
影响范围:
- 仅影响 CodeChecker 的初始化逻辑
- 不影响现有功能
- 向后兼容
相关文件:
autocoder/plugins/code_checker_plugin.pyautocoder/checker/core.py
提交信息:
fix(checker): 智能获取当前激活的模型
- 优先使用 chat_model,其次 model,最后自动选择
- 移除硬编码的默认模型 "deepseek/deepseek-chat"
- 添加友好的错误提示和配置指导
- 修复用户使用 /check 命令时的模型初始化失败问题
问题描述:
用户在项目目录执行 /check /folder 命令时,遇到大量文件检查失败(102个失败,0个成功),原因是当前目录没有规则文件。
解决方案:
在 RulesLoader 中添加自动初始化功能,当规则文件不存在时自动从模板复制。
主要改进:
-
智能模板查找(按优先级):
- 构造函数参数
template_rules_dir - 环境变量
CODE_CHECKER_TEMPLATE_DIR - 默认位置:项目根目录
rules/
- 构造函数参数
-
自动初始化流程:
- 检测规则文件不存在
- 查找模板目录
- 验证模板文件完整性
- 复制规则文件到当前目录
- 显示友好提示
-
用户友好的提示:
✨ 检测到当前目录没有规则文件
📋 正在从模板自动创建规则文件...
✓ backend_rules.md (63条后端规则)
✓ frontend_rules.md (105条前端规则)
✓ rules_config.json (配置文件)
✅ 规则文件初始化成功!
规则目录: /path/to/current/rules
修改文件:
autocoder/checker/rules_loader.py- 修改
__init__()添加template_rules_dir和auto_init参数 - 修改
load_rules()添加自动初始化逻辑 - 新增
_get_template_dir()方法 - 新增
_auto_initialize_rules()方法
- 修改
配置方式:
方式1:使用默认行为(推荐)
/check /folder # 自动从项目根目录的 rules/ 复制方式2:通过环境变量指定
export CODE_CHECKER_TEMPLATE_DIR=/path/to/template/rules
/check /folder方式3:禁用自动初始化
loader = RulesLoader(auto_init=False)特性:
- ✅ 默认启用,无需手动配置
- ✅ 向后兼容
- ✅ 友好的错误提示
- ✅ 支持多种模板路径配置
- ✅ 防止重复初始化
提交信息:
feat(checker): 添加规则文件自动初始化功能
- 当检测到没有规则文件时自动从模板复制
- 支持多种方式指定模板目录(参数/环境变量/默认位置)
- 提供友好的用户提示和错误指导
- 解决用户在项目目录执行检查时规则文件缺失的问题
- 默认启用,向后兼容
修复问题:用户执行 /check /folder 时 102 个文件失败
问题描述:
在使用 /check /folder 或 /check /resume 命令进行代码检查时,最后一个文件(如 DictItemServiceImpl.java)无法完成检查,表现为:
- 进度条显示 100% 但检查未完成
- 文件检查一直卡住,长时间无响应(10+ 分钟)
- Resume 后仍然卡在同一个文件
问题根因: 通过分析日志发现,文件检查卡在 LLM API 调用阶段:
- DictItemServiceImpl.java 在 03:41:57 开始检查,但从未完成
- Resume 时(03:46:57)再次开始检查,依然卡住
- 从日志来看,
check_code_chunk()方法中的self.llm.chat_oai()调用没有超时机制,导致:- API 调用超时或网络中断时,程序一直等待
- 无法继续检查下一个文件或下一个 chunk
- Resume 功能也无法解决问题
解决方案:
-
为 LLM 调用添加超时机制:
- 使用
ThreadPoolExecutor包装 LLM 调用 - 设置 180 秒超时时间
- 超时后返回空结果,继续处理
- 使用
-
增强异常处理和日志:
- 添加详细的 LLM 调用时间记录
- 记录每个 chunk 的检查进度
- 统计超时的 chunk 数量
-
改进文件级错误处理:
- 确保即使某个 chunk 超时,也能继续检查其他 chunks
- 记录 chunk 级别的失败情况
修改内容:
在 autocoder/checker/core.py 中:
# 1. 导入 TimeoutError
from concurrent.futures import ThreadPoolExecutor, as_completed, TimeoutError
# 2. 修改 check_code_chunk() 方法,添加 timeout 参数
def check_code_chunk(
self, code: str, rules: List[Rule], timeout: int = 180
) -> List[Issue]:
"""
检查代码块
Args:
code: 代码内容(带行号)
rules: 适用的规则列表
timeout: LLM 调用超时时间(秒),默认 180 秒
"""
try:
# ... 准备工作 ...
# 使用 ThreadPoolExecutor 实现超时
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(self._call_llm, conversations)
try:
response = future.result(timeout=timeout)
except TimeoutError:
logger.error(f"LLM 调用超时({timeout}秒),跳过此代码块")
return []
except Exception as e:
logger.error(f"LLM 调用失败: {e}", exc_info=True)
return []
# ... 处理响应 ...
# 3. 新增 _call_llm() 辅助方法
def _call_llm(self, conversations: List[Dict[str, str]]) -> Any:
"""
调用 LLM(内部方法,用于支持超时)
记录调用开始/结束时间,便于排查问题
"""
start_time = datetime.now()
logger.debug(f"开始 LLM 调用: {start_time.isoformat()}")
try:
response = self.llm.chat_oai(conversations=conversations)
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
logger.debug(f"LLM 调用完成,耗时: {duration:.2f}秒")
return response
except Exception as e:
end_time = datetime.now()
duration = (end_time - start_time).total_seconds()
logger.error(f"LLM 调用异常(耗时 {duration:.2f}秒): {e}", exc_info=True)
raise
# 4. 改进 check_file() 中的 chunk 处理
for chunk in chunks:
logger.info(
f"检查 chunk {chunk.chunk_index + 1}/{len(chunks)}: "
f"行 {chunk.start_line}-{chunk.end_line}"
)
try:
issues = self.check_code_chunk(chunk.content, rules)
# ... 处理结果 ...
logger.info(f"Chunk {chunk.chunk_index + 1} 完成,发现 {len(issues)} 个问题")
except Exception as e:
logger.error(f"检查 chunk {chunk.chunk_index} 时发生异常: {e}", exc_info=True)
chunk_timeout_count += 1
continue # 继续检查下一个 chunk
# 记录超时情况
if chunk_timeout_count > 0:
logger.warning(
f"文件 {file_path} 有 {chunk_timeout_count}/{len(chunks)} "
f"个 chunk 检查失败或超时"
)测试验证:
- ✅ 代码语法验证通过
- ✅ 模块导入成功
- ✅ 所有单元测试通过(18 个测试)
修复效果:
- LLM 调用超时后自动失败并继续:不再卡住整个检查流程
- Resume 功能正常:失败的文件会被标记为已处理,不会重复卡住
- 明确的超时日志:便于用户了解问题原因
- 部分成功处理:如果文件有多个 chunks,部分超时不影响其他 chunks
影响范围:
- 修改了
CodeChecker.check_code_chunk()方法签名(新增可选参数) - 新增了
CodeChecker._call_llm()辅助方法 - 向后兼容:默认超时 180 秒,可通过参数调整
相关文件:
autocoder/checker/core.py(第 19, 257-336 行)
提交信息:
fix(checker): 修复 LLM 调用超时导致最后文件无法处理的问题
问题描述:
- 检查时最后一个文件一直卡住,无法完成
- Resume 后仍然卡在同一个文件
- 原因:LLM API 调用没有超时机制
解决方案:
1. 为 LLM 调用添加 180 秒超时机制
2. 超时后返回空结果,继续处理下一个 chunk/文件
3. 增强日志记录,显示调用耗时和超时信息
4. 改进 chunk 级异常处理,确保部分失败不影响整体
测试验证:
- 所有单元测试通过(18/18)
- 代码语法和导入验证通过
影响:向后兼容,默认超时 180 秒
问题描述:
在 /projects/codecheck 项目的检查中发现两个问题:
- 最后一个文件卡住不动:
DictItemServiceImpl.java(556行) 在并发检查时无法完成 - 没有生成检查报告:即使已完成140个文件,也没有生成任何报告
问题分析:
-
卡住原因:
- 虽然之前添加了 chunk 级别的 180 秒超时,但没有文件级别的超时保护
DictItemServiceImpl.java虽然分成了多个 chunks,但所有 chunks 累计时间超过10分钟- 并发检查时,该文件的
check_file()方法一直阻塞在线程池中 check_files_concurrent()的future.result()调用没有超时,导致永久等待
-
报告缺失原因:
- 并发检查使用生成器模式,只有当所有文件都返回结果时才会退出循环
- 由于最后一个文件卡住,生成器永远无法完成迭代
- 插件的
_check_folder()方法在第 177 行的for result in ...循环被阻塞 - 报告生成代码(第 386-394 行)永远无法执行
解决方案:
在 autocoder/checker/core.py 中修改 check_file() 方法:
def check_file(self, file_path: str, file_timeout: int = 600) -> FileCheckResult:
"""
检查单个文件
Args:
file_path: 文件路径
file_timeout: 单个文件检查的最大超时时间(秒),默认 600 秒(10分钟)
"""
logger.info(f"开始检查文件: {file_path} (超时: {file_timeout}秒)")
# 使用 ThreadPoolExecutor 实现文件级超时
with ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(self._check_file_impl, file_path)
try:
result = future.result(timeout=file_timeout)
return result
except TimeoutError:
logger.error(f"文件 {file_path} 检查超时({file_timeout}秒)")
return FileCheckResult(
file_path=file_path,
check_time=datetime.now().isoformat(),
issues=[],
error_count=0,
warning_count=0,
info_count=0,
status="timeout", # 新增状态
error_message=f"文件检查超时({file_timeout}秒)"
)
def _check_file_impl(self, file_path: str) -> FileCheckResult:
"""检查单个文件的内部实现(用于支持超时)"""
# 原 check_file() 的实现逻辑移到这里
...关键改进:
- 将原有的
check_file()逻辑拆分为_check_file_impl() - 用
ThreadPoolExecutor包装,实现文件级超时控制 - 默认 600 秒超时(可配置)
- 超时时返回
status="timeout"的结果,而不是阻塞
修改 check_files_concurrent() 方法:
def check_files_concurrent(
self, files: List[str], max_workers: int = 5, file_timeout: int = 600
) -> Generator[FileCheckResult, None, None]:
"""
并发检查多个文件
Args:
file_timeout: 单个文件检查的最大超时时间(秒),默认 600 秒(10分钟)
"""
logger.info(f"开始并发检查 {len(files)} 个文件 (workers={max_workers}, file_timeout={file_timeout}秒)")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 提交所有任务(传递 file_timeout 参数)
future_to_file = {
executor.submit(self.check_file, file_path, file_timeout): file_path
for file_path in files
}
# 按完成顺序返回结果
for future in as_completed(future_to_file):
file_path = future_to_file[future]
try:
result = future.result() # 不需要额外 timeout,check_file 内部已有
yield result
except Exception as exc:
# ... 异常处理关键改进:
- 将
file_timeout参数传递给check_file() - 每个文件都有独立的超时控制
future.result()不需要设置 timeout,因为check_file()内部已经有超时机制
修改 autocoder/plugins/code_checker_plugin.py 的 _check_folder() 方法:
def _check_folder(self, args: str) -> None:
# ... 前置代码 ...
results = []
check_interrupted = False
try:
with Progress(...) as progress:
for result in self.checker.check_files_concurrent(files, max_workers=workers):
results.append(result)
# ... 更新进度 ...
except KeyboardInterrupt:
check_interrupted = True
# ... 处理中断 ...
finally:
# 确保即使中断或出错也生成部分报告
if results:
logger.info(f"生成部分报告,已完成 {len(results)} 个文件")
# 如果是正常完成,标记状态
if not check_interrupted:
state = self.progress_tracker.load_state(check_id)
if state:
state.status = "completed"
self.progress_tracker.save_state(check_id, state)
# 生成报告
report_dir = self._create_report_dir(check_id)
# 生成单文件报告
for result in results:
self.report_generator.generate_file_report(result, report_dir)
# 生成汇总报告
self.report_generator.generate_summary_report(results, report_dir)
# 显示汇总
if check_interrupted:
print(f"\n📄 已生成部分报告 ({len(results)}/{len(files)} 个文件)")
print(f" 报告位置: {report_dir}/")
print(f"\n💡 使用以下命令恢复检查:")
print(f" /check /resume {check_id}\n")
else:
self._show_batch_summary(results, report_dir)关键改进:
- 使用
try-finally结构确保报告一定会生成 - 即使中断或超时,已完成的文件也会生成报告
- 区分正常完成和中断两种情况的提示
修改 _show_batch_summary() 方法:
def _show_batch_summary(self, results: List, report_dir: str) -> None:
# 统计
total_files = len(results)
checked_files = len([r for r in results if r.status == "success"])
skipped_files = len([r for r in results if r.status == "skipped"])
failed_files = len([r for r in results if r.status == "failed"])
timeout_files = len([r for r in results if r.status == "timeout"]) # 新增
print(f"检查文件: {total_files}")
print(f"├─ ✅ 成功: {checked_files}")
print(f"├─ ⏭️ 跳过: {skipped_files}")
print(f"├─ ⏱️ 超时: {timeout_files}") # 新增
print(f"└─ ❌ 失败: {failed_files}")同时在单文件检查结果显示中添加:
elif result.status == "timeout":
print(f"⏱️ 文件检查超时: {file_path}")
print(f" 错误: {result.error_message}")修改文件清单:
-
autocoder/checker/core.py- 修改
check_file()添加文件级超时 - 新增
_check_file_impl()实现方法 - 修改
check_files_concurrent()传递超时参数
- 修改
-
autocoder/plugins/code_checker_plugin.py- 修改
_check_folder()使用 try-finally 确保报告生成 - 修改
_show_batch_summary()统计 timeout 状态 - 修改
_check_file()显示 timeout 状态
- 修改
测试验证:
- ✅ 所有单元测试通过 (4/4 check_file 相关测试)
- ✅ 代码语法验证通过
- ✅ 向后兼容:默认超时 600 秒
预期效果:
- 不再卡住:文件超时后自动返回超时结果,继续检查下一个文件
- 总是生成报告:即使有文件超时或中断,已完成的文件也会生成报告
- 明确的超时信息:用户可以看到哪些文件超时,超时时间是多少
- 支持自定义超时:可以根据项目大小调整
file_timeout参数
配置建议:
- 小文件(< 200 行): 180 秒足够
- 中等文件(200-1000 行): 300-600 秒
- 大文件(1000+ 行): 600-900 秒
- 特大文件(2000+ 行): 可考虑拆分或增加到 1200 秒
提交信息:
fix(checker): 修复文件检查超时和报告无法生成的问题
问题描述:
1. 大文件(如 DictItemServiceImpl.java)在并发检查时卡住不动
2. 由于一个文件卡住,导致整个检查无法完成,报告无法生成
根本原因:
1. 缺少文件级总超时保护(只有 chunk 级 180 秒超时)
2. 报告生成代码在检查完成之后,被阻塞无法执行
解决方案:
1. 为单个文件添加 600 秒总超时保护
2. 将 check_file() 拆分为带超时的外层和实现的内层
3. 并发检查传递超时参数给每个文件
4. 使用 try-finally 确保即使部分失败也能生成报告
5. 添加 timeout 状态的统计和显示
修改文件:
- autocoder/checker/core.py (文件超时控制)
- autocoder/plugins/code_checker_plugin.py (报告生成保护)
测试: 所有单元测试通过, 向后兼容
最后更新:2025-10-11 文档版本:1.0.4 作者:Claude AI
- ✅ 默认启用确定性 LLM 配置:
temperature=0.0、top_p=1.0、seed=42 - ✅ 支持通过
AutoCoderArgs、插件配置或环境变量覆盖 LLM 相关参数 - ✅ 文件分块结果缓存 + 默认 20000 token 分块阈值,可配置 overlap multiplier,保证多轮扫描分块一致
- ✅ 支持多次 LLM 调用并按共识保留问题,显著提升结果稳定性
- ✅ 合并问题时容忍 ±1 行偏差,并在信息冲突时保留描述更完整的版本
- ✅ 新增稳定性回归测试
tests/stability/test_deterministic_results.py
| 配置项 | 默认值 | 覆盖途径 | 说明 |
|---|---|---|---|
温度 temperature |
0.0 |
AutoCoderArgs(checker_llm_temperature=...)/plugins code_checker 配置 llm_temperature环境变量 CODECHECKER_LLM_TEMPERATURE |
控制输出随机性,推荐 0.0~0.3 |
核采样 top_p |
1.0 |
同上,字段名 checker_llm_top_p / llm_top_p / CODECHECKER_LLM_TOP_P |
1.0 表示禁用核采样 |
Seed seed |
42 |
同上,字段名 checker_llm_seed / llm_seed / CODECHECKER_LLM_SEED |
设为 -1 可显式关闭 |
| 额外 LLM 参数 | 空 | checker_llm_config / checker.llm 节配置字典 |
支持传入 presence_penalty 等模型特定参数 |
| Chunk token limit | 20000 |
checker_chunk_token_limit / chunk_token_limit / CODECHECKER_CHUNK_TOKEN_LIMIT |
达到阈值才拆分 chunk,避免对中小文件分块 |
| Chunk overlap multiplier | 按 FileProcessor.overlap 基础值 |
checker_chunk_overlap_multiplier / chunk_overlap_multiplier / CODECHECKER_CHUNK_OVERLAP_MULTIPLIER |
用于放大 overlap,增强分块上下文一致性 |
| LLM repeat | 1 |
checker_llm_repeat / llm_repeat / CODECHECKER_LLM_REPEAT |
对同一 chunk 重复检查多次以取交集或多数票 |
| Consensus ratio | 1.0 |
checker_llm_consensus_ratio / llm_consensus / CODECHECKER_LLM_CONSENSUS |
通过出现比例控制保留问题(1.0=全交集) |
提示:插件配置示例
{ "checker": { "llm": { "presence_penalty": 0.1 }, "llm_temperature": 0.1, "llm_seed": 99, "chunk_overlap_multiplier": 2.0 } }
CodeChecker.llm_config会在初始化时根据上述优先级构建,可通过日志查看实际生效的参数。FileProcessor现在会缓存分块结果(按文件mtime + size),命中缓存时返回深拷贝,避免多线程下共享状态。- 合并问题时,
rule_id相同且行号差 ≤1 会视作同一问题;最终行号范围取最小起点与最大终点。 - 稳定性测试需通过
pytest -m stability(或运行全量测试)确保配置调整不会破坏确定性。
版本号:1.0.5
问题描述:
用户在使用 /check /file ./DictItemServiceImpl.java 命令检查单个文件时,遇到错误:
❌ 检查过程出错: name 'options' is not defined
问题原因:
在 autocoder/plugins/code_checker_plugin.py 的 _check_file() 方法中(第 423-426 行):
- 第 401 行定义了变量
common_options = self._parse_common_options(option_tokens) - 第 423-426 行错误地使用了未定义的
options变量 - 第 429 行又调用了一次
_apply_checker_options(common_options),使用了正确的变量名
这是一个变量名错误,导致运行时报 NameError。同时,第 423-426 行和第 429 行是重复调用。
修复方案: 删除第 423-426 行的错误代码,只保留第 429 行的正确调用。
修改前代码:
# 第 401 行
common_options = self._parse_common_options(option_tokens)
try:
# 确保 checker 已初始化
self._ensure_checker()
self._apply_checker_options({
"repeat": options.get("repeat"), # ❌ options 未定义
"consensus": options.get("consensus"), # ❌ options 未定义
})
# 应用共识参数
self._apply_checker_options(common_options) # ✅ 正确修改后代码:
# 第 401 行
common_options = self._parse_common_options(option_tokens)
try:
# 确保 checker 已初始化
self._ensure_checker()
# 应用共识参数
self._apply_checker_options(common_options) # ✅ 正确修改文件:
autocoder/plugins/code_checker_plugin.py(删除第 423-426 行)
测试验证:
- ✅ Python 语法检查通过:
python3 -m py_compile autocoder/plugins/code_checker_plugin.py - ✅ 代码逻辑验证正确
代码逻辑流程(修复后):
- 第 401 行:解析参数 →
common_options - 第 422 行:初始化 checker
- 第 425 行:应用参数 → 使用
common_options
影响范围:
- 仅影响
/check /file命令的参数处理逻辑 - 修复后命令可以正常运行
- 不影响其他功能
- 向后兼容
提交信息:
fix(plugin): 修复 /check /file 命令中 'options' 未定义的错误
问题:
- /check /file 命令报错: name 'options' is not defined
- 第 423-426 行使用了未定义的 'options' 变量
- 实际定义的变量名是 'common_options' (第 401 行)
- 存在重复调用 _apply_checker_options
修复:
- 删除第 423-426 行错误的重复代码
- 保留第 429 行使用 common_options 的正确调用
- 代码逻辑流程现在正确:
1. 解析参数 → common_options
2. 初始化 checker
3. 应用参数 → 使用 common_options
测试:
- Python 语法检查通过
- 代码逻辑验证正确
相关文件:
- autocoder/plugins/code_checker_plugin.py
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Git 提交:951c94f
问题描述:
经过实际测试发现,当 consensus=1.0(要求完全一致)时,多次 LLM 调用可能出现"集体放水"现象,导致本应发现的问题被漏报。例如:
- 3 次调用分别返回 0、0、5 个问题
- 因为要求完全一致(consensus=1.0),最终只保留 0 个问题
- 这导致 5 个实际存在的问题被漏报
解决方案:
将默认 consensus 阈值从 1.0 调整为 0.34(3次中至少1次发现即保留)
计算逻辑:
- 默认
llm_repeat=3(重复调用 3 次) threshold = max(1, ceil(0.34 * 3)) = max(1, 1) = 1- 即:问题在 3 次调用中至少出现 1 次,就会被保留
- 这在保持准确性的同时,显著降低漏报率
主要修改:
-
核心模块 (
autocoder/checker/core.py):- 第 88 行:将
DEFAULT_LLM_CONSENSUS_RATIO从1.0改为0.34 - 增强了一致性检查逻辑(
_aggregate_attempt_results方法):- 计算变异系数(CV)检测输出不一致
- 检测"集体放水"现象(部分调用返回 0,部分返回正常数量)
- 发出警告提示用户检查 Prompt
- 改进了 Prompt(
check_code_prompt方法):- 强调核心原则:"宁可误报,不可漏报"
- 要求严格执行规则,不要因为"看起来还行"就放宽标准
- 提供错误示例和正确示例
- 强调一致性要求
- 优化日志格式,使用 f-string
- 第 88 行:将
-
插件配置 (
autocoder/plugins/code_checker_plugin.py):- 第 56 行:将插件默认值
consensus从1.0改为0.34 - 确保插件与核心模块的默认值一致
- 第 56 行:将插件默认值
效果对比:
| 场景 | consensus=1.0 | consensus=0.34 | 说明 |
|---|---|---|---|
| 3次调用:0, 0, 0 | 保留 0 个 ✅ | 保留 0 个 ✅ | 真的没问题 |
| 3次调用:5, 5, 5 | 保留 5 个 ✅ | 保留 5 个 ✅ | 完全一致 |
| 3次调用:0, 0, 5 | 保留 0 个 ❌ | 保留 5 个 ✅ | 防止漏报 |
| 3次调用:3, 4, 5 | 保留 3 个(交集) | 保留全部 ✅ | 包含所有发现 |
| 3次调用:2, 8, 3 | 保留 0 个 ❌ | 保留全部 ✅ | 可能有假阳,但不漏报 |
新增功能:
-
一致性监测:
- 自动计算变异系数(CV = std / mean)
- CV > 0.5 时发出警告
- 检测"集体放水"现象并警告
-
Prompt 增强:
- 明确"宁可误报,不可漏报"原则
- 列举常见错误和正确示例
- 强调客观量化判断(如行数、嵌套层数)
使用建议:
-
默认配置(推荐):
/check /file test.py # 使用默认: repeat=3, consensus=0.34 -
完全一致模式(严格模式,可能漏报):
/check /file test.py /repeat 3 /consensus 1.0
-
单次检查(最快,但不稳定):
/check /file test.py /repeat 1
-
高覆盖模式(最全面,可能有假阳):
/check /file test.py /repeat 5 /consensus 0.2
配置方式:
通过插件配置文件设置默认值:
{
"checker": {
"defaults": {
"repeat": 3,
"consensus": 0.34
}
}
}或通过命令设置:
/check /config /repeat 3 /consensus 0.34影响范围:
- ✅ 向后兼容:不会破坏现有代码
- ✅ 默认行为变更:检查更严格,覆盖更全面
- ✅ 可通过参数覆盖:用户可自行选择平衡点
- ✅ 性能影响:默认 3 次调用,检查时间约增加 2 倍(可接受)
相关文件:
autocoder/checker/core.py(第 88, 184-256, 928-1022 行)autocoder/plugins/code_checker_plugin.py(第 56 行)
提交信息:Git commit e22dc56
测试验证:
- ✅ 所有单元测试通过
- ✅ 实际代码检查验证通过
- ✅ 一致性监测正常工作
最后更新:2025-10-13 文档版本:1.0.7 作者:Claude AI