GitHub 分享 Copilot 代码审查从工具驱动转为流程驱动的改进经验,降低审查成本。对使用 AI 辅助审查的团队有借鉴价值。
给 Agent 更好的工具,理应让它把工作做得更好。至少直觉上是这样。
当你打开一个 pull request 时,Copilot code review 会读取 diff,并探索周边代码,以便在问题随代码发布之前找出真正重要的问题。为此,它原本使用自己的一套代码探索工具。因此,当我们将其替换为维护得更好、由 Copilot CLI 共用的 grep、glob 和 view 工具时,我们原以为这会是一次顺理成章的升级。
结果却恰恰相反:在基准测试中,我们发现代码审查的成本变高了,而捕获的问题却变少了。
但问题并不出在工具上,而在于指令。当我们按照审查者实际阅读 pull request 的方式重写指令后,性能退化反而变成了收益:在保持相同审查质量的同时,平均审查成本降低了约 20%。
下面就是我们如何通过调整工具周边的工作流,最终解决这个问题的故事。
如果你曾基于某个 Agent 框架构建产品,很可能也继承了它附带的工具。这些工具能用,于是你会继续沿用,直到有一天,你的使用场景与它们最初的设计目标偏离得足够远,它们开始悄无声息地妨碍你。我们当时正处于这种情况。在尝试使用共享的 CLI 工具之前,Copilot code review 使用的是自己的一套代码探索工具。这一工具层受到早期 Agent 系统的启发,包括 SWE-agent 风格的代码仓库导航思路以及 GitHub Copilot Autofix:列出目录、搜索文件、搜索目录和读取代码。这些工具确实有效,但它们专用于 Copilot code review,而且是根据当时模型的行为特点设计的。早期的 Agent 编码模型调用工具的次数更少,也不擅长自动获取必要的上下文。这意味着,在模型有限的几次工具调用中,把所有相关信息都包含进去尤为重要。
与此同时,Copilot CLI harness 提供了一套共享的、受 Unix 启发的代码探索工具:grep、glob 和 view。越来越多的 Copilot Agent 产品也在使用这套 harness,其中包括 GitHub Copilot cloud agent,因此对 harness 的改进能够惠及多个产品。我们希望尽可能清理并共享基础设施,于是尝试在 Copilot code review 中使用 Copilot CLI harness 的工具。我们的目标是减少重复的工具实现,建立一个统一改进代码探索工具的共享位置,并让这些改进更容易应用到不同的 Copilot 产品中。
从表面上看,这次迁移似乎很简单:
原有的审查工具并不只是简单的封装层。在搜索目录或读取某段代码时,它们不仅会返回匹配或请求的代码行,还会附带额外的周边代码上下文。这样做会增加 token 成本,但也符合早期模型的特点:自动附带附近的上下文,往往能让它们表现得更好。
最初,我们希望这只是一次简单的迁移:用一套工具替换另一套工具。但当我们在离线基准测试中测试共享工具时,审查 Agent 的效率和效果反而都变差了。平均成本有所增加,有价值的评论数量却减少了。
我们的内部 Copilot code review 基准测试很有用,因为它展示的不只是最终得分,还会呈现 Agent 完成任务时走过的路径,包括调用了哪些工具、返回了多少内容、哪里发生了错误,以及它是在逐步收窄范围、逼近证据,还是在不断扩大搜索范围。
当我们首次在离线基准测试中尝试共享的 Copilot CLI 工具时,Agent 的行为往往像是在浏览整个代码仓库,而不是调查一个 pull request。它会进行宽泛搜索、猜测可能的路径、大范围读取代码,找到更多需要搜索的内容,然后把这些额外上下文一直带到后续步骤中。
这种行为不难理解。当任务是“理解这个代码仓库”时,大范围探索可能很有用。但审查者通常不会这样审查 pull request。
当我审查一个 pull request 时,我会从 diff 开始,并提出有针对性的问题:
这个函数在哪里被调用?
这个配置项是否也在其他地方使用?
是否存在采用相同模式的测试或辅助函数?
能够解释这一行为的最小邻近代码范围是什么?
在明确自己要找什么之前,我并不想打开代码仓库中的一大片内容。我需要的是回答问题所必需的最小上下文,同时避免让无关代码给审查过程造成过多负担。
这一点很重要,因为每次工具调用的结果都会成为 Agent 工作上下文的一部分。额外的文件内容可能会被带入后续推理,不仅增加成本,有时还会让审查失去焦点。工具结果并不是用完即弃的打印输出;对 Agent 而言,它们是会留在 context window 中的额外 token。
Trace 清楚地呈现了这种差异。共享工具本身没有问题,真正的问题是指令赋予了 Agent 错误的直觉,使其无法高效、有效地完成审查。
工具本身工作正常,但它们的指令是针对 Copilot CLI 中的使用方式调优的,暗示了一套不适合代码审查的工作流:Agent 使用 grep、glob 和 view 时,更像一个进行广泛探索的编码助手,而不是审查者。编码助手在修改代码之前,可能会梳理整个相关区域,以确保修改不会破坏代码的其他角落。另一方面,审查者通常会从 diff 入手,判断这次变更是否引入了问题,然后寻找确认或排除该问题所需的、范围最小的邻近证据。
通用编码助手的工具指令,例如 Copilot CLI 或 Copilot cloud agent 使用的指令,对交互式助手来说非常合理。开发者可能会让它理解一个代码仓库、规划变更、编辑文件,并在多个对话轮次中继续推进工作。
Copilot code review 的任务则更加聚焦:从 pull request diff 开始,收集足够的周边证据,以判断变更是否引入了真实问题,同时避免加载与当前审查问题无关的上下文。
因此很明显,如果不额外调整 prompt,我们无法直接用 Copilot CLI 的工具替换原有的 Copilot code review 工具。问题于是变成了:如何设计工具指令,才能在代码审查场景中有效使用这些共享工具?
接下来的几轮迭代中,我们让指导规则更加贴合代码审查。我们希望 Copilot code review 遵循以下工作流:
从 diff 开始,形成具体的审查问题。
当路径不确定时使用 glob,使用 grep 查找候选文件、符号和调用位置。
先批量执行成本较低的探索操作,再读取文件。
只有当 Agent 明确知道需要哪个文件或哪段代码行时,才使用 view。
批量执行聚焦的读取操作,不要在单次搜索和单次读取之间反复交替。
极度简化后,我们编码进指令的行为大致如下:
通用姿态:使用可用工具检查可能相关的代码仓库上下文。
审查型指导:从 diff 开始。先使用 grep 和 glob 收窄范围,再使用 view 读取确切证据。如果 grep 未能找到相关上下文,就使用更简单、正确转义的搜索条件重试。如果路径错误,就改用 glob,而不是继续猜测附近的路径。
例如,假设 diff 修改了一个用于判断某项操作是否被允许的授权辅助函数。此时,相关的审查问题不应该是“向我展示调用这个辅助函数的每个文件的完整内容”,而应该是一个范围更窄的问题:“是否有处理请求的调用方依赖原有行为?”
预期的调查路径很短:
start from the helper changed in the diff
grep for callers of that helper
glob for likely route, handler, or controller files
view the most relevant caller ranges
decide whether any caller changes the risk
这些指导规则还改变了 Agent 从搜索失败中恢复的方式。如果某项输入导致 grep 失败,更好的下一步是进行一次更简单、修正后的搜索。如果路径错误,更好的下一步是使用 glob,而不是猜测相邻路径,然后读取碰巧存在于那里的任何内容。这会推动 Agent 避免让一次小小的工具调用失败演变成规模更大的探索循环。
措辞上的改动很小,产生的效果却很大。它把 Agent 的节奏从“浏览、读取、再次搜索”转变成了“提问、收窄、读取、判断”。
共享 harness 为我们提供了工具。内部 Copilot code review 基准测试则为我们建立了反馈循环。
我们可以运行相同的审查样例、比较工具调用 Trace、更新指令,然后再次运行。这让我们能够提出一些具体的问题:
Agent 是先收窄范围,还是先大范围读取?
它是否批量执行了相互独立的搜索?
它是否只在有明确理由时调用 view?
工具指令的改动是真的减少了工具错误,还是仅仅把错误转移到了其他地方?
Trace 是否始终聚焦于来自 diff 的证据?
审查是否仍然保持了我们关注的质量指标?
最有价值的信号并不是“指令变得更好了”,而是更具体的现象:Agent 调用工具的总次数大致相同,但更多调用被用于寻找相关证据,而不是反复扩大搜索范围。
这样一来,我们就能把产品层面的结果与可以理解的工程行为联系起来。我们不再需要猜测分数为什么发生变化,而是可以直接检查产生这一结果的工作流。
在生产环境中,与对照组相比,经过调优的行为使平均审查成本降低了约 20%。更重要的是,它没有出现任何足以阻止发布的质量信号。
成本下降并不是工具本身带来的,而是来自工具周边的工作流。共享的代码探索工具、Copilot code review 的自定义工具指令以及内部基准测试,让 Agent 的行为变得足够透明,从而可以进行调优。
在使用 Agent 构建产品时,这种理解方式非常重要。人们很容易把工具视为实现细节:用一个工具替换另一个工具,然后比较最终答案。但对 Agent 来说,工具界面本身就是产品体验的一部分。它会改变 Agent 注意到什么、如何搜索、会将多少上下文带入后续步骤,以及何时判断自己已经获得了足够的证据。
工具描述和 system instructions 更接近 API 文档。含糊不清的 API 文档会让开发者感到困惑,并导致低效或错误的决策。含糊不清的工具 prompt 对 LLM 也会产生同样的影响;一个很小的措辞变化,就可能影响成本、质量以及调查过程的形态,因为它改变了 Agent 分配注意力的方式。
我们也曾尝试在 CLI 中应用同样聚焦的工具指令,但并没有获得同样的收益。这是一个很有价值的反例,也为我们得出的结论划定了一条重要边界。
Copilot code review 以 diff 和审查问题为锚点。Copilot CLI 处理的则是范围更广的交互式编码任务,探索本身可能就是工作的一部分。任务可能没有单一的 diff 锚点,用户可能在多轮对话中改变方向,而且一开始未必能明确知道什么才是正确的上下文。同一套 grep、glob 和 view 工具可以同时服务这两种产品,但围绕这些工具设计的工作流必须与产品相匹配。
结论是:只有当指令和基准测试与具体任务相匹配时,共享工具才能真正实现规模化复用。
你也可以亲自试试 GitHub Copilot code review。
Napalys Klicius 是 GitHub 的 Software Engineer,负责构建 Agent 系统。他的职业经历横跨模型检查、底层 C++ 无人机系统和静态分析;最近,他开始教 Agent 如何检查代码而不在代码仓库中迷路。
Dependabot 会让你的依赖项保持最新,但它的默认设置可能会让大量 pull request 淹没你的代码仓库。本文介绍了如何通过对更新进行分组、放慢更新节奏,同时保持安全修复的响应速度,减少一个 Microsoft 开源项目中的噪声。
一套实用的 GitHub Copilot 工作流,用于完成软件原型设计、规划、实现和审查,而不必追逐每一种新出现的 AI 工具。
刚开始使用 GitHub Copilot app?了解如何启动项目、与 AI Agent 协作、探索 canvas,并简化你的开发工作流。