基于 LLM 的代码审查系统:用 RAG + AST 实现智能 Code Review 的工程探索

基于 LLM 的代码审查系统:用 RAG + AST 实现智能 Code Review 的工程探索
基于 LLM 的代码审查系统用 RAG AST 实现智能 Code Review 的工程探索一、代码审查的双输困境高级工程师的评审产出 vs 低级工程师的等待时间团队中有 4 名高级工程师Staff/Senior每人每天要审查 8~12 个 MRMerge Request。更关键的是MR 的等待队列均值是 3.2 小时而其中约 60% 的评审意见是机械性的——变量名不符合命名规范缺少错误处理SQL 语句可能存在注入风险这类问题不需要资深工程师的直觉和经验纯粹是规则的执行。如果能用一个 LLM 系统自动处理这 60% 的机械性审查将人工审查聚焦于架构合理性安全边界性能隐含风险等高阶问题MR 周转时间至少可以减半。二、AST 解析 LLM 的双引擎架构单纯的 LLM 拿到一段代码 diff 就去审查存在两个问题上下文不足以理解代码意图以及产生幻觉式建议。引入 AST 解析层做结构化抽取将代码变更转化为结构化的函数签名、类型定义和数据流# AST 解析层 —— 将代码 diff 转化为 LLM 友好的结构化表示 import ast from dataclasses import dataclass from typing import List, Optional dataclass class FunctionChange: name: str # 函数名 signature: str # 函数签名参数列表 返回类型 complexity: int # 圈复杂度 10 提出警告 added_lines: int # 新增行数 has_error_handling: bool # 是否包含异常处理 dependencies: List[str] # 调用了哪些外部函数 class CodeStructureExtractor: def analyze_diff(self, diff_content: str) - dict: 将 Git diff 解析为结构化实体列表 changed_files self._parse_diff(diff_content) result {functions: [], imports: [], classes: []} for file_path, additions in changed_files.items(): try: tree ast.parse(\n.join(additions)) for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): result[functions].append(FunctionChange( namenode.name, signatureself._get_signature(node), complexityself._cyclomatic_complexity(node), added_lineslen(additions), has_error_handlingself._has_try_except(node), dependenciesself._extract_calls(node) )) elif isinstance(node, (ast.Import, ast.ImportFrom)): result[imports].append(ast.unparse(node)) except SyntaxError: continue # diff 片段可能不完整跳过 return resultLLM 评审层接收结构化的分析结果和代码 diff分维度给出评审意见REVIEW_PROMPT 你是一位资深后端工程师请对以下代码变更进行代码审查。 ## 代码变更上下文 {code_structure} ## Diff 内容 {language} {diff_content}审查维度严格按此顺序正确性逻辑是否正确边界条件是否覆盖安全性是否存在注入、越权、敏感信息泄露风险性能是否存在不必要的内存分配、锁竞争、慢查询可维护性命名是否自解释函数是否过长注释是否必要输出格式每个问题用 JSON 输出{{issues: [{{severity: blocker|major|minor|nit,category: 正确性|安全性|性能|可维护性,file: 文件路径,line: 行号,title: 问题简述,description: 详细描述,suggestion: 修复建议含代码示例}}],summary: 一句话总结}}审查原则Blocker 级问题会导致服务崩溃、数据丢失或安全漏洞不要输出代码风格类建议命名、空格等由 linter 处理每条建议必须包含可执行的代码修复示例def review_diff(structure: dict, diff: str, language: str) - dict:response llm_client.chat.completions.create(modelgpt-4o,messages[{role: system, content: REVIEW_PROMPT.format(code_structurejson.dumps(structure, indent2),languagelanguage,diff_contentdiff)}],temperature0.1,response_format{type: json_object})return json.loads(response.choices[0].message.content)## 三、RAG 增强——让 LLM 参考团队的修复历史 相似问题在团队历史中往往已有修复方案。RAG 检索层查找与当前 MR 最相似的 3 个历史 MR python class MRKnowledgeBase: def __init__(self): self.encoder SentenceTransformer(microsoft/codebert-base) self.vector_db chromadb.PersistentClient(path./mr_kb) def index_mr(self, mr_id: str, diff_content: str, review_comments: list): 将已合并的 MR 索引到向量库 embedding self.encoder.encode(diff_content[:4096]) # 截断长 diff self.collection.add( ids[mr_id], embeddings[embedding.tolist()], metadatas[{comments: json.dumps(review_comments)}] ) def search_similar(self, diff: str, top_k: int 3) - list: embedding self.encoder.encode(diff[:4096]) results self.collection.query( query_embeddings[embedding.tolist()], n_resultstop_k ) # 提取历史 MR 的评审意见作为 few-shot 示例 return [json.loads(m[comments]) for m in results[metadatas][0]]四、效果评估与人工验收30 天试点期间的数据指标人工审查LLM 人工MR 平均等待时间3.2h0.8h机械性问题拦截率—94%遗漏率已知问题未被发现15%12%首次审查通过率42%58%高级工程师日审核量10 MR5 MRLLM 的遗漏率12%甚至低于纯人工15%原因在于 AST 解析 RAG 覆盖了一些人工容易疲劳漏掉的问题如深层嵌套中的 SQL 拼接。五、总结LLM 代码审查系统的关键设计点AST 解析是 LLM 的前置过滤器先结构化抽取函数签名、复杂度、依赖再交给 LLM 分析。纯 diff 输入会让 LLM 迷失在语法噪音中60% 机械性审查自动化是合理的目标线风格、规范、常规安全检查交给 LLM架构和性能的深度审查留给人工。过度自动化会引入危险的安全盲区RAG 的 few-shot 参考价值远超预期历史相似 MR 的修复方案被检索到后LLM 给出的建议与团队风格一致性从 45% 提升到 78%遗漏率比通过率更重要优化的首要指标是有没有安全问题被漏掉而不是审查通过了多少个 MR。LLM 的 12% 遗漏率仍然不可接受每条 Blocker 必须有双重检查。落地建议先做只推荐不拦截模式LLM 建议 人工裁决运行 2 周后切换为自动拦截 style 和 lint 问题。