智能体失效多源于周边,而很少是因为模型层
作者: CBISMB
责任编辑: 邹大斌
来源: CBISMB
时间: 2026-08-21 12:11
浏览: 1
点赞: 0
收藏: 0
模型受到关注,但在我运行的集群中,严重故障来自调度器、shell假设、过期凭证和从未真正运行的安全检查。
我在一台笔记本上运行49个定时AI智能体。另外24个放在旁边,被刻意关闭了。
这个区别比听起来更重要。经过十八个月,我不再对模型错误感到惊讶。模型会误解请求,偶尔产生自信的胡说八道。我对此有预期,也对此进行测试。
仍然让我措手不及的是,这些错误多么罕见地导致停机。我的集群中没有任何严重故障始于一个糟糕的模型回答。它们始于堆栈更底层:一个从未被加载的任务、一个只存在于我终端中的shell命令、一个在两次运行之间过期的凭证、一个基于操作系统不附带工具构建的超时保护。
智能体没有失控。是Unix出了问题。
大多数智能体集群指南都是从拟议架构向前编写的:一个路由器、专业智能体、工具和记忆、框间干净的箭头。我自己也画过这些图。但我实际使用的每条设计规则都是从一次事故中向后推导出来的——在箭头停止移动、仪表板仍然显示绿色之后。
一周内发生的三次故障解释了这个差距。
实际崩溃的是什么——以及为什么都不是模型的错
第一次故障在我检查的每个地方看起来都正确。任务存在于磁盘上,配置有效,符号链接指向预期目录,脚本手动调用时能运行。但它从未被注册到调度器。我写了定义,把它移到位,确认文件在正确的位置,却从未问过调度器是否已将其加载。
好几天它什么都没做,也没产生任何错误,因为不存在可以失败的进程。我检查了期望状态,将其误认为实际状态。
这听起来令人尴尬地基础。但它也是经典系统问题的一个缩影。Richard Cook关于复杂系统如何失败的论文指出,事故通常被归因于附近一个明显的诱因,尽管失败实际上源于多个通常被容忍的条件叠加。我的附近诱因是未加载的任务,但围绕它的条件更重要:手动执行通过、文件树看起来正确、没有新鲜度告警、我的审查止步于配置。
我现在将调度器定义视为关于行为的声明,而非行为的证据。证据是活动注册表、上次启动时间、上次成功完成时间,以及通过调度路径产生的新鲜工件。文件能告诉我什么应该运行,不能告诉我什么正在运行。
第二次故障始于一个我每天使用数十次的智能体命令。我将可执行文件及其参数缩短为一个别名。在我的终端中,这个短名称有效。在非交互式脚本中,它不存在。
失败的命令本应让这一点显而易见。然而包装器继续执行,运行了一行无害的簿记命令,并返回了那行命令的状态。调度器记录了退出码0。智能体没有运行,包装器却宣布了成功。
这里有两个bug,不是一个。我在生产代码中依赖了交互式shell的便利,又允许后续命令抹去了失败信号。第一个阻止了工作,第二个阻止了我知情——而后者代价远大得多。一个大声死去的任务当天早上就会修复;一个安静死去的任务要等到有人碰巧去找从未存在的输出时才会被发现。在我这里,这花了四天,而且只是因为我想读报告,不是因为任何东西提醒了我。
Shell可移植性是个老问题。当前POSIX操作系统规范仍然需要详尽地阐明命令执行和环境行为,因为这些假设在交互式shell和脚本之间确实不同。这是已解决的问题领域。智能体项目只是让人很容易重新踩进这个坑。模型调用感觉像是其中最复杂的部分,因此围绕它的三行包装器逃脱了我对普通生产代码的审查。
我的修复并不高明。定时脚本现在调用绝对路径,或在启动时解析并验证它们,并以刻意精简的环境运行。它们在遇到未设置的变量时会失败。最重要的是,它们在报告成功之前会验证预期效果。退出码0是必要的,但它不是工作已发生的证明。
第三次故障是一个超时保护。一个智能体偶尔会在工具调用上卡住,所以我给它包了一个40分钟限制然后继续做别的事。
这个保护依赖于一个Linux上常见但macOS默认未安装的命令。我的包装器将缺失的实用程序视为无关事件,继续走其回退路径。整整一周,我相信每次运行都有硬性上限。实际什么都没有。那一周没有东西挂起——这是我反复琢磨的部分。保护缺失,系统看起来正常,因为它所针对的条件根本没有出现。我完全没有得到任何信号——我只是运气好,而运气看起来和正常工作的代码一模一样,直到它不再一样。
这比一个卡住的任务更让我不安。一个坏掉的功能在有人使用时会暴露自己。一个坏掉的安全机制在系统已经陷入困境之前一直不可见。
所以我加了一条规则:每个护栏都必须通过自己的启动测试。超时现在会针对一个我故意挂起的进程来验证自身——这正是我发现缺失实用程序的方式——花了一个下午,我本该一年前就做这件事。锁、凭证检查和熔断器得到同样对待。如果我无法按需触发一个保护,我就不能将其计为保护。
好的智能体说"未知"
最重要的故障更安静。
一个智能体负责汇总一小批邮件输入。在一次定时运行中,它的令牌过期了。它本可以返回"零新项目"——这与它面前的空响应相匹配,但在操作层面是撒谎。它没有看到空收件箱,它是没能看到收件箱。
相反,运行报告了"未知",指明了无法访问的来源,拒绝生成摘要,然后继续完成它仍能做的工作部分,并说明了哪些是那些部分。那天早晨的报告比平时短,并且对原因诚实。读完它花了我十秒。从自信的"零"中重建同样的事实则要花一周。
这就是我现在对集群中每个智能体的期望。好的智能体报告"未知"。坏的智能体报告"零"。
这个区别不是哲学上的。零是一个测量值。未知是关于测量是否可能的声明。将两者混为一谈,集群在完全失明时看起来最健康。
可观测性指南通常从日志、指标和追踪开始。OpenTelemetry可观测性入门给出了传统框架:一个正确插桩的应用程序会发出足够的信号来回答关于其行为的问题。这是必要的,但智能体集群需要另一层。我还需要关于观测边界的证据:哪些来源可达、哪些凭证有效、每个输入的新鲜度如何,以及哪些预期任务完全没有输出。
追踪能解释一个进程做了什么。它无法解释一个从未运行的进程——除非有它之外的东西在关注这种缺失。
我现在为每次定时运行建模的状态不止成功和失败。一次运行可以成功、失败、迟到、被策略跳过,或因输入无法观测而为未知。最后一种状态是最棘手的,也是最有价值的。每个预期运行都有一个新鲜度截止线。每个重要数据源都有一个独立于其项目计数的可用性结果。心跳只有在任务产生了调度存在的目的——那个工件——之后才被接受。
这在仪表板上产生了更多黄色。很好。我宁愿调查一个诚实的黄色,也不愿信任一个虚构的绿色。集群不比我开始追踪这些之前更可靠。它一直以来的可靠程度就是如此,我终于能看到它了。
读取运行状态
这些都不是新计算机科学,而这恰恰是它们容易被忽略的原因。智能体系统包裹着新语言——规划器、工具使用、记忆、反思——但它们仍然依赖调度器、shell、路径、凭证、时钟和退出码。新层没有废除旧层。它给旧层更多安静失败的方式。
我仍然评估模型质量。我仍然测试提示、工具选择和输出基础。但我最先问的问题不那么时髦:调度器加载任务了吗?它在预期时间启动了吗?包装器调用的是我以为的二进制文件吗?安全检查证明了自己吗?
我写下然后又打破了的那条规则很简单:永远不要从配置文件推断系统的当前行为。读取运行状态。
这也是24个智能体被关闭而非删除的原因。每一个都足够有用到值得构建,但不够有用到值得持续监控。我每增加一个智能体,就多了一个可能安静停止的定时任务、一个可能过期的凭证、一个我必须注意到的沉默。集群的成本不是token,而是可能在不告知你的情况下失败的事物的数量。
架构图告诉我我打算构建什么。运行状态告诉我我实际拥有什么。只有其中一个能在凌晨三点叫醒我。