一个没有设最大步数上限的 AI Agent,遇到一个它搞不定的任务后陷入了死循环,一夜之间烧掉了我们大半个月的模型预算:一次 Agent 失控的深度复盘

上线了一个能自动调工具的 AI Agent,前一天测试一切正常,第二天一早账单告警:一夜 Token 消耗几百倍、大半月预算被烧光。日志显示一个任务循环了几万步,反复调同一个工具、失败、换法重试、再失败。根因是 ReAct 主循环用 while True、没有最大步数上限,唯一出口是大模型主动给最终答案——可任务无解时大模型会固执地永远重试、出口永不到达。本文讲透 Agent 自主循环为何必须有硬性边界,给出最大步数+预算+超时+重复检测四道刹车、工具幂等、全局成本熔断、高危人工确认的正解,梳理 Agent 工程常见坑,最后落到'自主能力不等于自我约束、必须从外部装上刹车'的认知。

一个没有设最大步数上限的 AI Agent,遇到一个它搞不定的任务后陷入了死循环,一夜之间烧掉了我们大半个月的模型预算:一次 Agent 失控的深度复盘

那是一个让我看到账单时手都在抖的早晨:我们上线了一个能自动调用工具(查数据库、调接口、读文件)来完成任务的 AI Agent,前一天测试一切正常。可第二天一早,模型 API 的账单告警疯狂轰炸——一夜之间,Token 消耗是平时的几百倍,大半个月的预算被一个晚上烧光了。我冲进日志一看,头皮发麻:有一个 Agent 任务,从头一天晚上到第二天早上,循环执行了几万步,反反复复地调用同一个工具、得到同样的失败结果、再换个说法调一遍、又失败……它就这么不知疲倦地、永不停止地循环了一整夜。我盯着那几万行几乎一模一样的日志,终于看清了真相:我们的 Agent 用的是经典的"思考→行动→观察→再思考"(ReAct)循环,可我们压根没给这个循环设一个"最大步数"的上限。当它遇到一个它能力范围内根本完成不了的任务(那条数据其实不存在,但它坚信能查到)时,它就陷入了"失败 → 换个方式重试 → 又失败 → 再换 → 再失败"的死循环,而没有任何机制能让它停下来——直到把钱烧光。这篇就把这次"Agent 循环失控、烧光预算"的坑,从头到尾复盘一遍。

故障现场:一个没有任何"刹车"的 Agent 循环

问题代码,是一个简化版的 Agent 主循环——它有"油门"(不停地思考、行动),却没有任何"刹车":

# ✗ 出问题的 Agent 主循环: 没有任何终止保护
def run_agent(task: str):
    messages = [{"role": "user", "content": task}]
    while True:                            # ✗ 雷! 无限循环, 没有最大步数上限
        response = llm.chat(messages, tools=TOOLS)   # 让大模型思考下一步
        if response.tool_calls:
            # 大模型决定调用工具
            for call in response.tool_calls:
                result = execute_tool(call)          # 执行工具(查库/调接口...)
                messages.append({"role": "tool", "content": result})
            # ✗ 然后回到 while 顶部, 继续让大模型思考——周而复始
        else:
            # 大模型给出了最终答案, 才退出
            return response.content
    # 致命问题:
    # - 唯一的退出条件是"大模型主动给出最终答案(不再调工具)";
    # - 但如果任务它【根本完不成】(数据不存在/工具一直失败/它想不通),
    #   大模型会【永远】认为"我还需要再试一个工具"→ 永远不给最终答案;
    # - → while True 永远转下去, 每一圈都是一次(昂贵的)大模型调用 + 工具调用;
    #   → 一夜几万圈, Token和钱哗哗地烧, 没有任何东西能让它停。

# 还缺的其他"刹车":
# - 没有"重复动作检测": 它一遍遍调同一个工具、传几乎一样的参数, 没人发现这是在原地打转;
# - 没有"Token/成本预算上限": 烧了多少了? 没人统计、没有阈值熔断;
# - 没有"超时": 一个任务跑了一整夜, 没有时间上限把它掐掉。

第一次理清这个 while True 时,我冷汗直冒:"我给了它调用工具的能力、给了它自主决策的循环,却唯独忘了给它一个'什么时候必须停下来'的约束。"这个坑最危险的地方在于:它把"自主性"和"失控"之间那条本就很细的线,彻底暴露了——Agent 的强大,正在于它能"自主地、循环地决策和行动";可一旦这个"自主循环"没有边界,这份强大就瞬间变成失控:它会用你给它的能力(调工具、花钱),不知疲倦地把灾难放大而且,和传统程序的 bug(崩了就停了)不同,Agent 的失控是"越界地、持续地消耗真实资源(钱、配额、对外部系统的调用)"——它不会崩,它会一直"努力地"做错事,直到把资源耗尽下面就来拆解,为什么 Agent 必须有"刹车"。

第一件事:搞懂 Agent 循环为什么必须有"硬性边界"

我顺着这次事故,把"Agent 自主循环为什么天然需要约束"彻底想清楚了。

为什么 Agent 的自主循环, 必须有"硬性的终止边界"?

【核心: Agent的退出依赖"大模型自己判断任务完成"——这是【不可靠】的, 必须有外部硬约束兜底】

1. Agent 循环的本质:
   它是一个"大模型决策 → 执行 → 把结果喂回大模型 → 再决策"的循环;
   循环的"出口", 是大模型判断"任务完成了, 我给最终答案"。

2. 问题: "大模型判断任务完成"这个出口, 是【不可靠】的:
   - 任务本身可能【无解】(数据不存在、工具坏了、目标自相矛盾);
   - 大模型可能【固执】: 它不会"认输", 反而一直"我再换个方法试试";
   - 大模型可能【绕圈】: 在几个动作之间反复横跳, 自以为在进展;
   - → 这些情况下, 那个"出口"永远不会到达 → 循环永不终止。

3. 危险被放大: 这个循环的每一圈, 都在【消耗真实资源】:
   - 每圈一次大模型调用(花钱、占配额);
   - 每圈可能调用真实工具(查库、调外部接口——还可能有副作用!);
   - → 失控的循环 = 持续地、加速地烧钱 + 轰炸外部系统。

4. 根本原则: 不能把"是否停止"【完全】交给大模型自己决定:
   - 大模型是"概率性、不保证收敛"的, 你不能假设它"一定会在合理步数内停";
   - 必须由【外层的、确定性的程序】给它套上【硬性的边界】:
     最大步数、预算上限、超时、重复检测——这些是"刹车", 由代码强制执行。

类比: Agent像一个很能干但有时钻牛角尖的实习生;
   你给他自主权(好), 但必须规定"最多试N次、最多花X钱、超时就来找我"(边界);
   否则他可能为一个死任务, 一个人在工位上耗到天亮、还刷爆了公司的报销额度。

一句话: Agent的退出靠大模型自判, 不可靠; 必须由外层程序强制套上最大步数/预算/超时/
   重复检测等硬性边界, 让"自主"始终在"可控"的笼子里——自主性必须配上确定性的约束。

这套道理,是整个坑的根。Agent 循环的本质是"大模型决策→执行→结果喂回→再决策",循环的出口是大模型判断"任务完成";但这个出口不可靠:任务可能无解、大模型可能固执("我再换个方法试")或绕圈,这些情况下出口永不到达、循环永不终止。每一圈都在消耗真实资源(每圈一次花钱的大模型调用 + 可能有副作用的真实工具调用),失控循环 = 持续加速地烧钱 + 轰炸外部系统。根本原则是:不能把"是否停止"完全交给大模型自己决定——它是概率性、不保证收敛的,必须由外层的、确定性的程序套上硬性边界(最大步数、预算、超时、重复检测)就像一个能干但会钻牛角尖的实习生,给自主权的同时必须规定"最多试 N 次、最多花 X 钱、超时来找我"一句话:Agent 退出靠大模型自判不可靠;必须由外层程序强制套上最大步数/预算/超时/重复检测等硬性边界,让自主始终在可控的笼子里。

第二件事:正解——给 Agent 套上最大步数、预算、超时、重复检测四道刹车

搞懂了原理,正解就清晰了:给 Agent 循环套上多道确定性的硬约束——最大步数上限、Token/成本预算上限、整体超时、重复动作检测;任一道触发就优雅终止并兜底

# ✓ 正解: 给 Agent 主循环套上多道"刹车"
def run_agent(task: str, max_steps=15, budget_tokens=50000, timeout_sec=120):
    messages = [{"role": "user", "content": task}]
    used_tokens = 0
    start = time.time()
    recent_actions = []                       # 记录最近的动作, 用于重复检测

    for step in range(max_steps):             # ★ 刹车一: 最大步数上限(不再 while True)
        # ★ 刹车二: 预算上限
        if used_tokens >= budget_tokens:
            return finish("达到Token预算上限", messages)
        # ★ 刹车三: 超时
        if time.time() - start > timeout_sec:
            return finish("达到超时上限", messages)

        response = llm.chat(messages, tools=TOOLS)
        used_tokens += response.usage.total_tokens

        if not response.tool_calls:
            return response.content           # 正常出口: 大模型给出最终答案

        for call in response.tool_calls:
            # ★ 刹车四: 重复动作检测(同样的工具+参数反复调 = 在原地打转)
            sig = (call.name, json.dumps(call.arguments, sort_keys=True))
            recent_actions.append(sig)
            if recent_actions.count(sig) >= 3:    # 同一动作出现3次 = 死循环征兆
                return finish(f"检测到重复动作 {call.name}, 疑似死循环, 终止", messages)

            result = execute_tool(call)
            messages.append({"role": "tool", "content": result})

    # ★ 兜底: 步数耗尽仍没完成 → 优雅退出, 而不是无限转下去
    return finish("达到最大步数仍未完成, 优雅终止", messages)

def finish(reason: str, messages) -> str:
    logger.warning(f"Agent提前终止: {reason}")
    # 让大模型基于已有信息给一个"尽力而为"的回答 + 说明没完成
    return llm.chat(messages + [{"role":"user",
        "content": f"由于{reason}, 请基于目前已知信息给出最好的回答, 并说明哪些没能完成。"}]).content
# ====== 配套防护(同样重要) ======

# 1. 工具调用要幂等/可控副作用: Agent可能重试调用工具, 有副作用的工具(下单/发邮件/写数据)
#    要做幂等(带幂等键), 否则重试会重复执行 → 重复下单/重复发信。

# 2. 全局成本熔断: 不只单个任务有预算, 整个系统/账号层面也要有日成本上限和告警,
#    一旦异常飙升就自动熔断(本文若有这层, 就不会烧一整夜)。

# 3. 高风险工具要人工确认(human-in-the-loop): 删数据、转账、对外发布等危险动作,
#    Agent不能自己拍板, 要暂停等人确认。

# 4. 可观测: 记录每一步的思考/动作/结果/token, 出问题能快速定位(本文靠日志才查到)。

# 核心: Agent循环必须有多道确定性硬约束——最大步数、预算上限、超时、重复检测, 触发即优雅终止;
#   配合工具幂等、全局成本熔断、高危人工确认、全程可观测; 给"自主"装上"刹车"和"护栏"。

修复的核心,是"给自主的 Agent 装上多道确定性的刹车与护栏"四道刹车:最大步数上限(用 for range(max_steps) 取代 while True)、Token/成本预算上限、整体超时、重复动作检测(同样的工具+参数反复调就判定死循环);任一触发就 finish 优雅终止(让大模型基于已知信息尽力回答并说明没完成)配套防护同样重要:工具幂等(有副作用的工具带幂等键,防重试重复执行)、全局成本熔断(系统级日成本上限+告警,本文若有这层就不会烧一整夜)、高危动作人工确认(human-in-the-loop)、全程可观测(记录每步思考/动作/结果/token)归根结底:Agent 循环必须有最大步数/预算/超时/重复检测多道硬约束,触发即优雅终止;配合工具幂等、全局成本熔断、高危人工确认、全程可观测。

第三件事:AI Agent 工程的其他常见坑

排查后我把 AI Agent 工程化相关的其他常见坑也系统梳理了一遍。

AI Agent 工程的其他常见坑

# 1. 循环没有终止边界(本文): 死循环烧光预算。→ 最大步数+预算+超时+重复检测。

# 2. 工具有副作用却没幂等: Agent重试导致重复下单/发信/扣款。→ 工具幂等(幂等键)。

# 3. 上下文无限增长: 每步都往messages堆, 很快超上下文窗口/越来越贵。→ 历史摘要/裁剪。

# 4. 工具返回结果过大: 一个工具吐回几万字塞爆context。→ 截断/摘要工具输出。

# 5. 高危操作Agent自己拍板: 删库/转账没有人工确认。→ human-in-the-loop审批。

# 6. 工具描述写不清: 大模型不知道何时该用/参数怎么填, 乱调工具。→ 清晰的工具说明+参数schema。

# 7. 没有可观测/追踪: 出了问题不知道Agent每步在想啥做啥。→ 记录每步trace。

# 8. 完全信任Agent的输出: 把它的结果直接当真执行。→ 关键结果要校验/兜底。

# 共同根源: Agent = "不确定的大模型" + "能产生真实副作用的工具" + "自主循环";
#   它把大模型的"不确定性"和工具的"真实威力"用循环放大了, 任何环节失控后果都被放大。

# 核心: 把Agent当成"强大但不可全信的自主体"来工程化: 套硬性边界(步数/预算/超时)、
#   工具幂等、上下文管理、高危人工确认、全程可观测、输出校验; 自主性必须配套强约束与护栏。

排查让我把 Agent 工程的其他坑也梳理清了。一、循环没终止边界(本文)。二、工具有副作用却没幂等(重试重复执行)。三、上下文无限增长(超窗口/越来越贵)。四、工具返回过大(塞爆 context)。五、高危操作 Agent 自己拍板(要人工确认)。六、工具描述写不清(乱调工具)。七、没有可观测/追踪八、完全信任 Agent 输出它们的共同根源是:Agent = "不确定的大模型" + "能产生真实副作用的工具" + "自主循环";它把大模型的不确定性和工具的真实威力用循环放大了,任何环节失控后果都被放大核心是:把 Agent 当成"强大但不可全信的自主体"来工程化:套硬性边界、工具幂等、上下文管理、高危人工确认、全程可观测、输出校验;自主性必须配套强约束与护栏下面这张图,是这次 Agent 循环失控的成因与解法:

第四件事:Agent 必备的"刹车与护栏"速查表

这次踩坑后,我把一个生产级 Agent"必须有哪些刹车与护栏"整理成一张表,逐条对照落地。

护栏 防的是什么 怎么做
最大步数 死循环(本文) for max_steps, 非while True
预算上限 烧光token/钱 累计token超阈值即停
超时 单任务跑太久 整体时间上限
重复动作检测 原地打转 同动作多次即判死循环
工具幂等 重试重复副作用 幂等键
全局成本熔断 系统级失控 日成本上限+告警
高危人工确认 危险操作误执行 human-in-the-loop
可观测 出问题难定位 记录每步trace

这张表把 Agent 的安全网钉清了。核心是:一个能真正放到生产的 Agent,光有"聪明的大脑(大模型)"远远不够,它必须被一整套"刹车与护栏"包裹起来——限制它能跑多久(步数/超时)、能花多少(预算/熔断)、防它原地打转(重复检测)、防它重复闯祸(幂等)、拦住它的危险动作(人工确认)、让它全程可被观察(trace)它给我的最大启发是:赋予一个系统"自主性/能力"和给它配套"约束/安全机制",必须是同步进行的——能力越大,需要的约束越强;本文的惨痛正源于"能力(自主调工具、花钱)给足了,约束(边界、护栏)却一个没配"的严重失衡这其实是一条工程通则:"能力"和"控制"要匹配——你给一个组件越大的权力(花钱、动数据、调外部、自主决策),就越要给它越严密的约束(限额、审批、隔离、可回滚、可观测);"放权"的同时必须"设限",二者失衡(有权无限)就是灾难的温床;这在 Agent、在微服务、在权限设计、在自动化系统里,都一样成立能力与约束同步配套、放权的同时设限——是这个 Agent 失控坑带给我的、关于设计自主系统的核心认知。

第五件事:Agent 与传统程序的本质差异

这次事故也让我深刻意识到,写 Agent 和写传统程序,思维上有几处根本不同。我整理成表。

维度 传统程序 AI Agent
控制流 开发者写死的确定逻辑 大模型动态决定下一步
是否会终止 逻辑保证(一般) 不保证, 需外部强制边界
出错方式 报错/崩溃就停 "努力地"持续做错事
资源消耗 大致可预估 可能失控暴涨
可预测性 高(确定性) 低(概率性)
该怎么对待 信任逻辑 约束+监控+兜底

这张表道出了 Agent 编程的"范式转变"。核心是:传统程序的控制流是开发者写死的、确定的(你能推断它一定会走到哪、一定会停);而 Agent 的控制流是大模型动态决定的、概率性的——它不保证终止、出错时会"努力地持续做错事"、资源消耗可能失控暴涨它给我的深刻启发是:写 Agent,需要一次思维范式的切换——从"我(开发者)精确地控制程序每一步做什么"(命令式、确定性),转向"我设定目标和边界,让大模型在边界内自主地探索如何达成"(目标式、概率性);在这种范式下,我的核心职责不再是"写死每一步逻辑",而是"设计好护栏、约束、监控和兜底,让一个不完全可控的智能体,在安全的框架内发挥作用"这是一种全新的工程心态:从"控制过程"转向"约束边界 + 监督结果"——你管不了(也不该试图精确管)Agent 中间怎么想、怎么试,但你必须管住它的边界(不能越界)、成本(不能失控)、危险动作(不能乱来)、和最终结果(要校验);就像管理一个能干的下属:你给目标、定规矩、看结果,而不是替他做每个动作完成从"控制过程"到"约束边界、监督结果"的范式切换——是这个坑带给我的、关于如何驾驭 AI Agent 这类概率性自主系统的最深认知。

第六件事:上线一个 Agent 前,我现在的检查习惯

现在每当我要把一个 Agent 放到生产,我都会按这张图把护栏过一遍:

这张图的精髓,是"上线前把刹车、护栏、监控一项项确认齐"先确认有最大步数上限(没有就停)、预算/超时/重复检测齐全有副作用的工具做幂等且高危动作人工确认有全局成本熔断和每步 trace,最后小流量灰度盯着监控再放量这套习惯,让我从"Agent 能跑通就上"变成了"上线前先确认护栏齐不齐"——核心始终是:Agent 上生产前,确定性的边界、护栏、监控、兜底必须先配齐,再灰度放量。

我立下的几条规矩

这场"Agent 死循环烧光预算"的事故,换来了我做 AI Agent 时,刻进骨子里的几条铁律:

  1. Agent 循环必须有最大步数上限。用 for max_steps,绝不裸用 while True。
  2. 退出不能只靠大模型自判。它不保证收敛,必须有外部确定性边界兜底。
  3. 配齐预算、超时、重复动作检测。多道刹车,任一触发就优雅终止。
  4. 有副作用的工具必须幂等。Agent 会重试,否则重复下单/扣款。
  5. 系统级要有成本熔断和告警。异常飙升自动刹住,别烧一整夜。
  6. 高危操作必须人工确认。删库/转账 Agent 不能自己拍板。
  7. 能力与约束同步配套。放权多大,设限就要多严;全程可观测、结果要校验。

写在最后

回头看,这场由"一个 while True 没有上限"引发的、一夜烧光预算的事故,真正教给我的,远不止"Agent 循环要设最大步数"这一个技巧。它让我对"赋予一个东西'自主行动的能力',就必须同时赋予它'不会失控的约束'",有了一次代价高昂的体会。我栽跟头,根源在于我被 Agent 的"智能"迷惑了,产生了一种危险的过度信任:我下意识地以为,既然它"聪明"到能自己思考、自己决定调什么工具,那它自然也"聪明"到知道什么时候该停下来——就像一个聪明人不会傻到为一件不可能的事耗一整夜。可这恰恰是我对它本质的误判:大模型的"智能",是一种"生成下一步看起来合理的行动"的能力,它并不内禀"知止"的智慧——它不会累、不会心疼钱、不会"觉得不对劲而停下来反思";你给它一个目标,它就会"锲而不舍"地一步步试下去,哪怕这条路根本走不通、哪怕代价高到离谱,它也不会自己喊停。"能干"和"知止",在它身上是两件完全分开的事。这让我领悟到一个驾驭一切"自主系统"的根本原则:"自主性"和"自我约束"不是一回事——一个系统能"自主地做很多事",绝不意味着它能"自主地、恰当地约束自己";"知道何时停止、何时收手、何时止损"这种"自我约束",往往不会自发产生,而必须由外部、用确定性的机制,明确地施加上去;越是给一个系统强大的自主能力,就越要从外部为它装好"它自己不会有"的那道刹车认清自主能力不等于自我约束、必须从外部为自主系统装上确定性的刹车——这,是我用一夜烧光的预算,换来的、关于 AI Agent、也关于如何安全地驾驭一切强大而不完全可控的系统的、最朴素也最昂贵的领悟。如果这篇复盘,能让你在上线下一个 Agent 前,先回头确认一句"它的循环,有上限吗?它失控了,有什么能刹住它?",那我们那一夜烧掉的预算,就还算买了个教训。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

一个用 as User 把后端返回的 JSON 强转成类型的写法,在字段结构对不上时让 TypeScript 的类型检查彻底成了摆设、运行时崩在 undefined 上:一次类型断言滥用的深度复盘

2026-6-2 15:04:05

技术教程

一段用 float 累加金额的 Python 代码,在几万笔订单后对账差了几分钱,让我栽进了二进制浮点精度的坑:一次用错数值类型的深度复盘

2026-6-2 15:14:36

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索