资讯动态

智能体死循环1.5亿次调用:根因排查与工程熔断方案

发布时间:2026/10/20 18:27:29 来源:尧图企业网站定制
1. 一个“死循环”是怎么被非科班选手揪出来的先把事情说清楚某头部厂商高调发布了一款号称“AI 时代拳头产品”的智能体平台宣传语里全是“自主规划”“多步推理”“工具调用”这类让人肾上腺素飙升的词。结果一个三本文科背景的独立开发者用最笨的办法——反复跑、反复看日志、反复数调用次数——发现这个智能体在特定任务下会陷入死循环单次会话内工具调用次数能飙到 1.5 亿次量级直到触发平台的硬性截断才停下来。这个数字不是精确统计出来的而是通过日志里循环调用的特征推算出来的同一个工具、同样的参数、几乎相同的返回被反复请求间隔时间稳定在毫秒级持续到超时上限。换句话说智能体卡在了一个“思考—调用—再思考—再调用”的闭环里自己走不出来。这件事值得写不是因为“大厂翻车”这种猎奇角度而是因为它暴露了一个所有做智能体的人都迟早会撞上的问题当模型获得自主调用工具的能力后谁来保证它不会把自己绕死这跟模型聪不聪明没关系跟工程约束做没做够有关系。我写这篇东西是想把“死循环”这件事从现象到原理到排查到修复完整地拆一遍。适合三类人看正在做智能体产品的开发者、被智能体不稳定问题折磨的测试同学、以及想理解“AI 自主性”边界的产品经理。不需要你有很深的算法背景但需要你愿意跟着日志一行行看。2. 智能体为什么会自己把自己绕进死循环2.1 从“单次问答”到“自主循环”的本质变化传统的大模型调用是一个开环你给一个 prompt模型返回一个结果结束。哪怕结果不好也不会自己再跑一遍。但智能体Agent引入了一个关键机制——循环。它的基本流程是模型输出一个动作比如调用某个工具→ 执行工具 → 把结果塞回上下文 → 模型再输出下一个动作 → 直到模型认为任务完成或触发终止条件。这个循环是智能体能力的来源也是所有稳定性问题的根源。开环系统最多是“答得不好”闭环系统可能“永远答不完”。你可以把它理解成一个自动导航单次问答是给你一张地图让你自己看智能体是你设了目的地后它自己开车。车会不会绕圈取决于导航逻辑和路况判断而不取决于发动机多强。2.2 死循环的三种典型触发路径根据我自己的复现和社区里同行的反馈智能体死循环基本逃不出这三类第一类是工具返回信息不足导致的重复尝试。模型调用了一个搜索工具返回结果为空或者格式不对模型判断“没拿到信息”于是用几乎相同的 query 再调一次。如果工具本身对这类 query 就是稳定返回空那这个循环可以无限持续。模型没有“换个思路”的元认知它只会重复它认为最可能有效的动作。第二类是目标判定模糊导致的“差一点”循环。任务要求是“找到满足条件 A、B、C 的结果”模型找到一个满足 A、B 但 C 差一点的于是它去调整参数再找找到的又是差一点来回震荡。问题出在终止条件没有被明确定义成可判定的布尔表达式而是留给了模型“自己感觉完成了没有”。第三类是多工具协作时的状态不一致。工具 A 写入的状态工具 B 读不到或者读到的格式对不上模型在 A 和 B 之间反复横跳试图“对齐”但底层数据模型本身就不自洽怎么调都对齐不了。2.3 为什么“1.5 亿次”这个量级是可能的很多人第一反应是1.5 亿次调用平台不早就该限流了吗这里有个关键点——循环的速度极快。如果每次循环只是模型生成一个短决策加上一次轻量工具调用单次往返可能只有几十到几百毫秒。按 100 毫秒算一小时就是 3.6 万次一天就是 86 万次。如果并发或者重试机制叠加上去量级会迅速膨胀。更关键的是很多平台的终止条件设计得过于宽松。常见的终止条件只有“模型主动输出 finish”和“达到最大步数上限”。如果最大步数上限设得很大比如为了支持复杂任务设成几千步而每一步内部又可能触发工具层面的重试那实际调用次数会是步数的若干倍。1.5 亿这个数字大概率是“步数上限 × 每步工具重试次数 × 某种并发放大”的结果而不是单条链路老老实实跑了一亿多次。这里有个容易被忽略的细节日志里看到的“调用次数”和计费口径的“调用次数”往往不是一回事。排查时一定要先确认你数的是哪一层。3. 复现与定位一个文科生是怎么数出来的3.1 最小复现环境的搭建思路要复现死循环不需要多复杂的架构。核心就三样东西一个能跑智能体的框架、一个会“稳定返回不理想结果”的假工具、一份能记录每次调用的日志。假工具是关键。我建议直接写一个最简单的函数输入什么就返回一个固定的、信息量不足的结果比如永远返回“未找到相关记录”。这样就能把变量控制住——不是工具时好时坏而是工具稳定地“不给力”看模型会不会自己走出来。# 一个故意“不给力”的假工具用于复现死循环 call_count 0 def fake_search(query: str) - str: global call_count call_count 1 # 记录每次调用的 query方便后续分析是否重复 with open(tool_calls.log, a) as f: f.write(f{call_count}\t{query}\n) return 未找到相关记录日志格式要包含三个字段调用序号、时间戳、入参。时间戳用来算循环速度入参用来判断是不是在重复同一个动作。这三个字段一摆出来死循环的特征就非常明显了——入参高度相似时间间隔稳定序号一路涨。3.2 从日志里识别死循环的三个特征拿到日志后不用写复杂的分析脚本用最朴素的方法就能看出来入参重复度高把 query 去重后看数量如果去重后只有个位数但总调用量是几万几十万那基本可以确定是循环。时间间隔稳定相邻两次调用的时间差如果集中在某个很窄的区间比如都是 80-120 毫秒说明是机器在稳定地循环不是人在操作。没有收敛趋势正常的多步推理调用次数应该随着任务推进逐渐减少或趋于稳定。死循环的调用次数是线性甚至指数增长的看不到头。我当时就是靠这三条在一个下午的时间里把问题从“感觉有点慢”定位到了“确定是死循环”。不需要什么高级工具sort | uniq -c | sort -rn这种命令行组合就够用了。3.3 定位到具体是哪一步开始跑偏找到循环还不够得知道是从哪一步开始进入循环的。方法是在日志里找“第一次出现重复入参”的位置。往前翻几步看模型在那之前收到了什么工具返回、做了什么决策。我的经验是跑偏往往发生在第一次工具返回不符合模型预期的那一步。模型原本预期工具会返回一个结构化结果结果返回了一段自然语言说“没找到”模型没有正确处理这个“没找到”的信号而是把它当成了“再试一次可能就有了”于是进入循环。排查心得不要一上来就看模型输出先看工具返回。大部分死循环的根因在工具返回的语义和模型预期的语义对不上。4. 根因拆解不是模型笨是约束没做够4.1 终止条件设计的常见误区大部分智能体框架的终止条件只有两个模型说“我完成了”或者步数超限。这两个都不够。“模型说我完成了”不可靠因为模型可能永远觉得自己“还差一点”。步数超限也不够因为超限之前可能已经跑了几百万次工具调用损失已经造成了。正确的做法是多层终止条件叠加终止条件类型具体规则作用硬性步数上限单次会话最多 N 步兜底防止无限跑重复动作检测连续 K 步入参相似度超过阈值提前掐断循环无进展检测连续 M 步没有产生新的有效信息识别“原地打转”时间上限单次会话总时长超过 T防止慢速循环成本上限累计 token 或调用次数超过预算控制经济损失这几层里重复动作检测是最有效的。实现起来也不复杂把最近几步的工具入参做哈希或者算相似度如果连续几步高度相似直接判定为循环并终止。4.2 工具返回语义与模型预期的错位这是最隐蔽也最常见的问题。工具返回“未找到”在人类看来是明确的“此路不通”但在模型看来这可能只是一个“暂时性失败”它会倾向于重试。解决办法是让工具返回携带明确的“可重试性”信号。比如返回结构里加一个字段{ status: not_found, retryable: false, suggestion: 该查询条件在当前数据源下无结果建议更换关键词或数据源 }retryable: false这个信号会显著降低模型重试的倾向。suggestion字段则给模型一个“下一步该干嘛”的明确指引避免它自己瞎猜。4.3 上下文膨胀反过来加剧循环还有一个恶性循环每次工具调用和返回都会塞进上下文上下文越来越长模型处理越来越慢但模型看到的“历史”里全是失败的尝试这反而强化了它“还没成功、需要继续”的判断。我实测过一个案例上下文超过一定长度后模型开始“忘记”自己已经试过某些参数于是重复调用之前已经失败过的工具。这就是上下文管理没做好导致的循环加剧。对策是对历史做摘要压缩尤其是失败的尝试不要原样保留而是压缩成一句“已尝试 X、Y、Z 方案均未成功”。这样既保留了信息又不会让上下文被无效尝试撑爆。5. 修复方案与工程加固实操5.1 给循环装上“熔断器”最直接的修复是在智能体执行循环里加一个熔断器。逻辑很简单维护一个最近 N 次调用的指纹列表每次新调用前先算指纹如果和列表里的高度重复直接中断并返回一个明确的错误。import hashlib from collections import deque class LoopBreaker: def __init__(self, window5, threshold3): self.window deque(maxlenwindow) self.threshold threshold def _fingerprint(self, tool_name, params): raw f{tool_name}:{sorted(params.items())} return hashlib.md5(raw.encode()).hexdigest() def check(self, tool_name, params): fp self._fingerprint(tool_name, params) self.window.append(fp) # 如果窗口内重复指纹超过阈值判定为循环 if self.window.count(fp) self.threshold: return False # 触发熔断 return True这个熔断器的参数需要根据任务特点调。窗口太小容易误杀正常重试窗口太大反应迟钝。我的经验是窗口 5、阈值 3 是个不错的起点然后根据实际误杀率微调。5.2 重写工具返回协议前面提到工具返回要携带可重试性信号这里给一个更完整的返回协议模板{ status: success | not_found | error | partial, retryable: true, data: {}, message: 人类可读的说明, next_action_hint: 建议的下一步动作可选 }关键点是status和retryable要分开。status描述事实retryable描述建议。有些错误是暂时性的比如超时retryable为 true有些是确定性的比如参数格式错误retryable为 false。模型看到retryable: false就不会傻傻重试了。5.3 上下文压缩与失败尝试的归档对于已经失败过的尝试不要原样留在上下文里。我的做法是维护一个“已尝试方案”的摘要列表每次失败后追加一条精简记录同时把详细的失败上下文移出主上下文。具体操作上可以在每轮循环结束后做一次检查如果本轮没有产生新的有效信息就把本轮的详细内容压缩成一行摘要替换掉原来的详细记录。这样上下文长度能控制在合理范围内模型也不会被大量重复的失败信息干扰。5.4 监控与告警让死循环在造成损失前被发现修复是一方面监控是另一方面。你不可能预判所有循环路径所以必须有一套监控能在循环刚冒头时就报警。我建议监控这几个指标单会话工具调用次数超过某个阈值比如 50 次就告警不用等到几百万次。重复调用率最近 10 次调用里重复指纹的占比超过 50% 告警。单会话时长超过预期时长的 3 倍告警。无新信息步数连续多步没有产生新的有效信息告警。这些指标不需要很精确关键是早发现。死循环的可怕之处在于它的增长速度早一分钟发现可能就少几万次无效调用。6. 常见问题与排查速查6.1 死循环排查速查表现象可能原因排查动作修复方向调用次数线性增长不收敛终止条件缺失或过松检查最大步数配置加多层终止条件入参高度重复工具返回语义不明确看工具返回是否含 retryable 信号重写返回协议上下文越来越长失败尝试未压缩统计上下文长度增长曲线加摘要压缩多工具间反复横跳状态不一致检查各工具读写的数据格式统一状态模型循环速度极快单步耗时短且无节流看时间戳间隔加节流或熔断6.2 几个容易踩的坑第一个坑是只设了步数上限就以为万事大吉。步数上限是兜底不是主要防线。等步数上限触发时可能已经跑了几十万次工具调用了。第二个坑是把重试逻辑放在工具层而不是智能体层。工具层的自动重试会放大循环——智能体调一次工具工具内部重试 10 次那循环速度直接乘 10。重试决策应该交给智能体工具层只负责如实返回。第三个坑是日志记录不完整。很多人只记了调用次数没记入参和时间戳导致发现问题后无法定位根因。日志三要素——序号、时间戳、入参——一个都不能少。第四个坑是用“模型应该能自己走出来”来安慰自己。模型没有这种元认知能力它只会沿着概率最高的路径走。约束必须由工程层提供不能指望模型自觉。6.3 一个反直觉的经验我踩过最深的坑是加了熔断器之后误杀率比预期高。原因是有些正常任务确实需要连续调用同一个工具多次只是参数略有不同。如果指纹算法太粗糙比如只哈希工具名不哈希参数就会把正常调用误判为循环。解决办法是把指纹做得更细工具名 关键参数 参数值的归一化。同时阈值不要设得太激进宁可漏杀也不要误杀。漏杀还有步数上限兜底误杀会直接破坏正常功能。7. 这件事对整个智能体行业的启示7.1 自主性的边界必须由工程定义“AI 时代拳头产品”这种宣传语背后隐含了一个危险的假设模型越自主越好。但工程实践反复证明自主性必须有边界而边界的定义权在工程层不在模型层。模型负责在边界内做决策工程负责画边界。边界画得好模型的能力才能安全释放边界画不好再强的模型也会把自己绕死。这跟给小孩划定活动范围是一个道理——不是不信任小孩而是有些风险必须提前隔离。7.2 评测体系里必须包含“稳定性”维度现在大部分智能体评测都在测“任务完成率”“推理准确率”很少有人测“在异常情况下会不会失控”。1.5 亿次死循环这种事在常规评测里是测不出来的因为常规评测的任务都是“正常路径”。我建议在做智能体评测时专门加一组“异常注入”测试工具返回空、工具返回格式错误、工具超时、工具返回矛盾信息。看智能体在这些情况下会不会进入循环。这组测试的价值比多测几个正常任务高得多。7.3 给独立开发者的机会大厂产品出这种问题对独立开发者来说反而是机会。因为大厂的架构往往很重改一个终止条件可能要走很长的流程。而独立开发者可以快速迭代把稳定性做得比大厂还好。具体来说独立开发者可以在自己的智能体产品里把“循环防护”做成一个卖点明确告诉用户“我们的智能体不会跑飞有熔断、有预算控制、有实时监控”。这在企业级场景里是很有吸引力的。7.4 我个人的几条实操建议最后分享几条我自己在做的习惯不一定对但确实帮我避免了不少坑第一任何智能体上线前先用假工具跑一遍异常路径。假工具稳定返回空、返回错误、返回超长文本看智能体怎么反应。这一步花不了多少时间但能提前暴露大部分循环问题。第二把工具调用次数做成一个显眼的面板。不要藏在日志里要放在监控首页。数字涨得快不快一眼就能看到。第三给每个智能体会话设一个“预算”不管是 token 预算还是调用次数预算。预算用完就停不管任务完没完成。这比任何复杂的终止条件都可靠。第四定期回看历史会话的调用分布。正常会话的调用次数应该是一个有长尾的分布如果出现几个异常高的离群点那就是循环的痕迹值得深挖。这件事说到底不是某个产品的问题而是整个行业在从“单次问答”走向“自主智能体”过程中必须跨过的一道坎。跨过去了智能体才真正可用跨不过去再炫酷的 demo 也只是 demo。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑