概述

最近在使用 AI 的时候遇到了一个问题,应该以前也遇到过,但是没有特别地去注意,最近因为自己对 Agent 做了一些额外的开发,所以特别地去关注 AI 的执行过程,然后就发现了这个问题。

问题的起因是最近飞书开源了一个客户端:https://github.com/larksuite/cli ,然后我就试用了一下,确实很不错,但是它有一个问题就是不能以用户的身份在某个群组发言。我遇到的问题就是,我让 AI 尝试以我的身份在某个群里发送一条消息,然后问题就来,以下是 AI 的一些操作历史过程:

  • 尝试以 lark-cli 的方式用用户的身份在群里发言:失败
  • 尝试以 API 的方式用用户的身份在群里发言:失败
  • 尝试以 lark-cli 的方式用机器人的身份在群里发言:失败
  • 尝试以 API 的方式用机器人的身份在群里发言:失败
  • 还想继续尝试其他方式,被我停止了

从上面的过程可以发现,在这次我和 Claude Code(Max Opus 4.6)的交互来看,它总是视图按照我提供的目标去完成目标(一个标准的牛马),但是他对于什么时候应该停止没有一个明显的判断标准,我查找了一些资料,发现这在 Agent 领域叫做死亡循环,在 Agent 中是一个很常见的问题,但是我目前还没有发现完整的解决方案,所以在这里我就来聊聊这个问题,以及大家比较常见的处理方法。

Agent 的工作模式

目前的 AI Agent,大家都使用 ReAct(Reasoning + Acting) 循环,也就是:

虽然这个图感觉有点复杂,但是实际上可以简化成:Planing -> Action -> Observation,也就是大模型给出计划,然后 Agent 去执行,执行完之后再由大模型观测输出的信息是否已经达成了目标,如果没有的话,大模型会继续给出下一步的计划,然后 Agent 继续执行,循环往复,直到大模型判断已经完成了目标。

Agent 的常见错误:死循环

那么这里就会出现一个常见的问题了,如果大模型认为一直都没有达到目标的话,他就会一直规划下一步的计划,然后你的 Agent 就会一直执行,这里有可能出现的问题就是,中间可能陷入了某种死循环,这里死循环的意思就是:

  • 大模型让你尝试执行操作 A,然后执行完之后,发现结果不满足条件;
  • 大模型让你尝试执行操作 B,然后执行完之后,发现结果也不满足条件;
  • 大模型又让你尝试执行操作 A,然后执行完之后,发现结果不满足条件;

就这样,大模型不断地在 A 和 B 之前循环,当然,这里是一个简单的说法,在实际的实践中,你会发现这里的循环可能不是 A 和 B 之间循环,它可能是 A、B 和 C、或者 A、B 和 A、C,然后和 B、C 这样的循环。还有一种比较离谱的就是它死磕 A,比如某个 Skill 说工具 A 可以做某项事情,然后操作是执行工具 A-cli,然后大模型就会调用 A-cli 工具尝试一下,然后发现结果不符合预期,然后大模型就是尝试 A-cli —help 去看 A-cli 的帮助选项,然后自己根据帮助选项做各种尝试,这也是非常常见的一种问题;

如何解决死循环?

这里我要讨论的是如何退出死循环,而不是如何退出循环,如果 LLM 工作地很好的时候,他是可以直接告诉我们任务已经完成了,不用再进行 Agent Loop 了。

对于退出死循环,我在学习了一下现有大家分享的方案,发现主要解决方案主要有以下几种。

1. 限制循环次数

这是一种非常通用且容易的实现,当你使用很多第三方的 SDK 开发 Agent 的时候默认都带这个参数选项,含义也很简单,那就是我们限制 Agent 最多能够执行多少次 Plan -> Act -> Observ 的循环,如果超过这个次数之后,无论 LLM 是否告诉我们已经达到目标,我们都不继续了,直接结束。

但是,这种方式的问题也很明显,对于复杂问题,我们往往真实地需要更多的步骤,这个时候如果设置了固定的循环次数,那么可能我们的任务是完不成的,即使中间的步骤都是合理的,所以这只能当做一个兜底的策略,不能作为一个主要的控制手段。

2. 限制工具调用次数

既然限制整个 Agent Loop 的次数有点太武断了,那么我们就限制 LLM 对于特定工具的调用次数,比如我上面提过的大模型发现直接按照 Skill 的执行 cli 工具的结果不符合预期,那么 LLM 就可能会调用 cli 的 help 命令去查看 cli 的其他选项,并进行尝试,一次尝试失败之后它 LLM 会进行二次尝试。

所以,基于这个思路,我们就限制,在几次循环内,对于一个工具的调用频率达到多少次之后,我们就停止任务,比如在 20 个循环内,大模型调用了 5 次某个工具,我们就退出循环,这种模式在很多情况下都是有用的,但是在某些情况下,比如 Skill 编写不合理的时候可能会误触发,比如你安排一个部署任务,你的 skill 里面说可以通过某个 cli 命令去查看部署任务是否完成,当这个部署任务的部署时间比较长时,可能你查询部署任务状态的 action 就会触发这个规则导致 Agent 提前退出,当然,在你发现这种问题之后解决方案也比较简单,那就是将检测的循环封装成一个 tool,直接在 tool 里面定期检测,直到检测到部署结果之后再反馈给 LLM。

虽然这种方案有比较大自由度了,但是相对来说也还是 Based On Rules 的,所以就有了基于 LLM 来检测的。

3. 基于 LLM 的检测方式

可能你会觉得奇怪,本来死循环就是 LLM 出现了一个问题,那用 LLM 还如何检测?这我们又要回到 Agent Loop 中的角色划分了,在 Agent Loop 中,比较常见的有几种角色:

  • LLM 生成的消息:我们常叫 assistant message
  • 用户输入的信息:我们常叫 user message
  • 工具产出的信息:我们常叫 tool message

这里我们针对的是 assistant message,我们可以维护一个 assistant message 的历史,然后定期用另外一个 LLM 的 session 去判断一下这些 assistant message 的系列,用来判断产生这些 assistant message 历史的 LLM 是否进入到了死循环,如果是的话,我们就切换任务。

使用这种方式,我们有相对弹性的控制,但是因为引入了另外一个 LLM 的 session,增加了复杂度,但是效果可能比单纯地使用前两种会好。

4. 绝杀技:人工干预

当然,当 LLM 犯傻的时候,最有用的还是我们人工干预,但是,这设计到另外一个问题,我们人工得知道 Agent 是否陷入了死循环,我的实践是在运行 Agent Loop 时,每一次的消息我不看完整的内容,而是让 Agent 总结一下这一步它在做什么事情,这样就很够了,我查看一下历史,就知道 Agent 是不是自己在打转了,例如我的一个操作历史:

一旦我发现 Agent 不干正事的时候,直接一个 Ctrl + C 下去他就老实了。

总结

在这篇文章中,我介绍了一下什么是 Agent 开发和使用中的死循环,并且给出了个人理解的一些解决方案,但是不是说只有这些解决方案,Agent 的发展还处于剧烈膨胀中,各种理论和实践都还在不断地涌现,如果你在实践中有更多其他的方案,欢迎一起留言交流讨论。