1. 为什么AI Agent的“出错处理”比让它跑起来更重要很多人搭AI Agent第一反应都是怎么把流程跑通接上大模型、挂几个工具、写个循环让它自己调。跑通那一刻确实爽但真正上线之后你会发现跑通只是及格线出错处理才是决定这套东西能不能用的分水岭。我见过太多demo阶段惊艳的Agent一进真实环境就翻车——工具返回了脏数据它照单全收中间步骤失败了它继续往下走最后产出一堆看似合理实则离谱的结果你还得从头查是哪一步歪的。AI Agent和普通程序最大的区别在于普通程序的执行路径是你写死的出错会抛异常而Agent的路径是模型自己规划的它“觉得”自己没出错这才是最要命的地方。所以一套能上生产的Agent必须内建四道防线校验Validation、暂停Pause、回滚Rollback、人工接管Human-in-the-loop。这四个词看着像运维术语但落到Agent身上每一个都有它独特的实现逻辑。这篇文章我打算把这四道防线拆开讲透。适合谁看如果你已经能搭出一个能跑的Agent但被它的“不可控”折磨过或者正准备把Agent从玩具推向真实业务那这篇就是给你写的。我会讲清楚每一步为什么这么设计、具体怎么落地、参数怎么定以及我自己踩过的那些坑。全程按从业者交流的口吻来不整虚的。先给个整体心智模型方便你后面理解把Agent的一次任务执行想象成一次数据库事务。事务有ACIDAgent的可靠性也类似——校验是保证“输入合法”暂停是“锁住现场别乱动”回滚是“撤销到一致状态”人工接管是“DBA介入兜底”。想通这个类比后面所有设计都顺了。2. 校验在Agent动手之前先把脏东西挡在门外2.1 校验到底在防什么三类最常见的“脏输入”校验这个词太泛了落到Agent场景你得先明确它防的是什么。我把它归成三类每一类的处理策略完全不同。第一类是工具返回值的结构校验。Agent调用一个API或函数返回的JSON字段缺了、类型错了、嵌套层级变了模型拿到这种数据会“脑补”出一个合理结果然后继续往下走。这种错误最隐蔽因为模型不会报错它会编。第二类是语义校验。结构没问题但内容不合理。比如查询订单返回了金额为负、日期是未来、用户ID对不上。这类错误结构校验抓不到得靠业务规则。第三类是前置条件校验。Agent要执行某个动作比如发邮件、扣款、删文件执行前必须确认前置状态满足。这一步不做后面回滚的代价会指数级上升。提示校验的黄金法则是“越早拦截代价越小”。在Agent链路里入口处一次校验的成本可能只有事后回滚的百分之一。2.2 结构校验用Schema把工具的“嘴”管住结构校验最省事的做法是给每个工具定义严格的输出Schema。现在主流的大模型工具调用框架基本都支持JSON Schema你要做的是把required、type、enum这些约束写死别偷懒。举个我实际用的例子一个查询库存的工具返回Schema我会这么定{ type: object, required: [sku_id, quantity, status], properties: { sku_id: {type: string, pattern: ^SKU-[0-9]{8}$}, quantity: {type: integer, minimum: 0}, status: {type: string, enum: [in_stock, out_of_stock, backorder]} } }注意几个细节pattern约束了SKU格式minimum挡住了负数enum把状态锁死。这三样加起来能挡掉我遇到过的八成结构类脏数据。校验失败怎么办不要让模型自己修直接抛一个明确的错误信息回给Agent让它重新调用或者走降级路径。让模型“猜”怎么修是灾难的开始。2.3 语义校验业务规则才是真正的守门人结构对了不代表能用。语义校验得你自己写规则这块没有银弹。我的经验是把规则分成硬规则和软规则。硬规则是绝对不能违反的违反了直接中断。比如金额不能为负、时间戳不能超过当前时间、用户ID必须在白名单里。软规则是“可疑但可能合理”的比如金额突然比历史均值高10倍这种不中断但打标记触发后面的暂停或人工复核。实现上我习惯用一个独立的校验层和工具调用解耦。工具只管返回数据校验层负责判断。这样规则改了不用动工具代码。校验层返回三种结果PASS、WARN、BLOCK。WARN会写进执行日志并可能触发暂停BLOCK直接终止当前分支。2.4 校验规则怎么维护才不烂尾校验规则最容易烂尾的地方是写的时候很全业务一变就没人更新最后规则和现实脱节要么误杀要么漏放。我的做法是给每条规则加元数据谁负责、什么时候加的、对应哪个业务场景、最近一次触发是什么时候。用表格管理起来长这样规则ID类型表达式动作负责人最近触发R001硬amount 0BLOCK张三2024-05-12R002软amount avg*10WARN张三2024-06-01R003硬ts now()BLOCK李四从未“最近触发”这一列特别有用。一条规则半年没触发过要么是业务变了要么是它根本没生效两种情况都值得你去查。我靠这个习惯揪出过好几条写错了正则、实际上永远返回PASS的废规则。3. 暂停让Agent在关键时刻“停得住”3.1 暂停不是卡死区分三种暂停语义很多人一听“暂停”就想到进程挂起其实Agent的暂停有三种完全不同的语义混在一起设计必然出问题。第一种是协作式暂停Agent主动在某个检查点停下来等待外部信号再继续。这是最健康的暂停因为现场是干净的状态是明确的。第二种是强制暂停外部监控发现异常命令Agent停下。这时候Agent可能正卡在某个工具调用中间状态是模糊的需要额外处理。第三种是条件暂停满足某个条件比如命中软规则、需要人工确认自动触发。这是业务里最常用的。我设计暂停机制时核心原则是能协作式就别强制能提前定义检查点就别事后补救。3.2 检查点设计暂停的“锚点”在哪检查点就是Agent执行链路里允许暂停的位置。不是随便哪都能停的你得在关键节点埋锚点。我的经验是至少在这几个位置埋每个工具调用之前准备动手先停一下确认每个工具调用之后拿到结果停一下校验跨阶段切换时比如从“查询”进入“写入”命中软规则时检查点要保存完整现场当前步骤、已完成的步骤、中间变量、模型上下文。这样恢复的时候才能无缝接上。我见过有人只存了个步骤号恢复时上下文丢了Agent直接失忆重来前面白干。3.3 暂停状态的持久化别把状态放内存里这是血泪教训。早期我把Agent的暂停状态放在进程内存里结果服务一重启所有暂停的任务全丢了用户那边显示“处理中”实际早就没了。暂停状态必须持久化落到数据库或至少是可靠的文件存储。我用的结构大概是这样class AgentCheckpoint: task_id: str step_index: int status: str # running / paused / failed context: dict # 模型上下文、中间变量 pending_action: dict # 待执行的动作 created_at: datetime resume_token: str # 恢复凭证resume_token这个字段很关键。恢复操作必须带凭证防止乱恢复。凭证可以是一次性的恢复后失效避免重复执行。3.4 暂停超时与自动降级暂停不能无限期挂着。用户不响应、人工审核排队太长任务就僵在那了。所以每个暂停都要设超时超时后走降级策略。降级策略我一般分三档超时5分钟发提醒超时30分钟自动走保守路径比如放弃当前分支返回部分结果超时2小时标记为失败并归档释放资源。注意超时时间别拍脑袋定。要看你业务的容忍度。支付类可能5分钟就急内容生成类挂一天也无所谓。这个参数必须和业务方对齐不能技术自己定。4. 回滚把Agent搞乱的东西收拾干净4.1 回滚的难点Agent的副作用不像数据库那么好撤数据库回滚有事务日志Agent回滚难就难在它的副作用是分散的、外部的、不可逆的。发出去的邮件撤不回调用的第三方API可能已经改了状态写进下游系统的数据可能已经被消费了。所以Agent的回滚设计第一原则是能不产生副作用就不产生能延迟产生就延迟产生。把副作用集中到执行链路的最后阶段前面全是只读操作。这样一旦出错回滚成本极低——因为压根还没写。4.2 补偿事务为每个写操作准备一个“撤销动作”对于必须产生的副作用用**补偿事务Compensating Transaction**模式。每个写操作都配一个对应的撤销操作。发邮件配一个“发更正邮件”扣款配一个“退款”创建记录配一个“软删除”。补偿动作要在设计阶段就定义好不能等出错了现想。我习惯在工具定义里直接带上补偿方法class Tool: def execute(self, params): ... def compensate(self, result): ... def is_idempotent(self): ...is_idempotent这个标记很重要。幂等的操作重试安全非幂等的重试会出大事。回滚时优先用幂等操作非幂等的必须配合去重键。4.3 回滚的粒度全量回滚还是部分回滚不是所有错误都要全量回滚。有时候前面几步是对的只有最后一步错了全量回滚反而浪费。我的策略是按检查点回滚回到最近一个干净的检查点而不是回到起点。判断“干净检查点”的标准是该点之后没有产生不可逆副作用且状态一致。如果中间有不可逆操作那就只能全量回滚加人工介入。这里有个坑回滚本身也可能失败。补偿动作调用第三方API第三方挂了怎么办所以回滚要有重试和告警。回滚失败的任务必须进人工队列不能静默吞掉。4.4 回滚日志事后复盘的唯一依据每次回滚都要记详细日志回滚了什么、为什么回滚、补偿动作执行结果、有没有残留。这份日志是事后复盘的唯一依据。我用的日志结构字段说明task_id任务标识rollback_from从哪个检查点回滚reason触发原因actions执行的补偿动作列表result成功/部分成功/失败residue残留的不可逆副作用residue这一列最容易被忽略但最重要。它明确告诉你“这次回滚没收拾干净的地方在哪”人工介入时直接看这一列就行。5. 人工接管最后的兜底也是最该设计好的一环5.1 什么时候该交给人接管触发条件人工接管不是失败是设计的一部分。关键是什么时候交。交早了浪费人力交晚了损失扩大。我的触发条件清单命中硬规则且无法自动修复回滚失败或回滚后有残留暂停超时且降级策略也无法处理连续N次重试失败N一般设3涉及高风险操作大额、不可逆、对外这些条件要可配置不同业务线阈值不同。别写死在代码里。5.2 接管界面让人能快速看懂Agent干了什么人工接管最大的痛点是信息不对称人接手时不知道Agent前面干了啥。所以接管界面必须把执行链路、中间状态、出错点、已产生的副作用一次性呈现清楚。我设计的接管面板包含四块执行时间线每步做了什么、当前状态快照、错误详情、可执行的操作按钮继续、回滚、终止、修改后继续。操作按钮要带二次确认尤其是回滚和终止。5.3 接管后的状态回写别让Agent“失忆”人工处理完之后结果要回写到Agent的状态里让它知道“这一步已经被人处理过了”。否则Agent恢复后可能重复执行。回写内容包括处理人、处理时间、处理动作、最终结果。这块我踩过坑早期没做回写人工处理完Agent又跑了一遍重复发了通知。后来加了human_override标记Agent看到这个标记就跳过对应步骤。5.4 人工接管的成本控制别让它变成常态人工接管是兜底不是常规流程。如果接管率超过某个阈值我一般设5%说明前面的校验、暂停、回滚设计有问题得回头优化而不是加人手。我会定期统计接管率、接管原因分布、平均处理时长。接管原因集中在某一类就去优化那一类的自动处理能力。这是个持续迭代的过程不是一次设计就完事。6. 四道防线怎么串起来一个完整的执行框架6.1 执行主循环校验-执行-暂停-回滚的编排把四道防线串起来主循环大概长这样def run_agent(task): checkpoint load_or_create(task) while not checkpoint.done: action plan_next(checkpoint) if not validate(action, checkpoint): handle_validation_failure(checkpoint) continue if should_pause(checkpoint): save_checkpoint(checkpoint, statuspaused) return # 等待恢复 result execute(action) if not validate_result(result): rollback_to_last_clean(checkpoint) continue checkpoint.advance(result) return checkpoint.output核心是每一步都过校验每个检查点都可暂停出错能回滚。这个骨架不复杂难的是每个环节的细节。6.2 状态机视角把Agent执行建模成状态迁移更严谨的做法是把整个执行建模成状态机。状态包括INIT、RUNNING、PAUSED、ROLLING_BACK、HUMAN_TAKEOVER、DONE、FAILED。迁移条件明确每个状态的行为明确。状态机的好处是不会出现“既暂停又回滚”这种模糊状态。任何时刻Agent只处于一个明确状态排查问题时一目了然。我用状态机重构过一次之前那些“任务卡住不知道在哪”的问题基本消失了。6.3 可观测性没有日志和指标四道防线都是摆设最后强调一点四道防线要生效前提是你能看见它们在工作。校验命中率、暂停次数、回滚成功率、接管率这些指标必须采集和展示。我用的最小指标集指标含义告警阈值validation_block_rate校验拦截率20% 需排查pause_timeout_rate暂停超时率10% 需优化rollback_failure_rate回滚失败率1% 立即告警human_takeover_rate接管率5% 需优化没有这些数字你根本不知道防线是在挡子弹还是在漏风。7. 我踩过的坑和几条实在的经验7.1 校验别过度误杀比漏放更伤早期我把校验写得太严结果大量正常请求被拦用户体验极差。后来学乖了硬规则只留真正致命的其余全降级成软规则加告警。先观察一段时间确认某类问题确实高发再升级成硬规则。宁可漏放几个也别误杀一片。7.2 暂停状态一定要能“看见”暂停的任务如果不可见就等于丢了。我现在的做法是每个暂停任务都在管理后台有明确展示带倒计时和负责人。谁的任务谁负责超时自动升级。这个机制上线后暂停任务的平均处理时长从几小时降到十几分钟。7.3 回滚要演练不能等真出事才第一次跑补偿逻辑最容易写错因为平时不触发。我强制要求每个补偿动作上线前必须演练手动构造错误跑一遍回滚确认干净。演练过的回滚和没演练过的成功率差一个数量级。7.4 人工接管界面要“傻瓜化”接管的人往往不是原开发者可能是运营或客服。界面必须傻瓜化一眼看懂发生了什么一键执行推荐操作。我见过太多接管界面做得像调试工具结果没人会用接管形同虚设。7.5 四道防线是迭代出来的不是设计出来的别指望一次设计就完美。我的做法是先上最基础的校验和暂停跑一段时间看数据哪里漏了补哪里。回滚和接管在业务真正需要时再加。过度设计比设计不足更常见很多团队一开始就搞复杂的状态机结果维护成本高到没人敢改。最后分享一个我一直在用的小技巧给每个Agent任务生成一个人类可读的执行摘要出错时直接看摘要就能定位问题不用翻日志。这个摘要用模型生成成本极低但排查效率提升巨大。踩过几次坑之后我现在所有Agent项目第一件事就是把这个摘要机制搭起来。
阅读完成 · 觉得有帮助?