大模型Agent死循环:根因、监控与工程终止方案全解析 1. 先搞明白Agent死循环到底是怎么发生的做Agent开发的朋友多少都遇到过这种场景凌晨两点你正准备收工运维群里突然炸锅——某条线上Agent任务跑了三个小时还没结束日志里刷屏的是同一句话。你点开面板一看好家伙这个Agent正在第47次调用同一个工具甚至调用的参数都一样唯一不同的是报错信息后面多了一串乱码。跑着跑着就死循环了这不是偶发现象而是所有非确定性系统里最经典的故障之一。先说清楚一个概念。这里说的Agent不是聊天机器人那样你问一句它答一句而是指具备多步骤自主决策能力的智能体它能自己拆解任务、选择工具、执行操作、观察结果再根据结果决定下一步。现在主流的实现都基于大语言模型靠模型在每一个决策点生成下一步动作。问题恰恰出在这个自主上模型本身是无状态的它不记得自己上一步干了什么也不知道自己已经重复多少遍了。每一步它都只是根据当前输入生成一个看似合理的输出如果这个输出又把环境状态推回了上一个相似的位置循环就产生了。1.1 一次典型死循环的完整过程我举个例子模拟项目X里有个客服Agent任务是帮用户催发货。第一步它去查订单系统结果说订单号不存在。正常的逻辑应该去问用户要新订单号但模型没有这个意识它觉得查不到就再查一次可能是系统延迟。于是第二步它又调了同一个查询工具传的还是那个不存在的订单号结果当然还是查不到。第三步模型在这个上下文里发现今天系统确实经常延迟我再刷新一次——注意模型已经给自己编了一个合理的解释所以它又调了一次。第四步、第五步、第六步全是一样的操作。真实日志里最夸张的案例某个Agent在13分钟内把同一个只读接口调了300多次直到触发我们预设的硬性次数上限才停下来令牌损耗接近一个中型知识库的全文长度。这类死循环有个统一特征每一步单看都是合理的连起来看就是荒谬的。模型没有全局视角它只能看到当前窗口里的几百个token这些token里可能包含查询失败可能延迟再试一次这样的信号它就会顺着这个信号往下走完全不知道这份日志已经是第10次重复了。1.2 死循环的三层根因真正死循环的根因我拆成三层来看第一层是意图保留失败。任务拆解后每一步执行完Agent需要把当前进度、剩余目标、已尝试方案传递到下一步。现在大多数Agent框架会用一个大字符串把所有历史消息拼在一起喂给模型但模型对超长上下文的注意力是稀疏的它会漏掉关键约束比如客户强调加急或者这个接口已经试过3次了。约束一丢上下文就变成了一个无向图模型随便选一条边就往下走走回来也不知道。第二层是工具反馈不可判别。工具返回的结果里缺少结构化信息比如查询失败和系统繁忙返回的都是同一段文本或者错误码都是500。模型无从判断这次失败到底是环境问题还是参数问题只能盲目重试。试想一下如果工具每次都返回一个状态码码表告诉你这是参数错还是权限错Agent就能在参数错时立刻转向澄清参数而不是重试一百遍。第三层是终止条件缺失。很多Agent框架默认给的循环上限要么没有要么给一个很大的数。30次、50次看起来很够用但一次死循环在极端情况下可能只需要900个token就能完成一圈50次也就是几万个token的事——大模型几分钟就能烧完。如果没有硬超时和重复检测机制这个循环就会一直跑到预算干枯为止。这三层根因对应三种解法约束注入、反馈标准化、工程兜底。后面的篇幅我会一个一个展开。2. 最常见的循环诱因工具调用与上下文失控死循环不是抽象概念它有非常具体的触发场景。我排查过的案例里90%以上集中在两类诱因工具调用失败后的盲目重试以及上下文增长导致的意图漂移。这两个问题单独看都不严重组合在一起就是恶性循环。2.1 工具调用失败后的重试陷阱先说工具调用。很多Agent框架会在系统提示词里写一句如果工具调用失败可以重试获取最新数据。出发点是好的因为线上确实有瞬时故障重试一次可能有结果。但问题在于这句话没有一个最大重试次数的边界模型会把可以重试理解成应该无限重试。我见过一个更隐蔽的陷阱工具本身的幂等性问题。比如一个生成优惠券的工具第一次调用其实已经成功了只是返回响应超时Agent以为失败了又调了一次结果生成了两张券。这种情况从Agent视角看不构成循环但从业务视角看它造成了重复操作。我们需要给工具调用加两层防护外层是次数限制内层是幂等键。次数限制好理解关键是幂等键——调用工具时生成一个唯一的请求ID工具侧缓存这个ID如果重复出现就直接返回上次结果而不是再执行一次业务逻辑。这样一来即使Agent发了疯一样连调十次真正落库的也只有一次。工具反馈这块还有一个容易被忽略的细节返回内容的长度。某个金融问答Agent调用行情接口接口返回的JSON里嵌套了30天K线数据动辄十几KB。这些数据全部塞进上下文后会冲淡前面已经产生的推理过程。模型在下一轮生成时注意力全被最新一段行情数据吸引就开始围绕要不要再拉一次行情打转。我的建议是工具返回的内容一定要做裁剪只保留任务当前需要的摘要字段历史数据压缩成指标值能不进上下文的就不进。2.2 上下文越长意图越容易漂移上下文失控是另一个重灾区。我这里说的上下文包括系统提示词里的任务目标也包括整个对话历史。很多Agent框架为了让模型记住更多信息倾向于把每轮工具调用、思考过程、中间结果全部拼进上下文。随着运行轮数增加上下文越来越长模型对早期目标的理解就会衰减。你可以做个简单实验把一段包含目标指令和20轮历史对话的文本喂给模型然后问它最初的目标是什么。20轮以内基本能答对超过30轮就开始丢三落四超过50轮就直接开始编造了。这不是模型变笨了而是注意力分配本身就有极限。死循环正是钻了这个空子当Agent遗忘初始目标时它就会把最近一次失败当作新的目标接着就是查一下→失败→再查→再失败的无限循环。解决思路是状态外置而不是把所有信息都塞进对话历史。具体做法是把任务拆成三步初始请求、当前进度、下一步计划。初始请求保持在系统提示词里不动当前进度用一个结构化字段动态更新下一步计划只保留未来2-3步的操作。模型每次生成时看到的不是流水账而是一张清晰的待办清单这样就算上下文再长核心目标也不会丢。我在实际项目里喜欢给Agent的上下文加一个重复预警段。这个段是动态生成的内容大致是[Task Memory] 当前已执行步骤1.查询订单 2.重新查询订单 3.再次查询订单。已尝试次数3。与第1次操作完全重复。建议转向询问用户输入正确的订单号或检查系统状态。这段文字放在上下文末尾模型就能重新聚焦。效果非常明显某些场景下重复调用率能降80%。3. 如何提前发现死循环监控与定位手段死循环最气人的一点是它往往不是一上来就循环的而是前几步还正常中间某一步开始拐进死胡同。这种情况下如果等到用户投诉才发现问题Agent已经烧掉不少预算了。所以监控和定位手段必须前置。3.1 事后考古回放Log找重复先说事后排查。我说的事后不是等用户投诉而是当Agent运行时以每次运行单元为单位把完整的决策轨迹记录下来。一个运行单元就是一次用户请求从进入到结束的全过程。记录的内容包括时间戳、当前步骤序号、模型输入的长度、工具名称、工具参数、工具返回的状态、模型输出的动作类型。有了这个日志结构回放就很容易。我判断死循环有一套简单粗暴但有效的算法轨迹指纹对比。对每一步生成一个指纹比如工具名 参数哈希 返回状态码然后在轨迹里搜索重复子序列。如果发现同一指纹连续出现3次以上就判定为疑似循环。这个方法不需要模型参与纯字符串匹配线上跑起来开销极小。伪代码如下def detect_loop(trace, min_repeat3): seen set() for i, step in enumerate(trace): fingerprint hash((step.tool_name, step.args_snapshot)) if fingerprint in seen: # 统计从当前位置开始的连续重复次数 repeat 0 for j in range(i, len(trace)): if hash((trace[j].tool_name, trace[j].args_snapshot)) fingerprint: repeat 1 else: break if repeat min_repeat: return i, repeat seen.add(fingerprint) return None这段代码是我在某个创新项目的Agent运行器里实际用过的效果还不错。它只能检测完全重复如果Agent每次换了个不存在的订单号继续查指纹就不一样了这种属于语义循环需要下一层的语义判断才能识别。语义循环可以用一个更轻的办法兜底统计最近5步里工具调用的主题分布。如果5步里有4步在调用同一类工具比如查询库存并且返回结果都没有对任务进度产生实质推进就值得人工看一眼了。实质推进怎么判断可以简单定义为工具返回结果导致了下游分支变化这个信息很多日志系统都能拟合出来。3.2 实时告警怎么判断Agent卡住了事后定位够了之后接下来是实时告警。我现在看监控指标重点不是看单步耗时而是看几个组合指标第一个是单位时间内的工具调用频率。正常情况下一个多步骤Agent每5-10秒调一次工具是合理的。如果单分钟调用次数超过20次或者连续3分钟每分钟都超过10次基本可以断定进入循环了。第二个是连续相同动作占比。最近10步里动作类型不是参数重复率超过60%说明有机械重复的风险即使不是完全相同的循环也离死循环不远了。第三个是无效操作比。工具调用里有超过30%返回的是数据未变化查询无结果操作失败这类非成功状态且没有触发Agent转向就需要告警了。这些指标的阈值不能设得太激进。我一开始设的告警阈值是连续3步相同结果线上大量误报因为有些任务的前置检查本来就是重复做三次。后来调成5步内有4步相同、且返回状态相同误报率才降下来。告警之后怎么办我的经验是不要自动杀掉Agent而是把一个人工确认指令插进当前运行管线里。因为线上Agent很多都在处理真实业务突然强杀可能导致事务不一致让人看一眼再决定更稳妥。还有一种做法是请求级超时熔断。在Agent运行器的编排层配置一个整体超时时间比如单次运行最长30分钟。快到时间了直接终止并且把当前轨迹存档供后续复盘。这个参数的确定一定要结合业务场景调。我见过一个数据采集Agent正常执行一次要25分钟如果硬设30分钟它偶尔正常跑完但统计失败率会变得异常高。所以超时时间要给正常用时的1.5-2倍才合理。4. 实战拆解三种场景下的终止方案分析做完接下来就是动手解决问题。所有终止方案里我的核心原则只有一个Engineer for failure。不要假设模型每次都聪明到知道何时停止而是在架构层面允许它失败但不至于失控。4.1 兜底max_iterations与硬超时最基础也最不能少的是迭代次数上限和硬超时。这块看起来简单真正做得严谨的不多。我推荐的参数设计是分层的而不是拍脑袋给一个总数。第一层是单类型操作上限同一工具连续调用不得超过5次。这个用于拦截查询同一个订单号查了10遍这种机械重复。第二层是全局步数上限整个任务流程最多运行30步到了直接收敛。第三层是时间硬上限任务的wall time超过45分钟立刻熔断。每一层触发后的动作也不一样。单类型操作上限触发时不是直接终止而是强制Agent进入澄清模式——要求它先向用户确认意图或者换一个工具。全局步数上限触发时进入收尾模式不再允许调用工具只允许模型基于已有信息生成最终答复。时间硬上限触发时直接终止并告警交由人工接管。这套分层逻辑的好处是模型多了一个改错的机会而不是被一刀切干掉。我在几个项目里看到过强制进入澄清模式后Agent反而找回了最初目标后面的流程变得正常了。4.2 循环识别与语义回溯次数上限是保险丝真正能让Agent聪明地走出来的是循环识别机制。循环识别不只限于前面说的指纹对比还可以做一个轻量级的语义回溯在上下文的末尾附上最近N步的执行摘要然后给模型一次回顾的机会。具体做法是当工具调用失败且下一步是重复动作时注入一段提示注意以下动作与第step步完全相同且未获得新的有效信息。 请判断是否应该继续重复该操作如果不应重复请选择一个完全不同的操作。 要求直接回答 continue 或 switch 并说明理由。这个注入不复杂但效果出奇好因为模型虽然不记得历史但你提醒它回头看时它还是能对齐上下文与当前状态的。我实测的效果是约60%的机械重复能在第3次调用前被拦截下来剩下的还是靠次数上限截断。另外一种语义回溯方法是摘要压缩。每隔5步用模型对话历史里已产生的关键决策点总结成2-3句话替代原始历史加入上下文。这就相当于让Agent每5步回看一次全局避免前期目标在长上下文中被淹没。我做过对比实验不压缩的历史中第20步时模型对初始目标的还原度显著下降压缩之后即使到第40步还原度仍能保持在初始水平。4.3 状态机与呼吸灯式等待还有一个容易被忽视的循环诱因Agent调了一个需要异步执行的工具工具返回了PENDING状态Agent却把它当成失败来重试。比如它请求生成一个报告服务端返回任务已提交预计5分钟后完成模型看不懂这个状态以为是报错于是又提交了一遍。大量重复就是这样发生的。解决思路是给Agent设计一个等待状态。工具返回PENDING时不是让模型立刻决定下一步而是显式告诉模型当前任务处于等待状态不需要额外操作请返回等待指令。在代码层面工具调用结果里增加一个status字段取值为pending、success、failedAgent框架根据status决定如何进入下一步而不是全交给模型自由发挥。这里我加了一个呼吸灯机制就是当Agent进入等待状态时框架会在上下文中持续注入一个静态文本等待结果中请勿重复调用并且以每分钟一次的频率拉取异步结果。一旦结果就绪静态文本被替换成真实结果模型立即恢复推理。这个机制让我负责的某调度类Agent重复调用率降了很多因为大部分循环根本不是模型不想停而是它不知道自己在等什么。5. 绕开弯路设计阶段就该做对的几件事前面聊的都是运行时怎么防循环。说实话等Agent真跑起来再抢救已经算是事后诸葛了。真正的高手是在设计阶段就把循环的滋生土壤掐掉让Agent不容易走进死胡同。这部分我想聊几件在系统设计上容易做错的几件事。5.1 给Agent一块情绪面板请求级状态注入我刚开始做Agent的时候和大多数人一样所有状态都靠模型从上下文里自行推断。后来发现有一个巨大的问题模型对自己已经试过某方案但没有效果这件事缺乏感知。它只有我下一步要做什么没有我目前处于什么状态。后来我给Agent的运行时加了一块状态面板也叫请求级元状态。这块面板不参与模型业务推理但它会以固定格式显示在系统提示词末尾[Current State] - Step: 8 / 30 - Last Action: query_order(order_id12345) - Last Result: not_found - Repeated Action Count for query_order: 3 - Remaining Tools: verify_user, ask_user_for_input, escalate这样做有几个好处。第一模型不需要从漫长的历史里回忆起自己干了什么状态面板替它记好了。第二模型能看到重复次数这个数字数字一旦超过阈值模型自己就能意识到这条路走不通了。第三面板里列出了剩余的可选操作相当于给模型划了个圈它不会天马行空地乱转。这个状态面板的灵感来自一个很朴素的经验人在迷路时不是靠回忆走过哪条路走出来的而是靠我现在在哪里、还有哪些路可以走这一张地图走出来的。给Agent一张地图比让它自己画地图靠谱得多。落地的时候有一点要特别注意状态面板里的内容必须是结构化生成的而不是模型自己生成的。不然模型会在状态面板里撒谎例如明明已经重复调了5次它却写已尝试次数0那就麻了。5.2 让工具自己说再做一次也不会更好工具定义是设计阶段真正拉开差距的地方。同样的查询工具一种定义返回查询成功订单状态为已发货另一种返回查询成功订单状态为已发货。提示该订单状态稳定重复查询不会获得新信息请勿重复调用。后者就好得多。这个方案不只用在查询场景。凡是Agent可能反复调用的工具我都建议在返回结果里加一个terminal_hint字段。这个字段表示当前结果是否已经达到最终状态重复调用是否可能产生新结果。对查余额查时间查配置这类幂等工具terminal_hint固定为true对异步任务状态查询这类工具terminal_hint由服务端判断。我有段时间懒得写这些提示觉得模型能自己理解。后来在排查一个死循环案例时发现模型把所有返回结果都当作中间状态于是它每隔几秒就重新查一次直到压垮下游服务。从那以后我立了个规矩工具返回必须明确告诉模型你再调用我多少次我还是这个答案把这个作为工具开发的标准要求写进了团队的规范文档。5.3 协议上的护栏response_format与终止标记最后是协议层。我把Agent的每一步输出都规范成结构化JSON其中包含一个should_continue字段它的值只能是continue或terminate。当模型判断任务已完成、无法完成、或需要用户介入时它必须显式输出terminate。这个设计看起来简单但它把要不要结束从模型的潜意识里抽出来变成了一个显式的决策点。模型在生成最后一步时就必须思考当前是否达成了目标而不是稀里糊涂地再生成下一个tool_call。我在某个模拟项目里做了一组对照实验加了should_continue字段后Agent的平均运行步数缩减了近40%原因是模型比人类想象的要懒——只要给它一个体面的退出按钮它会更倾向于在合适的时候退出而不是硬撑着继续干。另一个协议上的护栏是输出格式强约束。我见过不少死循环是模型生成的JSON格式崩坏导致的比如括号不匹配然后解析器抛错框架自动重试模型再生成一遍同样的格式错误形成了解析器—模型层面的循环。解决方法是提示词里给JSON Schema示例同时在解析失败时不是重试原输出而是把这条错误输出注入上下文提醒模型你刚才的格式不符合要求原因是什么。这样一来模型下次生成的正确率会显著提升。6. 常见问题速查与个人踩坑记录到这一个章节我把平时排查Agent死循环时常遇到的情形整理成了一张速查表和一些个人心得。表格不是万能的但拿着它能少走不少弯路。6.1 Agent死循环排查速查表症状可能根因快速验证方式对应解法同工具连续高频调用工具反馈无区分度/失败码相同查看相邻步骤的参数哈希与返回码工具返回增加状态码分类 单工具调用上限上下文越长越频繁重复模型注意力衰减导致目标遗忘在第20、40步分别询问模型任务目标摘要压缩 状态外置 状态面板模型不断调用异步查询工具PENDING状态被误判为失败查看工具返回里是否有pending关键字等待状态机 呼吸灯机制模型反复生成解析失败输出JSON格式崩坏/解析器重试查看日志中parse error出现的频率输出Schema强约束 解析失败信息注入模型卡在某一步无法前进可用操作列表未显式暴露检查状态面板是否有剩余工具清单在提示词中保留可选项列表任务快完成时突然跑偏结论已生成但未显式声明终止查看最后几步是否仍输出tool_call增加should_continue显式标记这张表我贴在了团队的告警墙上每次遇到新循环问题就对照一下。它的好处是让排查从凭感觉变成了按套路走效率至少提升一半。6.2 避坑清单五种最像真理的错觉最后分享几个我踩过的坑每一条背后都有一个真实的加班夜晚。第一坑模型不会傻到重复调用同样的失败接口。它真的会。模型的每一步都只基于当前上下文做判断它没有记忆也没有羞耻感。你不给它护栏它就敢用同一个错误参数永远试下去。所以我现在的原则是凡是能用工程手段挡住的绝不指望模型自觉。第二坑把max_iterations设大一点任务就能跑完。恰恰相反。设太大只会让它把预算烧在循环里。我现在的做法是把上限设成理论上限的1.2倍加一点缓冲然后把多余预算留给重试机制而不是给模型无限发挥。第三坑死循环只是偶发问题不用专门设计。概率比你想的高。一旦Agent的决策空间足够大、工具足够多循环出现的可能性按指数级增长。我见过一个配置了6个工具的Agent在24小时的压测里出现了17次循环。不事先设计好终止机制就是在给自己埋雷。第四坑给模型更强一点的推理能力它就能自己走出来。推理能力解决的是能不能想明白循环问题解决的是有没有退出机制。你给Agent配上再强的模型它想明白了也可能收不住手。就像一个分析能力很强的人面对一个没有尽头的任务他也能不眠不休地干下去直到累倒。第五坑循环检测就是查重复重复了就终止。别只做完全重复检测。Agent更常见的是每次换一个参数重复同一类操作这种语义层面的循环靠字符串匹配根本查不出来。一定要配合状态面板里的重复次数计数和工具类型占比分析一起用。写到这里有个小技巧再分享一次我现在所有的Agent项目上线前必做一次疲倦测试。人为设计一个总是返回同一错误的假工具让Agent跑5轮观察它会不会在循环中游荡。大多数循环问题都在这个测试里现了原形。这个测试成本极低但价值远超我在任何文档里看到过的模型评估指标。如果你们的Agent系统还没做过类似测试建议尽快补上能救不少周末。