GitHub Copilot 如何在不牺牲质量的前提下降低 AI 编程成本

在使用 AI 编码助手时,输出质量固然重要,但真正的效率在于能够快速、高效地完成工作,并且始终拥有恰当的上下文。

因此,仅以单次交互的 token 数来衡量效率并不具有实际意义。我们的目标不应是减少 token 使用量,而是在推进任务时恰到好处地调用所需上下文。如果工具响应过于简略,遗漏了助手所需的信息,有时反而需要额外的调用或更多工作,最终导致任务耗时更长、成本更高。

正因如此,我们更应以最终结果为导向,而非单纯优化工具调用。本文将探讨 GitHub Copilot 的四项改进,它们正是这一原则的具体实践:

  • 在减少重复输出的同时保留有用上下文。
  • 移除对任务无价值的格式化内容。
  • 在不改变有用行为的前提下缩短指令长度。
  • 无需额外检索步骤即可直接交付已完成的后台工作。

所有潜在改动均先通过代理式编码基准在离线环境中评估,再经受控的在线实验验证后才正式上线。本文中的示例均来自 GitHub Copilot CLI。包括 GitHub Copilot 应用和 Copilot 代码评审在内的其他多款 Copilot 产品,也采用相同的底层框架,并因这些优化而变得更加高效。

Chart showing 3.1% 'Remove view previxes', 5.5% 'Selective output compaction', 2.9% 'Compact task-tool prompt', and 2.3% 'Reduce notification roundtrips'.
Figure 1: Four independent A/B experiments using the same AI-credit metric. The segments are shown together for comparison; their effects are not necessarily strictly additive.

局部指标的陷阱

为了降低代理成本,人们常倾向于缩减每次工具调用的输出。RTK(Rust Token Killer)是一款在代理读取前压缩 shell 输出的实用工具。我们利用代理式编码基准对其在 GitHub Copilot 中的效果进行了评估。

在我们的测试环境与基准配置下,RTK 确实能缩短部分响应;然而,当被截断的内容至关重要时,模型有时会重新打开原始输出,或再次执行该命令以补全缺失信息。

这些补救操作增加了交互轮次,并携带了更多上下文。虽然单次工具响应变短了,但从整体看,任务的 token 消耗反而更多,耗时也更长。我们在局部节省了 token,却在全局付出了更高的代价。

Flow chart showing: RTK, compresses shell output > Local win, tool output gets shorter > Useful detail is missing > Recovery, reread or rerun > More turns and context carried forward. Then the option of finishing at 'End-to-end result, Tokens and cost up, Task duration up, Task completion: steady,' or 'Recovery repeats' going back to 'useful detail is missing'.
Figure 2: A shorter tool response can make the completed task more expensive when missing details force the agent to reread output, rerun commands, and carry more context forward.

这一结论仅适用于我们所测试的集成与工作负载,并不意味着所有 RTK 配置或通用的输出压缩都存在类似问题。这也表明,单纯追求每次工具调用的 token 数并非正确的优化目标。效率的提升必须从用户请求开始,直至最终结果,对整个任务流程进行端到端的评估。

更有价值的是,思考哪些内容可以去除,而又不会迫使模型重复劳动。

压缩噪声,保留有用信息

我们的目标是在缩短重复性输出的同时,保留助手顺利完成任务所需的上下文,避免其不得不回溯重做。

对基准测试运行的分析显示,安装、构建、测试及 lint 的输出往往包含大量重复的冗余信息,而类似源码的输出以及任意命令的结果则更可能承载助手所需的关键信息。这一分析促成了一个选择性输出压缩器的设计,其思路部分借鉴了 RTK 等方法。

该原型先后在代理式编码基准以及多个开源项目上进行了评估,覆盖其构建、测试与 lint 流程。

早期版本过于激进,导致模型不得不重复某些步骤,或读取完整的原始保存输出,从而推高了端到端成本并降低了任务成功率。例如,我们最初曾尝试压缩 git diff 的输出,但在基准测试发现助手为找回缺失信息而重新打开原始输出后,便撤下了该过滤器。

这些早期失败促使我们确立了三项原则:

  1. 保留类源码及任意输出。catgit diffgit show 以及各类脚本的输出均保持原样。
  2. 重组搜索结果而不丢弃内容。 来自 grep 等工具的匹配项与文件列表可在保留全部结果的前提下更高效地归类。
  3. 选择性压缩重复噪声。 安装、构建、测试及进度类的输出仅在压缩收益显著时才会被处理。

最终上线的版本是在反复评估与打磨中逐步成型的。它显得相对保守,并非出于设计上的刻意,而是因为评估结果确实支持这样的做法。

即便输出已被压缩,助手仍可通过一条直接的恢复路径获取完整的原始内容。

Flowchart showing how GitHub Copilot handles shell-command output. Copilot calls a shell command, classifies the output, then chooses one of three paths: keep arbitrary/source output unchanged, reorganize search results without losing any matches, or selectively compress repetitive noise (like install/build/test logs) while preserving full output and providing a recovery path. The processed result is returned to Copilot.
Figure 3: The shipped compressor preserves source-like output, reorganizes search results without loss, and compresses only predictable repetitive noise while retaining the full original.

这条恢复路径既是安全机制,也是重要的评估信号。我们记录了助手是否会打开保存的原始输出、是否重新执行命令、是否重复探索、是否缩小搜索范围,或是增加了额外的交互轮次。频繁的恢复操作意味着压缩器可能误删了有价值的信息。

在触发输出压缩的离线任务中,未观察到任务成功率的统计学显著下降,助手极少打开保存的原始内容。在线实验中,平均成本略有下降,且我们追踪的各项质量指标未见明显恶化。

先去格式,再删信息

一项简洁的 token 优化来自 view 工具,该工具用于将文件内容引入上下文供助手读取。

此前,在向模型展示内容之前,view 会在每行前面加上行号。早先的文件编辑工具会借助这些行号定位修改位置,但如今的工具主要依据周边代码匹配,不再依赖行号。尽管常规工作流已不再使用行号,这些前缀却依然存在。

单个前缀看似微不足道,但在每次读取、每行每文件之间不断叠加,最终在整个会话中累积成可观的开销。于是,我们将其移除了。

Before-and-after image of code snippets. The line-number prefixes re removed from the 'After' image.
Figure 4: Removing line-number prefixes preserves the source exactly while eliminating formatting that was repeated across every file read.

行号在差异比较和短小片段中仍有用处。但在当前场景下,它们附加于每一次文件读取,却并未服务于实际的编辑流程,因而显得多余。

移除行号后,在离线的代理式编码基准测试中,模型推理成本下降约 5%。成功率维持在预期的波动范围内,编辑失败并未增加。

随后,我们面向 Copilot CLI 用户开展了在线实验。结果显示,每位用户的日均模型推理成本下降约 3%,且我们监测的质量与满意度指标未见明显恶化。

对开发者而言,这意味着更多的上下文窗口得以用于实际编码工作,而非那些助手并不使用的格式化内容。

这正是理想的改动:无需向模型发出新指令,无需担心信息丢失,也不必做出额外决策。文件内容原封不动地送达模型手中。

压缩提示,不改意图

提示语承载着塑造助手行为的指令,并在每一回合都会发送给模型。只有当助手仍能保持开发者依赖的行为模式时,缩短提示才能真正提升效率。

在 GitHub Copilot 中,任务工具会启动专门的子代理进行并行工作。其指导说明历经多年积累,散见于工具描述、Schema、代理定义、系统说明以及配套工具之中。

围绕“元提示”的循环——Copilot 不断迭代自身提示——使整体提示篇幅缩减了约一半。Copilot 自主筛选并优化候选方案,同时通过行为测试确保我们希望保留的需求得以贯彻。

首次在线实验揭示了一处离线评估未能察觉的隐患:元提示循环竟将原本谨慎的并行指导改写成了硬性调度策略,导致独立的定制代理被迫串行运行。

我们随即叫停了实验。在再次调整提示之前,我们针对用户反馈的行为问题开展了一次回归评估。最终的修复方案用一句话取代了明确的白名单与黑名单:

独立代理可并行运行;请综合考虑副作用。

这句话更加简练且约束力更弱,将是否让子代理并行运行的选择权交还给了模型,而非沿用先前的硬性规定。借此,我们的新版行为测试顺利通过,且未对既有行为测试造成任何影响。

提示相关的行为需要经过充分测试。若未经验证,缩短后的提示很可能悄然移除某些关键功能,而无人察觉。

Three-stage diagram labeled Compression → Regression + fix → Completed. Left panel shows an original prompt compressed by about 50%. Middle panel highlights a regression where agents became serialized, then a fix by editing one sentence to restore parallelism. Right panel shows final shipped prompt with restored behavior and cumulative savings of about 1,300 fewer tokens per turn across steps.
Figure 5 Prompt compression became safe only after a regression test exposed serialized agents and a one-sentence fix restored parallelism; the resulting token savings recur on every model turn.

上线后的提示平均每回合削减约 1,300 个 task 工具提示 token,相当于每会话总提示 token 减少约 1.8%,每小时活跃成本降低 2.9%,且各项测量指标未见质量下滑。

无需额外检索,直接交付已完成的后台工作

助手经常会在后台执行一些独立任务,例如一边运行长时间的 shell 脚本,一边展开子代理的调查。通知机制允许助手在等待这些任务完成期间继续推进其他工作,而无需为此耗费一次工具调用。

如果助手并未明确指定等待某项任务,则框架会在 shell 脚本或子代理完成后唤醒模型并发出通知。

以往,这类通知并不包含已完成的结果,助手还需再花一轮时间去检索 Copilot 已经收到的输出。当多项任务集中完成时,这种额外往返可能会反复发生。如今,Copilot 会将符合条件的完成通知批量整合,并以现有工具结果的格式直接递送已完成成果。助手可凭此继续推进工作,无需再为索要结果而额外花费一轮交互。对于仍在运行的任务,则按原有方式处理。

Before-and-after sequence diagram comparing orchestration behavior. Before: model waits on separate shell and sub-agent completions, causing retrieval detours and four LLM calls to process two results. After: a harness batches related completions and emits synthetic tool events so background work continues while waiting; both results are processed together in a single LLM call. The visual emphasizes reduced latency and fewer model round trips.
Figure 6 Before, each background completion could wake a retrieval-only model turn. After, the harness batches eligible completions and delivers completed results in the existing tool-result format.

在此项改进之前,每项已完成的任务都需要助手先发起一次模型调用索取结果,再发起一次调用来处理该结果。以上文提到的 shell 脚本与子代理为例,这意味着在工作继续推进之前,助手总共要经历四次模型调用。

现在,框架会将两项完成事项合并打包,统一递送结果,一次模型调用即可同时处理两者。取消这些额外的检索环节,也避免了不必要的调用进一步扩散整个会话的上下文。

通过直接递送完整结果,不做压缩、摘要或任何保留,框架在以 AI Credits 计算的 token 相关用量上实现了约 2.3% 的降幅。

在具体场景中衡量改动

一项在某个 Copilot 工作流中节省 token 的改动,可能在另一个流中反而增加成本。

例如,一套更为精简的文件工具指令,其灵感源自 Copilot 代码评审中的积极效果。但在 Copilot CLI 的在线实验中,它却导致了成本上升,因此我们并未将其上线。

相比之下,移除行号前缀并选择性压缩输出的做法,在基于生产模型的大型代码评审任务集上进行的独立评估中,每轮评审的平均提示 token 消耗均减少了约 5%。我们监测的评审质量指标未见明显变化。

这些发现与 Copilot代码审查早期迁移到共享文件工具 所带来的效果相互独立;后者连同评审指令的调优,使代码评审成本降低了约 20%。

每一项改动都应在实际运行的工作流中加以衡量。

构建高效 AI 编码助手的五点经验

  1. 优化完整任务,而非单次工具调用。 如果助手为弥补被移除的内容而不得不花费更多轮次,那么更短的输出并不意味着更低的成本。
  2. 优化编排逻辑,而不仅仅是模型输出。 尽量消除那些本可由框架以确定性方式完成、却仍需模型介入的工作环节。
  3. 按输出所承载的内容来压缩。 保留原始内容,优先采用无损变换,并密切跟踪助手使用恢复路径的频率。
  4. 提示的改写有时会产生意料之外的后果。 务必验证预期行为是否得以保留。
  5. 证据只在特定工作负载下成立。 在离线基准、在线实验以及产品上线的各个场景中,都要重新评估改动的影响。

上述改动并未让模型变得更聪明,它们只是替模型省去了本不需要做的工作。

本文所述的改进正在应用于所有使用同一底层框架的 GitHub Copilot 体验中。

让代理式工作流走进您的终端
尽在 GitHub Copilot CLI >

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论