文老师课堂-春节特别版:如何搭建一个智能代码审查助手
摘要:随着AI生成代码比例激增,代码审查成为新瓶颈。GitHub前CEO指出"发布代码的瓶颈是审查AI代码"。文章提出构建智能代码审查助手的三阶段方案:1)基础版采用RAG+代码分块架构,解决上下文缺失问题;2)进阶版引入多智能体并行评审降低误报;3)最终实现完全Agentic架构。核心在于让AI理解代码上下文而非仅语法,通过项目类型识别、语义分块、规则检索等技术,使审查助手能
做智能体当然是为了省事,跟程序员息息相关的有哪些事情呢?现在有一个现实问题,随着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 演进方向
下一步,我计划加入以下能力:
-
工具调用:让智能体能自动运行测试、查看依赖、搜索历史PR,获取更多上下文。
-
自动修复:Cursor已经在测试Bugbot Autofix,它会自动启动一个Cloud Agent来修复PR中发现的缺陷。
-
持续扫描:不是等到PR才触发,而是持续扫描整个代码库,主动发现技术债务-5。
-
解决率指标:像Cursor那样,用“解决率”来衡量真实效果——即最终被开发者采纳的评审意见比例。
Atlassian的RovoDev已经做到38.70%的代码解决率,将PR周期时间缩短30.8%,减少35.6%的人工评论。这是一个值得追赶的目标。
尾声
这是我的所有思路,搭起一个能用的智能审查助手,最大的感受不是技术的复杂,而是思路的转变:我们不再是在“写程序”,而是在“训练同事”。
这个“同事”不懂业务,但懂规范;不会偷懒,但会误判;需要引导,但永不知疲倦。它不会取代我们,但能帮我们省下大量重复劳动,让我们能专注于那些真正需要人类智慧的事情——架构设计、业务理解、技术创新。
GitHub前CEO托马斯·多姆克说:“未来需要更多智能体。” Qodo的CEO说:“如果无法信任你发布的内容,AI的速度就毫无意义。”信任,需要一步步建立。而建立信任的第一步,就是从零开始,亲手打造一个值得信赖的智能助手。
下周,等我实现后,我打算把这个助手集成到团队的CI流水线里,让它开始真正干活。到时候再和大家分享实战效果。
更多推荐


所有评论(0)