各位下午好。在日常的软件开发流程里,代码审查一直是保障工程质量的核心环节。但说实话,传统的人工 Review 方式,既耗费资深工程师大量时间,又容易出现“盯了半天没发现问题,合并之后线上炸了”的尴尬状况。
所以当看到 Uber 分享的 uReview 多智能体代码审查引擎相关思路时,我的第一反应是:终于有大厂把 AI Agent 往这个深水区推进了。这篇内容不是去拆解 Uber 内部某个具体版本的功能清单,而是从工程落地角度,完整拆解一个“多智能体(Multi-Agent)代码审查系统”该怎么设计、怎么实现、怎么避坑。
不管你是后端开发、平台工程师,还是对 AI Engineer 方向感兴趣的同学,这篇文章都能给你一套可执行的架构蓝图和代码骨架。
1. 背景与核心概念
1.1 为什么传统代码审查需要被重构
先讲一个实际场景。假设一个中大型项目,每次 Pull Request 平均改动 300 到 500 行代码。人工审查时,Reviewer 需要关注:
- 业务逻辑是否正确;
- 是否有潜在的并发安全问题;
- 异常处理是否覆盖了边界条件;
- 新引入的依赖是否有漏洞;
- 代码风格是否与团队规范一致;
- 数据库索引和查询是否存在性能隐患。
这么多检查维度,靠人肉逐一核对,效率极低。更麻烦的是,不同背景的 Reviewer 关注点不一样,有人擅长业务、有人擅长性能,很难有一个“全能型”审查者。
而 uReview 这类系统的核心思路,就是把代码审查拆分成多个独立任务,分配给不同的智能体(Agent)并行负责。每个 Agent 只关注一个维度,最后由一个聚合模块统一汇总、过滤、排序,生成一份高质量且可执行的审查意见。
1.2 什么是多智能体系统
多智能体系统(Multi-Agent System,MAS)是人工智能领域的一个重要方向。通俗理解,就是让多个具备独立推理能力的 AI Agent 协同工作,像一支分工明确的团队一样完成复杂任务。
和单智能体模式相比,多智能体模式有四个明显优势:
- 专业化分工:每个 Agent 专注一个方向,提示词(Prompt)可以设计得更加精准;
- 并行计算:多个独立的审查任务可以并发执行,缩短整体耗时;
- 降低幻觉概率:每个 Agent 只处理它擅长的问题,输出质量更高;
- 流程可编排:可以像流水线一样编排 Agent 的执行顺序和依赖关系。
放到代码审查场景里,就可以设计成:安全审查 Agent、性能审查 Agent、并发正确性审查 Agent、代码风格审查 Agent、依赖漏洞审查 Agent,最后再来一个 Review 汇总 Agent。
1.3 uReview 类系统在整个研发流程中的位置
uReview 并不是要取代人工 Reviewer,而是把机器擅长的“确定性检查”和“模式识别”做到极致,把人类从重复劳动中解放出来,让工程师把精力集中在架构设计、业务合理性、可维护性这些真正需要人类判断的问题上。
它在研发流程中的位置大致如下:
代码提交 → CI 触发 → 拉取 Diff 数据 → 多智能体并行审查 → 汇总生成审查报告 → 回写到 PR/MR 评论区 → 工程师处理意见从 AI Engineer 的角度看,这本质上是一个 RAG + Agent + 工作流编排的综合工程问题。
2. 多智能体代码审查引擎的整体架构
在设计一个多智能体代码审查引擎时,我的习惯是先画清楚架构,再写代码。为了避免依赖具体的内部实现,这篇文章做的是通用化的架构设计,你可以直接套用到自己的项目里。
架构上分为五层,分别是:输入接入层、代码分析层、智能体编排层、上下文管理层、反馈输出层。
2.1 输入接入层
负责对接不同的代码托管平台,比如 GitHub、GitLab、Gitee,以及企业内部自建的代码仓库。
核心任务是:
- 监听合并请求(Merge Request / Pull Request)事件;
- 拉取变更文件列表;
- 获取每个文件的 Diff 内容;
- 获取提交信息、评论信息等元数据。
这一层更推荐使用 Webhook 加消息队列的方式实现。Webhook 负责事件通知,消息队列负责削峰填谷,避免代码提交高峰期把下游服务冲垮。
# 以 GitLab Webhook 为例,推送触发地址 POST http://your-review-system/api/webhook/gitlab2.2 代码分析层
这一层不是直接用大模型去读全部代码,而是先用传统静态分析工具把代码做一轮预处理。
主要工作包括:
- 解析改动文件的语言类型;
- 提取变更的函数、类、方法;
- 识别新增的依赖项;
- 扫描明显的基础问题,比如语法错误、未使用变量、明显的空指针风险。
为什么要先做这一层?原因很简单:大模型有上下文窗口限制,也不可能真的编译代码。如果能把代码结构提前解析成结构化数据,再喂给 Agent,效果会稳定很多。
常用的静态分析工具有 ESLint(JavaScript/TypeScript)、Pylint(Python)、SpotBugs(Java)、SonarQube 等。
2.3 智能体编排层
这是整个系统的核心。
编排层会启动多个细分的审查 Agent,例如:
| Agent 名称 | 审查方向 | 关注点 |
|---|---|---|
| 安全审查 Agent | 安全性 | SQL 注入、XSS、硬编码密钥、越权访问 |
| 并发审查 Agent | 并发与线程安全 | 共享变量、锁的用法、线程池配置 |
| 性能审查 Agent | 性能 | 循环内查询数据库、大对象复制、索引使用 |
| 异常处理审查 Agent | 健壮性 | 吞异常、空指针、边界条件缺失 |
| 代码风格审查 Agent | 可读性 | 命名、函数长度、重复代码 |
| 依赖审查 Agent | 供应链安全 | 新引入依赖的版本漏洞、许可证问题 |
每个 Agent 可以基于不同的 Prompt 模板运行,也可以针对不同语言配置不同的审查规则。
2.4 上下文管理层
大模型上下文窗口资源非常宝贵,不可能把所有代码都塞进去。
所以需要做上下文裁剪与检索增强:
- 只提取变更函数的前后 20 行代码作为上下文;
- 按语义相似度检索仓库内相近实现,作为参考;
- 把团队自定义规范向量化存储,按需注入;
- 对超过窗口的内容,采用分块摘要后再聚合。
2.5 反馈输出层
各 Agent 执行完毕后,返回的是结构化结果。需要一个汇总 Agent 做二次加工:
- 合并重复问题;
- 按严重程度排序;
- 自动生成修改建议;
- 过滤低价值评论;
- 以评论或报告形式回写到 PR 页面。
3. 环境准备与项目结构说明
3.1 技术选型
本文示例采用以下技术组合来演示一个最小可运行的多智能体代码审查引擎:
| 组件 | 选型 | 说明 |
|---|---|---|
| 开发语言 | Java 17 | 使用 Spring Boot 3.x 搭建服务 |
| AI 模型接入 | OpenAI 兼容接口 | 通过 HTTP 调用,不绑定具体厂商 |
| 编排框架 | Spring AI / 自研编排 | 用代码手动编排多智能体协作流程 |
| 消息队列 | 先用内存队列 | 演示阶段不做复杂依赖 |
| 存储 | H2 内存数据库 | 存储任务记录与审查结果 |
| 构建工具 | Maven | 统一依赖管理 |
实际生产项目中,这套架构可以很方便地替换为更专业的组件,比如 LangChain4j、Ray、Temporal,或者公司内部自研的 Agent 编排平台。
3.2 示例项目目录结构
先创建一个 Maven 工程,目录结构如下:
ureview-demo/ ├── pom.xml ├── src/main/java/com/example/ureview/ │ ├── UReviewApplication.java │ ├── controller/ │ │ └── ReviewController.java │ ├── model/ │ │ ├── ReviewRequest.java │ │ ├── DiffFile.java │ │ ├── AgentResult.java │ │ └── ReviewReport.java │ ├── agent/ │ │ ├── AgentExecutor.java │ │ ├── SecurityReviewAgent.java │ │ ├── PerformanceReviewAgent.java │ │ ├── ConcurrencyReviewAgent.java │ │ └── SummaryAgent.java │ ├── service/ │ │ ├── DiffParserService.java │ │ └── ReviewOrchestrator.java │ └── config/ │ └── LlmConfig.java └── src/main/resources/ └── application.yml4. 完整实战:从零构建一个多智能体代码审查服务
4.1 创建 Maven 工程并添加依赖
在pom.xml中添加核心依赖:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.0</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>ureview-demo</artifactId> <version>1.0.0-SNAPSHOT</version> <name>ureview-demo</name> <description>Multi-Agent Code Review Engine Demo</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>这里需要说明一点:为了避免绑定某个具体大模型厂商,我在示例中用 OkHttp 直接调用兼容 OpenAI 协议的 HTTP 接口。你可以自行配置 base-url 和 api-key,指向本地部署的模型服务,或者任何兼容的服务。
4.2 编写核心配置类
在application.yml中配置模型服务和线程池参数:
server: port: 8080 llm: base-url: https://your-llm-service.example.com/v1 api-key: ${LLM_API_KEY:sk-demo} model: deepseek-chat temperature: 0.2 max-tokens: 2000 review: thread-pool-size: 4 max-diff-size: 50000注意,LLM_API_KEY使用了环境变量注入,这是生产环境的标准做法,密钥不允许硬编码在配置文件里。
4.3 定义数据模型
首先定义统一的审查请求对象:
// 文件路径:src/main/java/com/example/ureview/model/ReviewRequest.java package com.example.ureview.model; import lombok.Data; import java.util.List; @Data public class ReviewRequest { private String repoName; private String branchName; private String commitId; private List<DiffFile> diffFiles; }定义变更文件对象:
// 文件路径:src/main/java/com/example/ureview/model/DiffFile.java package com.example.ureview.model; import lombok.Data; @Data public class DiffFile { private String filePath; private String changeType; private String patch; private String language; }定义 Agent 的审查结果:
// 文件路径:src/main/java/com/example/ureview/model/AgentResult.java package com.example.ureview.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class AgentResult { private String agentName; private List<String> issues; }定义最终审查报告:
// 文件路径:src/main/java/com/example/ureview/model/ReviewReport.java package com.example.ureview.model; import lombok.Data; import java.util.List; @Data public class ReviewReport { private String repoName; private String commitId; private String summary; private List<AgentResult> details; }4.4 实现 LLM 调用封装
这一层负责与大模型交互,是所有 Agent 的基础能力。
// 文件路径:src/main/java/com/example/ureview/config/LlmConfig.java package com.example.ureview.config; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import okhttp3.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.concurrent.TimeUnit; @Component public class LlmConfig { private final OkHttpClient httpClient; private final ObjectMapper objectMapper; @Value("${llm.base-url}") private String baseUrl; @Value("${llm.api-key}") private String apiKey; @Value("${llm.model}") private String model; @Value("${llm.temperature}") private double temperature; public LlmConfig() { this.httpClient = new OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .build(); this.objectMapper = new ObjectMapper(); } /** * 调用兼容 OpenAI 协议的接口 */ public String chatCompletion(String systemPrompt, String userPrompt) throws IOException { ObjectNode requestBody = objectMapper.createObjectNode(); requestBody.put("model", model); requestBody.put("temperature", temperature); ArrayNode messages = requestBody.putArray("messages"); ObjectNode systemMessage = messages.addObject(); systemMessage.put("role", "system"); systemMessage.put("content", systemPrompt); ObjectNode userMessage = messages.addObject(); userMessage.put("role", "user"); userMessage.put("content", userPrompt); Request request = new Request.Builder() .url(baseUrl + "/chat/completions") .addHeader("Authorization", "Bearer " + apiKey) .addHeader("Content-Type", "application/json") .post(RequestBody.create( requestBody.toString(), MediaType.parse("application/json; charset=utf-8") )) .build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("LLM API error: " + response.code() + " " + response.body().string()); } String responseBody = response.body().string(); JsonNode root = objectMapper.readTree(responseBody); return root.path("choices").path(0).path("message").path("content").asText(); } } }这段代码的核心逻辑是:构造 OpenAI 兼容的 Chat Completion 请求,把 System Prompt 和 User Prompt 发给模型,然后返回模型生成的文本。所有 Agent 都会复用这个方法。
4.5 实现 Diff 解析服务
真实场景中,代码托管平台返回的 Diff 格式是需要解析的。本文做一个简化版,把 patch 内容按文件整理成可读文本。
// 文件路径:src/main/java/com/example/ureview/service/DiffParserService.java package com.example.ureview.service; import com.example.ureview.model.DiffFile; import org.springframework.stereotype.Service; import java.util.List; import java.util.stream.Collectors; @Service public class DiffParserService { /** * 将 Diff 文件列表转换为可供 Agent 阅读的格式化文本 */ public String toAgentInput(List<DiffFile> diffFiles) { return diffFiles.stream() .map(this::formatSingleFile) .collect(Collectors.joining("\n\n")); } private String formatSingleFile(DiffFile diffFile) { StringBuilder sb = new StringBuilder(); sb.append("### 文件: ").append(diffFile.getFilePath()).append("\n"); sb.append("变更类型: ").append(diffFile.getChangeType()).append("\n"); sb.append("```").append(diffFile.getLanguage() == null ? "" : diffFile.getLanguage()).append("\n"); sb.append(diffFile.getPatch()).append("\n"); sb.append("```\n"); return sb.toString(); } }4.6 实现 Agent 执行器
这是多智能体最核心的部分。先定义一个抽象执行器,子类只需要提供各自的 System Prompt。
// 文件路径:src/main/java/com/example/ureview/agent/AgentExecutor.java package com.example.ureview.agent; import com.example.ureview.config.LlmConfig; import com.example.ureview.model.AgentResult; import com.example.ureview.model.DiffFile; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; /** * 所有 Agent 的抽象基类。 * 子类只需要指定 agentType 和对应的 systemPrompt。 */ public abstract class AgentExecutor { protected final LlmConfig llmConfig; protected AgentExecutor(LlmConfig llmConfig) { this.llmConfig = llmConfig; } public abstract String agentType(); protected abstract String systemPrompt(); protected abstract String userPromptTemplate(); public AgentResult execute(List<DiffFile> diffFiles) throws Exception { String diffContent = diffFiles.stream() .map(this::formatDiff) .collect(Collectors.joining("\n")); String userPrompt = String.format(userPromptTemplate(), diffContent); String modelOutput = llmConfig.chatCompletion(systemPrompt(), userPrompt); // 解析模型输出为问题列表。每行一个 issue,以 - 开头。 List<String> issues = Arrays.stream(modelOutput.split("\n")) .map(String::trim) .filter(line -> line.startsWith("- ")) .map(line -> line.substring(2).trim()) .collect(Collectors.toList()); return new AgentResult(agentType(), issues); } private String formatDiff(DiffFile diffFile) { return "文件: " + diffFile.getFilePath() + "\n" + diffFile.getPatch(); } }这里的关键设计是让模型以固定格式输出,也就是每行一个以-开头的问题。这种结构化输出方式,比让模型自由发挥可靠得多。
4.7 实现核心审查 Agent
安全审查 Agent
// 文件路径:src/main/java/com/example/ureview/agent/SecurityReviewAgent.java package com.example.ureview.agent; import com.example.ureview.config.LlmConfig; import org.springframework.stereotype.Component; @Component public class SecurityReviewAgent extends AgentExecutor { public SecurityReviewAgent(LlmConfig llmConfig) { super(llmConfig); } @Override public String agentType() { return "SecurityReviewAgent"; } @Override protected String systemPrompt() { return "你是一名资深的安全工程师,专注于代码安全审查。" + "你擅长发现 SQL 注入、跨站脚本攻击(XSS)、硬编码密钥、不安全的反序列化、越权访问等安全问题。" + "请基于给定的代码变更内容进行分析。"; } @Override protected String userPromptTemplate() { return "请审查以下代码变更,找出所有可能的安全问题。\n" + "要求:\n" + "1. 只输出你发现的问题,不输出没有问题的内容。\n" + "2. 每个问题单独一行,以 \\\"- \\\" 开头。\n" + "3. 格式:- [严重级别: 高/中/低] 问题描述\n\n" + "代码变更内容:\n%s"; } }性能审查 Agent
// 文件路径:src/main/java/com/example/ureview/agent/PerformanceReviewAgent.java package com.example.ureview.agent; import com.example.ureview.config.LlmConfig; import org.springframework.stereotype.Component; @Component public class PerformanceReviewAgent extends AgentExecutor { public PerformanceReviewAgent(LlmConfig llmConfig) { super(llmConfig); } @Override public String agentType() { return "PerformanceReviewAgent"; } @Override protected String systemPrompt() { return "你是一名资深的性能优化专家。" + "你擅长发现循环内查询数据库、N+1 查询问题、不必要的对象创建、大对象复制、缺少索引等性能隐患。" + "请基于给定的代码变更内容进行分析。"; } @Override protected String userPromptTemplate() { return "请审查以下代码变更,找出所有可能的性能问题。\n" + "要求:\n" + "1. 只输出你发现的问题,不输出没有问题的内容。\n" + "2. 每个问题单独一行,以 \\\"- \\\" 开头。\n" + "3. 格式:- [严重级别: 高/中/低] 问题描述\n\n" + "代码变更内容:\n%s"; } }并发安全审查 Agent
// 文件路径:src/main/java/com/example/ureview/agent/ConcurrencyReviewAgent.java package com.example.ureview.agent; import com.example.ureview.config.LlmConfig; import org.springframework.stereotype.Component; @Component public class ConcurrencyReviewAgent extends AgentExecutor { public ConcurrencyReviewAgent(LlmConfig llmConfig) { super(llmConfig); } @Override public String agentType() { return "ConcurrencyReviewAgent"; } @Override protected String systemPrompt() { return "你是一名精通 Java 并发编程的专家。" + "你擅长识别线程安全问题,例如共享可变状态、缺少同步、锁顺序不一致、线程池使用不当、死锁风险等。" + "请基于给定的代码变更内容进行分析。"; } @Override protected String userPromptTemplate() { return "请审查以下代码变更,找出所有可能的并发或线程安全问题。\n" + "要求:\n" + "1. 只输出你发现的问题,不输出没有问题的内容。\n" + "2. 每个问题单独一行,以 \\\"- \\\" 开头。\n" + "3. 格式:- [严重级别: 高/中/低] 问题描述\n\n" + "代码变更内容:\n%s"; } }4.8 实现汇总 Agent
汇总 Agent 不直接审查代码,而是对多个 Agent 的结果做合并和排序。
// 文件路径:src/main/java/com/example/ureview/agent/SummaryAgent.java package com.example.ureview.agent; import com.example.ureview.config.LlmConfig; import com.example.ureview.model.AgentResult; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component public class SummaryAgent { private final LlmConfig llmConfig; public SummaryAgent(LlmConfig llmConfig) { this.llmConfig = llmConfig; } /** * 汇总多个 Agent 的结果,生成最终审查报告摘要 */ public String summarize(String repoName, List<AgentResult> agentResults) throws Exception { String systemPrompt = "你是一名高级代码审查协调者。你的职责是汇总多名专家 Agent 的审查结果," + "生成一份清晰、可执行的最终审查报告。"; String agentOutput = agentResults.stream() .map(result -> "### " + result.getAgentName() + "\n" + result.getIssues().stream() .map(issue -> "- " + issue) .collect(Collectors.joining("\n"))) .collect(Collectors.joining("\n\n")); String userPrompt = "以下是多个审查 Agent 对仓库 " + repoName + " 代码变更的审查结果:\n\n" + agentOutput + "\n\n请生成最终审查报告,包括:\n" + "1. 按严重程度从高到低排列问题。\n" + "2. 去掉重复问题。\n" + "3. 提出修改建议。\n" + "4. 以 Markdown 格式输出。"; return llmConfig.chatCompletion(systemPrompt, userPrompt); } }4.9 实现核心编排服务
这是整个系统的执行中枢。编排器的职责是获取所有 Agent,按配置并行执行,最后统一汇总。
// 文件路径:src/main/java/com/example/ureview/service/ReviewOrchestrator.java package com.example.ureview.service; import com.example.ureview.agent.AgentExecutor; import com.example.ureview.agent.SummaryAgent; import com.example.ureview.model.AgentResult; import com.example.ureview.model.DiffFile; import com.example.ureview.model.ReviewReport; import com.example.ureview.model.ReviewRequest; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.*; @Service public class ReviewOrchestrator { private final DiffParserService diffParserService; private final List<AgentExecutor> agentExecutors; private final SummaryAgent summaryAgent; private final ExecutorService executorService; @Value("${review.max-diff-size}") private int maxDiffSize; public ReviewOrchestrator( DiffParserService diffParserService, List<AgentExecutor> agentExecutors, SummaryAgent summaryAgent, @Value("${review.thread-pool-size}") int threadPoolSize) { this.diffParserService = diffParserService; this.agentExecutors = agentExecutors; this.summaryAgent = summaryAgent; this.executorService = Executors.newFixedThreadPool(threadPoolSize); } /** * 编排多智能体审查流程 */ public ReviewReport runReview(ReviewRequest request) throws InterruptedException { List<DiffFile> diffFiles = filterLargeFiles(request.getDiffFiles()); // 第一步:并行启动所有 Agent List<Future<AgentResult>> futures = new ArrayList<>(); for (AgentExecutor agent : agentExecutors) { Future<AgentResult> future = executorService.submit( () -> agent.execute(diffFiles) ); futures.add(future); } // 第二步:收集所有 Agent 的结果 List<AgentResult> agentResults = new ArrayList<>(); for (Future<AgentResult> future : futures) { try { agentResults.add(future.get(120, TimeUnit.SECONDS)); } catch (ExecutionException e) { agentResults.add(new AgentResult( "ErrorAgent", Collections.singletonList("Agent 执行失败: " + e.getCause().getMessage()) )); } catch (TimeoutException e) { agentResults.add(new AgentResult( "ErrorAgent", Collections.singletonList("Agent 执行超时,已跳过。") )); } } // 第三步:调用汇总 Agent String summary = ""; try { summary = summaryAgent.summarize(request.getRepoName(), agentResults); } catch (Exception e) { summary = "摘要生成失败,请人工查看详细结果。原因: " + e.getMessage(); } ReviewReport report = new ReviewReport(); report.setRepoName(request.getRepoName()); report.setCommitId(request.getCommitId()); report.setSummary(summary); report.setDetails(agentResults); return report; } private List<DiffFile> filterLargeFiles(List<DiffFile> diffFiles) { return diffFiles.stream() .filter(file -> file.getPatch() == null || file.getPatch().length() <= maxDiffSize) .toList(); } }这段代码体现了多智能体编排的核心要点:
- 通过
List<AgentExecutor>自动注入所有 Agent 实例,后续新增 Agent 时不需要修改编排器代码; - 采用线程池并发执行,避免串行耗时;
- 对异常和超时做了兜底,防止单个 Agent 故障拖垮整个审查流程;
- 对超大文件做了过滤,防止上下文溢出。
4.10 编写 Web 控制器
最后提供一个 HTTP 入口,方便测试调用。
// 文件路径:src/main/java/com/example/ureview/controller/ReviewController.java package com.example.ureview.controller; import com.example.ureview.model.ReviewReport; import com.example.ureview.model.ReviewRequest; import com.example.ureview.service.ReviewOrchestrator; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/review") public class ReviewController { private final ReviewOrchestrator orchestrator; public ReviewController(ReviewOrchestrator orchestrator) { this.orchestrator = orchestrator; } @PostMapping("/run") public ReviewReport runReview(@RequestBody ReviewRequest request) throws InterruptedException { return orchestrator.runReview(request); } @GetMapping("/health") public String health() { return "uReview demo is running."; } }4.11 运行与验证
启动应用:
mvn spring-boot:run然后在另一个终端窗口,模拟一次审查请求:
curl -X POST http://localhost:8080/api/review/run \ -H "Content-Type: application/json" \ -d '{ "repoName": "demo-project", "branchName": "feature/user-login", "commitId": "a1b2c3d4", "diffFiles": [ { "filePath": "src/main/java/com/demo/UserService.java", "changeType": "modified", "language": "java", "patch": "@@ -10,15 +10,18 @@\n public class UserService {\n public User login(String username, String password) {\n+ String sql = \"SELECT * FROM users WHERE name = '\" + username + \"' AND password = '\" + password + \"'\";\n+ return jdbcTemplate.query(sql, userRowMapper);\n }\n }" } ] }'如果配置正确,模型会返回安全 Agent 发现的 SQL 注入问题,以及其他 Agent 的分析结果。
这里要强调一个很重要的现实:不同模型对复杂推理任务的表现差异很大。如果你使用的是参数量较小或能力较弱的模型,Agent 可能无法发现细节问题。在生产环境中,建议针对目标代码语言和审查维度做 Prompt 调优,并通过样例评测集来控制输出质量。
5. 多智能体系统的核心架构与运行原理解析
这一段回到更一般化的视角。在做 uReview 这类系统时,我们实际上是在实现一个通用的多智能体系统。它的核心架构与运行原理可以拆成四个关键点。
5.1 任务分解与角色设计
多智能体系统能够工作的前提,是被执行的“任务”能够被清晰地拆解为多个子任务。
在代码审查场景中,任务分解相对容易,因为审查维度是相对清晰的。但如果是更开放的任务,比如“重构这个模块”,就必须由上层规划 Agent 先做任务分析,得出子任务清单,再分发给下层执行 Agent。
任务分解完成后,每个 Agent 需要有自己的角色定义(Role),包括:
- 职责范围;
- 输入输出格式;
- 可调用的工具集合;
- 行为约束。
HR 领域有个概念叫“职位说明书”,Agent 的角色定义本质上就是一份给大模型的职位说明书。定义得越清楚,Agent 的行为就越可控。
5.2 上下文管理策略
多智能体系统的运行瓶颈通常不是模型推理,而是上下文管理。
每个 Agent 在运行时需要读取大量的上下文:用户需求、代码仓库结构、相关历史代码、团队规范、项目文档。但上下文窗口是有限的,所以需要做取舍。
常见的策略有:
- 即时裁剪:只保留当前 Agent 完成任务所需的最小片段;
- 层级摘要:先对文档做摘要,按需展开详细内容;
- 向量检索:用 Embedding 检索最相关的代码片段;
- 外部记忆:把历史审查记录存放在外部存储,减少重复注入。
5.3 智能体间的通信与协作
多智能体系统的协作模式大致分为三种:
| 协作模式 | 特点 | 适用场景 |
|---|---|---|
| 管道模式 | 上一个 Agent 的输出作为下一个 Agent 的输入 | 代码分析 → 审查 → 汇总 |
| 主从模式 | 一个主控 Agent 分发任务、收集结果 | 任务动态拆分的复杂流程 |
| 去中心化模式 | Agent 之间相互通信协商 | 复杂博弈、群体决策场景 |
uReview 当下最合适的模式是“主从 + 管道”的混合结构:ReviewOrchestrator 作为主控,分发任务给各审查 Agent;审查 Agent 之间相互独立,最后交给 SummaryAgent 做管道式汇总。
5.4 流程控制与容错设计
发过线上事故的同事应该都有体会:任何一个引入 AI 的系统,都必须预设“AI 会犯错”的可能性。
多智能体系统里的容错体现在多个层面:
- 超时控制:防止模型响应过慢拖垮流水线;
- 结果校验:Agent 返回结果后做格式校验,不符合规范的触发重新生成;
- 降级策略:当主模型不可用时,切换备用模型或直接跳过该 Agent;
- 人工兜底:所有 AI 审查结论都需要标记为“参考意见”,保留人工复核通道。
6. 常见问题与排查思路
在实际开发和部署多智能体代码审查引擎的过程中,下面这些问题我基本都踩过,提前整理出来供大家参考。
6.1 Agent 返回内容为空或格式混乱
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 模型没有按照要求输出 | Prompt 约束不够具体 | 在 System Prompt 中增加严格格式说明,并给出 few-shot 示例 |
| 输出是 JSON 但不是预期的结构 | 没有使用 JSON Mode 强制格式 | 兼容接口支持时开启response_format: {"type": "json_object"} |
| 英文模型偶尔输出中文标签 | 模型指令跟随不稳定 | 用规则解析做后处理,把非结构化文本归一化 |
6.2 审查结果总是“假阳性”
很多团队试用时会遇到这个问题:Agent 报告了一堆问题,人工一看全是误报。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 把没有问题的代码报出来 | Diff 上下文不足,模型无法判断真实逻辑 | 把变更函数的前后若干行代码一并提供给 Agent |
| 结果离谱且无参考价值 | 模型本身能力不足 | 使用更强的模型跑审查,或走小模型初步筛选 + 大模型复核的路线 |
6.3 并发执行导致大模型 API 限流
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 大量 Agent 同时发起请求 | 线程池并发度过高 | 使用信号量或令牌桶限流,控制 QPS |
| 429 限流报错 | API 配额不足 | 引入重试机制,按指数退避重试 |
6.4 代码上下文超过模型窗口
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 请求被拒或截断 | 补丁内容过大 | 按文件大小过滤、按变更块拆分、对超大文件做摘要后再审查 |
| 审查结果遗漏问题 | 关键词高密度的部分被截断 | 调整拆分策略,考虑按函数级别分片审查 |
7. 最佳实践与工程建议
这一节整理几条从工程落地角度最值得注意的建议。
7.1 从单 Agent 起步,逐步演进到多 Agent
不要一开始就追求复杂的 Agent 编排。我比较推荐的演进路径是:
- 先用一个 Agent 跑通全流程:拉 Diff → 调模型 → 输出报告;
- 验证模型在真实代码上的审查准确率和误报率;
- 再按审查维度拆分 Agent,比如先拆出安全审查和性能审查;
- 最后再加入汇总 Agent、日志埋点、人工反馈回路。
每一步都要有可量化的指标。比如:
- 误报率;
- 漏报率;
- 单次平均审查耗时;
- 人工采纳率。
没有指标衡量的多智能体系统,最后只会演变成“看起来很高级,但没人真用”的玩具。
7.2 用 Static Analysis 配合大模型,而不是完全依赖大模型
大模型做代码审查的优势是理解语义和意图,但劣势也很明显:不能真正执行代码,存在幻觉风险。
而传统的静态分析工具,比如 ESLint、SpotBugs、SonarQube、Semgrep,可以非常确定地检查出一类模式化问题。最佳实践是:
- 静态分析工具负责第一层确定性扫描;
- 大模型负责第二层语义分析;
- 两层结果做合并去重。
这样既保证准确率,又降低对模型能力的依赖。
7.3 审查结果必须关联代码位置
如果只是给一句“这里存在 SQL 注入风险”,工程师根本没有精力去定位。
更好的做法是为每条审查意见关联:
- 文件路径;
- 行号;
- 原始代码片段;
- 模型推理说明;
- 修改建议。
在代码实现层面,可以要求 Agent 输出 JSON 结构:
{ "file_path": "src/main/java/com/demo/UserService.java", "line_start": 13, "line_end": 14, "severity": "high", "type": "sql_injection", "description": "直接拼接用户输入构造 SQL 查询,存在注入风险", "suggestion": "使用 PreparedStatement 参数化查询" }7.4 建立人工反馈闭环
任何一个 AI 驱动的系统,都必须建一个“人类反馈”回路。
具体做法是:在审查报告页面增加两个按钮,“采纳”和“忽略”。把人工采纳的数据收集起来,定期分析,找出系统误报率高和漏报率高的类别,再针对性优化 Prompt 和 Agent 分工。
甚至可以更进一步,用人工采纳数据构造微调数据集,对自建模型做增量训练。
7.5 安全与权限控制
代码审查系统能接触到最敏感的代码资产,安全边界必须重视:
- 系统内部服务只在内网暴露,不直接对外公网访问;
- Webhook 回调要做签名校验,防止伪造请求;
- 调用大模型时不要上传不必要的敏感配置字段;
- 所有审查记录要定期清理或加密存储;
- 对操作行为做审计日志。
8. 总结
这篇文章从 Uber uReview 的思路出发,完整拆解了一个多智能体代码审查引擎的设计与实现。
我们从传统代码审查的痛点谈起,介绍了多智能体系统的核心概念和通用架构:输入接入层、代码分析层、智能体编排层、上下文管理层、反馈输出层。然后用 Java + Spring Boot 实现了一个最小可运行的 Demo,包括 Diff 解析、三个独立审查 Agent、一个汇总 Agent,以及核心编排器。
在工程层面,重点讨论了几件事:多智能体系统如何做任务分解、上下文管理、协作模式和容错;落地过程中常见的假阳性、上下文溢出、API 限流问题;以及从单 Agent 起步、静态分析工具配合大模型、建立人工反馈闭环等最佳实践。
说句实在话,多智能体代码审查目前还远没有到“全自动取代人工”的阶段。它更像是一个不知疲倦的初级Reviewer,能帮你提前扫掉一批低级的、模式化的、肉眼容易漏掉的问题,让你把精力省下来去关注真正需要人类经验的部分。
如果你正在做 AI Engineer 相关方向,不妨从今天这个最小骨架开始,往里面加你自己的 Agent、模型和规则。框架本身不复杂,复杂的是让每个 Agent 都足够可靠。建议直接拿一个真实的 PR 试试,看看你的 Agent 能发现哪些问题,又会漏掉哪些问题——这种真实反馈,比任何理论都更有价值。