代码审查 Agent Harness 实战:AI 自动 Code Review

一、引言: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,我们需要:

  1. 检出目标分支的代码到沙盒工作区
  2. 让 Agent 读取全部变更文件,理解改动的上下文
  3. 执行多维度审查:正确性、安全、性能、代码风格
  4. 输出结构化的审查报告,包括问题位置、严重级别、修改建议

整个流程的核心是第 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 目录。

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

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)大致如下:

## 文件: 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 定义三个专用子代理

import org.noear.solon.ai.harness.agent.AgentDefinition;

// 安全审查员
AgentDefinition securityDef = AgentDefinition.builder()
    .name("security-reviewer")
    .description("应用安全专家,负者审查代码中的安全风险")
    .toolsAdd("read", "ls")
    .systemPrompt("""
        你是一名应用安全专家。审查代码中的安全风险:
        - SQL / NoSQL 注入
        - XSS、CSRF、SSRF
        - 硬编码密钥、Token、密码
        - 权限校验缺失
        - 不安全的反序列化

        输出 JSON 格式:
        {"issues": [{"severity":"critical|major|minor", "line":N, "type":"security", "desc":"...", "fix":"..."}]}
        """)
        .build();

// 性能审查员
AgentDefinition perfDef = AgentDefinition.builder()
    .name("performance-reviewer")
    .description("性能优化专家,负责审查代码中的性能问题")
    .toolsAdd("read", "ls")
    .systemPrompt("""
        你是一名性能优化专家。审查代码中的性能问题:
        - N+1 查询、循环内调用远程服务
        - 不必要的对象创建、自动装箱
        - 线程阻塞、锁竞争
        - 大事务、长连接、缓存使用不当
        
        输出 JSON 格式同上,type 为 "performance"。
        """)
        .build();

// 代码风格审查员
AgentDefinition styleDef = AgentDefinition.builder()
    .name("style-reviewer")
    .description("代码规范专家,负责审查代码风格问题")
    .toolsAdd("read", "ls")
    .systemPrompt("""
        你是一名代码规范专家。审查代码风格问题:
        - 命名不规范(不遵循驼峰/常量命名)
        - 方法过长(超过 50 行应拆分)
        - 异常捕获过于宽泛(catch Exception)
        - 魔法数字未定义常量
        
        输出 JSON 格式同上,type 为 "style"。
        """)
        .build();

// 全部注册到引擎
engine.getAgentManager().addAgent(securityDef);
engine.getAgentManager().addAgent(perfDef);
engine.getAgentManager().addAgent(styleDef);

5.2 主 Agent 编排子代理

主 Agent 通过 system prompt 教会自己如何编排:

// 在 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

最终报告带维度标签:

## 文件: UserService.java

### [严重][security] SQL 注入漏洞 - 第 6 行
...
### [严重][correctness] 参数缺失 - 第 12 行
...
### [minor][style] 常量命名不规范 - 第 3 行
...
### [minor][performance] 循环内调用数据库 - 第 20 行
...

每个问题前面带 [维度] 标签,一目了然。团队可以配置 CI 规则:只要存在 critical 级别的安全漏洞,就阻止合并。

六、总结

用 Solon AI Harness 构建 Code Review Agent,本质上是把"人工审查"的经验沉淀为系统提示词 + 工具权限 + 子代理编排的组合。回顾核心要点:

  1. 主引擎配置:通过链式 API 在一处完成所有设置,包括模型、工具、会话、安全策略
  2. 子代理拆分:用 AgentDefinition 定义专精角色,主 Agent 按需调度,每个子代理只关注一个维度
  3. 沙盒 + StopLoop:两个安全机制确保 Agent 在可控范围内工作,不会"越狱"也不会"空转"
  4. 完整的会话管理sessionProvidersessionWindowSizecompressionThreshold 共同确保长对话不丢上下文

这套方案,可以每天自动帮我们审查大量 PR,拦截大量低级缺陷和安全漏洞。下一步可以计划接入私有知识库(团队编码规范、历史事故文档),让 Agent 的审查标准与团队沉淀对齐——那是 Harness 的 memory 能力的用武之地。

posted @ 2026-07-08 07:13  带刺的坐椅  阅读(197)  评论(0)    收藏  举报