我的 AI Agent 陷入了死循环,把同一个工具翻来覆去调了几十次就是不肯停,token 哗哗烧光、任务却永远完不成的深度复盘

我做了个 AI Agent,给目标让它自己"思考→调工具→看结果→再思考"循环到完成。简单任务很好,可任务一复杂,它就陷入死循环:用几乎一样的参数把同一个搜索工具翻来覆去调了几十次,每次拿到没用的结果又再试,永远到不了最终答案,token 哗哗烧、账单飙升,不手动杀进程就烧穿预算。深究才懂这不全怪模型:我的循环是 while True、没有强制终止,终止全靠模型自己喊停;没有进展检测发现原地打转;工具失败反馈又太模糊让模型只能瞎重试。这篇从自主循环必须有外部护栏讲起,到最大迭代次数/重复检测/成本预算/优雅降级的正解、让 Agent 能进展的工具反馈与退出路径、可观测性,以及那句最戳心的——越自主越不确定的系统,越要装上确定性的护栏。

我的 AI Agent 陷入了死循环,把同一个工具翻来覆去调了几十次就是不肯停,token 哗哗烧光、任务却永远完不成的深度复盘

这是一个让我对"自主系统的护栏"刻骨铭心的故事。我做了一个 AI Agent:给它一个目标,它会自己"思考→调用工具→看工具返回的结果→再思考下一步",像这样循环往复,直到它认为任务完成、给出最终答案。这套"自主循环"的设计,正是 Agent 的魅力所在——它能自己规划、自己一步步完成任务。在简单任务上,它表现得很好,我一度非常满意。

可当任务变得复杂一点、或者遇到一些"边角情况"时,灾难发生了:我的 Agent,陷入了死循环。它把同一个工具(比如一个搜索工具),用几乎一模一样的参数,翻来覆去地调用了几十次——调一次,拿到一个没什么用的结果,然后它"思考"了一下,又调一次几乎一样的,再拿到一个没用的结果……它就这样,在原地无限地打转,永远到不了"给出最终答案"的那一步。而我眼睁睁看着的,是:每一次循环,都是一次大模型调用,token 像流水一样哗哗地烧;我的账单,在以肉眼可见的速度飙升;而那个任务,却永远完不成。如果不是我手动把进程杀掉,它能这么循环到天荒地老、把我的预算烧穿。我当时又惊又怒,第一反应还是"模型怎么这么笨,连'这样调下去没意义、该停了'都意识不到?"可冷静下来深究之后,我意识到,这不能全怪模型,根子在我的 Agent 设计上,缺了几样关键的东西:第一,我的 Agent 循环,根本没有一个"强制终止"的机制——我天真地以为,模型"自己会知道"什么时候该停;可一旦模型陷入某种困惑(比如工具总返回没用的结果),它就可能意识不到自己在原地打转,而我的循环,没有任何一道"硬性的刹车"(比如"最多循环 N 次就强制停下")去兜底。第二,我没有任何"进展检测"——我没去判断"这一步,和上一步,是不是在做几乎一样的事(原地踏步)";如果有,我本可以及时发现它卡住了。第三,也是更深层的,我那个工具,在失败或无结果时,返回的反馈太模糊、没有营养,导致模型不知道该如何调整,只能用差不多的参数,一遍遍地重试。一个能自己循环、自己决策的"自主系统",我却忘了给它装上一道"确定性的护栏"——结果,它的"自主",就变成了失控的"自燃"。

故障现场:一个没有刹车的自主循环

我把这个"Agent 死循环"的现场,用代码摊开给你看:

# ✗ 灾难: 一个没有任何"护栏"的 Agent 循环

def run_agent(goal):
    messages = [{"role": "user", "content": goal}]
    while True:                          # ✗ 死循环! 没有最大次数限制!
        # 1. 让大模型思考下一步(每次都烧 token)
        response = llm.chat(messages, tools=my_tools)

        if response.is_final_answer:     # 模型认为任务完成了
            return response.answer

        # 2. 模型决定调用某个工具
        tool_call = response.tool_call
        result = call_tool(tool_call)    # 调用工具
        messages.append({"role": "tool", "content": result})
        # 3. 回到循环顶部, 继续...

    # 问题: 如果模型一直不返回 is_final_answer(比如它陷入困惑),
    #   这个 while True 就永远转下去——
    #   - 反复调用同一个工具(参数几乎一样), 拿到差不多的没用结果。
    #   - 每一轮都是一次 LLM 调用, token 哗哗烧。
    #   - 任务永远完不成, 直到你手动杀进程 / 烧穿预算。

# 为什么模型会陷入循环?
#   - 工具返回的结果"没用 / 报错信息模糊", 模型不知道怎么调整,
#     只好用差不多的参数再试一次……再试一次……
#   - 模型没有"我已经试过这个了"的清醒, 在原地打转而不自知。
#   - 而我的代码, 没有任何机制去"打断"这种打转。

# 根因: 把一个"自主、不确定"的系统(LLM Agent), 放进了一个
#   "没有强制终止、没有进展检测、没有护栏"的循环里 → 失控的死循环。

看着这段代码,我才算真正理解了这个"烧钱的死循环"的根源。问题的核心,是我把一个本质上"自主、不确定"的系统(LLM 驱动的 Agent),放进了一个"没有任何护栏"的 while True 循环里。这套 Agent 循环的逻辑本身没错——"思考→调工具→看结果→再思考",这正是 Agent 工作的方式;但它的终止,完全寄托在一件事上:模型"自己"会在某一步,返回"任务完成(is_final_answer)"的信号。而我天真地假设,模型一定会、也总能走到那一步。可现实是:模型是会"陷入困惑"的——当那个工具,反复返回"没用的结果"或"模糊的报错"时,模型不知道该如何调整策略,于是它最"自然"的反应,就是用差不多的参数,再试一次;试出来还是没用,它就又试一次……它意识不到自己在"原地打转",更没有"我已经试过这个了、此路不通"的清醒。而最致命的是:我的代码里,没有任何一道机制,去"打断"这种打转——没有"最多循环 N 次"的硬性刹车,没有"这步和上步是不是在重复"的进展检测。于是,这个 while True,就真的永远地转了下去:每一轮,一次大模型调用、一笔 token 开销;任务,永远完不成。归根结底,我犯的错,是把一个不确定系统的"终止",完全托付给了这个不确定系统自己——我指望那个会犯糊涂的模型,自己永远能清醒地喊停;却忘了,作为这个系统的设计者,给它装上一道"确定性的、外部的护栏"(强制终止),才是我的责任。一个能自主循环、自主决策的系统,如果没有一道可靠的"刹车",那它的"自主",随时可能滑向失控的"自燃"——而我的 token 账单,就是这场自燃,寄来的灰烬。

第一件事:搞懂自主循环必须有"强制终止"和"进展检测"

定位到根源,我必须把"为什么 Agent 的自主循环必须有外部护栏"这件事,彻底想清楚:

Agent 的自主循环, 为什么必须有"外部护栏"

# Agent 的本质: 一个"由不确定的 LLM 驱动的"自主循环
#   循环: LLM 思考 → 决定调哪个工具 → 执行 → 看结果 → 回到 LLM 思考...
#   终止: 靠 LLM 在某步"自己判断"任务完成。

# 风险: 终止条件, 寄托在"不确定的 LLM"身上!
#   - LLM 可能陷入困惑、原地打转, 迟迟不给出"完成"信号。
#   - 一旦它不停, 这个循环就没有尽头。
#   → 把"终止"完全交给"会犯错的一方", 是不可靠的。

# 所以, 必须由"外部代码(确定的一方)", 给它加上护栏:

# 护栏1: 最大迭代次数(硬性刹车)——最重要!
#   while iteration < MAX_STEPS:  # 而不是 while True
#   → 不管模型停不停, 到了上限, 强制结束。这是兜底的最后防线。

# 护栏2: 进展检测——发现"原地打转"
#   - 检测是否在反复调用"相同工具+相同参数"。
#   - 检测多步之后状态有没有"实质进展"。
#   → 发现打转, 提前打断 / 改变策略 / 提示模型换个思路。

# 护栏3: 成本/时间预算
#   - 限制总 token 消耗 / 总耗时, 超了就停。
#   → 防止"烧穿预算"。

# 护栏4: 工具反馈质量(治本)
#   - 工具失败/无结果时, 返回"清晰、可指导下一步"的信息,
#     帮模型"跳出"重复, 而不是模糊报错让它干瞪眼。

# 核心原则: 任何"自主的、不确定的"系统, 都必须配一道"确定的"护栏。
#   别把系统的"安全/终止", 完全寄托在那个不确定的部分自己身上。

想清楚之后,我对"自主系统的设计",有了全新的认识。一个 Agent,本质上,是一个"由不确定的 LLM 驱动的"自主循环:它靠 LLM 在某一步"自己判断"任务完成,来终止。而这里就藏着一个根本性的风险——它的终止条件,寄托在"不确定的 LLM"身上!可 LLM 是会陷入困惑、会原地打转、会迟迟不给"完成"信号的;一旦它不停,这个循环就没有尽头。把一个系统的"终止",完全交给这个系统里"会犯错的那一方",本身就是不可靠的设计所以,我领悟到:必须由"外部代码(确定的那一方)",给这个自主循环,加上几道"护栏"。护栏一,最大迭代次数(硬性刹车)——这是最重要的一道:用 while iteration < MAX_STEPS 代替 while True,不管模型停不停,到了上限就强制结束,这是兜底的最后防线。护栏二,进展检测——检测是不是在反复调用"相同工具+相同参数"、多步之后状态有没有实质进展,一旦发现打转,就提前打断、或提示模型换个思路。护栏三,成本/时间预算——限制总 token 消耗和总耗时,超了就停,防止烧穿预算。护栏四,工具反馈质量(治本)——让工具在失败时,返回"清晰、能指导下一步"的信息,帮模型跳出重复。而贯穿这一切的,是一条我用 token 账单换来的核心原则:任何一个"自主的、不确定的"系统,都必须给它配上一道"确定的"护栏——绝不要把这个系统的"安全"和"终止",完全寄托在它那个不确定的部分自己身上。我之前,正是只享受了 Agent"自主"的美好,却忘了为这份自主,系上一道确定性的安全带。

第二件事:正解——给自主循环装上多道护栏

搞懂了根因——"自主循环没有护栏、终止全靠模型自觉"——正解就清晰了:由外部代码,给 Agent 的循环,装上几道"护栏":最大迭代次数(硬性刹车,最重要)、重复调用检测(发现原地打转)、成本预算(防烧穿),再加上改善工具的失败反馈(治本,帮模型跳出重复)。

# 正解: 给 Agent 循环装上多道护栏

MAX_STEPS = 15          # 护栏1: 最大迭代次数(最重要的硬刹车)

def run_agent(goal):
    messages = [{"role": "user", "content": goal}]
    recent_calls = []                            # 用于护栏2: 重复检测

    for step in range(MAX_STEPS):                 # ✓ 用 for + 上限, 不用 while True
        response = llm.chat(messages, tools=my_tools)
        if response.is_final_answer:
            return response.answer

        tool_call = response.tool_call

        # 护栏2: 重复调用检测——同样的工具+参数, 短期内调了好几次 = 打转
        sig = (tool_call.name, str(tool_call.args))
        recent_calls.append(sig)
        if recent_calls[-3:].count(sig) >= 3:    # 连续3次一模一样
            # 打断打转: 明确告诉模型"你在重复, 换个思路 / 或直接给出结论"
            messages.append({"role": "system", "content":
                "你已多次用相同参数调用同一工具且无进展。请换一种方法, "
                "或基于现有信息直接给出最终答案, 不要再重复。"})
            continue

        # 护栏4: 工具失败时, 返回"清晰、可指导"的反馈(治本)
        result = call_tool(tool_call)
        # 好的工具应返回: 成功的数据, 或失败时"为什么失败 + 建议怎么办"
        messages.append({"role": "tool", "content": result})

    # 护栏1 兜底: 到达最大步数仍没完成 → 强制结束, 给个兜底回应
    return "抱歉, 我尝试了多步仍未能完成任务。已知信息: ..." # 优雅降级, 而非死循环

# 护栏3(贯穿): 成本/时间预算
#   在循环里累计 token / 计时, 超过预算就 break, 同样优雅降级。

# 核心: 外部代码("确定的一方")负责"兜底和刹车";
#   LLM("不确定的一方")负责"思考和决策"。各司其职, 系统才可控。

这套正解,核心是给那个不确定的自主循环,套上几道由确定性代码把守的护栏。护栏1(最大迭代次数,最重要):用 for step in range(MAX_STEPS) 代替 while True——不管模型停不停,跑到上限就强制结束;这是最关键、最兜底的一道硬刹车,有了它,无论如何都不会再无限循环。护栏2(重复调用检测):记录最近几次的"工具名 + 参数",一旦发现连续几次都一模一样(在原地打转),就主动打断——给模型一条明确的提示:"你已经在重复了,请换个思路、或者基于现有信息直接给出结论",帮它跳出那个死胡同。护栏3(成本/时间预算):在循环里累计 token 消耗、计算耗时,一旦超过预算,就 break,防止烧穿。护栏4(工具反馈质量,治本):这是从根上减少循环的关键——让工具在失败或无结果时,不要返回模糊的报错,而要返回"为什么失败 + 建议下一步怎么办"这种清晰、可指导的反馈,这样模型才能"知道怎么调整",而不是干瞪眼、用同样的参数瞎试。而所有护栏触发时,都不该是"硬崩",而应是优雅降级——比如到达最大步数时,给一个"抱歉我尽力了,以下是已知信息"的兜底回应,而不是让进程卡死或抛个异常。归根结底,这套设计体现了一个清晰的分工:外部代码("确定的一方"),负责"兜底和刹车";而 LLM("不确定的一方"),负责"思考和决策"。两者各司其职,这个系统,才既能享受 Agent 自主的智能,又始终处在可控的边界之内。

下面这张图,对比了"无护栏的裸循环"和"有护栏的循环"两条路径:

这张图的对比很清楚:左边红色那条,无护栏的 while True、终止全靠模型自觉,模型一陷入困惑就反复调同一工具、没有刹车、无限打转,最终 token 烧穿、任务永远完不成;右边绿色那条,有最大步数、重复检测、预算等护栏,每一步都检查"到上限了吗/在打转吗/超预算了吗",触发就强制停止、优雅降级,一切正常就继续直到完成。两条路的根本分野,在于你有没有用确定性的外部护栏,去兜住那个不确定的自主循环。

第三件事:Agent 死循环的其它诱因和可观测性

填平了"没护栏"这个坑,我系统排查了 Agent 陷入死循环/异常的其它诱因,以及如何用"可观测性"来及时发现它们:

Agent 死循环/异常的其它诱因 & 可观测性:

# 死循环/卡住的其它诱因:
# 1. 工具反馈差(本文重点): 报错模糊/结果没用 → 模型瞎重试。
# 2. 目标不清/无法完成: 给的任务本身做不到, 模型却一直试图完成。
#    → 要让模型有"承认做不到、优雅放弃"的出口。
# 3. 两个工具/步骤互相拉锯: A 改了状态, B 又改回去, 来回拉锯。
# 4. 上下文过长导致模型"忘了"前面试过什么 → 又去试一遍。
#    → 注意上下文管理, 必要时总结历史。
# 5. 工具调用失败没处理(超时/异常), 模型收到乱七八糟的结果而卡住。

# 可观测性(让你能"看见"Agent 在干嘛, 及时发现异常):
# 1. 记录每一步: 第几步、思考了什么、调了什么工具+参数、返回了什么。
#    → 出问题时能回放整个决策链, 一眼看出它卡在哪、在重复什么。
# 2. 监控指标: 平均步数、超过 MAX_STEPS 的比例、token 消耗、耗时。
#    → "超上限比例突然升高" = 有一类任务让 Agent 频繁打转, 该排查了。
# 3. 告警: token/耗时异常飙升时告警, 别等账单出来才发现。
# 4. 评估集: 用一批典型任务定期跑, 看成功率/平均步数有没有退化。

# 核心: 自主系统更要"可观测"——因为它的行为是动态、不确定的,
#   你必须能"看见"它每一步在想什么、做什么, 才能发现问题、持续改进。

这一排查,让我对 Agent 的"失控风险"和"如何驾驭它",有了系统的认识。除了工具反馈差,Agent 陷入死循环或卡住,还有不少诱因:目标本身无法完成(任务做不到,模型却一直试图完成——要给模型一个"承认做不到、优雅放弃"的出口);两个步骤互相拉锯(A 改了状态、B 又改回去,来回拉锯);上下文过长导致模型"忘了"前面试过什么(又去试一遍——要注意上下文管理、必要时总结历史);工具调用失败没处理好(超时/异常让模型收到乱七八糟的结果而卡住)。而要驾驭这种"行为动态、不确定"的自主系统,可观测性至关重要:第一,记录每一步(第几步、思考了什么、调了什么工具和参数、返回了什么),这样出问题时,能回放整个决策链,一眼看出它卡在哪、在重复什么;第二,监控指标(平均步数、超过 MAX_STEPS 的比例、token 消耗、耗时),"超上限比例突然升高"往往就是"有一类任务让 Agent 频繁打转"的信号;第三,告警(token/耗时异常飙升时及时告警,别等账单出来才发现);第四,评估集(用一批典型任务定期跑,看成功率和平均步数有没有退化)。归根结底:自主系统,比传统系统更需要可观测性——正因为它的行为是动态的、不确定的,你必须能"看见"它每一步在想什么、做什么,才能在它失控前发现问题、并持续地改进它。让一个会自己做决策的系统"裸奔"在黑盒里,是最危险的。

第四件事:为什么 Agent 会陷入循环——以及怎么让它"能进展"

借着这次复盘,我深入想了一个更本质的问题:Agent 为什么会陷入循环?以及,除了"硬刹车",怎么从根上让它"能往前走"?我把思考沉淀了下来:

为什么 Agent 会陷入循环, 以及怎么让它"能进展":

# Agent 陷入循环的本质: 它"没有得到能让它前进的新信息"
#   - 它调工具, 是为了获取信息/改变状态, 来推进任务。
#   - 如果工具返回的, 是"没有新信息 / 看不懂 / 没法据此决策"的东西,
#     那它就"原地没动"——状态没变, 它自然倾向于"再试一次"。
#   → 循环, 往往是"缺乏有效进展"的表现。

# 怎么让 Agent "能进展"(治本):
# 1. 工具返回"信息丰富、可指导下一步"的结果
#    ✗ 失败时返回 "Error"      ✓ "查询失败: 参数 date 格式应为 YYYY-MM-DD"
#    ✗ 空结果返回 "[]"         ✓ "未找到结果, 建议放宽关键词 / 换个字段查"
#    → 让模型每次都能从结果里, 获得"下一步怎么改"的线索。

# 2. 让 Agent 有"记忆/反思"——知道自己试过什么
#    在上下文里清晰保留"已尝试的动作和结果", 让它别重复踩坑。
#    (有些框架会做"反思 reflection"步骤: 让模型回顾'我是不是在重复')

# 3. 给 Agent 明确的"退出路径"
#    告诉它: "如果尝试 N 次仍无法获取所需信息, 就基于现有信息作答,
#     或明确告知用户'无法完成', 不要无限尝试。"
#    → 给模型一个"体面放弃"的选项, 而不是逼它"必须成功"。

# 4. 任务分解 / 限定范围
#    把大而模糊的目标, 拆成小而明确的子任务, 每步更容易"有进展"。

# 进展 vs 打转的区别:
#   有进展 = 每一步都获得了新信息 / 状态向目标前进了。
#   打转   = 步骤在重复, 状态没变化, 没有获得新的有效信息。
#   → 设计 Agent, 要时刻保证它"每一步都有可能取得进展"。

这一深挖,让我对"Agent 循环"的本质,有了更透彻的理解。Agent 陷入循环的本质,是它"没有得到能让它前进的新信息":它调用工具,本是为了获取信息、改变状态,来推进任务;可如果工具返回的,是"没有新信息、看不懂、没法据此决策"的东西,那它就原地没动——状态没变,它自然就倾向于"再试一次"。所以,循环,往往是"缺乏有效进展"的表现由此,除了"硬刹车"这种兜底,要从根上让 Agent"能进展",有几个关键:第一,让工具返回"信息丰富、可指导下一步"的结果——失败时别只返回 "Error",而要说清"参数 date 格式应为 YYYY-MM-DD";空结果别只返回 "[]",而要建议"放宽关键词、换个字段查";让模型每次都能从结果里,获得"下一步怎么改"的线索。第二,让 Agent 有"记忆/反思"——在上下文里清晰保留"已尝试过的动作和结果",甚至加一个"反思"步骤让它回顾"我是不是在重复",别重复踩同一个坑。第三,给 Agent 明确的"退出路径"——告诉它"如果尝试 N 次仍拿不到所需信息,就基于现有信息作答、或明确告知无法完成",给它一个"体面放弃"的选项,而不是逼它"必须成功"。第四,任务分解——把大而模糊的目标,拆成小而明确的子任务,每一步都更容易"有进展"。而这背后,是"进展"和"打转"的本质区别:有进展,是每一步都获得了新信息、状态向目标前进了;打转,是步骤在重复、状态没变化、没获得新的有效信息。所以,设计一个 Agent,核心目标之一,就是要时刻保证它"每一步都有可能取得进展"把"让 Agent 打转"和"让 Agent 进展"的设计,对比成一张表:

维度 让 Agent 打转(踩坑) 让 Agent 进展(正解)
工具失败反馈 "Error" 模糊报错 说清原因+下一步建议
空结果 返回 "[]" 建议怎么调整再查
记忆 不记得试过什么 保留已尝试动作+反思
退出路径 逼它必须成功 给"体面放弃"的出口
目标 大而模糊 拆成小而明确的子任务

第五件事:给"自主、不确定"的系统,装上"确定"的护栏

这次踩坑,在认知层面给了我最大的纠偏——它让我明白了驾驭自主系统的核心法则。我把这层反思,沉淀了下来:

认知纠偏: 越是"自主、不确定"的系统, 越要有"确定"的护栏

# 我的误解(错误的):
#   "Agent 很智能, 它自己会处理好的。" —— 我对"自主"过于乐观,
#   把系统的"安全和终止", 完全托付给了那个不确定的智能体自己。

# 真相: "自主/智能"不等于"可靠/可控"
#   - LLM 是强大的, 但也是"不确定、会犯错、会陷入困境"的。
#   - 你越是让一个系统"自主决策", 它的行为就越不可预测。
#   - 而越不可预测的系统, 就越需要"确定性的边界"去框住它。
#   → 自主性 与 可控性, 要靠"护栏"来平衡。

# 这是设计一切"自主/AI 系统"的核心法则:
#   把系统分成两部分:
#   - "智能/不确定"的部分(LLM): 负责思考、决策、处理模糊问题。
#   - "确定/可靠"的部分(你的代码): 负责兜底、限制、校验、刹车。
#   → 让确定的部分, 去"约束和兜底"不确定的部分。
#   (类比: 给一个聪明但鲁莽的实习生, 配上明确的流程红线和审批。)

# 护栏的典型形态:
#   - 硬限制: 最大步数、超时、预算上限(到了就强制停)。
#   - 校验: 对 LLM 的输出/工具调用做合法性检查。
#   - 人在回路(human-in-the-loop): 高风险操作, 让人来确认。
#   - 优雅降级: 失控时, 安全地退出 + 兜底, 而不是崩溃/失控。

核心: 越自主、越不确定的系统, 越需要确定性的护栏。
  别迷信"智能"会自我约束——把"可控"亲手设计进去, 是你的责任。

这层反思,是这次踩坑给我最高维度的收获。复盘我的误解,根源是我对"自主"过于乐观——"Agent 很智能,它自己会处理好的"——我把系统的"安全和终止",完全托付给了那个不确定的智能体自己。可真相是:"自主/智能",不等于"可靠/可控"。LLM 是强大的,但它同样是"不确定、会犯错、会陷入困境"的;而你越是让一个系统"自主决策",它的行为就越不可预测;越不可预测的系统,就越需要"确定性的边界"去框住它。自主性,和可控性,正是要靠"护栏"来平衡的由此,我领悟到一条设计一切"自主/AI 系统"的核心法则:把系统,清晰地分成两部分——"智能/不确定"的部分(LLM),负责思考、决策、处理模糊的问题;"确定/可靠"的部分(你写的代码),负责兜底、限制、校验、刹车;然后,让确定的部分,去约束和兜底那个不确定的部分。这就像,你给一个聪明、但可能鲁莽的实习生,配上明确的流程红线和审批环节——你信任他的聪明,但你用制度,框住他的鲁莽。而护栏,有几种典型的形态:硬限制(最大步数、超时、预算上限,到了就强制停)、校验(对 LLM 的输出和工具调用做合法性检查)、人在回路(高风险操作让人来确认)、优雅降级(失控时安全地退出加兜底,而不是崩溃)。归根结底:越是自主、越是不确定的系统,就越需要确定性的护栏。千万别迷信"智能"会自我约束——把"可控"这件事,亲手地、显式地,设计进系统里,是作为设计者的你,不可推卸的责任。我那次,正是迷信了 Agent 的"智能",忘了亲手为它,筑起那道确定的护栏,才有了那场烧钱的失控。把"迷信自主"和"装上护栏"两种心态对比成一张表:

维度 迷信自主(踩坑) 装上护栏(成熟)
对智能体 它会自己处理好 它会犯错,需兜底
终止/安全 托付给模型自觉 外部代码强制保证
系统分工 全交给 LLM 智能决策+确定护栏
失控时 无限循环/烧穿 强制停止+优雅降级
设计责任 指望智能自约束 亲手设计可控性

一套"Agent 循环设计该有哪些护栏"的决策流程

把这次踩坑的全部教训,我浓缩成了一张"设计 Agent 自主循环时,该装哪些护栏"的决策图,贴在了团队做 Agent 的文档里:

这张图,把我"血泪换来"的整套方法论,串成了一条可执行的路径:设计 Agent 循环,第一道就是硬刹车(用 for range(MAX_STEPS) 而非 while True);每一步都做重复检测(在打转就打断、提示换思路);累计成本超预算就停;让工具失败时返回清晰可指导的反馈;完成或触发护栏时,要么返回答案、要么优雅降级给兜底回应,绝不死循环;而且全程记录每一步,做到可观测、可回放。这条"先有刹车、再谈智能"的设计准则,现在是我们团队设计每一个 Agent 循环时,必须先落实的底线。

我立下的几条 Agent 设计规矩

这次"Agent 死循环烧钱"的踩坑,让我把设计 AI Agent 的注意事项,认真地立成了几条规矩:

  1. 自主循环必须有最大迭代次数。for range(MAX_STEPS) 而非 while True,这是最重要的硬刹车。
  2. 加重复调用检测。发现反复用相同工具+参数(原地打转),就打断并提示模型换思路或直接作答。
  3. 设成本/时间预算。累计 token 和耗时,超了强制停,防止烧穿预算。
  4. 工具失败要返回清晰、可指导的反馈。别只返回模糊 Error,让模型能据此调整、跳出重复。
  5. 给 Agent 体面放弃的退出路径。试不出来就基于现有信息作答或承认无法完成,别逼它必须成功。
  6. 触发护栏要优雅降级。给兜底回应,而不是崩溃或卡死。
  7. 自主系统更要可观测。记录每一步、监控指标、异常告警;越自主、越不确定,越要给它配确定性的护栏。

写在最后

这次"我的 Agent 陷入死循环、把 token 烧穿、任务却永远完不成"的经历,是我在 AI Agent 开发路上,一次很惊险、也很烧钱、却也很受用的成长。它教给我的,远不止"Agent 循环要加最大步数"这一条具体的技术经验,更是一条驾驭一切"自主系统"的根本法则——越是自主、越是不确定的系统,就越需要确定性的护栏。我那次的失控,根源在于,我被 Agent"自主"的智能所迷惑,天真地把系统的安全和终止,完全托付给了那个本质上不确定、会犯糊涂的模型自己,却忘了作为设计者,亲手为这份自主,系上一道确定的安全带。

所以,当你设计任何一个能"自己做决策、自己循环、自己行动"的系统时——尤其是 AI Agent 这种由大模型驱动的自主系统——请永远记得:在赋予它自主之前,先为它装好护栏。给它硬性的刹车(最大步数)、给它打转的检测、给它预算的上限、给它体面退出的路径、给它可观测的眼睛。就像我的 Agent,只要它的循环里,有一道 MAX_STEPS 的硬刹车,就绝不会经历那场把我账单烧穿、又永远完不成任务的失控自燃。用"确定性的护栏",去框住"不确定的智能";让"可靠的代码",去兜底"会犯错的模型"——这种"自主与可控并重"的设计思维,是从一个"会调用大模型"的开发,走向一个"能交付可靠 AI 系统"的工程师,必经的修炼。愿你的 Agent,既有自主的智慧,更有可控的边界;也愿你我,永远不迷信智能会自我约束,而是把那份可控,亲手、扎实地,设计进系统的每一道循环里。共勉。

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

我一直以为 TypeScript 的类型能在运行时帮我挡住脏数据,直到一个接口返回了不符合类型的 JSON,我的类型注解形同虚设、程序当场崩溃的深度复盘

2026-6-1 22:27:08

技术教程

我给函数参数设了个默认空列表,本以为每次调用都会拿到全新的一个,结果它竟在一次次独立的调用之间,诡异地记住了上一次追加进去的数据的深度复盘

2026-6-1 22:38:36

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