做智能体当然是为了省事,跟程序员息息相关的有哪些事情呢?现在有一个现实问题,随着AI生成代码的比例越来越高——看新闻Cursor的Bugbot每月审查超过两百万个PR,Copilot X、腾讯云代码助手们每天吐出海量代码。是不是发现我们发现陷入了一个悖论:代码写得越来越快,但审查的速度跟不上。GitHub前CEO托马斯·多姆克说得一针见血:“发布代码的瓶颈不是编写代码,而是审查智能体编写的代码。”

Atlassian的调研数据更扎心:46%的开发者不信任AI生成代码的准确性,60%的人认为现有审查工具经常遗漏关键上下文。信任危机和质量瓶颈,成了AI编程时代的一体两面。今天就如果要搭一个智能代码审查助手,这种不是那种只会喊“这里没写注释”的玩具,而是能真正理解代码逻辑、发现隐藏Bug、给出可操作建议的“数字同事”。

那应该怎么做呢?

一、需求分析

先想清楚了一件事:一个合格的智能审查助手,需要解决哪些问题?

1.1 传统方案的三大死穴

网上看资料京东云的工程师们总结得很到位。他们在双十一大促前做了大量代码评审,发现现有方案有三个难以逾越的坑:

  • 上下文缺失。很多工具为了省钱省Token,只把变更处附近10行代码发给大模型。这就像让一个医生只看病人的一根手指,就要求他诊断全身疾病。结果是大量的误报和漏报——因为评审完全丧失了项目架构、模块依赖和完整业务逻辑的视野。
  • 提示词膨胀。所有评审规则都硬编码在Prompt里,规则一多就触及模型上下文上限。更糟的是,规则之间会互相竞争,真正需要的规则可能被挤掉。
  • 知识无法沉淀。效果提升完全依赖“换更强的模型”和“调Prompt”,没有可持续积累领域知识的机制。每个项目都得从头调教。

1.2 真实痛点

回忆去年遇到的两次线上事故,更能看清需求。

一次是三方系统空值处理异常。代码里写着String.valueOf(address.getCode()),当getCode()返回null时,String.valueOf(null)会返回字符串"null"。下游系统用Integer.parseInt()解析时直接爆炸。传统工具要么漏报,要么规则被其他规则挤掉。

另一次是EDI项目的配置文件语法错误。一个XML里写了<case value="${orderType}">,应该用字面常量<case value="NORMAL">。EDI平台对配置文件要求极其严格,但大多数审查工具根本不知道这是个EDI项目,自然也不知道该用哪套规则。

这两个案例让我意识到:智能审查的核心,不是“懂语法”,而是“懂上下文”

二、架构设计

经过几天的调研和思考,我决定采用一种三阶段演进架构,从简单到复杂,逐步迭代。

2.1 阶段一:RAG+代码分块

第一个版本的目标是解决“上下文缺失”和“规则膨胀”问题。架构如下-1-9

[Git Diff] → [项目类型识别] → [代码分块] → [RAG检索] → [LLM评审] → [结果聚合]
                          ↓              ↑
                    [知识库] → [检索规则]

核心组件:

  • 项目类型识别器:根据文件扩展名(如.flow.dt)识别项目类型,决定用哪套评审规则。EDI项目用EDI规范,Java项目用Java规范。

  • 代码分块器:按文件和方法边界分割代码,保证语义完整性。单块Token数控制在60K以内,为模型输出预留40%空间-1

  • RAG检索器:基于代码变更内容,从知识库召回相关评审规则。

  • LLM评审器:将代码块和召回规则组合,生成行级评审意见。

  • 结果聚合器:合并多轮评审结果,去重、排序。

2.2 多智能体并行评审

Cursor的Bugbot团队给了我启发。他们在早期发现,并行运行多次检测并用多数投票合并结果,能大幅降低误报。于是我设计了第二个版本:

                  → [智能体1(随机顺序)] → 
[Git Diff] → 复制  → [智能体2(随机顺序)] → [结果聚合] → [验证器] → [去重] → [输出]
                  → [智能体3(随机顺序)] → 

每个智能体接收一个随机排序的代码变更,这会引导模型走不同的推理路径。当多个智能体独立标记出同一个问题时,就说明该缺陷真实存在的概率很高。

2.3 完全Agentic架构

去年秋天,Cursor把Bugbot切换到完全Agentic设计,取得了最大幅度的能力提升。我也打算朝这个方向演进:

[Git Diff] → [主智能体] → [规划任务]
                     ↓
            [工具调用循环]
        ├─ [读取文件] ─→ [分析依赖]
        ├─ [搜索历史] ─→ [理解上下文]
        ├─ [运行测试] ─→ [验证假设]
        └─ [提交修复] ─→ [生成报告]

智能体不再是“一次问答”,而是能自主决定在哪里深入探索,调用工具获取额外信息,直到对代码有足够理解才下结论。

三、代码落地

理论说够了,该动手了。下面是我用TypeScript实现的简化版本,核心模块逐一展开。

3.1 项目结构

code-review-agent/
├── src/
│   ├── index.ts                 # 入口
│   ├── detectors/
│   │   └── projectDetector.ts    # 项目类型识别
│   ├── chunkers/
│   │   └── codeChunker.ts        # 代码分块
│   ├── rag/
│   │   ├── knowledgeBase.ts      # 知识库管理
│   │   └── retriever.ts          # 规则检索
│   ├── reviewers/
│   │   ├── baseReviewer.ts       # 评审基类
│   │   ├── singleReviewer.ts     # 单智能体评审
│   │   └── multiReviewer.ts      # 多智能体并行评审
│   ├── validators/
│   │   └── qualityChecker.ts     # 质量检查
│   └── utils/
│       ├── gitParser.ts          # Git Diff解析
│       └── tokenCounter.ts       # Token计数
├── knowledge/                    # 知识库文件
│   ├── java-rules.md
│   ├── edi-rules.md
│   └── security-rules.md
└── package.json

3.2 核心代码实现

3.2.1 项目类型识别器
// detectors/projectDetector.ts
export interface ProjectType {
  name: string;        // 'java', 'edi', 'python', etc.
  priority: number;     // 优先级,越高越优先
  rules: string[];      // 适用的规则标签
}

export class ProjectDetector {
  private patterns: Map<RegExp, ProjectType> = new Map();
  
  constructor() {
    // EDI项目优先识别
    this.patterns.set(/\.(flow|dt)$/i, { 
      name: 'edi', 
      priority: 10,
      rules: ['edi-config', 'xml-schema']
    });
    
    this.patterns.set(/\.java$/i, { 
      name: 'java', 
      priority: 5,
      rules: ['java-best-practice', 'security-java']
    });
    
    this.patterns.set(/\.(py|python)$/i, { 
      name: 'python', 
      priority: 5,
      rules: ['python-style', 'security-python']
    });
  }
  
  detect(files: string[]): ProjectType[] {
    const projects = new Map<string, ProjectType>();
    
    for (const file of files) {
      for (const [pattern, type] of this.patterns) {
        if (pattern.test(file)) {
          // 保留优先级最高的
          const existing = projects.get(type.name);
          if (!existing || type.priority > existing.priority) {
            projects.set(type.name, type);
          }
        }
      }
    }
    
    return Array.from(projects.values())
      .sort((a, b) => b.priority - a.priority);
  }
}

京东云的经验表明,项目类型识别是后续RAG检索准确性的关键——在EDI项目中,必须优先使用EDI专用规则,而不是通用Java规则。

3.2.2 代码分块器

分块的核心原则:在方法或类的自然边界处分割,保持代码块的语义完整性

// chunkers/codeChunker.ts
export interface CodeChunk {
  file: string;
  startLine: number;
  endLine: number;
  content: string;
  tokenCount: number;
  type: 'class' | 'method' | 'block';
}

export class CodeChunker {
  private readonly MAX_TOKENS_PER_CHUNK = 60000; // 60K tokens
  private tokenCounter: TokenCounter;
  
  constructor() {
    this.tokenCounter = new TokenCounter();
  }
  
  async chunk(diffOutput: string): Promise<CodeChunk[]> {
    const chunks: CodeChunk[] = [];
    
    // 1. 按文件分割
    const filePattern = /diff --git a\/(.+?) b\/(.+?)\n/g;
    let fileMatch;
    
    while ((fileMatch = filePattern.exec(diffOutput)) !== null) {
      const filePath = fileMatch[1];
      const fileContent = this.extractFileContent(diffOutput, fileMatch.index);
      
      // 2. 对每个文件进行方法级分割
      const fileChunks = await this.chunkFile(filePath, fileContent);
      chunks.push(...fileChunks);
    }
    
    return chunks;
  }
  
  private async chunkFile(filePath: string, content: string): Promise<CodeChunk[]> {
    const chunks: CodeChunk[] = [];
    
    // 识别方法边界(简化版)
    const methodPattern = /(public|private|protected)?\s+\w+\s+\w+\s*\([^)]*\)\s*\{/g;
    let methodMatch;
    const methodStarts: number[] = [];
    
    while ((methodMatch = methodPattern.exec(content)) !== null) {
      methodStarts.push(methodMatch.index);
    }
    
    // 按方法分割
    for (let i = 0; i < methodStarts.length; i++) {
      const start = methodStarts[i];
      const end = (i < methodStarts.length - 1) 
        ? methodStarts[i + 1] 
        : content.length;
      
      const methodContent = content.substring(start, end);
      const tokenCount = await this.tokenCounter.count(methodContent);
      
      // 如果方法本身超过阈值,需要进一步分割
      if (tokenCount > this.MAX_TOKENS_PER_CHUNK) {
        const subChunks = await this.splitLargeMethod(filePath, methodContent);
        chunks.push(...subChunks);
      } else {
        // 计算行号(简化)
        const startLine = content.substr(0, start).split('\n').length;
        const endLine = content.substr(0, end).split('\n').length;
        
        chunks.push({
          file: filePath,
          startLine,
          endLine,
          content: methodContent,
          tokenCount,
          type: 'method'
        });
      }
    }
    
    return chunks;
  }
}

这个实现借鉴了京东云的智能分块策略:先用Git Diff识别文件边界,再用方法签名识别代码结构边界,确保单个文件的代码完整性-1

3.2.3 RAG检索器

RAG检索的关键是:基于代码变更内容,召回最相关的评审规则

// rag/retriever.ts
export interface ReviewRule {
  id: string;
  tags: string[];
  content: string;
  priority: number;
  languages: string[];
}

export class RuleRetriever {
  private knowledgeBase: KnowledgeBase;
  private embeddingService: EmbeddingService;
  
  constructor() {
    this.knowledgeBase = new KnowledgeBase();
    this.embeddingService = new EmbeddingService();
  }
  
  async retrieve(codeChunk: CodeChunk, projectTypes: ProjectType[]): Promise<ReviewRule[]> {
    // 1. 基于项目类型过滤规则
    const applicableTags = projectTypes.flatMap(pt => pt.rules);
    let rules = await this.knowledgeBase.getRulesByTags(applicableTags);
    
    // 2. 基于代码内容生成查询向量
    const queryEmbedding = await this.embeddingService.embed(codeChunk.content);
    
    // 3. 计算相似度并排序
    const withScores = await Promise.all(
      rules.map(async rule => {
        const ruleEmbedding = await this.embeddingService.embed(rule.content);
        const similarity = this.cosineSimilarity(queryEmbedding, ruleEmbedding);
        return { rule, score: similarity };
      })
    );
    
    // 4. 返回最相关的5条规则
    return withScores
      .sort((a, b) => b.score - a.score)
      .slice(0, 5)
      .map(item => item.rule);
  }
  
  private cosineSimilarity(a: number[], b: number[]): number {
    // 简化实现
    const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
    const magA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
    const magB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
    return dotProduct / (magA * magB);
  }
}

Atlassian的RovoDev采用了类似的“零样本上下文感知”设计,无需微调即可生成高质量的评审意见。

3.2.4 多智能体并行评审

这是降低误报的核心技巧:

// reviewers/multiReviewer.ts
export interface ReviewFinding {
  file: string;
  line: number;
  severity: 'high' | 'medium' | 'low';
  title: string;
  description: string;
  suggestion: string;
  ruleId?: string;
}

export class MultiAgentReviewer {
  private numAgents: number = 8; // 8路并行
  private llmService: LLMService;
  
  async review(chunks: CodeChunk[], rules: ReviewRule[]): Promise<ReviewFinding[]> {
    // 1. 创建多个评审任务,每个任务使用不同的随机种子
    const tasks = [];
    for (let i = 0; i < this.numAgents; i++) {
      tasks.push(this.reviewSingle(chunks, rules, i));
    }
    
    // 2. 并行执行
    const allFindings = await Promise.all(tasks);
    
    // 3. 合并相似发现(按位置和内容聚类)
    const clustered = this.clusterFindings(allFindings.flat());
    
    // 4. 多数投票过滤(只在至少3个智能体中发现的问题才保留)
    const validated = clustered.filter(cluster => cluster.agentCount >= 3);
    
    // 5. 去重并生成最终报告
    return this.deduplicate(validated);
  }
  
  private async reviewSingle(
    chunks: CodeChunk[], 
    rules: ReviewRule[],
    seed: number
  ): Promise<ReviewFinding[]> {
    // 随机打乱chunks顺序,让模型走不同的推理路径
    const shuffled = this.shuffleArray([...chunks], seed);
    
    const prompt = this.buildPrompt(shuffled, rules);
    const response = await this.llmService.complete(prompt, {
      temperature: 0.3,
      seed: seed // 固定种子确保可复现
    });
    
    return this.parseResponse(response);
  }
  
  private clusterFindings(findings: ReviewFinding[]): Array<ReviewFinding & { agentCount: number }> {
    // 简化聚类:按文件和行号分组
    const groups = new Map<string, ReviewFinding[]>();
    
    for (const f of findings) {
      const key = `${f.file}:${f.line}`;
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push(f);
    }
    
    const result = [];
    for (const [_, group] of groups) {
      if (group.length > 0) {
        // 取第一个作为代表
        result.push({
          ...group[0],
          agentCount: group.length
        });
      }
    }
    
    return result;
  }
}
Cursor的实践表明,这种“随机化+多数投票”的方法,在早期迭代中是最有效的质量改进措施之一。
3.2.5 质量检查器

生成评审意见后,还需要过滤掉那些不准确或无价值的建议。

// validators/qualityChecker.ts
export class QualityChecker {
  private validationModel: LLMService;
  
  async validate(findings: ReviewFinding[]): Promise<ReviewFinding[]> {
    const validFindings = [];
    
    for (const finding of findings) {
      // 1. 检查事实准确性
      const isFactual = await this.checkFactualAccuracy(finding);
      if (!isFactual) continue;
      
      // 2. 检查可操作性
      const isActionable = await this.checkActionability(finding);
      if (!isActionable) continue;
      
      validFindings.push(finding);
    }
    
    return validFindings;
  }
  
  private async checkFactualAccuracy(finding: ReviewFinding): Promise<boolean> {
    const prompt = `
      请判断以下代码评审意见是否基于事实(即,它指出的问题确实存在):
      
      文件:${finding.file}
      行号:${finding.line}
      问题:${finding.title}
      描述:${finding.description}
      建议:${finding.suggestion}
      
      请只返回 "true" 或 "false"。
    `;
    
    const response = await this.validationModel.complete(prompt, { temperature: 0 });
    return response.trim().toLowerCase() === 'true';
  }
  
  private async checkActionability(finding: ReviewFinding): Promise<boolean> {
    // 检查建议是否具体可执行
    const hasSuggestion = finding.suggestion && finding.suggestion.length > 20;
    const isVague = this.isVagueLanguage(finding.description);
    
    return hasSuggestion && !isVague;
  }
  
  private isVagueLanguage(text: string): boolean {
    const vaguePatterns = [
      /可以考虑/i,
      /建议优化/i,
      /最好/i,
      /可能有问题/i,
      /不太规范/i
    ];
    
    return vaguePatterns.some(pattern => pattern.test(text));
  }
}

Atlassian的RovoDev专门设计了“质量检查”组件,用于过滤掉无关、不准确或无意义的评论-9。IBM Bob也强调,高质量的评审应该包含具体的修复建议-3

3.3 入口文件

// index.ts
export class CodeReviewAgent {
  private detector: ProjectDetector;
  private chunker: CodeChunker;
  private retriever: RuleRetriever;
  private reviewer: MultiAgentReviewer;
  private validator: QualityChecker;
  
  constructor() {
    this.detector = new ProjectDetector();
    this.chunker = new CodeChunker();
    this.retriever = new RuleRetriever();
    this.reviewer = new MultiAgentReviewer();
    this.validator = new QualityChecker();
  }
  
  async reviewPullRequest(prData: {
    files: string[];
    diff: string;
  }): Promise<ReviewFinding[]> {
    // 1. 识别项目类型
    const projectTypes = this.detector.detect(prData.files);
    console.log(`检测到项目类型: ${projectTypes.map(p => p.name).join(', ')}`);
    
    // 2. 代码分块
    const chunks = await this.chunker.chunk(prData.diff);
    console.log(`生成 ${chunks.length} 个代码块`);
    
    // 3. 并行评审
    const allFindings = [];
    for (const chunk of chunks) {
      // 为每个代码块检索相关规则
      const rules = await this.retriever.retrieve(chunk, projectTypes);
      
      // 评审该代码块
      const chunkFindings = await this.reviewer.review([chunk], rules);
      allFindings.push(...chunkFindings);
    }
    
    // 4. 质量检查
    const validFindings = await this.validator.validate(allFindings);
    
    // 5. 按严重程度排序
    const sorted = validFindings.sort((a, b) => {
      const priority = { high: 3, medium: 2, low: 1 };
      return priority[b.severity] - priority[a.severity];
    });
    
    return sorted;
  }
}

// 使用示例
async function main() {
  const agent = new CodeReviewAgent();
  
  const findings = await agent.reviewPullRequest({
    files: ['OrderService.java', 'edi-flow.flow'],
    diff: `diff --git a/OrderService.java b/OrderService.java
index 1234567..890abcd 100644
--- a/OrderService.java
+++ b/OrderService.java
@@ -42,7 +42,7 @@
 public Order createOrder(OrderRequest request) {
   Order order = new Order();
   order.setUserId(request.getUserId());
-  order.setAmount(request.getAmount());
+  order.setAmount(String.valueOf(request.getAmount()));
   return orderRepository.save(order);
 }`
  });
  
  console.log(JSON.stringify(findings, null, 2));
}

四、效果与演进

4.1 初步效果

在我的本地测试中,这个简化版的智能审查助手已经能发现一些有价值的问题。比如上面那个String.valueOf(null)的例子,它给出了这样的评审意见:

{
  "file": "OrderService.java",
  "line": 45,
  "severity": "high",
  "title": "空值处理可能导致 'null' 字符串",
  "description": "当 request.getAmount() 返回 null 时,String.valueOf(null) 会返回字符串 'null',而不是空字符串或抛出异常。如果下游需要解析为数字,将导致 NumberFormatException。",
  "suggestion": "使用 Objects.toString(request.getAmount(), null) 并显式处理 null 情况,或改用 request.getAmount() != null ? String.valueOf(request.getAmount()) : null",
  "ruleId": "java-null-handling"
}

4.2 演进方向

下一步,我计划加入以下能力:

  1. 工具调用:让智能体能自动运行测试、查看依赖、搜索历史PR,获取更多上下文。

  2. 自动修复:Cursor已经在测试Bugbot Autofix,它会自动启动一个Cloud Agent来修复PR中发现的缺陷。

  3. 持续扫描:不是等到PR才触发,而是持续扫描整个代码库,主动发现技术债务-5

  4. 解决率指标:像Cursor那样,用“解决率”来衡量真实效果——即最终被开发者采纳的评审意见比例。

Atlassian的RovoDev已经做到38.70%的代码解决率,将PR周期时间缩短30.8%,减少35.6%的人工评论。这是一个值得追赶的目标。

尾声

这是我的所有思路,搭起一个能用的智能审查助手,最大的感受不是技术的复杂,而是思路的转变:我们不再是在“写程序”,而是在“训练同事”

这个“同事”不懂业务,但懂规范;不会偷懒,但会误判;需要引导,但永不知疲倦。它不会取代我们,但能帮我们省下大量重复劳动,让我们能专注于那些真正需要人类智慧的事情——架构设计、业务理解、技术创新。

GitHub前CEO托马斯·多姆克说:“未来需要更多智能体。” Qodo的CEO说:“如果无法信任你发布的内容,AI的速度就毫无意义。”信任,需要一步步建立。而建立信任的第一步,就是从零开始,亲手打造一个值得信赖的智能助手。

下周,等我实现后,我打算把这个助手集成到团队的CI流水线里,让它开始真正干活。到时候再和大家分享实战效果。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐