修复智能体已经做过的事

11 分钟读完

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

修复智能体已经做过的事。

你真正会碰上的智能体事故不是一次宕机——而是某个星期二,有人发现某个工具从本月 3 号起就一直在写入某种细微错误的东西,于是你建的那套遏制机制在四秒内触发,对付的却是三周前就已落地的故障。关于智能体事故,公开写下来的几乎全是怎么把循环停住;几乎没有人谈已经提交出去的那四千次错误动作,而修复它们是另一门手艺:每一次错误都是一个判断而不是一行记录,所以你没法回滚一张表——你必须重新判断,规模化地判断,而且是在与当初制造错误相同的条件下判断。

STEP 1

智能体的故障总是在你察觉之前几周就抵达,因为它们语义上是错的、语法上却挑不出毛病。

作业崩了会呼你。写入格式不对会被约束挡下。而一个悄悄判断错了的智能体,产出的是满足你手上每一条 schema 的规整记录,并且以队列供给的任何速率持续产出。检测既不免费也不迅速,所以请按照「发现至少滞后于第一次错误动作数天」这个前提去设计修复路径。

会出现三类问题,它们需要不同的查法:

  • 错得合法的写入。退款按错误的档位发出、工单被路由到错误的队列、记录被一个从错误字段推导出来、看上去很合理的值更新掉。一切都能通过校验,只有领域层面的核查才找得出来。
  • 结论对、理由错的动作。这类能通过抽查、却过不了全量复核。在有人复核过的那些案子上结果碰巧是对的,这意味着样本低估了总体——故障在智能体所应用的那份政策里,因此它的发生率取决于案件构成,而不取决于智能体。
  • 没有发生的动作。隐形的那一类,而且通常是最大的一类。一个不再对某一类案子做升级处理的智能体,不会在任何地方留下记录;你只能靠数「本应存在多少」来找到它,而不是靠检查「已经存在的是什么」。如果你的监控只看已发出的动作,这一类在结构上就是检测不到的——数数那一侧见质量回退检测

从上一次真实事故里,把你的检测滞后当成一个数字写下来,就一次:从第一次错误动作到第一个人起疑,隔了多少小时。本页所有的设计取舍都由这个数字定尺寸,而多数团队会发现它是以周为单位的。这个数字乘以你的动作速率,就是你应当有能力执行的那次修复的规模。

STEP 2

按决策而不是按行来划定范围——如果你没法用一条查询把受影响的运行枚举出来,那件事本身就是结论。

会议室里的第一个问题永远是「有多少受影响?」,而第一个回答永远是「我们正在汇总」。这两句之间的间隔,就是这次事故的全部成本,因为下游的一切——通知、与监管方的对话、修复——都在等那个分母。

行级取证到不了那里。错误的退款和正确的退款在结构上没有区别;它们共有的是:在某个提示词版本、某个工具版本、某次检索结果、某个时间窗之下做出的判断。所以你需要的那条查询是针对决策的,而它要求这些字段存在:

run_id
started_at, finished_at
principal / on_behalf_of
prompt_version, model_id, tool_versions[]
inputs_digest, retrieved_doc_ids[]
decision (the classification, not the prose)
actions[]  -> external_ref, reversible: yes|compensable|no

有了它,划定范围就是一次过滤,几分钟的事。没有它,划定范围就是一场针对保留期只有 30 天的厂商日志的考古,而你事故报告里诚实的那个版本会写「大约」。请特别留意最后一个字段:可逆性是在动作发生的当时被记录下来的,而不是事后由凌晨两点值班的人去推断的。这与幂等性与重试为了保证写入安全而要求的,是同一本台账,只是在干第二份活儿——而那些决策级的字段,正是决策回执与审计存在的意义所在。

先往宽了划,再往窄了收,两个数都留着。从你守得住的最宽边界起步——凡是在可疑提示词版本下跑过的全都算——然后用领域谓词收窄。宽的那个数是你的暴露面,窄的那个数是你的工作清单;在还没为收窄给出理由之前就只报窄的那个,正是第二次事故诞生的方式。

STEP 3

撤销是一张分类表,不是一个按钮。

「回滚吧」这句话是从数据库那里借来的,而它只在损害中「住在你自己数据库里」的那一部分上经得起现实。把受影响的动作分进四个桶,因为每个桶的代价、责任人与前置时间都不同:

  • 你自己拥有的内部状态。重算或恢复。便宜,而且这是唯一一个「回滚」这个词与人们想象的意思相符的桶。
  • 有补偿动作可用的外部影响。退款可以重新扣回、取消的订单可以重下、工单可以重开、权限可以重授。这个桶最吃时间:补偿是一套业务流程,带着自己的限流、审批要求与失败模式,而跑上四千次补偿本身就是一份智能体工作负载,风险一样不少。
  • 不可逆的影响。已经送达的邮件。已经清算的付款。发到客户频道里的消息。被披露给了不该看见的人的数据。这里没有一样能撤回;唯一可用的动作是通知,以及在适用时的重大事故报告。这个桶的大小,早在几个月前的设计决定里就已经定死了——这也正是「不可逆动作要先确认」的理由,见人在回路
  • 派生状态。人人都会忘掉的那个桶。错误的动作被总结进了报表、被嵌入进了索引、被写进了智能体记忆、被缓存成了答案,还被别的智能体读到并据此行动了。只修源头而不重建派生物,等于让错误在第二个总体里继续活着——索引那一侧的机制在重建索引与嵌入迁移里,记忆那一侧在针对智能体记忆的删除请求里。

动手之前先把分类做完。伤害最小的次序通常是:冻结,然后通知不可逆的那个桶,然后做补偿,最后重建派生物——而这几乎绝不是第一个小时里感觉最有产出的次序,那种感觉会让你先去修那些容易的内部记录。

STEP 4

重新判断要用的是当时生效的政策,而不是你刚刚修好的那一份。

对受影响集合里的大多数条目,你并不知道正确结论是什么;你只知道你手上这个可疑。要产出正确答案,就得把这次判断再做一遍——而这件事最天真的做法,也就是拿今天的智能体去跑上个月的案子,错得很容易被忽略。

有两个原因。第一,案子必须对照当时适用的规则重新判断:3 号那天的价目表、修订之前的资格政策、客户当时的状态。今天正确的判断,可能正是对一次当时应作判断的错误修复。第二,你需要把「智能体坏了」与「智能体是对的、坏的是别的东西」分开,而这只能靠重放原始输入来做到——这要求你按运行钉住并记录了提示词、模型与工具的版本,正如发布、版本化与钉版所主张的,也要求那些输入仍然取得到,而不是已经被就地覆盖掉了。

三条规则让重新判断值得信赖:

  • 对照钉住的版本与归档的输入重放。如果当初是检索喂给了这次判断,那么被检索到的文档就是输入的一部分,必须一并重放,而不是重新取一次——重新取会悄悄把今天的语料替换进去,把你的修复变成一次新实验。
  • 别让同一台机器在无人监督下给自己的修复打分。要抽样。抽样率取决于后果,但「修好的智能体把所有东西重跑了一遍、并报告一切正常」正是第二次事故的形状。批量跑之前手工复核一份分层样本,跑完之后再来一份。
  • 把修复本身当作一次独立的决策记录下来。同样的字段,链接到它所取代的那次运行。日后会有人问你,为什么账户 44219 被退了两次款,而答案必须是一份记录,而不是一段回忆。
STEP 5

把回填当成一次迁移来跑,而不是当成一次事故。

到这一步,紧急状态已经结束:循环停了、范围清楚了、结论定了。剩下的是一次针对生产的批量变更,而「时间压力下的批量变更」的失败模式,文档齐全,也完全可以避免。哪怕它感觉很急,也请按迁移的常规礼数去办。

  • 先做产出差异的空跑,并在任何写入之前由人复核。这份差异同时也是你拿去给业务负责人看、换取放行的那件东西,所以它的价值收回了两次。
  • 用原始 run id 推导幂等键。修复会被打断、会被重试,也会被一个不知道有人已经开跑的第二个人再跑一遍。一个形如 repair:v1:<run_id> 的键,把「退两次款」从「不太可能」变成「不可能」。
  • 有界的批次,配上急停开关与逐批核验。急停开关一样的直觉,只不过这次用在你自己的修复作业上——毕竟它也是一个自动化流程,在无人逐条盯着的情况下做出几千次有后果的写入。
  • 尊重下游的限流。对着一家支付处理商打四千次补偿 API 调用是一次流量事件;十分钟内发出四千封邮件是一次投递事件,还会顺带把你的域名弄进黑名单。
  • 把与客户的联络合并。每位受影响的人只收到一条准确的消息,而不是每一次错误动作一条。一个收到九封致歉邮件的客户,从你这次事故里学到的东西比你打算披露的要多,而接下这通电话的人会记住那个「九」。

把修复拆成风险画像不同的两个作业:一个计算作业,负责判断本来应当发生什么,并把结论写进一张没人读的暂存表;一个执行作业,从那张表里往外做。计算作业可以随便重跑、可以复核、可以做差异;执行作业则小、笨、幂等、可中断。把两者焊在一起,正是把一次修复变成一次事故的做法。

STEP 6

决定上面这一切可不可行的,是那些很便宜的准备。

上面每一项能力,都是你在一切正常时所做决定的属性。事故当中现补是没得选的,所以有用的问题是:哪些便宜到干脆就一直备着。

  • 一本按运行记录的副作用台账,每一条都带可逆性分类。本页杠杆最高的那件产物。它是一份写入时的记录:我们碰了哪个外部的东西、它的引用是什么、我们要怎么把它撤回来?在设计时记下这些,只需在工具定义上加一个字段;在事故中把它重建出来,要一个星期。
  • 每个写入工具都配一条撤销路径。新增一个工具时,连同它的撤销方式一起写明——一次补偿调用、一次恢复,或者一句明确的「没有,这不可逆」;而后者在设计评审上被迫大声说出来,本身就很有价值。
  • 保留期要长过你的检测滞后。检测滞后三周、追踪只留 14 天,意味着这次事故在构造上就是划不出范围的。对智能体决策而言,保留期首先是一项修复能力,其次才是一项合规要求——取舍在追踪采样与保留里;并请注意,采样过的追踪即便让监控变得负担得起,也会让修复变得不可能。
  • 一份独立且范围受限的修复凭据。修复作业需要智能体从未拥有过的权力——冲正已清算的交易、往已关闭的记录里写——而智能体拥有的一些权力,修复作业不该有。两个身份、两套范围,都要审计。
  • 演练一次。挑一个无聊的错误写入场景,在预发环境里注入,然后把整条路径跑完:检测、划范围、分类、重放、分批、通知。第一次得知「当初检索到的文档从来没被归档」的那一天,不该是它真正要紧的那一天。

如果只做一件事,就把可逆性从一个假设变成一项被记录下来的属性。每个写入工具在定义时就声明它的影响如何撤销——恢复、补偿调用,或者不可逆——而每次运行都保留一本带着这个分类的副作用台账。就这一条纪律,能把决定一次智能体事故走向的那两个问题,从研究课题变成查询:有多少受影响,其中又有多少是我们真的放得回去的。本页其余的一切——对照钉住的版本重放、先计算后执行的分段、合并后的通知——在你能回答这两个问题之后,都只是直截了当的工程。而那本台账里不可逆动作的计数才是那个真正的数字:它是你的最大暴露面,它是由设计决定而不是由事故定下来的,而且它是你今天仍然改得动的唯一一个。