Featured image of post Multi-Review:多 Agent 并发代码审查的工程实践

Multi-Review:多 Agent 并发代码审查的工程实践

从单模型审查到多 Agent 并发审查,参考 Cloudflare 的 orchestrator 架构,在 opencode-actions 中实现多维度并发代码审查。

语速

上一篇文章中,我介绍了 opencode-actions——一个基于 OpenCode 构建的 GitHub Action,用来做自动化的 PR 代码审查。那个版本的 review action 用的是一个模型、一个 prompt,针对整个 PR diff 做一次综合审查。

这个方案能用,但有个明显的问题:一个 prompt 要同时关注代码质量、安全漏洞、性能问题和架构设计,每一项都不够深入。 就像让一个工程师同时扮演 QA、安全审计、DBA 和架构师——每个角色都能说两句,但都不专业。

Cloudflare 在今年四月发了一篇博客,详细介绍了他们用 OpenCode 构建的多 Agent 并发代码审查系统。他们用了 7 个专业 reviewer 加 1 个 coordinator,在 5169 个仓库上跑了 13 万次审查。核心思路是:不要让一个模型什么都看,而是让多个专业模型各看各的,最后用一个 coordinator 统一汇总。

看完这篇文章,我决定在 opencode-actions 中实现类似的能力。这篇文章就是 multi-review action 的设计记录。

为什么需要多 Agent 并发审查?

单模型审查的问题不只是"不够深入"。实际使用中有几个具体的痛点:

  1. 维度冲突:安全审查要求严格(宁可误报不能漏报),性能审查要求务实(关注可测量的指标而非理论风险)。一个 prompt 很难同时表达两种截然不同的审查哲学。
  2. 上下文稀释:当 prompt 中塞了太多"你要检查 A、B、C、D、E"时,模型的注意力被分散,每个维度的审查质量都打折扣。
  3. 结果模糊:一个模型输出的"有条件合并"到底是因为安全问题还是性能问题?很难区分。

Cloudflare 的做法是把审查拆成独立的维度,每个 reviewer 只关注自己擅长的领域。他们的 security reviewer 有明确的 “What to Flag” 和 “What NOT to Flag” 指令——告诉模型什么该忽略,比告诉它什么该检查更重要。

架构:并发 Reviewer + Coordinator

multi-review 的核心架构用一句话概括:N 个专业 reviewer 并发审查,1 个 coordinator 汇总去重。

整个流程是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
PR 触发 → 获取 diff
     启动 OpenCode Server(单实例)
  ┌───────────┼───────────┐───────────┐
  ↓           ↓           ↓           ↓
Quality   Security   Performance  Architecture
(reviewer) (reviewer) (reviewer)   (reviewer)
  └───────────┴───────────┴───────────┘
              ↓ (Promise.all)
         Coordinator
         (去重 + 交叉验证)
         PR Comment

关键设计选择:

  1. 单 Server 多 Session:只启动一个 OpenCode Server 实例,所有 reviewer 和 coordinator 共享。这样避免了 N 个 reviewer 各自冷启动 MCP server 的开销。
  2. Promise.all 并发:所有 reviewer 通过 Promise.all 同时启动,不排队。实际运行时间取决于最慢的那个 reviewer,而不是所有 reviewer 的总和。
  3. 全局超时 + 自适应余量:一个全局 deadline,每个 reviewer 的剩余时间 = max(30s, deadline - now)。先完成的 reviewer 不影响后面的,后面的 reviewer 自然获得更少的时间。

四个专业 Reviewer

默认启动四个 reviewer,每个有独立的 YAML 定义:

Reviewer关注领域决策标签
Quality代码质量、bug、逻辑错误、风格一致性、错误处理可合并 / 有条件合并 / 不可合并
Security输入验证、认证授权、注入漏洞、数据暴露、OWASP Top 10安全无虞 / 存在风险 / 高危漏洞
Performance算法复杂度、内存模式、N+1 查询、缓存、并发性能良好 / 性能有疑虑 / 性能问题严重
Architecture耦合度、模块划分、分层、接口设计、霰弹式修改架构合理 / 架构有疑虑 / 架构有问题

每个 reviewer 的 prompt 都遵循同一个约束:第一行必须是决策标签,然后是总结、阻塞项和建议项。这让 coordinator 能结构化地解析每个 reviewer 的输出。

和 Cloudflare 的做法一样,每个 reviewer 的 prompt 都明确告诉它只看当前代码中存在的问题,不要重复早期版本的问题,不要建议修改未变更的代码。

Team 配置

reviewer 的组合通过 default-team 参数配置,格式是 persona:count

1
2
3
4
5
6
7
8
# 默认:每种角色各一个
default-team: "quality:1,security:1,performance:1,architecture:1"

# 自定义:加强安全审查
default-team: "quality:1,security:2,performance:1"

# 极简:只看质量和安全
default-team: "quality:1,security:1"

当 count > 1 时,同角色的 reviewer 会带上编号(如 security-1security-2),用同一个 prompt 但独立运行。相同 prompt 的多次运行可以起到交叉验证的效果——如果两个 security reviewer 都发现了同一个漏洞,可信度自然更高。

Cloudflare 有个类似的"风险分层"机制:trivial 改动(≤10 行)只跑 2 个 agent,full 改动(>100 行)跑全部 7 个。multi-review 目前靠用户手动配置 team,未来可以加入自动分层。

Coordinator:去重和交叉验证

并发审查带来的直接问题是多个 reviewer 可能发现同一个问题。比如一个 SQL 注入,quality reviewer 可能从 bug 角度报告,security reviewer 从安全角度报告。如果不处理,最终报告会有重复。

coordinator 的工作就是整合:

  1. 跨 reviewer 去重:同一问题只报告一次
  2. 交叉验证:至少 2 个 reviewer 同意的问题标记为"已确认"
  3. 冲突处理:意见不一致时取多数意见
  4. 保留领域见解:安全发现即使只有一个 reviewer 提到,也要保留
  5. 最严重决策:如果任何一个 reviewer 认为不可合并,最终决策就是不可合并

coordinator 的 prompt 模板支持自定义,通过 {{REVIEWS}} 占位符注入各 reviewer 的输出:

1
2
3
4
5
6
7
8
你是一个代码审查协调员。以下审查由独立的专家 reviewer 生成。
你的任务是整合为一个去重后的综合报告。

规则:
1. 跨 reviewer 去重(同一问题只提一次)
2. 交叉验证:至少 2 个 reviewer 同意的问题标记为"已确认"
3. 冲突时取多数意见
...

最终生成的 PR comment 包含两部分:coordinator 的综合报告在上,各 reviewer 的详细审查结果折叠在 <details> 标签中。

和 Cloudflare 方案的对比

两者使用了相同的思路——多 Agent 并发 + Coordinator 汇总——但在规模和复杂度上有明显差异:

维度Cloudflaremulti-review
Agent 数量7 个专业 agent + coordinator4 个(可配置) + coordinator
并发模型SDK 多 session,circuit breaker + failbackSDK 多 session,Promise.all + 全局超时
模型路由按任务复杂度分配不同模型(Opus/Sonnet/Kimi)单一模型,通过 model 参数指定
弹性三级 circuit breaker,provider 级别的 failback chain超时 + fallback comment(coordinator 失败时直接展示原始结果)
风险分层自动(trivial/lite/full)手动配置 team
扩展性插件架构,7 个 plugin 组合YAML persona 定义,内置 4 种
Token 优化共享 context file,7x 的 MR 上下文不重复每个 reviewer 独立接收完整 diff
适用场景内部 GitLab,5169 仓库,企业级GitHub/Gitea,开源项目,开箱即用

最核心的差异在弹性上。Cloudflare 的 circuit breaker 和 failback chain 是为生产环境设计的——7 个并发 AI 调用,provider 限频和宕机是必然事件。multi-review 的规模小得多,4 个并发的失败概率可控,用超时 + fallback comment 就够了。

另一个差异是 token 优化。Cloudflare 把 MR diff 写到一个共享文件,7 个 reviewer 读同一个文件而不是各拷一份。注意这里共享的是原始 diff(输入数据),而不是 reviewer 之间的审查结论。每个 reviewer 的审查过程仍然是完全独立的。这个优化在 7 个 reviewer 的情况下能省 85% 的重复 token。multi-review 目前每个 reviewer 都接收完整 diff,在 4 个 reviewer 的规模下重复还能接受,但如果 team 配置到 7+ 就需要考虑类似优化了。

Reasoning Effort 和 Thinking

multi-review 支持 reasoning-effortenable-thinking 两个参数,直接传递给 OpenCode SDK 的 agent 配置:

1
2
3
4
5
- name: Run Multi-Review
  uses: sun-praise/opencode-actions/multi-review@v1
  with:
    reasoning-effort: max    # low / medium / high / max
    enable-thinking: true    # 启用思维链

默认 reasoning-effort: maxenable-thinking: true,这意味着每个 reviewer 和 coordinator 都会用最大推理力度并输出思维链。对于代码审查这种需要深度分析的任务,这个默认值是合理的。

Cloudflare 也有类似的分层:coordinator 用 Opus(最强推理),code quality 和 security 用 Sonnet(平衡速度和能力),文档审查用 Kimi(便宜)。multi-review 暂时没有按角色分配不同模型的能力,但这是一个明确的优化方向。

使用方式

.github/workflows/ 中添加:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
name: Multi Review

on:
  issue_comment:
    types: [created]

jobs:
  multi-review:
    if: |
      github.event.issue.pull_request &&
      contains(fromJSON('["/multi-review", "/mr"]'), github.event.comment.body)
    runs-on: ubuntu-latest
    steps:
      - uses: sun-praise/opencode-actions/multi-review@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          zhipu-api-key: ${{ secrets.ZHIPU_API_KEY }}
          # 可选配置
          default-team: "quality:1,security:1,performance:1,architecture:1"
          reasoning-effort: max
          enable-thinking: true

这段配置的效果是:在 PR 中评论 /multi-review/mr,就会触发多 Agent 并发审查。

代码实现

核心实现在 orchestrator.ts 中的两个函数。

并发审查——runParallelReviewers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const promises = reviewers.map(async (reviewer) => {
  let sessionId: string | undefined;
  try {
    const remaining = () => Math.max(30_000, deadline - Date.now());

    // 1. 创建独立 session
    const sessionResult = await withTimeout(
      client.session.create({ throwOnError: true }),
      remaining(), reviewer.name,
    );
    sessionId = sessionResult.data.id;

    // 2. 发送 persona + diff
    await withTimeout(
      client.session.prompt({
        path: { id: sessionId },
        body: {
          parts: [{ type: "text", text: reviewer.prompt + "\n\nPR Diff:\n```\n" + prDiff + "\n```" }],
        },
      }),
      remaining(), reviewer.name,
    );

    // 3. 提取结果
    const messages = await withTimeout(
      client.session.messages({ path: { id: sessionId } }),
      remaining(), reviewer.name,
    );
    return { reviewer: reviewer.name, content: extractText(messages.data), success: true };
  } catch (err) {
    return { reviewer: reviewer.name, content: "", success: false, error: err.message };
  } finally {
    // 4. 清理 session
    if (sessionId) await client.session.delete({ path: { id: sessionId } });
  }
});

return Promise.all(promises);

Coordinator 汇总——runCoordinator

1
2
3
4
5
6
7
// 把各 reviewer 的结果拼接成 {{REVIEWS}}
const reviewsText = reviews
  .map(r => `## ${r.reviewer}\n${r.success ? r.content : `(失败: ${r.error})`}`)
  .join("\n\n---\n\n");

const fullPrompt = coordinatorPrompt.split("{{REVIEWS}}").join(reviewsText);
// 创建 session、发送 prompt、提取结果...

如果 coordinator 本身失败了,还有个 fallback:

1
2
3
4
export function buildFallbackComment(reviews: ReviewResult[]): string {
  // coordinator 挂了,直接把各 reviewer 的原始输出拼起来
  return "**Multi-Review (fallback — coordinator failed)**\n\n" + parts.join("\n\n---\n\n");
}

这确保了即使汇总环节出问题,审查结果也不会丢失。

和单模型 Review 的关系

multi-review 和之前单模型 review action 不是替代关系,而是互补:

reviewmulti-review
定位快速审查,适合每次 PR 都跑深度审查,适合关键 PR 或手动触发
模型数1 个4+ 个(可配置)
耗时1-3 分钟3-8 分钟
Token 消耗中(N 倍于单模型)
触发方式PR 创建/更新自动触发手动评论触发
适合场景日常迭代,快速反馈重要功能、安全相关、大型重构

日常开发用 review 做快速检查,关键 PR 用 multi-review 做深度审查。两个 action 可以共存于同一个仓库。

未来优化方向

参考 Cloudflare 的经验和当前实现的局限,有几个明确的优化方向:

  1. 自动风险分层:根据 diff 行数和文件类型自动决定跑几个 reviewer。typo 修改变动不需要 4 个 agent 并发审查。
  2. 共享上下文:把 diff 写到文件而不是每个 reviewer 都拷贝一份,减少 token 消耗。但需要注意一个微妙的问题:共享的边界在哪里? 共享原始 diff(输入数据)是安全的,但如果过早共享 reviewer 之间的中间结论,会导致严重的偏差问题——确认偏差、锚定效应、同质化审查。正确的设计是:reviewer 阶段完全隔离,只共享输入;coordinator 阶段才汇总结论。这不仅是 token 优化的问题,更是一个审查独立性的架构决策。
  3. 模型路由:coordinator 用强模型,文档审查用轻量模型,按角色分配不同 provider。
  4. 增量审查:push 新 commit 后,只审查变更的部分,不从头开始。

总结

multi-review 的核心思路来自 Cloudflare 的文章:把一个复杂任务拆成多个专业子任务,并发执行后统一汇总。这比一个全能模型覆盖所有维度更有效,因为每个子任务的 prompt 更聚焦,模型的注意力更集中。

实现上,multi-review 用 OpenCode SDK 的多 session 能力做并发,用 Promise.all 做编排,用全局超时做兜底,用 coordinator 做去重和交叉验证。这是一个在开源项目规模下足够实用的架构——没有 circuit breaker 和 failback chain 的复杂度,但覆盖了并发审查的核心需求。