工具目录的生命周期

9 分钟读完

O22
运维 · 智能体运维:部署与运营

工具目录的生命周期。

加一个工具感觉上是「只增不减」,其实不是:工具选择是整份目录的函数,所以第四十个工具会改变那三十九项本来跑得好好的任务上的行为,而唯一能看出这件事的地方,是一个没人在盯的成功率。每个组织最后都会得到一份「什么都删不掉、什么也说不清为什么在里面」的目录。请把这份目录当作智能体所执行的每一项任务的、带版本的、受评测的依赖来对待——配上准入闸门、下线路径与「按任务的视图」——否则它会变成你系统里移动最慢、最没人认领的那份提示词。

STEP 1

一次新增,是对每一项既有任务的一次变更。

模型是把所有描述放在一起读、再挑最匹配的那个来选工具的。这让「选择」成了一场竞赛,而引入一名新选手,会改变那些它本不该参加的比赛的结果。这个失败既不冷门,也不是模型笨:

  • 描述重叠。一个新的 search_documents 挨着既有的 query_knowledge_base 摆着,于是原本路由正确的调用现在在两者之间分流了。两条描述单看都没错;合起来就有歧义——那正是 工具设计反模式 所归档的那个失败。
  • 注意力稀释。目录规模越过某个点之后,选择准确率是全面下滑而不是只在新工具上下滑;而且最先下滑的,是那些描述最不具辨识度的工具。
  • 前缀失效。工具定义位于每一次请求的缓存前缀里。加一个就改变了前缀,于是目录变更后的第一次调用,对所有人来说都是一次完整的缓存写入——而这些定义此后会在每一次运行的每一步上被重发,永远如此;那是一笔永久性的「每步成本」,不是一次性开销。见 提示词缓存
  • 名称与 schema 冲突。来自不同服务器的两个同名工具,或者同一个名字在不同环境里参数形状不同,都会产出看起来像模型错误、实则不是的失败。

由此推出的运维结论是:新增一个工具的评测闸门,是那份既有的评测集,而不是一份用来展示新工具能用的新评测集。谁都能演示自己的工具被正确调用了;闸门要回答的问题是「本来就能跑的那些任务现在还跑不跑得动」。这与一次 质量回归 检查是同一个形状,只不过被用在了一次看起来不像代码变更的变更上。

STEP 2

准入:一个工具要么带着证据来,要么就别来。

多数目录是靠堆积长起来的——某个团队需要一项能力,接上去了,而没有人对这个总体负责。一道准入流程花不了多少钱,却是唯一能让这份目录保持可评审的东西:

  • 一个具名的负责人。不是一个团队邮箱。是那个在工具报错时会被呼到、并且要为它的描述变更签字的人。
  • 它使之成为可能的那项任务,以及证明这一点的评测。如果这项能力没法被陈述成「智能体此前无法完成的一项任务」,那这个工具就只是对智能体已有之物的一层包装。
  • 回归运行。完整的既有评测集,前后各跑一遍,并把差值附在申请上。不相干任务上的下降是一项阻塞性发现,而不是一条备注。
  • 一份消歧说明。它与哪个既有工具最接近?现在两条描述里各有哪一句把它们分开?这一步防的正是那个重叠失败,而且它逼你去改既有工具描述的频率,与改新工具描述的频率一样高。
  • 一份作用域声明。哪些任务类型或哪些智能体应当看到它——默认取「能满足申请方的最窄集合」,而不是默认给所有人。

关于单个工具怎样才算好——粒度、错误信息、schema 形状——的设计指引,在 面向智能体的工具设计工具粒度 里。本页讲的是任何单工具评审都看不见的那个性质:这个集合整体还工不工作。

STEP 3

模型看到的目录,不是你托管的那份目录。

上面几乎所有问题最便宜的解法,是别再呈现一份全局目录。一份「按任务的白名单」,把「这个组织运维着多少工具」与「有多少工具在为这个决定竞争」解耦开;随着你长大,这两个数字应当急剧分叉:

  • 先按任务类型划范围。一条退款流程不需要那些部署工具。静态白名单不光鲜、确定,而且在那些你本就知道答案的场景上,胜过任何动态检索方案。
  • 再按租户与权限划。调用方无权使用的工具,压根就不该出现在提示词里;因为模型看得见的工具就是它会去尝试的工具,也是它随后要绕开的一次拒绝。
  • 只在集合确实开放时才做动态检索。按相关性挑出工具子集是一项真实可用的技术,同时也加进了一个可能出错的检索步骤——而且是静默出错、没有报错。当目录既大又开放时再用它,而不是拿它来代替「想清楚这项任务需要什么」。
  • 把解析出来的集合打在 trace 上。每次运行都应记录它实际看到的工具集与目录版本。没有这个,「智能体从上周二起就不用工具 X 了」是无解的,而你会为它搭进一天。

Skills 与渐进披露机制只是把同一个问题挪到了另一层,而不是消掉它——智能体技能 按需加载指令,但被加载进来的东西仍要争夺同一份注意力,而「要不要加载」这个决定本身就是一个选择问题,带着同样的失败模式。

STEP 4

删除是没人规划的那一部分,而它需要一块墓碑。

删掉一个工具不是加上它的逆运算,因为等到你想让它消失时,这个名字早已扩散到了你管不着的地方:缓存下来的提示词、少样本示例、存好的计划、智能体记忆,以及别的团队写的 skill。智能体会接着去调它,而一个光秃秃的「未知工具」错误,产出的是一个重试循环,而不是一次适应。

  • 分三阶段退役。公告并停止新接入;让这个工具在仍然可用的同时返回一个点名替代者的结构化弃用错误;然后再移除。跳过中间那一阶段,正是把一次退役变成一次事故的做法。
  • 给这个名字立墓碑。被移除的名字仍应解析到一个错误,说明发生了什么、该改调什么;并且要保留到「任何地方都不再可能持有对它的引用」为止。一个模型能据以行动的错误,价值高于一个干净的 404。
  • 移除一个工具同样是一次行为变更。跑与新增时一样的回归闸门。智能体可能一直在用你正要删掉的这个工具,绕开别处的某个缺陷。
  • 去找那些从没被调用过的。任何三十天内零调用的工具,要么是在每一份提示词里白交房租的死重,要么是一项模型找不到的能力——而这两者需要相反的处置,这也正是这份清单必须由人去看、而不能被自动化掉的原因。

这里的生命周期纪律,与 模型弃用与迁移 就模型 ID 所主张的是同一条:你公布出去的名字,是别人已经接下的一项依赖;移除它是一次迁移,不是一次删除。

STEP 5

给目录做版本,并把版本打在每一次运行上。

行为就是 灰度与版本化 坚持要钉死的那个(模型,提示词,工具)三元组,而工具这一项最常被放任漂浮着——部分原因是,一份在启动时从若干服务器拼装出来的目录,根本没有一件可以指着说「就是它」的产物。

  • 把目录做成一件产物。把解析出来的集合序列化——名称、描述、schema——取哈希,并把这个哈希当作版本。这样一次目录变更就成了一份人读得懂的 diff,和一个 trace 带得走的取值。
  • 像代码一样灰度它。把一次目录变更放在开关后面、灰度给一小部分流量,以评测差值设闸,并留一个改配置即回滚的路径。没有任何理由让「加一个工具」比「改一句提示词」发布得更草率,而它通常草率得多。
  • 盯住不是你写的那些漂移。一条不归你所有的描述可以在没有提交、也没有报错的情况下在你脚下改变,那正是 第三方工具漂移 的全部主题。哈希就是把它从「看不见」变成「一份 diff」的东西。
  • 按环境钉版本,并做对账。预发布与生产在工具描述上各自漂开,产出的会是「对着一份没人在跑的目录通过了的评测」。
STEP 6

去量这个集合,而不是量那些工具。

逐工具的看板是标配,而它们大多只告诉你可用性如何。真正描述目录健康度的数字是比较性的,而且几乎没人在留:

  • 选择错误率——为某项任务调用了错误工具的运行数,抽样并打标。这是你一加进重叠工具就会移动的那个数,也是唯一能抓住它的那个数。
  • 目录规模 vs 有效规模——托管的工具数,与每次运行实际呈现出去的中位数。如果这两个数随着你长大而收敛,说明你的范围划分没起作用。
  • 每步花在定义上的令牌数——目录的常驻成本,在每一次运行的每一步上支付。它通常比团队预期的要大,也是「我们加上去就是了」的老实价码。
  • 三十天内每个工具的调用次数——其中那份零调用清单要由人来审,而不是自动清理。
  • 从提出工具申请到准入的时长——因为一道没人过得去的闸门会被绕过去,而一份靠绕道拼出来的目录,正是本页想要防止的那个结局。

先做那两件便宜的事:给解析出来的工具集取哈希并打在每一条 trace 上,以及把既有评测集设为新增工具的闸门。这两件加在一起,就把目录从一份看不见、没人认领的提示词,变成了一项你能 diff、能灰度、能回滚的带版本依赖——而它们只花你一个下午。在那之后,先按任务类型划范围,再去够动态工具检索;每移除一个名字就给它立块墓碑;每月拉上一个人一起审那份从没被调用过的清单。相关:工具发现与文档 讲模型怎么找到它需要的东西,灰度与版本化 讲这份目录只占三分之一的那个三元组。