大多数 agentic 循环故障来自三个错误:用文本内容当完成信号、用迭代上限当主控制、或者没把 tool result 传回模型。三个的根因相同——没把 stop_reason 当作权威的控制机制来信任。
反模式 1:文本内容当完成信号
bug:if response.has_text_content: stop_loop = True
为什么坏了:text block 和 tool_use block 共存在同一条响应里。模型经常在工具调用请求旁边发解释性文本(“我现在去查账单记录……”)。检查文本是否存在就在任务做到一半时终止了 agent。
镜像 bug:if response.has_text_content: continue_loop = True——导致无限循环,因为每条响应都包含文本,包括 stop_reason 已经是 end_turn 时的最终摘要。
间歇版本:if has_text OR stop_reason == 'end_turn': stop——有时能用(当 end_turn 恰好和正确停止点重合时),有时提前终止(当文本伴随工具调用时)。OR 逻辑通过偶尔产出正确行为来掩盖问题。
修复:删除所有文本内容检查。只用 stop_reason。
反模式 2:迭代上限当主控制
bug:max_iterations = 15 作为主要终止机制。
为什么坏了:复杂任务合理地需要 20、30 甚至 50+ 次工具调用。激进的迭代上限在 agent 工作到一半时掐断它。某生产系统 7% 的客户工单被不完整地终止,因为 20 的上限不够处理复杂的账单争议。
微妙之处:迭代上限作为安全网是没问题的(慷慨地设为 100)来捕获真正的无限循环。当它成为主要终止机制——当正常的成功完成依赖于撞到上限而不是模型发出 end_turn 信号——它就成了反模式。
修复:正常终止用 stop_reason: "end_turn"。保留宽松的迭代上限(100+)仅作为无限循环的兜底。
反模式 3:没传 tool result
bug:执行了工具但下一次 API 调用前没把 tool_result 追加到 messages 数组。
为什么坏了:API 是无状态的。没有 tool_result 在历史里,模型下一轮看到的是一个它请求了工具但从未收到输出的对话。很自然地,它再次请求同一个工具。无限循环。
怎么发现:模型一致地重复它已经调过的完全相同的工具调用。不是间歇性的——每次都这样。看到这个模式就查 tool result 是否被追加到了 messages 数组。
相关 bug:只发最新的 tool_result 但没带之前的对话历史。模型收到了结果但没有关于为什么要请求它或整体任务是什么的上下文。每一轮变成一个隔离的、无上下文的交互。
复合故障
这些反模式会叠加。一个既检查文本内容又有紧凑迭代上限的循环,要么过早停止(文本检查触发),要么转到上限杀死它(文本检查没触发因为模型发了不带文本的 tool_use)。三个的修复都一样:信任 stop_reason 作为唯一权威的循环控制,每次请求发完整历史,迭代上限只做宽松的安全网。
一句话总结: 三个杀手:文本内容当完成信号(文本和工具调用共存)、迭代上限当主控制(复杂任务需要很多调用)、缺失 tool result(无状态 API 丢上下文)——信任 stop_reason 就能修复全部三个。