为 AI 智能体臃肿的指令文件进行无损瘦身
一个始终加载的代理指令文件增长到了 33,000 个 token。以下是我们如何在不删除任何一个句子的情况下,将其削减为一个 10KB 的路由中心,以及我们如何证明没有任何内容丢失。
大多数 AI 编码助手会在每个会话中加载一个项目指令文件。它最初只是一页约定,然后慢慢变成了团队的杂物抽屉:部署手册、事故复盘、模式迁移笔记、前端怪癖。我们的文件达到了 253 行和 67KB,大约 33,000 个 token,在助手读取任何一行代码之前就被注入。本文描述了将其削减至一个 10KB 路由中心的重构过程,必须保留的安全规则,以及证明此次迁移毫无损失的验证工作。塑造整个过程的约束是:任何内容都不能被删除,只能被移动或合并。
指令文件为何膨胀:一条没有目的地的规则
文件变大并非因为有人粗心大意。它变大是因为一条听起来很负责任的规则:“每当策略或设置发生变化时,立即更新指令文件。”这条规则没有路由机制。每个项目都将其操作细节添加到那个保证会被读取的文件中,因为那是未来的智能体唯一保证会查看的地方。
这就是在您自己的设置中值得注意的结构性原因。一个采用只追加文化的、总是被加载的单一文件会单调增长。没有人能够移除一条警告,因为每条警告都是通过过去的某个事件才被加入的。一年后,我们的表格单元格里包含了十个段落的历史记录,而智能体在每次会话中都要为此支付 token 成本,即使这些会话从未触及那些段落所描述的子系统。
浪费的不仅仅是金钱。一个 33,000 token 的前序内容会分散注意力。有证据表明,模型会忽略掉埋藏在长上下文中间的指令。我们最需要智能体遵守的警告,正在与数月前关于某个爬虫怪癖的段落争夺注意力。
按主题拆分,但保留停止关卡
解决方法显而易见:将细节移入按需阅读的主题文档中,并保持中心文档的短小。我们最终得到了八个运维文档(部署、云资源、管理后台、授权、语音管道、数据收集、爬虫、夜间批处理),外加一个与迁移文件本身放在一起的迁移自述文件。每个文档完全拥有其领域。中心文档为每个主题保留一行文字和一个指针。
有两项设计决策比文件布局更重要。
首先,索引不能只是一个客气的链接列表。在时间压力下,执行者会粗略地看一下列表然后开始写代码。我们将其写成一个条件路由表:“修改授权或许可席位逻辑:你必须先阅读 docs/ops/license.md。”这种差异听起来无关紧要。在实践中,表述为前置条件的指令会被遵守;而表述为参考资料的链接则会被跳过。我们还加了一行,告诉执行者在不确定某项内容的位置时,应该在 docs 目录中用 grep 搜索关键字,而不是通读整个文件。
其次,这也是防止事故的部分:一些警告决不能移动。如果“部署前运行待处理的模式迁移”只存在于执行者可能不会打开的文档中,那么总有一天,执行者会在没有阅读它的情况下进行部署,而那个警告所记录的事故就会再次发生。我们在中心文档中保留了七条这样的警告,放在一个名为“停止关卡”的标题下,每一条都是一个总结性的不变量,其细节则委托给了其他文档:
- 任何部署前都需检查迁移状态;如果不清楚,请勿部署。回滚时应先恢复镜像;然后才考虑恢复模式。
- 更改托管的运行时环境变量时,只能使用增量式标志;全量替换的变体会静默清除不相关的配置。
- 绝不能将用户的语音转录、翻译或机密值写入日志、文档或提交记录中。
- 数据库 IP 允许列表标志会覆盖整个列表;应先读取当前列表,然后修补其并集。
- 所收集音频的保留和同意规则是不变量;在接触它们之前,请阅读数据文档。
- 针对生产存储的破坏性或覆盖式命令需要先确认目标、影响范围和回滚方案。
- 绝不能绕过、删除或削弱付费端点的身份验证、速率限制或故障关闭的试用逻辑。
我们使用的筛选规则是:如果忽略某条警告会导致服务中断、数据丢失、合规性违规或客户可见的故障,那么它就留在中心文档中,而如果忽略它仅仅是浪费一个下午的时间,那么它就移出去。依据是忽略的代价,而非使用的频率。
在中心文档的最顶端,所有内容之上,还有另外一行字:当文档与代码不一致时,代码是事实;更新文档,绝不要为了匹配过时的文档而去“修复”正常工作的代码。当执行者发现矛盾并朝错误的方向解决时,一个分裂的文档系统会遭遇最严重的失败。
证明迁移未丢失任何内容
“我们移动了所有东西,相信我们”——这不是验证。我们最初的计划是使用 grep 抽查一小部分独特的标记。一位审阅者指出了抽样无法发现的三种失败模式:一个标记可能幸存,但其所在的句子却被删减了;即使文本从未离开旧文件,新旧文件的并集也能通过检查;以及文本可能被放到了错误的文档中。抽样只能证明样本本身,证明不了别的。
我们实际运行的,是全自动化的检查:
首先,从编辑前创建的原始文件快照中,提取每一个内联代码片段。这意味着每一个用反引号引起来的命令、标志、路径和标识符。我们得到了 583 个独特的片段。在对空白字符进行规范化后,每一个片段都必须作为固定字符串(而非正则表达式)出现在新文档的并集中,因为像 $1::text[] 这样的片段充满了正则表达式的元字符。
其次,提取每一个包含警告标记的句子(即我们文档中使用的表示禁止、必需、小心的词语以及警告表情符号)。我们得到了 61 个句子。因为这次迁移是逐字移动而非重写,所以在规范化之后,每个句子都必须原封不动地存在于某个地方。这就是让这项检查如此强大的诀窍:一个被移动的句子会随之携带它的条件、严重性和原因。只有三个句子未能通过检查,且每一个都有记录在案的理由:一个与重复内容合并了,一个被拆分到了多个表格行中,一个的内部交叉引用被有意重写了。任何没有理由的失败都会导致停止发布。
第三,将字节数统计作为异常检测器,而不是作为准入条件。新文件的总大小是原始文件的 129%(因为页眉、指针和新增的“停止门”部分增加了文本)。如果这个数字是 60%,我们就会去追查原因。我们特意没有将其设为通过/失败的阈值,因为一次合法的去重操作可能会缩减总大小,而一次错误的移动也可能在保留了错误文本的情况下通过阈值检查。
片段检查立刻就证明了其价值。有一个句子,即一条关于 web server 子命令不同于一个名称相似的 realtime 子命令的注释,在迁移图中掉进了空隙。没有人能在两次阅读 67KB 的内容后发现一个缺失的子句。对 583 个片段进行的固定字符串比较在几秒钟内就发现了它,我们在发布前恢复了它。
使用全新的代理对路由进行烟雾测试
文本验证证明了内容的保留,但它不能证明新布局是否有效。为此,我们给一个全新的代理会话只提供了新的 10KB 中心文件,并问了三个问题:在修改席位许可逻辑之前,你应该阅读什么?你如何部署管理 Web 服务?以及,请清除生产环境中的缓存。
前两个问题的回答都路由到了正确的文档,并引用了迁移门。第三个问题才是值得我们为此设计的:代理拒绝执行任何操作,引用了破坏性工作中止门,询问了具体是哪个环境以及哪个键前缀,并提议进行范围性删除,而不是完全清空。这种拒绝正是我们将中止门保留在始终加载的文件中的全部意义所在。如果你的烟雾测试不能让代理拒绝某些操作,那么这个测试就太宽松了。
保持精简:修复规则,而不仅仅是文件
最后一次变更是为了防止六个月后重蹈覆辙。最初的增长规则(“始终更新指令文件”)被一个路由规则所取代:常规规则、坐标和停止门放入中心枢纽(hub),硬性上限为 120 行和 10KB;操作细节、标志和事件历史放入拥有该领域的主题文档中;决策理由放入设计日志中;代码本地陷阱则放入它们所保护的代码旁边的代码注释中。当中心枢纽超出上限时,要求的响应是将内容拆分出去,而不是协商上限。
最后提供一些数字以供衡量规模。中心枢纽的大小从 67KB 减少到不足 10KB,在每次 agent 运行时,会话上下文都节省了大约 28,000 个 token。验证套件大约是 40 行 Python 和 shell 代码。整个迁移过程,包括三轮审查和验证工具的开发,花了一天的工作量。如果你的 agent 的指令文件已经超过几千个 token 并且还在持续增长,那么“瘦身”的成本很低,节省的 token 会每日复利,并且通过范围级别的验证,你无需在小文件和安全文件之间做出选择。