
把AI Agent放进真实业务环境之后我会默认一件事它一定会出错。不是“可能出错”是“必然出错”。LLM本身是概率系统每一步决策都带着不确定性工具返回的数据、外部服务的可用性、上下文窗口的截断随便哪个环节抖一下整个多步任务就可能跑偏。这两年从Demo原型到线上流程我折腾过不少Agent从页面抓取、数据整理到多Agent协作的订单处理流程踩坑踩到今天最有用的经验就四个词校验、暂停、回滚、人工接管。这篇不绕弯子也不堆名词直接把错误链路上每个环节怎么补救讲清楚。适合正准备把Agent从“玩具”推向“工具”的人不管自己搭脚本还是带团队做产品都应该能少走一些弯路。1. 先搞清楚AI Agent为什么会出错1.1 Agent是流水线不是接口很多人容易把Agent当成一个“高级API”发一段话它回一段话。其实Agent是一个完整的执行系统LLM只是它内部的推理引擎。像DeepSeek、GPT、Claude这些模型本质上仍然是LLM负责理解和生成内容Agent则是在LLM外面套了目标拆解、工具调用、记忆管理、结果校验这一整层执行壳。你交给Agent一个任务它先规划出步骤再一步步调用工具拿结果每拿到一步结果又把它喂回给LLM继续推理。整个过程是一条流水线不是一次问答请求。既然是流水线问题就会出在任何一个工位上。我更愿意把Agent出错理解为“概率性错误被流水线放大”LLM在单个环节出错的概率也许只有几个百分点但如果一个任务要跑二十步中间又不对任何结果做检查最后任务的失败率会高到让人怀疑人生。这就是为什么我一直认为Agent工程的复杂度从来不在“怎么让模型听懂话”而在于“怎么在执行过程中及时发现错误、并快速恢复”。处理得好的Agent项目往往不是模型选得多强而是错误处理链路做得足够扎实。1.2 出错的四类根源我把自己项目里最常见的故障整理了一下四类基本能覆盖线上遇到的大部分问题而且每类错误的处理方式都不一样。第一类是感知错也就是Agent“看错了”。比如让Agent读取订单表格里的金额模型把1,000读成1000又比如上下文太长早先的关键信息被截断Agent直接忘了用户需求是什么。这类错误最隐蔽因为结果看起来非常合理实际上已经偏离了目标。第二类是推理错也就是“想错了”。典型的例子任务要求先检查库存再下单Agent却先下了单才发现库存不足或者修复A模块时顺带改了B模块的配置理由是“它们看起来是关联的”但实际是两个独立系统。推理错往往不是单一动作引发的而是整条决策链的逻辑出了问题。第三类是动作错也就是“做错了”。工具调用时参数传反、API路径拼错、用删除命令时误删了目录。这类错误一旦发生通常伴随真实的副作用比如文件被覆盖、数据被清掉需要立刻考虑回滚而不是继续重试。第四类是环境错也就是“外部不配合”。目标服务宕机、网络超时、权限突然失效或者某个第三方平台把结算账户暂停了。这都不是Agent自身的问题但最终表现同样是任务失败。环境错还需要注意“外部服务状态未知”的情况Agent往往感知不到外部已经变了还在按原计划执行。这四类错误处理手段并不相同。校验解决的是感知错和动作错暂停解决的是错误正在扩散的风险回滚解决的是已经产生的脏状态人工接管则兜住前面所有手段都搞不定的剩余情况。先把分工理清楚后面每个动作才不会白做。2. 校验把“做完了”和“做对了”分开看2.1 三条校验线一条都不能少校验是错误处理的第一道闸门却是大多数人做得最粗糙的地方。我见过不少Agent项目所谓“校验”只是让LLM自己评价一下自己的回答是否满意这基本等于没校验。真正能落地的校验至少有三条线必须分开看。第一条线是“任务完整性校验”核心回答“跑完了没有”。一个多步任务中途可能因为某次工具调用失败而提前退出也可能因为LLM误判任务已完成就收工。完整性校验的做法是在任务开始前定义一个明确的检查清单比如“必须生成3个文件且真实存在”“必须写入的消息都到达目标队列”结束时逐条核对缺一不可。没有这个清单Agent就很容易把“跑完流程”当成“完成任务”。第二条线是“动作合法性校验”核心回答“这个动作被允许吗”。Agent要执行的每个动作都应该经过白名单过滤。删除文件、覆盖生产配置、转账、调用测试环境外的接口都要被标记为高危动作。判断规则本身不需要多复杂可以是一份静态清单也可以像表单校验规则那样在执行前对字段做类型、范围、权限的校验。第三条线是“结果质量校验”核心回答“执行出来的东西对不对”。这层最容易被忽略但对最终交付最致命。比如Agent生成了一份配置文件你可以用md5sum和模板文件比对校验和不匹配就说明生成过程有问题Agent写出的JSON数据用固定的schema做结构校验字段缺失立刻暴露。CRC这类校验算法虽然古早在数据完整性场景里依然非常可靠。结果好不好不能靠感觉要落到一条可执行的规则上。2.2 校验规则放在哪一层决定可靠性有了校验内容还得想清楚校验放在哪一层。我自己的经验是可靠性从低到高依次是提示词层、结构化输出层、工具层、独立校验器层。提示词层就是在System Prompt里写rules校验规则。你可以告诉LLM“不允许调用删除接口”它本质上只是参考建议模型有可能无视所以可靠性很低。它最大的作用是降低出错概率但不能把它当安全底线。结构化输出层就是要求LLM返回符合JSON Schema的结果字段类型、取值范围、必填项都提前定好。这一层能拦截一批格式错误但对语义错误无能为力模型可能返回了合法格式内容却是错的。工具层是在每个真实工具函数里做入参校验和返回值校验。比如调外部服务之前要像安卓开发里“服务器未进行严格的证书校验”的教训一样把TLS证书、签名、时间戳都验一遍不通过直接拒绝执行。这个层能覆盖“动作合法性校验”但它管不到Agent的整体流程决策。独立校验器层是最可靠的一层。它完全是一个独立代码模块不依赖大模型用确定性逻辑检查Agent的产出。任何“不能只让模型自己审自己”的校验都应该放到这层。四层关系不是互斥的而是叠加的。我在实际项目中会在提示词里写基础规则输出层卡格式工具层做人参过滤最后再放一个独立校验器做终审。2.3 校验设计最容易犯的两个错第一个错是校验过松。只校验“有没有返回结果”不校验“结果对不对”。Agent跑了十分钟最后告诉你“操作完成”你过去一看文件压根没生成出来。这种情况不是校验是流程惯性。我需要看到的是具体证据文件路径、文件大小、校验和一个都不能少。第二个错是校验过严。每个字段都要求绝对精确连接口返回的时间戳格式都要和预期一模一样这会让Agent变得极其脆弱。校验的目的是拦截“会影响后续结果的错误”而不是惩罚任何一点波动。所以校验规则要有优先级关键字段必须精确匹配非关键字段可以容忍。怎么把握这个度我的习惯是看这个字段如果错了对最终交付目标的影响有多大影响大的进硬校验影响小的进软校验。这样既不会放过真错误也不会频繁误伤。3. 暂停给错误一个止损窗口3.1 暂停不是终止而是保持现场校验发现异常之后下一步不是立刻回滚而是先考虑要不要暂停。很多人把这个顺序搞反了一看到错误就马上终止任务或者马上回滚。但终止会丢失所有上下文回滚则可能把还有救的中间结果一起销毁。在错误无法立即定性时最好的选择是暂停。暂停的含义可以类比操作系统里的“挂起”也可以比作实验中常说的“实验暂停”任务从运行状态切出去但进程、内存、中间数据都保留着。Windows暂停更新也是这个思路先延缓执行把可恢复性留在那里你随时可以继续或彻底放弃。暂停最大的价值是让你有一个干净的窗口去观察现场而不用害怕副作用继续扩散。对Agent来说暂停意味着停止调用新工具、停止执行后续步骤、把当前上下文和状态快照保存下来然后通知负责人。暂停这段时间Agent还可以回答一些诊断性问题比如“你刚才为什么选这一步”这能帮你判断到底是不是推理逻辑出了漏。3.2 什么情况必须暂停什么情况不用暂停并不是所有错误的默认处理方式否则你每天会被任务卡点烦死。我一般把场景分成三类。第一类是“连续失败的次数超阈值”。单个动作失败一次可以允许重试但同样的失败连续出现三次以上说明问题不是临时的继续重试只会消耗资源。这时候暂停让人来决策是换策略还是人工处理。第二类是“心跳超时”。Agent在执行工具调用时如果长时间没有上报心跳要么卡在外部请求上要么陷入了死循环。我不允许Agent无限期地“沉默”超过设定上限就暂停并且把当前的调用栈和最近几步操作暴露出来。第三类是“触发高危动作”。删除数据、覆盖生产配置、转账、批量向客户发消息任何进入高危清单的动作都必须在执行前暂停并走确认流程。这里也要注意外部依赖的异常。比如外部某个平台自己暂停了服务或者某个结算账户被暂停你的Agent怎么调都是失败。这类环境错误不需要Agent硬扛直接暂停并退回人工是最省力的方案。之前我遇到过第三方支付接口的账户状态变了Agent连续重试了十几次最后还是靠暂停检查外部状态才定位到问题。那哪些情况不用暂停呢单次非关键失败、外部接口偶发超时、校验错误可以自动修正的情况都应该走重试或自动修复路径而不是动不动就停下来。暂停是钝器用多了会破坏自动化效率也会让维护者对暂停通知产生疲劳真正高危的暂停反而被淹没。3.3 暂停之后必须能恢复暂停本身不是终点它必须绑定“恢复点”。恢复点是一整块被保存的状态包括已完成步骤的记录、当前步骤的输入输出、下一个该执行的步骤编号。有了恢复点任务才能断点续跑而不是从头再来。我实际开发中踩过一个大坑Agent在某一步被暂停后负责人在后台点了“继续执行”结果Agent却从头开始跑因为我没有保存“已经执行到哪一步”的状态。这种坑很低级但很伤人。线上还能看到“Windows资源监视器进程显示已暂停且无法结束”这类诡异状态其实Agent里也常见暂停逻辑没处理好任务既不能继续也不能终止变成僵尸任务。所以暂停时要保存好恢复点还要让Agent能接受三种指令继续、放弃、回滚。4. 回滚让Agent装上后悔药4.1 回滚的本质是记录可逆轨迹不是所有错误都能靠暂停解决。当你恢复执行之后发现Agent已经把任务带偏了这时要回滚把系统状态恢复到出错之前。回滚这件事前后端的朋友都很熟。git代码回滚、Jenkins项目发布及回滚、NixOS系统级回滚都是同一套底层逻辑先把当前状态保存下来再定义逆操作出错时沿逆操作回到稳定点。Agent的回滚也一样本质上就是“记录可逆轨迹”——你沿着轨迹走岔了就沿着轨迹倒退回来。区别在于git回滚回的是代码版本Jenkins回滚回的是发布版本NixOS回滚回的是系统快照而Agent要回滚的对象是“业务状态”是它操作过的文件、数据库、配置、外部API的调用记录。所以在设计回滚之前先想清楚你的Agent会动到哪些状态哪些状态可以被撤销哪些撤销不了。这个想清楚了回滚方案基本就定了一半。4.2 三种回滚口径按代价排序从轻到重我常用三种口径。第一种是会话级回滚。只把Agent的对话历史和内部推理上下文回退到某个检查点。实现最简单代价也最小但作用有限。如果Agent已经调用了外部工具光回退对话没有意义外部副作用还在那里。第二种是操作级回滚也是我主力使用的方案。每个动作执行之前先记一条undo日志写清楚这个动作会导致什么副作用、如何补偿。比如Agent往数据库里插入了一行记录undo就是删除这行记录Agent创建了一个临时文件undo就是把这个文件移入回收区而不是彻底删除。把undo日志逐个回放就能最大程度还原现场。第三种是系统级回滚。如果操作污染范围太广单个补偿动作已经收拾不了就用容器快照、数据库备份或整个工作目录快照回滚。代价高、恢复时间长但很彻底适合操作范围大、难以精确定位损坏点的场景。做AI Agent相关的自动化操作时如果Agent能控制一个独立的工作环境最省心的方案就是给整个工作区定期做快照。4.3 回滚最难的是处理副作用回滚的难点不在写回滚代码而在于副作用不可逆。Agent发了邮件就收不回来调用支付接口把钱转出去也不能原地退回。这个层面只能靠“补偿”而非“还原”。比如错误发送了一封邮件回滚时自动再发一封更正邮件调用外部API改了对方的数据回滚时调用对应的反向API。你没法消除已发生的事实但可以减少损害并把整个过程记录下来备查。另一个让我吃过亏的地方是“回滚不干净”。像游戏物理引擎里的跨平台rollback经常回滚不干净一样Agent的回滚如果只恢复了主数据库而把缓存、日志、关联配置忘了恢复就会出现“表面回滚了脏状态还在”的情况。所以我实现回滚时会把所有关联状态源登记在一张清单上逐一确认回滚前后的差异而不是只盯主状态。还有一类系统你压根不应该尝试回滚。就像某些手机系统中的防回滚策略限制你回到旧版本因为旧版本可能带着已知漏洞。放在Agent场景里如果旧版本代码已经被判定为不安全或者外部接口已经不再支持旧协议那就要禁止回滚只在当前位置继续修复。这种“防回滚”是刻意的但它的存在恰恰提醒我们哪些状态该回滚、哪些不该回滚一定要在设计阶段写清楚而不是出事之后临场拍脑袋。5. 人工接管AI不行的时候人不能不行5.1 接管触发条件要明确写出来校验、暂停、回滚都覆盖不了的情况最终要落到人工接管上。但人工接管不是一句“出了问题就派人”的空话触发条件得定义清楚否则要么该接管的没人管要么不该管的时候频繁打扰。我目前使用的触发条件有三类按优先级排列。最高优先级是“自动补救循环耗尽”。比如关键动作重试了N次还是失败回滚也回滚不干净系统自动处理后仍然处于异常状态。这时候再循环下去只是空耗资源直接进入人工接管。第二优先级是“高风险动作需要人来确认”。Agent准备执行删除生产库、批量发送邮件、调用支付接口等动作而且这个动作没有预先被授权那必须停下来等人点头。别指望Agent自己判断“这个删除是否安全”人在环里才是最稳的。第三优先级是“外部反馈显示异常”。Agent上线之后客户或运营通过其他渠道反馈操作结果不对但Agent自身没有任何报错。这种情况下只有人能综合外部信息判断Agent看不到全局。触发条件写清楚之后还要落到监控告警里让接管事件有一个明确的入口而不是靠群聊里喊人。5.2 接管工作台要能“接住现场”我把“接住现场”四个字作为人工接管工作台的设计标准。首先必须有完整的上下文。Agent报了错但你只给它看一行“执行失败”接管的人什么都做不了。合格的接管界面要能展示会话历史、动作序列、每一步工具的真实入参和出参、以及当时的校验结果最好还有一整套可视化时间线。其次要能手动执行工具。接管不等于只能看更不等于重新搭一个Agent手动跑。需要在后台界面里直接调用Agent可用的工具传参数、看返回值像调试接口一样验证你的判断。最后要能调整状态。接管人员把问题处理完之后不是去改代码而是直接在管理面板里把Agent从PAUSED或FAILED状态切回RUNNING并指定从哪个检查点继续。这样可以避免“问题处理了但Agent永远卡在暂停里”的尴尬——也就是前面提过的僵尸任务。满足这三个条件一次人工接管通常几分钟内就能完成而不是把整个任务推倒重来。5.3 人工介入的结论要回馈给系统人工接管不能只救火否则同样的问题还会再次出现。每次处理完一个真实故障我都会做一套“经验沉淀”把错误特征、复现路径、最终处理方案整理成记录一部分注入到校验规则里另一部分反馈到提示词的规则中。这样Agent下次遇到相似场景能在更早的环节被拦住。同时要保留审计。谁接管了、做了什么操作、为什么这么判断全部写进日志。Agent跑错了是Agent的事但人工接管意味着有一个真实的人在决策这个决策的记录本身就是系统质量的一部分。没有审计的人工接管和在多人共用的服务器上不写变更记录一样危险。沉淀下来的修正规则也应该像代码一样有版本管理这样一旦新规则引发了新的误判还可以回滚规则本身。6. 一个可直接参考的Agent错误处理骨架6.1 状态机设计把上面的思路落到工程上我一般会先设计一个状态机。状态机的好处是强制你考虑每个状态之间的转移条件而不是让Agent随意发挥。核心状态IDLE(空闲)、RUNNING(运行中)、WAITING_VALIDATION(等待校验)、PAUSED(已暂停)、ROLLING_BACK(回滚中)、AWAITING_HUMAN(等待人工接管)、DONE(完成)、FAILED(失败)。转移逻辑RUNNING期间执行每个动作前先校验校验失败累计超阈值则转PAUSEDPAUSED之后由决策者选择继续、回滚或人工接管回滚成功后回到RUNNING回滚本身失败则到AWAITING_HUMANAWAITING_HUMAN期间等人工处理完成后可以回到RUNNING或置为FAILED。这套状态机的关键是不允许从任意状态直接跳到完成所有出口都要经过校验确认。宁可让流程慢一点也不要让一个没跑完的任务被当成跑完了。6.2 Python骨架代码下面这个例子精简但能跑重点在错误处理链路上。核心思路每一步执行之前做校验和风险确认出错时基于undo日志回滚回滚不了就请求人工接管。import time import logging from dataclasses import dataclass, field from enum import Enum logger logging.getLogger(agent) class AgentState(str, Enum): IDLE idle RUNNING running WAITING_VALIDATION waiting_validation PAUSED paused ROLLING_BACK rolling_back AWAITING_HUMAN awaiting_human DONE done FAILED failed dataclass class Action: name: str # 动作名如 write_file / delete_file / send_email payload: dict # 动作参数 risk_level: int 0 # 0无风险 1低 2中 3高 require_approval: bool False dataclass class Snapshot: files: dict field(default_factorydict) db_rows: list field(default_factorylist) class AgentWithRecovery: def __init__(self, llm, tools, validator, max_retries3, heartbeat_timeout30): self.llm llm # LLM推理引擎 self.tools tools # Agent可用的工具集合 self.validator validator # 独立校验器 self.max_retries max_retries self.heartbeat_timeout heartbeat_timeout self.state AgentState.IDLE self.checkpoints [] # 检查点列表 self.undo_log [] # 回滚日志 self.retry_count 0 self.last_heartbeat time.time() def _check_heartbeat(self): if time.time() - self.last_heartbeat self.heartbeat_timeout: self._pause(heartbeat timeout) def _pause(self, reason): if self.state AgentState.PAUSED: return logger.warning(pause agent: %s, reason) self.state AgentState.PAUSED # 通知人保留完整上下文和恢复点 def _request_human(self, reason): self.state AgentState.AWAITING_HUMAN logger.error(request human takeover: %s, reason) # 这里把checkpoints、undo_log、当前状态完整暴露给人工 def _begin_rollback(self, step_index): self.state AgentState.ROLLING_BACK target None for cp in self.checkpoints: if cp.step_index step_index: target cp if target is None: self._request_human(no checkpoint found, rollback failed) return # 逆序回放undo_log for action, _snapshot in reversed(self.undo_log): try: self.tools.execute(action.name, action.payload, modeundo) except Exception as e: logger.exception(e) self._request_human(frollback failed: {action.name}: {e}) return self._restore_snapshot(target.snapshot) self.retry_count 0 self.state AgentState.RUNNING def _snapshot_current(self): return Snapshot(filesself.tools.snapshot_files(), db_rowsself.tools.snapshot_db()) def _restore_snapshot(self, snapshot): # 根据快照恢复文件/数据库状态 self.tools.restore_files(snapshot.files) self.tools.restore_db(snapshot.db_rows) def run(self, task): self.state AgentState.RUNNING step_index 0 while self.state AgentState.RUNNING: self._check_heartbeat() # 1. LLM基于当前上下文规划下一步 action: Action self.llm.plan(task, self.checkpoints, self.undo_log) if action is None: # LLM认为任务结束交给独立校验器验证完整性 if self.validator.check_finished(self): self.state AgentState.DONE else: self.retry_count 1 if self.retry_count self.max_retries: self._pause(task marked finished but validator not passed) continue # 2. 动作校验 err self.validator.check_action(action) if err: self.retry_count 1 if self.retry_count self.max_retries: self._pause(faction validation failed: {err}) else: logger.warning(action invalid, retry: %s, err) continue # 3. 高风险动作审批 if action.require_approval or action.risk_level 3: if not self._approval_request(action): self._pause(fapproval needed for {action.name}) return # 4. 执行前快照 snapshot self._snapshot_current() self.checkpoints.append(Checkpoint(step_index, snapshot)) # 5. 执行动作 try: result self.tools.execute(action.name, action.payload) self.undo_log.append((action, snapshot)) self.last_heartbeat time.time() except Exception as e: logger.exception(e) self.retry_count 1 if self.retry_count self.max_retries: self._begin_rollback(step_index) if self.state AgentState.AWAITING_HUMAN: return continue step_index 1代码里我刻意把校验、暂停、回滚、人工接管四个动作都做成独立分支这样任何一个环节出问题都不会把整个Agent拖进不可控状态。6.3 参数与阈值怎么选参数不是拍脑袋定的要结合业务实际。心跳超时取决于单步工具调用的最长耗时。如果Agent最慢会调用一个30秒才返回的接口心跳超时就不能设成10秒否则会频繁误暂停。我惯用的做法取正常单步耗时上限的1.5到2倍再往上加一点缓冲。重试次数max_retries设置在3到5之间比较合适。低于3偶发抖动也容易触发暂停高于5失败时会消耗太多时间而且重试的边际收益明显下降。风险分级按影响面定。只影响Agent自身临时文件的操作风险等级给0或1影响业务数据库的操作风险等级至少给2删除或覆盖生产数据风险等级直接给3强制人工确认。暂停后的恢复期限也应该有上限。比如暂停后30分钟无人处理系统自动升级告警到更高一级负责人避免任务长时间卡死。这些参数生成之后要用历史故障数据反推验证一遍而不是直接在线上试错。7. 常见问题与排查技巧7.1 常见问题速查表我把线上遇到的高频问题整理成了表格排查时可以对照看。症状可能原因排查思路Agent反复校验失败提示不明确校验规则过严或LLM输出格式不稳定放宽非关键字段校验增加结构化输出约束暂停后任务无法继续点了恢复没反应没有保存恢复点或状态被外部改动用检查点和undo日志恢复对照步骤编号回滚之后数据仍然异常回滚不干净存在遗漏的关联状态列出所有状态源清单逐一比对前后差异人工接管处理完了Agent仍然重复犯错只修了现场没把经验反馈回规则库将故障特征注入校验规则和提示词进程卡在PAUSED状态既不能继续也不结束暂停时没处理外部资源句柄或状态机转移缺失清理外部连接允许“放弃”指令强制终止外部接口报错Agent一直重试不停没有识别外部依赖故障对连续同类错误直接改暂停检查外部服务状态7.2 四条避坑经验第一条校验别只让LLM自己审自己。让AI评价AI等于让运动员给自己判分结果通常都很好。独立校验器必须是非模型的确定性代码哪怕是几十行if-else也比LLM自评可靠得多。第二条回滚之前先确认“有没有同一时刻的完整快照”。如果你在每步执行前都记录了快照回滚就是恢复现场如果只记录了操作日志没有完整快照回滚时很难保证所有关联状态一致。实际操作中快照成本只要可控就不要省。第三条暂停千万别只暂停“动作”不暂停“资源”。Agent暂停时如果外部API连接还挂着、临时文件还锁着恢复时就会撞上“资源拒绝访问”的坑。暂停处理要主动释放资源恢复时再重新初始化。第四条别只测happy path要主动注入故障测试。我在测试环境里会故意模拟网络中断、工具返回值篡改、LLM输出异常格式看Agent是否真的能走上暂停或回滚路径。光测正常路径错误处理链路往往是一堆只写了但没跑通的死代码。最后再分享一个小技巧上线Agent之前先在低风险场景跑一两个月把校验规则和暂停阈值磨到顺手再逐步开放高风险动作。我见过不少项目一上来就把Agent接入生产环境结果第一周就翻车原因不是模型不行而是错误处理链路根本没经历过真实故障的检验。先让它在小范围内出错把兜底机制练扎实再让它去碰业务的核心区域这才是把Agent工程化的正路。