派大猩 · Agent 工程笔记

Back

讨论 Multi-Agent 贵不贵的时候,最容易被忽略的一件事是:拆分本身不省钱。多个 Agent 并行,就意味着多份系统提示词、多份初始化上下文和多份工具清单。把任务拆得越细,账单也跟着复制一份。

把该显式化的知识显式出来同样要付费,它长期占着上下文,越是当成常识写进提示词的东西,每一轮都要反复付费。这篇算的就是这笔账。

我的主张是先给上下文定预算,再决定怎么拆。预算不到位的并行,只是把一条贵链路拆成几条同样贵的链路。拆分、摘要化、脚本化这三条我真正用过的路径,我会分别写清每一条省的是哪一块 token,以及这个数是在什么口径下算出来的。

没看账单就动手,是最顺手的错#

成本优化最容易走岔的地方,是直接跳过账单挑熟悉的手段:换个便宜模型、压一压提示词、少跑两轮。挑手段顺手,看账单要先做基建,所以大多数人会跳过这一步。

我记账时按调用链把一段任务拆开:链路自上而下分成任务层、并行批次层、子 Agent 层、工具层。每层再按输入 token、输出 token、缓存命中、轮次分开计。不拆到这一层,看到的只有一个总数,而总数会骗人:输入降了输出可能没降,某一轮特别贵、摊平了完全看不出来。

六处来源里,最便宜的是用户自己输入的那一句,大头是工具定义、原始返回和滚雪球一样的历史。按来源分,token 大致落在六处:系统提示词、工具返回、文件内容、长期记忆、历史消息、用户输入。这六处的价钱差别很大,摊到几十轮上,比用户那句话贵几个数量级。不清是谁在花钱,优化就会挑最显眼的那个下手,最贵的那个反而没人管。

换便宜模型算不算止损,要看总账。主力模型换成便宜一档,账算下来往往更贵:一次贵调用变成多次便宜调用,轮次翻倍,总数跟着上去。

缓存命中率要单独拎出来看,它是整张账单里最容易被忽略的一项。命中率掉了,先看前缀里有没有东西在漂:时间戳、随机 ID、每次顺序不同的工具清单,都会让前面的稳定段失效。

前缀怎么排是有讲究的:把全局稳定、会话稳定、易变三段依次排好,稳定段全量命中,易变段每次都在变。次序定死之后,命中率只剩一个变量——易变段有多长。

缓存读取只收大约一成的价格,这一项因此值得单独拎出来算一遍。前提是这一段前缀跟上一次请求逐字一致,完全一致才复用上次的 KV 矩阵,差一个字符这一段就得按原价重算。算这一项的账时,命中的那部分按一折算,没命中的按原价折算,别拿一个命中率数字直接当总账。

拆分不会天然省钱#

一个反直觉的事实是:把任务拆给多个 Agent,很多时候比一个 Agent 从头做到尾更贵。原因是每个子 Agent 都要重新付一遍启动成本——系统提示词、角色定义、工具清单、记忆加载,这些跟任务难不难无关,跟开了几个头有关。六个 Agent 并行跑,等于同时有六份系统提示词在计费。

所以分流要有依据。我按任务体量分成小、中、大三档:小任务单 Agent 直做,省掉的就是那份重复的启动成本;中等任务才拆,取的是并行带来的隔离收益。大任务必须拆,因为单条历史会滚到装不下,装不下就跑不完,这一关排在钱的前面。

想知道该不该拆,去算两笔账:并行省下的时间乘以单次成本,对比多出来的启动成本乘以并行度。这个算式不复杂,但它要求你先知道一份启动成本是多少,而这一步恰恰是大多数人没做的。

并行度有个够用的上限,往上加未必划算。子 Agent 开多了,协调它们本身要消耗轮次,而协调用的上下文同样是钱。判断并行度该停在哪,看并发再往上加之后单任务成本还往下不往下,跑到第几个子 Agent 只是这个判据的结果。这一条口径要先说死:同一任务重复跑多次,取单轮成本的中位数,均值会被一次偶发的高开销带偏。

主 Agent 端到端 token 从 708,783 降到 315,266,降幅 55.5%,口径是主 Agent 的端到端总量。同一条链路上的轮次从 17 降到 9,降幅 47%。

账单里还有一项极易算漏:子 Agent 单轮的固定开销。改造前单个子 Agent 起跑那一轮常驻几十万 token,改造后单次少掉约 20,000 token,口径是同一角色子 Agent 起跑轮次的常驻 token 差值。轮次砍掉近一半之后,这份固定开销要在两个地方各算一遍——开得少,每一份也薄。

该不该拆开,我只看两个子任务之间要不要交换大段内容:要交换的该放一起,我按领域拆而不用步骤拆。按步骤拆出来的子 Agent 往往要共享同一段历史,历史跟着复制一份;按领域拆则各自持有自己的上下文,重复的部分少。

三样东西把账单顶上去#

按我拆出来的账,把账单顶上去的是三类东西:常驻上下文、每轮重复打包的历史、每次工具调用的四份固定开销。这三类跟模型贵不贵关系不大,改的是组织方式。

三类里最难压的是第三类。前两类改的是上下文怎么组织,组织方式一变,后面每一轮都跟着变便宜;第三类只由工具清单和调用次数决定,跟组织方式没有关系,能靠单点优化解决的只有前两类。分不清这三类,优化动作就会全打在同一处。

成本项谁把它拉高典型症状
常驻上下文系统提示词、工具清单、全量加载的记忆从开跑那一刻就贵,之后每一轮都带着它
历史消息滚雪球式累积、每轮完整重打包越到后面越贵,跟任务难度无关
工具调用Schema、决策轮次、返回结果、下一轮上下文单次最贵,且随调用次数线性增长

工具调用这一项要单独解释一下。一次调用其实要付四份钱——工具定义本身占的 token、决定要不要调用的那轮推理、返回的结果,以及结果塞进下一轮的上下文。前三份是一次性的,第四份会一直跟着往下滚。所以「删掉不用的工具」和「压缩工具返回」看似两件小事,差出来的是一整档成本。

压历史的办法要往摘要上找,截断只会丢掉中间的约束,约束一丢,模型在后面会重新犯一遍已经纠过的错。我按层摘要:每过若干轮把那段已经完成的对话压成一段要点,要点里只留结论、约束和待办,丢掉过程,再配一个检查点接上,省掉从头重放那一步。

历史这一项有个特点值得记住:它的增长跟任务难度无关,只跟轮次有关。同一个任务,模型更聪明、轮次更少,历史反而更便宜。这就是为什么「换更贵的模型」在某些任务上总成本会下降——它压缩的是轮次,而轮次才是历史的乘数。

轮次上限是轮次这一项里最省钱的配置,写出来只一行,省下的是一整档。轮次这一项能贵到什么程度,看一个极端样本就够:有个 Agent 拿着一件本该很快收工的事一直空转下去,每一轮都要把历史重新打包一遍,直到人出手才停。模型够用,缺的是有人给它写一条停止条件。轮次上限因此不该被当成安全护栏,少写这一行,账单就往上翻一整档,零头谈不上。

工具清单既是成本项,也是安全边界,算账时这一点常常没人算进去。角色专属的工具白名单是一举两得的做法——一个只做数据分析的 Agent,不需要看到写库和删除的接口。

把工具数砍半,省掉的就是每轮都要付一遍的 Schema 成本。一个挂了 40 个工具的 MCP Server,光工具定义每轮约 10 到 15KB,砍半就是每轮省下 5 到 7KB,顺带的好处是模型不会误调用它本来不该碰的东西。降本和安全在这里是同一个动作,也是这套做法值得优先做的原因——它不用先做测量就能开始。

工具描述本身也占 token,而且每一轮都带着。写工具说明我只按「够用来决定要不要调用」这个标准控长度,详细参数语义放到按需加载那一层。一份几十个工具的清单,说明每段多写一行,这一轮就多付一行的钱,乘上整条链路的轮次不是小数目。

选模型要先定线再挑模型,顺序反了就在质量和延迟上反复返工。我的做法是先定质量下限、p50 与 p99 延迟、单次成本预算三条线,再拿生产查询的分布去扫模型和推理档位,三条线同时过线才入选。

换模型我只在这种情况下做:把规则性强、轮次又多的子 Agent 换成单价更低的一档。自动化测试、视觉还原对比这两个角色就是这么处理的,替换之后它们的模型成本降了约 64%,口径是同一工作流下替换前后的模型成本,省下的幅度随修复轮次倍增。前提是先把角色切干净:只有当某个 Agent 的职责足够窄,判断用不上来回翻上下文,才敢给它配便宜模型。

高德那条经营分析线上也有一组可对照的量级,它量的东西跟 token 不一样,是引用来源条数和最大引用深度。同样是只改上下文的组织方式,这一组也能压下来。

高德本地生活分析 Agent 的批量交付里,来源从 731 条收敛到 179 条,口径是一次返修实际携带的引用来源条数,从改动之前的全部来源,收敛到改动范围内的引用来源。同时把共享引用压平,最大引用深度从 13 层降到 4 层,指的是一条引用最多还能往下追几层。

两项压下去之后,批量效率约为人工的 22 倍,这个倍数是折算出来的。一轮全行业报告的产出时间压进了 1 小时,人工侧按每个业务单元约 1 小时、一轮约 22 个业务单元折算,等于 22 人时,分母是人工单单元时长乘单元数。

常驻上下文那一栏乘在轮次上,压它比压单轮那一块划算,来源收敛靠的正是这一条。省掉的是每一轮都要重复携带的那部分材料,摊到几十轮上不是小数目。

优化动作它省的是哪一块
渐进式披露常驻上下文
子 Agent 摘要化后续轮次的重复计费
代码图谱先行盲搜带来的反复重打包
确定性操作脚本化推理轮次与串行时间
工具白名单与模型分层Schema 成本与模型单价
无依赖调用并行历史重复打包与等待

上下文要先有预算,才有加载策略#

知道贵在哪之后,加载策略可以定得很机械:任何东西进上下文,都要先有预算,再看它有没有用。 渐进式披露是这条原则的直接推论:安装二十个 Skill,不等于初始化的时候把二十份正文都读完。

落到具体写法就是:能力正文只留骨架——触发条件、输入输出、关键约束;规则模板、示例和长篇说明放进按需加载的那一层。骨架短了,常驻成本就低,细节还在,需要的时候能取到。

发现层、入口层、执行资源层要分开。初始化只加载能标识「我有什么能力」的那层元数据,正文和规则压到真要用某个 Skill 时才进来。Skill 的个数因此不再和启动成本绑定,这是我后来把 Skill 拆得越来越细的底气。

预算必须写成数字,否则等于没定。我给 Skill 这一类定的额度是:装 20 个 Skill,初始化那一次只放进来 1000 到 2000 token,约为单体式提示词的一到两成,比全量常驻少约 90%。这个数留的是可算的余地,它让「这一次初始化有多重」随时能算出来,算得出来才知道还能往里放什么。

一条规则在多少个任务里用得上,是决定它进不进正文的唯一标准。用得少的写进正文,代价是它在每个任务里都要被加载一次。规则越多、越只适用于少数任务,这一笔浪费越明显。

长期记忆按同一套逻辑走,只是多一层。先读几十行的 INDEX——标题、标签、摘要——再按相关度取命中条目里最靠前的几篇读正文,原始证据不进上下文,需要时按路径单独取。这样走下来,记忆的价钱跟文档总数脱钩,跟回源路径本身挂钩。

SQL 场景里那组数字挺能说明问题:候选的表结构与取值样例合计约 3 万行,实际进上下文的是按相关度排在前 200 到 400 行的切片。选得准不准比模型强不强更决定结果,全塞进去注意力就摊薄了,结果反而更差。

大 payload 不要待在最长的生命周期里#

另一类成本来自 payload 的生命周期错配。一个大体积的返回值不该落在生命周期最长的一层,那层是主 Agent 的历史,后面几十轮都要为它重复付费,错位的代价一路滚下去。

处理办法是给取数类操作配短生命周期的子 Agent,让它在自己那一层里消化原始 payload,只把结构化摘要交回上层。我手上有一条可参照的对照:某个拉取外部系统数据的链路,优化前单轮输入 1,030,000 token,优化后 634,905 token,降幅 38.4%。

1,030,000 是常态,它来自把需求描述、设计稿节点树这些原始 payload 全程留在主 Agent 上下文里。换成短生命周期子 Agent 消化之后,这一轮只剩几万 token。口径也要说清:首次调用两种方式花的基本一样,38.4% 全部来自后续轮次不再携带原始 payload。

摘要会压缩掉事实,所以配套一条硬规则:摘要必须带上原始证据的访问路径。省下来的是常驻成本,丢掉的东西要能按路径取回来。丢关键事实的摘要比不摘要更贵,它会用一次返工把省下的钱全还回去;同一份信息在最上游收集一次、通过文档传给下游,也比让每个 Agent 各自重新取一遍便宜。

判断一个 payload 该不该摘要,我只看一件事:它后面还要用上几次。只用来判断「有没有数据」的返回值,摘要成一句结论就够;后面还要逐条处理的,反而要付两次钱,一次压缩,一次重新取。这类内容整体外置、按需取,是最省的一档。

稳定前缀解决的是每轮开头都一样的那一段。进度同样不该留在上下文里:主 Agent 每轮先去读进度文件,不回放历史,阶段切换只输出一行状态;会话中断之后能直接读文件恢复现场。

能被脚本拿走的,别让它占推理轮次#

有一类钱花得非常不值:让 Agent 干脚本早就能干的活。判据只有一条:这段活是确定性的,还是需要判断。迁移、编译、批量重命名、跑测试,这些写成脚本的执行成本和正确性都远好过让模型一轮轮地按步骤做。

确定性操作脚本化还有一个隐藏收益:它把「做没做对」变成可验证的。脚本的退出码是一个硬信号,模型自己一步步做,做完之后没人知道它到底做全了没有。把这一类动作移出模型,等于同时买到成本、速度和确定性。

代码检索是这一类的典型。让 Agent 在仓库里盲搜,代价是反复读取和反复重打包;换成先用代码图谱锁定文件范围再读那几行,一次到位。

我量过一次同任务的前后对照。总 token 从 875,352 降到 676,987,降幅 22.7%,口径是同一任务改造前后的总量;输入侧从 868,544 降到 670,281,降 22.8%,口径相同。缓存命中那一段从 820,175 降到 609,903,未命中反而从 48,369 升到 60,378,命中率从 94.4% 掉到 91.0%。输出基本不动,这一点说明省下的钱来自路径,少走的正是那一段没必要的检索路径。

并行是整条链路上最便宜的一条。没有依赖关系的工具调用放进同一轮执行,它降低的不只是等待时间,更主要的是历史重复打包的次数。判断依赖的办法是问「后面的调用要不要用前面的输出」,流程习惯只能当参考,很多看起来有先后关系的调用其实只是习惯。

脚本化还有个副作用要提前防:脚本的输出往往比模型需要的大得多。我的习惯是让脚本默认只回结论和失败明细,成功时静默。这一条改动很小,但在调用频繁的链路上,省下来的量级跟换模型差不多。

命令行输出也要压。进程列表、仓库状态、测试回显原样丢回去极占上下文,压过再给模型能省一整块。我实测的两个点:进程列表压缩掉约 98.9%,仓库状态压缩掉约 31%,口径都是压缩后的字符数除以压缩前的字符数。命令之间差异极大,按各自实测值分别记账,别信统一数字。

上下文里只留判断要用的那一部分,其他都外置到文件,这是最便宜的一档优化。大段中间产物、检索到的原始片段、过程中的日志,都写成文件留个路径,需要时再取。

数字口径能用来做什么
单轮输入降 38.4%同一条取数链路改造前后的一轮判断该不该做摘要化
总 token 降 22.7%同一任务、同一版本的前后对比判断该不该上代码图谱
整体降 50% 到 65%分项降幅代入批次消耗分布反推排优先级

所以我的习惯是:降幅先分证据等级,再决定它能被用来做什么。单点明确的对照可以直接拍板,前后时间的对比用来看趋势,反推估算只用来排优先级。

预算定住之后,真正卡住生产的是谁来判定这次做对了没有,构建过没过、测试过没过,那是环境与验证要解决的。成本数字要带着口径才比得出结果:工具栈、任务分布、模型档位都不一样,跨团队对比没有意义。能迁移的只有那句判断——先定预算,再谈加载。预算立住之后,常驻上下文、历史重打包、工具 Schema 这三处的裁法就都有依据了。