我说我们做 Agent 从来不怕死循环,面试官调出兜底日志:“第 47 次强制终止,是你半夜爬起来按停的?”

面试日记 第 23 天

面试官翻简历,翻到一半,手指停在项目经历那一栏。

"物流查询 Agent,你负责开发和上线?"

"对,跟了快一年。"

她把简历放下,问了今天的第一道题。

Agent死循环问题有遇到过吗?如何解决?

我答得很快:"说实话,真没遇到过。我们提示词管得比较严,上线半年,没出过一次死循环。"

她没接话,把笔记本转过来,屏幕上是一张监控后台的截图。

"你们系统的兜底触发记录。"她手指顺着日志往下滑,"强制终止,47 次。最近一次,上个月。"

我盯着那行字看了两秒。

"......好吧,遇到过。"

她往椅背上一靠:"那就从头讲。怎么死的,怎么救的。"

我把那次事故原原本本交代了。

我们那个 Agent,正常流程是"用订单号查物流单号 → 用物流单号查物流详情 → 返回结果"。上线之后发现有一定概率会卡死:订单号给到大模型,模型拿到了物流单号,再把订单号和物流单号一起喂回去,模型又返回一个物流单号------既不调用查物流详情的工具,也不返回终止标识。Java 侧就一直循环调用,直到超时。

"原因呢?"

"分析下来三个:提示词没有明确状态流转规则、缺少步骤追踪机制、没有兜底保护策略。"

"怎么改的?"

"三个改造。"我竖起手指,"第一,提示词里加状态约束,明确告诉模型:已经拿到物流单号就直接调用查详情接口,不许重复生成单号。第二,加状态变量追踪,用 step=1/2/3 跟踪执行到哪一步,每次调用模型把当前状态带上。第三,设兜底,最多调用 5 次,超过强制终止;同时比对每次返回的内容,连续两次一模一样就直接判死循环,触发降级。"

她听到这里,身子往前倾了一点。

"你说状态变量追踪。"她问,"模型每次调用不是无状态的吗?你这个状态,存在哪?"

这题我接住了:"存在调用侧,不在模型里。每次调用模型,我们把当前状态作为输入的一部分传进去,比如 current_step: 2,completed_steps: 查询订单, 获取物流单号。模型看到这个就知道自己该干什么。本质是用外部存储弥补模型无状态的缺陷,LangChain 的 Memory 组件也是这个思路。"

"那被兜底强制终止的会话呢?"她追问,"总不能直接给用户返回'任务超时'吧?"

"这个......"我顿了一下,"我们会尽量返回中间结果。比如物流详情没查到,但单号拿到了,就告诉用户'已查询到物流单号,详情获取失败请稍后重试'。同时记日志,系统性问题会触发降级,临时切到规则引擎走固定流程。"

她听完,把电脑合上。

"47 次兜底,说明你的三层防护拦住了 47 次事故。"她说,"这不叫没遇到过,这叫遇到过、解决了、还上了保险。下次面试,第一句话就该这么答。"

我把这句话记在了本子上。

回答重点

遇到过,这是 Agent 开发里非常典型的问题。

我们当时做的是一个物流查询 Agent,正常流程是"用订单号查物流单号 → 用物流单号查物流详情 → 返回结果"。但上线后发现有一定概率会出现死循环:订单号给到大模型,拿到了物流单号,再把订单号和物流单号一起给到大模型,模型又返回一个物流单号,没有调用查物流详情的工具,也没有返回终止标识。Java 侧就一直循环调用,直到超时。

分析下来主要是三个原因:提示词没有明确状态流转规则、缺少步骤追踪机制、没有兜底保护策略。

我们做了三个改造:

1)提示词里加状态约束,明确告诉模型"如果已经获取到物流单号,直接调用查物流详情接口,不要重复生成物流单号"。

2)加状态变量追踪,用 step=1/2/3 这种标识跟踪当前执行到哪一步,每次调用模型时把当前状态带上,模型按状态执行对应动作。

3)设置兜底机制,最多调用 5 次,超过就强制终止。同时检查每次返回的内容是否有变化,连续两次返回一样的内容直接判定为死循环,触发降级逻辑。

扩展知识

为什么大模型容易陷入死循环

大模型本质上是一个"输入→输出"的映射函数,它没有真正的状态记忆能力。每次调用都是独立的,模型只能从当前输入里推断应该做什么。

当输入信息复杂或者有歧义的时候,模型很容易混淆。比如同时给它订单号和物流单号,它可能把物流单号当成"之前的输出"而不是"下一步的输入",然后重复之前的动作。这就是所谓的语境短视问题。

另一个常见原因是工具调用的返回格式不一致。有时候工具返回成功但数据为空,有时候返回错误信息,模型不知道怎么处理就开始重试,一重试就停不下来。

防死循环的系统设计

生产环境的 Agent 必须有完整的防死循环机制,主要从四个层面设计:

1)调用次数硬限制,这是最基础的兜底。设一个最大调用次数,比如 10 次或者 20 次,超过就强制终止返回"任务超时"。LangChain 里有个 max_iterations 参数就是干这事的。

2)语义重复检测,记录每次模型输出的关键信息,比如工具名、参数、返回值。如果连续 2-3 次输出完全一样,大概率是死循环了,直接中断。可以用简单的字符串比对,也可以算 embedding 相似度。

3)状态机管理,把 Agent 的执行流程建模成状态机,每个状态只能转移到特定的下一个状态。比如"查询物流单号"状态只能转移到"查询物流详情"或"返回错误",不能转回"查询物流单号"。这样从架构上就杜绝了循环。

4)超时控制,除了调用次数,还要设总执行时间上限。复杂任务可能每一步都不一样但就是跑不完,这时候次数限制没用,时间限制能兜住。

提示词层面的防护

提示词设计得好可以大幅降低死循环概率。关键是让模型清楚知道三件事:当前在哪、要去哪、什么时候停。

1)明确当前状态,每次调用时把"你已经完成了 xxx,当前需要执行 xxx"写清楚。比如"你已经获取到物流单号 SF123456,现在请用这个单号调用查询物流详情的接口"。

2)定义终止条件,告诉模型什么情况下应该结束。比如"当你获取到完整的物流轨迹信息后,返回 FINISH 标识并输出结果"。

3)处理异常情况,告诉模型工具调用失败怎么办。比如"如果工具返回错误,最多重试 1 次,仍然失败就返回错误信息给用户,不要继续尝试"。

ReAct 框架的死循环问题

ReAct 是目前最流行的 Agent 框架,"思考→行动→观察"的循环本身就容易出问题。

常见的死循环模式是:模型思考后决定调用工具 A,观察到结果,又思考决定还是调用工具 A,无限循环。原因可能是观察结果不够明确,模型判断任务还没完成。

解决方案是在 observation 里加更多上下文,比如"这是第 3 次调用该工具,之前两次的结果分别是 xxx",让模型意识到自己在重复。或者直接在 prompt 里写"如果连续两次调用同一个工具得到相同结果,请停止并返回当前结果"。

监控和报警

线上 Agent 必须配监控。关键指标包括:

1)单次会话的工具调用次数分布,正常应该是长尾分布,如果出现大量 10 次以上的会话就要排查。

2)死循环触发率,被兜底机制强制终止的会话占比,这个指标反映 Agent 的健康度。

3)平均执行时间,如果突然变长可能是死循环增多的信号。

我们用 Prometheus 采集这些指标,Grafana 做可视化,设了阈值报警。死循环率超过 5% 就会触发告警,值班的同学去看日志排查。

面试官追问

追问:你刚才说用状态变量追踪步骤,这个状态是存在哪的?模型每次调用不是无状态的吗?

回答:状态存在调用侧,不是模型里。每次调用模型时,我们把当前状态作为输入的一部分传进去,比如"current_step: 2, completed_steps: 查询订单, 获取物流单号"。模型看到这个就知道自己该干什么了。本质上是用外部存储弥补模型无状态的缺陷,LangChain 里的 Memory 组件也是这个思路。

追问:状态机的方案听起来不错,但实际业务流程可能很复杂,状态机不好维护怎么办?

回答:确实,复杂流程用状态机会很臃肿。这种情况可以用 DAG 来管理,每个节点是一个任务,边表示依赖关系。执行时按拓扑序跑,已经执行过的节点打标记,不会重复执行。LangGraph 就是这个思路,比纯状态机灵活,也比纯 ReAct 可控。

追问:死循环被强制终止后,用户体验怎么保证?总不能直接返回"任务超时"吧?

回答:肯定不能这么粗暴。我们的做法是尽量返回中间结果,比如虽然没查到物流详情,但物流单号已经拿到了,就告诉用户"已查询到物流单号 SF123456,详情获取失败请稍后重试"。同时记录日志方便复盘,如果是系统性问题会触发降级策略,比如暂时切到规则引擎走固定流程。

追问:有没有遇到过不是死循环但执行路径特别长的情况?比如模型绕了一大圈才完成任务?

回答:有,这叫路径爆炸问题。模型可能会调用一堆不必要的工具来"确认"信息。解决思路一是优化提示词,给模型更明确的执行路径建议;二是加工具调用成本的概念,prompt 里写"尽量用最少的步骤完成任务";三是做事后分析,统计每个任务的平均路径长度,长得离谱的 case 单独拎出来优化。


回去我把那 47 条兜底日志又翻了一遍,顺手又看了眼手机里那条新闻------OpenClaw 团队 30 天烧掉 6030 亿 token,原因也是智能体反复循环、每轮把结果重新塞回上下文。只不过他们烧的是钱,我们烧的是超时,本质是同一种病:循环没人掐停。

这道题最容易答错的地方,不是技术方案,而是第一句"没遇到过"。面试官问"有遇到过吗",要的不是一个干净的履历,而是你有没有真的被线上毒打过、打完有没有长出机制。标准答案就一句话:死循环来自模型无状态和提示词缺约束,解法靠提示词状态约束、步骤追踪、次数兜底加重复检测,线上再配监控报警兜住长尾。

完整题解和更多追问已经整理在面试鸭小程序,准备 Agent / 大模型应用方向的同学,建议把这题和监控、降级相关的一组题一起过一遍。

相关推荐
小小猪的春天17 小时前
团队级 Code Review Skill 实践:4个人、4个AI、1套规则,CR从找bug变成做决策
后端·架构
长栎17 小时前
AI帮我写了个Spring Boot校验,线上漏掉了这组边界条件
后端
jinyishu_17 小时前
C++ 多态完全指南:从基础语法到底层原理
开发语言·c++·程序人生·面试
外滩运维专家17 小时前
Let's Encrypt 的 ACME 协议到底干了什么?用抓包带你看一遍
后端
程序员多吃鸭eatmoreduck18 小时前
# 开源初体验,39 天 800 Star:我把项目发到了哪里,流量到底从哪来
后端
雪隐18 小时前
个人电脑玩AI-10让5060 Ti给你打工——我让 Claude Code 喝上了本地杂粮:Ternary-Bonsai-27B 部署历险记
前端·人工智能·后端
云上小朱18 小时前
Intel SGX相关软件部署
后端
Reart18 小时前
Leetcode 1143.最长公共子序列(720)
后端·算法