首页 / 文章 / Token数量减少并不代表成本更低:为何编程智能体是在浪费精力而非Token

Token数量减少并不代表成本更低:为何编程智能体是在浪费精力而非Token

压缩工具的输出结果可能会提高整个编码任务的成本。了解为何需要衡量每项已完成任务的成本、准备恢复方案,以及为何要为自动化代理设计专用工具。

2258 词

自从编码智能体兴起后,开发者们一直关注着一个指标:令牌数。模型生成了多少令牌,工具返回了多少令牌,上下文大小是多少,智能体又消耗了多少令牌?这种推理看似合乎逻辑——令牌数越少,费用应该就越低。

GitHub对Copilot编码智能体的分析表明这一假设存在风险。如果被截取的文本包含重要信息,删除工具响应中的相关内容反而会让整个任务的成本上升。此时智能体需要重新执行命令,找回原始输出,耗费更多推理时间,并在后续所有操作中携带更大的上下文。

因此,成本最低的交互并不一定是令牌数最少的那种。实际上,人们追求的是以最少无效工作完成软件任务的方案,这也就改变了评估智能体性能的标准。

本地优化陷阱

假设某个代理执行了一个测试命令:

npm test

它会输出50,000个标记,这看起来很浪费,于是你编写了一个封装代码将输出量压缩到2,000个。从理论上讲这是成功的:省下了48,000个标记。

但现在假设这2,000个标记遗漏了智能体所需的信息,它的回复可能会是类似这样的:

I need more information about the failing test.

因此它会重新执行该命令,搜索已保存的输出结果,打开日志文件,或尝试更具体的命令。流程变为如下所示:

Original command
      ↓
Compressed output
      ↓
Missing information
      ↓
Agent searches again
      ↓
Command runs again
      ↓
More context
      ↓
More reasoning

单步处理的成本降低了,而整个任务的成本却上升了。GitHub在尝试减少Copilot的输出时遇到了这个问题。有一项实验使用了名为RTK的工具来缩短shell输出,而每当缺少关键信息时,智能体就会频繁重新打开或重新运行命令。结果就是需要更多轮次处理,平均而言生成的标记数更多,完成任务的速度也更慢。事后看来这很显然,但在优化错误的目标时却很容易被忽视。

评估任务本身,而非调用方式

智能体的存在是为了完成软件任务,而非最小化单次API请求的成本。这两个目标之间的差距比想象的要大。以两个智能体为例:智能体A:

Task
↓
8 tool calls
↓
100K tokens
↓
Completed in 4 minutes

以及特工B:

Task
↓
15 tool calls
↓
80K tokens
↓
Completed in 9 minutes

仅从令牌数量来看,B 获胜。但若要管理一个工程团队,答案就复杂多了。B 可能完成得更快,需要重试的次数更少,使用的计算资源更少,生成的补丁质量更高,同时还能减少开发者的等待时间。如果必须有人再照看 B 五分钟,B 的实际成本还会进一步上升。

这就是为什么 GitHub 认为每次交互的令牌计数并不能反映智能体的效率,真正需要优化的是整个任务的结果。

端到端成本模型

经验丰富的工程师从其他工作中早已了解这一点。将数据库查询时间从 200 毫秒缩短到 100 毫秒看似是明显的优势,但如果新查询导致另一个服务多发出三次请求,系统的整体速度反而可能会变慢。需要调整的是整个系统,而非单一操作。

智能体也遵循同样的原则。关于总成本的一个有用思维模型是:

Total agent cost =
model inference
+ tool execution
+ retries
+ repeated work
+ context growth
+ developer waiting
+ infrastructure

还有一个经常出现的模式:

smaller response
+
missing context
=
more recovery work

这就是为什么人工智能工程越来越类似于系统工程的原因。

并非所有输出的价值都相同

一个值得注意的发现是,命令输出的信息密度各不相同。重复出现的安装、构建、测试及进度日志可能充满冗余信息,而类似源代码的输出以及随机生成的命令结果则可能恰好包含代理所需的内容。GitHub没有采用统一的压缩规则,而是采取了选择性处理方式。其原理如下:

Command output
      │
      ▼
┌──────────────────────┐
│ What kind of output? │
└──────────┬───────────┘
           │
     ┌─────┼─────┐
     │     │     │
     ▼     ▼     ▼
 Source  Search  Noise
 output  results logs
     │     │     │
     ▼     ▼     ▼
  Keep   Reorganize Compress
  all     without   selectively
          losing
          content

这总比那种直白的替代方案强:

if output.length > X:
    truncate()

简单的规则用于优化数值,而选择性规则则用于提升智能体持续工作的能力。

git diff带给我们的启示

想象一个已编辑过五个文件且目前正在运行的智能体:

git diff

输出内容很长。一个简单的压缩器会认为其体积过大而进行压缩。但差异信息可能恰好包含智能体判断自身修改是否正确所需的所有内容。GitHub指出,其早期版本的压缩器确实会对git diff结果进行压缩,而测试表明智能体不得不重新打开原始输出以获取这些信息。因此该过滤功能被移除了。

这是个针对人工智能的宝贵教训:不能仅仅因为某事物体积庞大就认为它毫无用处。有些庞大的输出其实包含高价值的上下文信息,而一些微小的输出却毫无价值。仅凭token数量无法区分二者。

设计自己的工具

如果你正在构建一个编程智能体,可以参考以下代码实现:

const result = await exec(command);

随后是上限限制:

return result.stdout.slice(0, 5000);

这看起来合理,但实际上暗含了一个假设,即前5000个字符已包含代理所需的所有信息。更智能的设计会先对输出内容进行分类。起始方式可以相同:

const result = await exec(command);

然后根据输出类型进行分支:

const outputType = classifyOutput(result);switch (outputType) {
  case "source":
    return result.stdout;  case "search":
    return reorganize(result.stdout);  case "repetitive-log":
    return compress(result.stdout);  default:
    return result.stdout;
}

即便如此,也需要一条逃生路径,以便特工能够取回原件:

return {
  summary,
  compressedOutput,
  fullOutputAvailable: true
};

代理能够获得简洁的视图,实际上并没有任何内容消失。对于自主系统而言,这是一种更好的设计。

压缩应延迟而非删除

压缩不应意味着删除信息,而应是将其推迟到需要时再使用。对比这两种方法。糟糕的压缩效果如下:

Original output
       ↓
Throw away 90%
       ↓
Agent cannot recover it

更好的压缩能保持返回路径畅通:

Original output
       ↓
Create useful summary
       ↓
Agent continues
       ↓
Needs more detail?
       ↓
Retrieve original

这更接近普通软件中的缓存工作原理。代理无需立即获取所有信息,但也不应失去对任何重要数据的访问权限。GitHub的实现方式在数据被压缩后提供了直接的恢复途径。

有时更多的上下文反而更省事

这听起来似乎有悖常理。开发者们多年来一直在努力减少上下文数据量。但实际上,更多的上下文可以避免更多工作。假设代理检测到的是一种简化的故障情况:

Test failed:
Expected 401
Received 500

完整输出可能还会显示:

Database connection refused
JWT middleware initialized correctly
Request reached /api/auth/login
PostgreSQL timeout after 5 seconds

简短版本会告知智能体某件事出了故障。完整版本则说明认证逻辑很可能没有问题,问题出在数据库连接上。后者需要更多令牌,但或许能减少五次工具调用。上下文本身有成本,缺乏上下文同样也有成本。工程上的挑战就在于如何平衡这两者。

为何智能体似乎会陷入循环

你可能见过智能体做出类似这样的行为:

Run command
↓
Read partial output
↓
Run another command
↓
Search same file
↓
Run original command again
↓
Read more output
↓
Change code
↓
Run test
↓
Go back to original command

这看起来像是混淆,有时确实如此。但有时只是基础设施未能为模型提供足够的信息,智能体正在试图弥补不完整的接口缺陷。如果智能体不断请求相同的内容,问题可能出在工具设计而非模型本身。

工具设计即模型的用户体验

开发者通常从输入、输出、延迟、错误和认证等方面来评估API。而用于AI的工具还需要考虑另一个问题:这种输出会如何影响模型接下来的决策?以两种响应为例。API A:

{
  "status": "failed"
}

以及 API B:

{
  "status": "failed",
  "error": "PostgreSQL connection timeout",
  "service": "auth-api",
  "retryable": true,
  "attempt": 2,
  "duration_ms": 5040
}

B 的容量更大,却能降低整体系统成本,因为智能体可以当场做出正确决策。目标并非最小的 JSON 格式,而是能让智能体下次再次做出正确决策的信息。

调整整个流程

GitHub 并未将此视为单纯的压缩问题。相关文档列出了几项优化措施:在减少重复内容的同时保留有用信息,剔除对任务无帮助的格式设置,在不改变行为的前提下缩短指令长度,以及避免为后台工作进行不必要的数据检索。每项改动都在离线环境下通过智能体编程基准测试进行评估,随后在受控的在线实验中得到验证,之后才正式应用。

这就是针对智能体的性能工程。问题从如何让响应更简短转变为如何帮助智能体以更少的无效工作完成任务,后者显然是一个更好的问题。

代理的可观测性

这会改变你在生产环境中需要监控的内容。对于普通服务,你需要关注的是:

Latency
Error rate
CPU
Memory
Throughput

对于代理,可添加更多内容:

Task completion rate
Tool-call count
Retry rate
Repeated-command rate
Context growth
Model escalation rate
Time to completion
Cost per completed task
Human intervention rate

其中大部分是工作流指标,而非令牌指标。令牌固然重要,但它们影响的是成本结构,而非效率水平。

每项成功任务的成本

最简单的改进方式就是停止追踪这一指标:

cost per request

请跟踪以下内容:

cost per successfully completed task

它们并不等同。以具有以下数值的智能体为例:

Agent A
$0.20/task
70% success rate

与这个相比:

Agent B
$0.35/task
95% success rate

价格更低的方案并不一定意味着系统成本更低。请对其进行标准化处理:

Effective cost
= inference cost / successful tasks

针对A:

$0.20 / 0.70
≈ $0.286

关于B:

$0.35 / 0.95
≈ $0.368

根据这一简单标准,A会更胜一筹。但如果A需要更多的开发者监督,成本结构又会发生变化,这也是为什么团队最终需要更全面的任务成本定义。

选择模型时也适用同样的思路

这便引出了模型路由的概念。GitHub的HydraFusion研究预览已将编码工作流视为一个优化问题,针对每项任务在单一模型、升级到更强大的模型,或使用另一模型作为独立评估器之间进行选择。据称,在受控条件下的离线测试显示出了显著的节省效果,某些基准测试中的质量甚至达到了他们所使用的Opus 5基准水平或更高。

将这一点与之前的结论结合起来看,一个智能代理不会仅仅询问哪种模型成本最低,而是会询问哪种工作流能以最少的资源浪费完成这项任务。这样的工作流可能会结合使用:

Cheap model
+
Good context
+
Efficient tools
+
Verification

再打败这个:

Expensive model
+
Poor context
+
Repeated tool calls

智能不再仅存在于模型中,而是遍布整个系统。

如果您正在开发人工智能产品

人们很容易首先优化那些显而易见的指标:减少Token使用量、缩短提示词长度、降低输出长度、选择更便宜的模型。但不要只调整这些方面,而应对整个工作流程进行监控:

User request
    ↓
Model inference
    ↓
Tool call
    ↓
Tool response
    ↓
Model reasoning
    ↓
Another tool call
    ↓
Verification
    ↓
Final result

逐一衡量每个阶段,然后找出智能体在何处做了不必要的工作。可能是数据库工具返回的上下文太少,或是搜索工具返回了过多无关信息。也有可能是智能体无端重复尝试,或者你的提示语导致了多余的规划。又或许更强大的模型能在一半的步骤内解决问题,而成本较低的模型也足以完成首次尝试。这些都是软件工程领域的问题。

未来不在于更少的标记数

人们很容易将整个成本问题简化为:

tokens ↓
cost ↓

实际的关系更像是这样:

Useful context
      ↓
Better decisions
      ↓
Fewer retries
      ↓
Fewer unnecessary tool calls
      ↓
Shorter task duration
      ↓
Lower total cost

有时,降低成本的途径在于为模型提供更多信息。这看似违反直觉,也正是为什么人工智能工程正在演变成一门系统学科,而非仅仅依赖编写提示语的技巧。

衡量已完成的工作

在评估某个智能体时,不要只关注它使用了多少个标记符。而应考察它生成了多少有用的软件成果,以及在此过程中浪费了多少精力。这意味着需要跟踪正确性、延迟时间、工具调用次数、重试次数、上下文大小、推理成本、人工干预情况以及任务是否成功完成。

最优的编码系统并不一定会产生最少的标记。关键在于找到那种能够将每个标记、工具调用和模型选择转化为实际进展的配置方式。GitHub近期的工程实践指明了方向:优化整个任务流程,保留智能体所需的信息,只有在确定某项操作毫无帮助时才予以剔除。其目的并非让智能体执行更少的任务,而是减少它们的资源浪费。