技术速递|更好的工具反而让 Copilot Code Review 变差了。我们是如何真正解决这一问题的?
作者:Napalys Klicius
排版:Alan Wang
通过将 Copilot Code Review 迁移到共享的 Unix 风格代码探索工具,我们围绕 Pull Request 中的代码证据重新设计了智能体工作流,从而降低了代码审查成本,并提升了整体审查效率。

给智能体配备更好的工具,它理应完成更好的工作。至少,直觉上是这样认为的。
当你打开一个 Pull Request 时,Copilot Code Review 会读取代码变更,并探索周边代码,以便在问题合并上线之前找出真正值得关注的问题。为此,它一直使用自己的一套代码探索工具。因此,当我们将其替换为由 GitHub Copilot CLI 提供、维护更完善且可共享的工具(grep、glob 和 view)时,我们原本以为这会是一次顺利的升级。
然而,在我们的基准测试中却发现,代码审查成本更高了,能够发现的问题反而变少了。
但问题并不在工具本身,而在于工具的使用指令。我们按照代码审查者实际阅读 Pull Request 的方式重写了这些指令后,原本的性能回退反而变成了优势:平均审查成本降低约 20%,同时保持了相同的审查质量。
这就是一个围绕工具重新设计工作流,并最终找到解决方案的故事。
相同的工具,错误的直觉
如果你曾基于某个智能体框架进行开发,那么你很可能也继承了它自带的工具。它们一直运行良好,所以你会继续使用它们——直到有一天,你的使用场景逐渐偏离了这些工具最初的设计目标,而它们开始在不知不觉中拖你的后腿。我们当时遇到的正是这种情况。在尝试使用共享的 CLI 工具之前,Copilot Code Review 一直使用自己的一套代码探索工具。这套工具层借鉴了早期智能体系统的设计思路,包括类似 SWE-agent 的代码仓库导航方式,以及 GitHub Copilot Autofix 的一些理念:列出目录、搜索文件、搜索目录以及读取代码。这些工具能够正常工作,但它们是专门为 Copilot Code Review 定制的,也是基于当时模型的行为特点设计的。早期的智能体编程模型调用工具的次数较少,也不擅长自动获取完成任务所需的上下文。因此,在模型有限的几次工具调用中尽可能返回所有相关信息,就显得尤为重要。
与此同时,Copilot CLI 的执行框架提供了一套共享的、受 Unix 启发的代码探索工具:grep、glob 和 view。这套执行框架同时也被越来越多的 Copilot 智能体产品所使用,包括 GitHub Copilot Cloud Agent,因此,对执行框架的任何改进都能够惠及多个产品。我们希望尽可能清理并共享基础设施,因此尝试让 Copilot Code Review 也使用 Copilot CLI 执行框架中的这套工具。我们的目标是减少重复实现的工具、建立统一的代码探索工具维护体系,并让这些改进能够更容易地在各个 Copilot 产品之间共享。
从表面上看,这次迁移非常简单:
| 原 Copilot Code Review | GitHub Copilot CLI | 用途 |
|---|---|---|
list_dir |
glob |
在打开代码之前发现候选文件和目录。 |
search_file 和 search_dir |
grep |
在代码中搜索匹配的文本、符号或调用位置。 |
read_code |
view |
在已知文件路径或代码范围后读取对应内容。 |
原有的代码审查工具并不是这些能力的简单封装。例如,在搜索目录或读取某段代码时,它们不仅会返回匹配的内容,还会自动附带周围的一部分代码上下文。虽然这样会增加 Token 消耗,但对于早期模型来说,这种自动补充附近上下文的方式通常能够带来更好的效果。
起初,我们希望这只是一次简单的迁移:用一套工具替换另一套工具。然而,当我们在离线基准测试中使用这套共享工具后,却发现代码审查智能体不仅效率下降,效果也变差了。平均成本增加了,而真正有价值的评论数量却减少了。
执行轨迹揭示了一个“浏览循环”
我们的内部 Copilot Code Review 基准测试之所以有价值,不仅因为它能给出最终评分,更因为它能够展示智能体执行任务的完整路径,包括调用了哪些工具、每次返回了多少内容、在哪里发生了错误,以及它究竟是在逐步收敛到证据,还是不断扩大搜索范围。
当我们第一次在离线基准测试中尝试使用共享的 Copilot CLI 工具时,智能体的行为更像是在浏览整个代码仓库,而不是调查一个 Pull Request。它会进行大范围搜索、猜测可能的文件路径、读取大量代码、再根据这些内容继续搜索,并不断将越来越多的上下文带入后续推理。

这种模式其实很好理解。当任务是“理解整个代码仓库”时,进行广泛探索确实很有帮助。但这并不是一位代码审查者通常审查 Pull Request 的方式。
当我审查一个 Pull Request 时,我会从代码变更开始,并围绕它提出一些有针对性的问题:
-
这个函数在哪里被调用?
-
这个配置项是否在其他地方也被使用?
-
是否存在采用相同模式的测试或辅助函数?
-
解释这一行为所需的最小附近代码范围是什么?
我并不希望在明确目标之前就打开仓库中的大量代码。我需要的是回答当前问题所需的最少上下文,而不是让无关代码淹没整个审查过程。
这一点非常重要,因为每一次工具调用返回的内容,都会成为智能体工作上下文的一部分。额外的文件内容会一直保留在后续推理过程中,不仅增加了 Token 成本,还可能让整个代码审查失去重点。对于智能体来说,一次工具返回的结果并不是一张可以随手丢弃的打印件,而是会一直占据上下文窗口的额外 Token。
执行轨迹让这种差异变得非常清晰。真正的问题并不是共享工具本身,而是我们给智能体的指令,让它形成了错误的代码审查直觉。
这些工具本身没有问题,但它们的使用说明是针对 Copilot CLI 的场景设计的,因此暗示了一种错误的工作流:智能体把 grep、glob 和 view 当成了一个通用编程助手来使用,而不是代码审查者。对于一个编程助手来说,在修改代码之前先全面了解相关区域,确保不会破坏其他部分,是一种合理的策略;而代码审查者通常会从代码变更开始,判断这次修改是否引入了问题,然后只寻找能够确认或排除这一问题所需的最小范围证据。
像 Copilot CLI 或 GitHub Copilot Cloud Agent 所使用的那类通用编程助手工具指令,非常适合交互式助手。开发者可能会让它理解整个仓库、制定修改计划、编辑文件,并在多个对话轮次中持续完成任务。
而 Copilot Code Review 的职责则更加聚焦:从 Pull Request 的代码变更开始,收集足够的周边证据,判断这次修改是否真正引入了问题,并避免加载那些与当前审查问题无关的上下文。
因此,我们很快意识到,不能简单地用 Copilot CLI 的工具替换原有的 Copilot Code Review 工具,而必须配合重新设计 Prompt。真正的问题变成了:如何设计工具指令,才能让这套共享工具在代码审查场景下发挥最佳效果?
针对代码审查工作流,重新设计工具指令
接下来的几轮迭代,我们开始让工具指令真正贴合代码审查场景。我们希望 Copilot Code Review 遵循如下工作流程:
-
从代码变更开始,提出具体的代码审查问题。
-
当文件路径不确定时使用
glob,使用grep查找候选文件、符号和调用位置。 -
在读取文件之前,先批量完成低成本的信息发现。
-
只有当智能体明确知道需要查看哪个文件或哪一段代码时,才使用
view。 -
批量读取相关代码,而不是在一次搜索和一次读取之间来回切换。
简化来说,我们希望智能体遵循的是下面这种行为模式:
通用模式: 使用现有工具检查代码仓库中可能相关的上下文。
面向代码审查的指引: 从代码变更开始。先利用 grep 和 glob 缩小范围,再使用 view 查看精确证据。如果 grep 没有找到相关上下文,则使用更简单、经过转义处理的搜索重新尝试;如果路径错误,则切换到 glob,而不是继续猜测附近的路径。
例如,假设这次代码变更修改了一个用于判断某项操作是否允许执行的授权辅助函数。那么,一个合理的代码审查问题并不是“把所有调用这个辅助函数的文件完整内容都展示出来”,而应该是一个更加聚焦的问题:“是否有任何处理请求的调用方依赖于旧的行为?”
理想的执行路径应该很短:
从代码变更中被修改的辅助函数开始
使用grep查找该辅助函数的调用位置
使用glob查找可能的路由、处理器或控制器文件
使用view查看最相关调用位置附近的代码;
判断这些调用方是否会因此引入风险
我们还调整了智能体在搜索失败时的恢复策略。如果某个输入导致 grep 搜索失败,更好的下一步应该是使用更简单、修正后的搜索重新尝试一次;如果文件路径错误,更好的做法是使用 glob 查找正确路径,而不是继续猜测相邻路径并读取碰巧存在的文件。这样可以避免一次小小的工具调用失败,演变成一轮越来越大的探索循环。

这次改动在措辞上很小,但带来的影响却非常大。它将智能体的工作节奏从“浏览、读取、再次搜索”,转变为“提问、缩小范围、读取、做出判断”。
基准测试帮助我们调试行为,而不仅仅是分数
共享的执行框架为我们提供了工具,而内部 Copilot Code Review 基准测试则为我们提供了持续优化的反馈闭环。
我们能够反复运行相同的代码审查示例,对比工具调用轨迹,修改指令,再重新测试。这使我们能够回答一些非常具体的问题:
-
智能体是先缩小范围,还是先大范围读取?
-
它是否能够批量执行彼此独立的搜索?
-
它是否只会在确有需要时才调用
view? -
修改工具指令后,工具调用错误是否真正减少了,还是只是转移到了其他地方?
-
整个执行轨迹是否始终围绕代码变更中的证据展开?
-
代码审查质量是否仍然保持在我们关注的指标范围内?
最有价值的信号并不是“这些指令更好了”,而是更加具体的现象:智能体调用工具的次数几乎没有变化,但更多的调用都用于寻找真正相关的证据,而不是一遍又一遍地扩大搜索范围。
这让产品层面的最终结果,与工程层面的具体行为建立起了联系。我们不再需要猜测为什么分数发生变化,而是能够直接查看产生这些结果的工作流程。
最终结果:平均代码审查成本降低约 20%
在线上环境中,相比对照组,经过优化后的行为使平均代码审查成本降低了约 20%。更重要的是,我们没有观察到任何会阻碍上线的质量下降信号。
成本的降低并不是工具本身带来的,而是围绕这些工具重新设计工作流所带来的结果。共享的代码探索工具、Copilot Code Review 定制化的工具指令,以及内部基准测试,共同让智能体的行为足够透明,也足够容易进行优化。
这一思路对于构建智能体应用来说非常重要。很多时候,人们容易把工具当作实现细节,只是简单地把一种工具替换成另一种工具,然后比较最终答案。但对于智能体而言,工具接口本身就是产品体验的一部分。它决定了智能体会关注什么、如何进行搜索、会保留多少上下文,以及什么时候认为自己已经拥有了足够的证据。
工具说明和系统指令,更接近于 API 文档。模糊的 API 文档会让开发者困惑,并导致低效甚至错误的决策;对于大语言模型来说,不清晰的工具提示也是一样。哪怕只是很小的一处措辞变化,都可能影响成本、质量,以及整个问题调查过程,因为它改变了智能体如何分配自己的“注意力”。
相同的工具,不同的工作
我们也尝试将这种更加聚焦的工具指令应用到 Copilot CLI 中,但并没有获得同样明显的效果。这是一个很有价值的反例,也是一个重要的经验。
Copilot Code Review 始终围绕代码变更和代码审查问题展开;而 Copilot CLI 面向的是更加开放、交互式的编程任务,在这些任务中,探索本身就是工作的一部分。很多时候,并不存在一个固定的代码变更作为起点,用户可能会在多个对话轮次中不断调整方向,而真正需要的上下文在一开始也未必明确。同样的 grep、glob 和 view 工具可以同时服务于这两个产品,但围绕这些工具构建的工作流,必须与产品本身的使用场景保持一致。
真正值得总结的经验是:共享工具能够实现规模化复用,但前提是,它们的工具指令和基准测试必须与具体任务相匹配。
你可以亲自体验 GitHub Copilot Code Review。
更多推荐



所有评论(0)