第 11 章 被忽略的鲁棒性
做 Agent 项目的时候,我犯了一个特别普遍的错误——精力全扑在功能开发上,传统软件的鲁棒性保证被我忽略了。这一章就是几个真实的例子。
开篇:一个空转了一整夜的系统
我有一个项目的错误日志,记录了一件挺荒唐的事。
系统的调度器每隔几分钟触发一次任务,调 LLM 做决策。某天晚上,LLM 的接口开始报错——原因很简单,账户余额用完了。但调度器不知道,照常每隔几分钟触发一次。LLM 照常失败。没有任何东西把这个循环打断——没有熔断、没有降级、没有告警。
从晚上十点开始,日志一直在重复刷同一行:"LLM 调用失败"。每隔几分钟一次。一直刷到第二天早上九点多,我起床打开电脑看到日志,手动把服务停了。整整一整夜。
这件事让我意识到一个被我忽略的问题:我花了大量时间让 Agent 学会做各种事,但几乎没有花时间保证它在出问题时能安全地活着。
鲁棒性这件事,传统软件里我都会做
说起来挺矛盾的。在传统软件项目里,这些事情我都会做。
调一个外部 HTTP 接口——我会配超时,会加重试,失败了会记日志降级。跑一个后台任务——我会加健康检查,挂了会自动重启。数据库连接——我会配连接池,配超时,配失败回退。
这些是传统软件工程师的肌肉记忆。不需要谁教,做项目的时候自然就会想到。
但到了 Agent 项目里,这些肌肉记忆好像突然失灵了。为什么?
为什么会忽略
我后来想了想,大概有三个原因。
第一个原因:Agent 功能的正反馈太强了。 你让 Agent 学会一个新技能,跑一遍就能看到效果——"它居然能记住我说的话了""它居然会自己查资料了"。这种即时的、可视的成就感,会让你不自觉地把所有精力往功能上堆。而鲁棒性是"不出事你感觉不到"的东西——它没有正反馈,只有"没出事"这个隐性状态。
第二个原因:Agent 项目的节奏太快了。 用 Claude Code 写代码,一个功能从想法到能跑,可能就半天时间。这种速度让你产生一种惯性——"赶紧做下一个功能"。停下来配超时、加重试、写降级逻辑?太慢了,下个功能还在等我。
第三个原因:LLM 本身就是"不稳定"的,让我产生了"反正也不稳定,鲁棒性也没用"的错觉。 这个想法是错的——正因为 LLM 不稳定,鲁棒性才更重要。但在当时,"Agent 本来就偶尔抽风"这个认知,反而成了我忽略鲁棒性的借口。
具体忽略了什么
盘点一下,我忽略的东西主要有这几类:
外部调用没有超时和重试。 调 LLM 的 HTTP 请求,很多地方没有配超时。LLM 不响应的时候,请求就卡在那里,可能卡几分钟。如果一个请求卡住了,后面的请求也会排队等着——链式阻塞,整个服务就僵住了。
这件事我在项目的待办清单里写得很清楚——"HTTP 客户端无超时,LLM 不响应时阻塞,消息队列卡死"。白纸黑字写着,但一直没补。
没有熔断和降级。 LLM 接口偶尔会限流或者波动。正常做法应该是——连续失败 N 次之后熔断,一段时间内不再调用,直接返回降级响应。但我没有这套机制。开头那个空转一整夜的例子,就是因为没有熔断——调度器不知道 LLM 已经持续失败了,还在傻乎乎地触发。
工具执行的副作用没有兜底。 Agent 调工具写数据的时候,如果中途出了错——比如写了一半网络断了——数据可能处于不一致的状态。正常应该有事务保证,要么全成功要么全回滚。但早期代码里很多写操作没有包在事务里。
这些忽略的后果是什么?单独看每一个都不致命——"没超时而已嘛""没熔断而已嘛"。但它们叠在一起,就是一个平时能跑、一旦遇到边缘情况就连锁崩溃的系统。而且崩溃的方式还特别难排查——因为不是某个明确的 bug,而是多个"没做"的地方互相放大。
鲁棒性应该是默认配置,不是事后补丁
这件事我后来想通了一点:鲁棒性不应该是"做完功能再补"的东西,它应该是默认配置。
什么叫默认配置?就是你写一个外部调用的时候,超时是默认带的,不是"想起来再加"的。写一个后台任务的时候,健康检查是默认有的。写一个数据操作的时候,事务是默认包的。
这些不是"高级功能",是"基本卫生"。就像你写代码不会忘记声明变量类型一样——不是因为你每次都"想起"要声明,而是因为它已经变成了你写代码的默认方式。
鲁棒性也应该这样。写一个 HTTP 调用的时候,超时、重试、降级应该是随手就加的,不是"先跑起来再说,回头补"。因为"回头"通常意味着"永远不会"。
前面章节里那些"护栏",其实就是鲁棒性
回头看看这份手记前面讲的很多东西,其实都和鲁棒性有关:
- 第 5 章讲的循环护栏——迭代上限、回复保证、卡住检测——这些就是在"Agent 行为层面"的鲁棒性保证
- 第 4 章讲的"错误信息要可操作"——让 LLM 碰到错误时能自我修正——这也是一种鲁棒性
- 第 9 章讲的双层验证和自动重试——这是在"多 Agent 协作层面"的鲁棒性
这些我做到了。但它们都偏"Agent 层面"。而在更底层的"系统层面"——HTTP 调用的超时重试、服务的熔断降级、数据操作的事务保证——我反而没做好。
完整的鲁棒性应该是两层都有的:Agent 行为层面的护栏(做到了一部分)+ 系统层面的防御(基本没做)。 这份手记前面讲了前者,这一章补上后者被忽略的事实。
小结
这一章没有讲什么高深的东西。它就是几个朴素的例子,说明一个朴素的问题:做 Agent 项目的时候,我过度关注功能开发,忽略了传统软件的鲁棒性保证。
这不是什么"认知重建",也不需要"心智升级"。就是那些你在传统软件里本来就会做的事——超时、重试、熔断、事务——在做 Agent 项目的时候别忘了做。
忘记的原因不复杂:功能太好玩了,鲁棒性太无聊了。但无聊的东西不做,代价迟早会来——可能就是某个晚上你的系统空转了一整夜,你第二天才知道。
下一章
第 12 章 · 质量门禁 —— CI、测试、监控闭环——那些"不性感但不能没有"的东西。