一、引言:Code Review 的日常之痛
如果你在团队里做过 Reviewer,大概率经历过这些场景:上午打开 GitHub,10 个 PR 等着你,每个 PR 上千行变更,业务逻辑弯弯绕绕,你一个一个文件翻,看到眼睛发酸;好不容易审到一半,业务方过来说"这个 PR 今天必须合",你只能加速扫读,结果低级 bug 溜进了生产环境;更别提那些缩进混乱、命名随意、硬编码密钥的"风格问题"------你说吧显得啰嗦,不说吧又膈应。
这些痛背后是同一个矛盾:好的 Code Review 需要深厚的知识储备和极大的耐心,而人恰好在这两样上都有限度。 AI 不会倦怠,不会漏看一个空指针,也不会对连续十次同样的缩进错误感到厌烦。问题只在于:怎么让 AI 真正理解你的代码库、你的审查标准,并且能安全地操作文件系统?
Solon AI Harness 给出的答案是:用 Agent 封装审查能力,用沙盒隔离风险,用子代理拆分关注点,用 StopLoop 防止失控。 本文基于 Harness 构建一个可投入使用的自动 Code Review Agent,完整代码均可运行,你可以直接拿来做团队内部的 AI 审查 Bot。
二、场景规划:自动审查 PR 的完整流程
先定义我们要做什么。假设团队使用 Git 工作流,每次 PR 触发一个 Webhook,我们需要:
- 检出目标分支的代码到沙盒工作区
- 让 Agent 读取全部变更文件,理解改动的上下文
- 执行多维度审查:正确性、安全、性能、代码风格
- 输出结构化的审查报告,包括问题位置、严重级别、修改建议
整个流程的核心是第 2-3 步,这正是 Harness Agent 的职责范围。我们不需要 Agent 替我们做 CI/CD,只需要它像一个人肉 Reviewer 一样:读代码、思考、写评论。
Harness 为这个场景提供了几个关键能力:
| 能力 | 对应配置 | 解决什么问题 |
|---|---|---|
| 文件工具集 | ToolPermission.TOOL_PI |
Agent 可以 read/write/edit 工作区文件 |
| 沙盒隔离 | .sandboxEnabled(true)、sandboxSystemRestrict(true) |
防止 Agent 误操作宿主机 |
| 子代理 | AgentDefinition + createSubagent |
把不同维度审查交给专门 Agent |
| 循环防护 | StopLoopInterceptor(5, 10) |
避免 Agent 陷入死循环 |
| 会话压缩 | compressionThreshold + sessionWindowSize |
长对话不爆 Token |
| 显式记忆 | .memoryEnabled(true) |
跨轮次保持上下文 |
下面我们直接用代码实现。
三、基础实现:一个能审查 PR 的 Agent
3.1 配置主引擎
一切从 HarnessEngine 开始。我们需要指定工作区(PR 代码所在目录)和 Harness 的 Home 目录。
java
import org.noear.solon.ai.harness.HarnessEngine;
import org.noear.solon.ai.harness.permission.ToolPermission;
import org.noear.solon.ai.chat.ChatConfig;
import org.noear.solon.ai.agent.session.AgentSessionProvider;
import org.noear.solon.ai.agent.session.InMemoryAgentSession;
import org.noear.solon.ai.agent.react.intercept.StopLoopInterceptor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class CodeReviewApp {
public static void main(String[] args) {
String workspace = "/workspace/pr-1024";
String harnessHome = ".harness-home";
AgentSessionProvider sessionProvider = new AgentSessionProvider() {
private final Map<String, AgentSession> sessionMap = new ConcurrentHashMap<>();
@Override
public AgentSession getSession(String instanceId) {
return sessionMap.computeIfAbsent(
instanceId, k -> InMemoryAgentSession.of(k)
);
}
};
HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
.systemPrompt("""
你是一个资深代码审查专家 CodeReviewer。
你的工作流程:
1. 首先用 ls -R 列出工作区文件结构
2. 读取变更文件(通常位于 src/ 下)
3. 逐文件审查,关注:
- 逻辑正确性:空指针、边界条件、并发隐患
- 安全性:SQL 注入、硬编码密钥、权限绕过
- 性能:不必要的循环、重复查询、内存泄漏
- 风格:命名规范、异常处理、注释质量
4. 用 write 工具输出审查报告到 review-report.md
审查报告格式:
## 文件: {路径}
### [严重/一般/建议] {问题描述}
- 位置: {行号}
- 说明: {详细解释}
- 建议: {修改方案}
""")
.sessionProvider(sessionProvider)
.sessionWindowSize(20)
.compressionThreshold(100, 512_000)
.maxTurns(30)
.autoRethink(true)
.toolsAdd(ToolPermission.TOOL_PI)
.modelAdd(new ChatConfig().then(slf -> {
slf.setApiUrl(System.getenv("LLM_API_URL"));
slf.setApiKey(System.getenv("LLM_API_KEY"));
slf.setModel("deepseek-v4-flash");
}))
.sandboxEnabled(true)
.sandboxSystemRestrict(true)
.stopLoopInterceptor(new StopLoopInterceptor(5, 10))
.memoryEnabled(true)
.build();
// 执行审查
AgentSession session = engine.getSession("pr-1024-review");
var result = engine.prompt("""
请审查以下 PR 变更内容:
分支: feature/user-login -> main
变更文件:
- src/main/java/com/example/UserService.java
- src/main/java/com/example/UserController.java
- src/main/resources/application.yml
请按照系统提示词的流程进行全面审查,并将报告写入 review-report.md。
""")
.session(session)
.call();
System.out.println("审查完成:" + result.getContent());
}
}
这段代码定义了 Agent 的"人格"和"边界"。系统提示词是关键------它告诉 Agent 它是什么角色、应该按什么流程工作、输出什么格式。通过 write 工具让 Agent 自己生成审查报告到工作区,方便后续 CI 流程读取。
3.2 StopLoop 与沙盒:安全兜底
StopLoopInterceptor 是 Harness 的安全阀。LLM 在推理中可能陷入重复循环------比如不断修正同一个问题、反复读取同一个文件。new StopLoopInterceptor(5, 10) 的含义:如果 Agent 连续 5 次输出语义上相似的内容,或累计循环超过 10 次,拦截器主动终止推理并返回当前结果。
沙盒模式 (sandboxEnabled(true))确保所有文件操作被限制在工作区目录内。Agent 即使产生了"幻觉"想要写入系统目录,沙盒会直接拒绝并返回权限错误。
四、运行效果:一次真实的审查输出
假设 PR 中有一个典型的 Java 文件 UserService.java:
java
public class UserService {
private JdbcTemplate jdbc;
public User findUser(String id) {
String sql = "select * from users where id = " + id; // SQL 注入!
return jdbc.queryForObject(sql, User.class);
}
public void updateUser(User user) {
String sql = "update users set name = ? where id = ?";
jdbc.update(sql, user.getName()); // 缺少 id 参数!
}
}
Agent 生成的审查报告(review-report.md)大致如下:
markdown
## 文件: src/main/java/com/example/UserService.java
### [严重] SQL 注入漏洞
- 位置: 第 6 行
- 说明: 使用字符串拼接构建 SQL,参数 id 未做任何转义,恶意用户可构造特殊 id 执行任意 SQL。
- 建议: 改用占位符方式:`jdbc.query("select * from users where id = ?", User.class, id)`
### [严重] 参数缺失导致 SQL 执行异常
- 位置: 第 12 行
- 说明: update 方法声明了两个占位符 ?,但只传入了一个参数 user.getName(),缺少 id 参数。
- 建议: 补充 id 参数:`jdbc.update(sql, user.getName(), user.getId())`
### [一般] 注入依赖未使用接口
- 位置: 第 4 行
- 说明: 直接注入 JdbcTemplate 实现类,建议面向接口编程。
- 建议: 通过构造器注入 JdbcTemplate。
这个报告的质量已经超过很多初级开发者的审查水平。更关键的是:它不挑食------100 行和 10000 行的 PR 对它来说只是时间问题。
五、进阶:多维度子代理分布式审查
当项目规模变大,单个 Agent 的"全能"模式开始吃力:它既要懂安全,又要懂性能调优,还要背代码规范。Harness 的子代理机制让这件事变得优雅------主 Agent 做路由,子 Agent 做专精。
5.1 定义三个专用子代理
java
import org.noear.solon.ai.harness.agent.AgentDefinition;
import org.noear.solon.ai.harness.agent.AgentMetadata;
// 安全审查员
AgentDefinition securityDef = new AgentDefinition("security-reviewer")
.systemPrompt("""
你是一名应用安全专家。审查代码中的安全风险:
- SQL / NoSQL 注入
- XSS、CSRF、SSRF
- 硬编码密钥、Token、密码
- 权限校验缺失
- 不安全的反序列化
输出 JSON 格式:
{"issues": [{"severity":"critical|major|minor", "line":N,
"type":"security", "desc":"...", "fix":"..."}]}
""")
.metadata(AgentMetadata.create().addTools("read"));
// 性能审查员
AgentDefinition perfDef = new AgentDefinition("performance-reviewer")
.systemPrompt("""
你是一名性能优化专家。审查代码中的性能问题:
- N+1 查询、循环内调用远程服务
- 不必要的对象创建、自动装箱
- 线程阻塞、锁竞争
- 大事务、长连接、缓存使用不当
输出 JSON 格式同上,type 为 "performance"。
""")
.metadata(AgentMetadata.create().addTools("read"));
// 代码风格审查员
AgentDefinition styleDef = new AgentDefinition("style-reviewer")
.systemPrompt("""
你是一名代码规范专家。审查代码风格问题:
- 命名不规范(不遵循驼峰/常量命名)
- 方法过长(超过 50 行应拆分)
- 异常捕获过于宽泛(catch Exception)
- 魔法数字未定义常量
输出 JSON 格式同上,type 为 "style"。
""")
.metadata(AgentMetadata.create().addTools("read"));
// 全部注册到引擎
engine.getAgentManager().addAgent(securityDef);
engine.getAgentManager().addAgent(perfDef);
engine.getAgentManager().addAgent(styleDef);
5.2 主 Agent 编排子代理
主 Agent 通过 system prompt 教会自己如何编排:
java
// 在 engine 构建时调整 systemPrompt
.systemPrompt("""
你是代码审查的主协调员 CodeReviewOrchestrator。
工作流程:
1. 列出工作区文件,识别变更文件列表
2. 对于每个变更文件,依次分发给三个专家子代理:
- security-reviewer:安全审查
- performance-reviewer:性能审查
- style-reviewer:风格审查
3. 通过 getAgentManager().getAgent(name) 获取子代理定义
4. 用 createSubagent(definition).build() 创建子代理实例
5. 调用子代理审查文件
6. 收集所有子代理的结果,合并为完整报告
7. 写入 review-report.md
""")
5.3 多维度审查的执行流程
一次完整的 PR 审查经历以下步骤:
主 Agent 启动
├─ 列出文件清单
├─ 读取 UserService.java
├─ 创建 security-reviewer 子代理 → 返回安全问题
├─ 创建 performance-reviewer 子代理 → 返回性能问题
├─ 创建 style-reviewer 子代理 → 返回风格问题
├─ 读取 UserController.java
├─ 重复上述子代理分发...
├─ 合并所有 JSON 结果
└─ 写入 review-report.md
最终报告带维度标签:
markdown
## 文件: UserService.java
### [严重][security] SQL 注入漏洞 - 第 6 行
...
### [严重][correctness] 参数缺失 - 第 12 行
...
### [minor][style] 常量命名不规范 - 第 3 行
...
### [minor][performance] 循环内调用数据库 - 第 20 行
...
每个问题前面带 [维度] 标签,一目了然。团队可以配置 CI 规则:只要存在 critical 级别的安全漏洞,就阻止合并。
六、总结
用 Solon AI Harness 构建 Code Review Agent,本质上是把"人工审查"的经验沉淀为系统提示词 + 工具权限 + 子代理编排的组合。回顾核心要点:
- 主引擎配置:通过链式 API 在一处完成所有设置,包括模型、工具、会话、安全策略
- 子代理拆分 :用
AgentDefinition定义专精角色,主 Agent 按需调度,每个子代理只关注一个维度 - 沙盒 + StopLoop:两个安全机制确保 Agent 在可控范围内工作,不会"越狱"也不会"空转"
- 完整的会话管理 :
sessionProvider、sessionWindowSize、compressionThreshold共同确保长对话不丢上下文
这套方案,可以每天自动帮我们审查大量 PR,拦截大量低级缺陷和安全漏洞。下一步可以计划接入私有知识库(团队编码规范、历史事故文档),让 Agent 的审查标准与团队沉淀对齐------那是 Harness 的 memory 能力的用武之地。