在构建具备自主规划能力的 AI Agent智能体时我们常常为大模型展现出的“主观能动性”感到惊叹给它一个目标它能自动分析、拆解任务先后调用订单查询、物流追踪、退款计算等多个微服务 Tool最后把结果汇总给用户。但只要你在生产环境中跑过多轮 Agent你就一定会遭遇过那个令人冷汗直流的幽灵现象——Agent 陷入死循环调用模型调用某个 Tool 时因为参数少传了一个必填字段服务端报错返回了Invalid Parameter: coupon_id is required大模型读了报错换了个姿势再次发起该 Tool 调用结果参数依然不合规接着它又开始换姿势重试……在短短 30 秒内模型不知疲倦地连续递归调用了 50 多次同一个接口与此同时多轮对话历史被无节制地滚雪球般拼接到 Prompt 中单次请求的上下文直接吃满 32k Token接口调用费像坐了火箭一样直冲云霄直到超时掐断。如果这类死循环发生在面对真实用户的 C 端接口不仅会让用户的请求彻底挂死更可能在一夜之间把公司的云厂商 API 额度直接刷爆停机。今天我们就来深入剖析多轮 Agent 工具调用中的失控机理并基于 Spring AI 构建一套包含最大递归深度限制与滑动窗口 Token 熔断的双重安全防护网。一、Agent 是如何一步步走入死循环陷阱的很多研发同学以为大模型永远是理性的但事实上Agent 的多轮决策完全建立在概率分布与注意力机制之上。导致工具死循环的诱因主要有三类[用户目标: 查询跨店铺优惠券] | v -------------- Tool Call 1: query_coupon(sku123) | Agent 大模型 | --------------------------------------------- [内网优惠券服务] -------------- | ^ v | ----------------- (返回错误: 需要传入 shop_id) ------------- | | (大模型尝试“自愈”但出现幻觉脑补了一个假的 shop_id) v -------------- Tool Call 2: query_coupon(shop_idmock_999) | Agent 大模型 | --------------------------------------------- [内网优惠券服务] -------------- | ^ v | ----------------- (返回错误: shop_id 不存在) --------------- | | (大模型不甘心继续递归重试...) v [陷入无限重试死循环直到系统 OOM 或超时熔断]错误反馈的语义陷阱微服务返回的错误信息不够明确如System Error 500大模型无法推导该如何修正入参只能基于概率不断微调参数发起盲目重试多工具间的乒乓震荡Ping-Pong Oscillation工具 A 的返回结果提示去查工具 B工具 B 的返回结果又提示需要工具 A 的数据两个工具互相依赖大模型在二者之间来回横跳未收敛的“过度探索”大模型在开放式任务中无法判断何时达成终止条件Stop Condition不断调用各种边缘查询工具试图寻找“更多证据”。二、防线一物理级最大执行轮数限制Max Step Guard在任何自主 Agent 循环中绝不允许出现没有物理计数的while(true)必须在最外层的执行控制器中硬性植入最大递归深度Max Execution Steps。一旦达到阈值无论任务是否完成必须强制熔断循环并优雅地通知模型进行总结收尾。package com.yali.ai.agent.guard; import lombok.extern.slf4j.Slf4j; Slf4j public class AgentExecutionStepGuard { private final int maxSteps; private int currentStep 0; public AgentExecutionStepGuard(int maxSteps) { this.maxSteps maxSteps; } /** * 步进计数如果超出最大步数则抛出熔断异常 */ public void step() throws AgentLoopExceededException { currentStep; if (currentStep maxSteps) { log.error(Agent 执行步数触发硬上限熔断当前步数: {}, 上限: {}, currentStep, maxSteps); throw new AgentLoopExceededException(Agent 任务执行轮数超过最大限制: maxSteps); } } public int getCurrentStep() { return currentStep; } }三、防线二基于滑动预算的 Token 消耗熔断器除了限制调用次数更致命的往往是Token 膨胀。随着多轮 Tool 的输出拼入上下文第 5 轮调用可能比第 1 轮多消耗 10 倍的 Token。我们设计一个面向单次任务执行的TokenBudgetBreakerToken 预算熔断器package com.yali.ai.agent.guard; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.metadata.Usage; Slf4j public class TokenBudgetBreaker { private final long maxAllowedTokens; private long totalConsumedTokens 0; public TokenBudgetBreaker(long maxAllowedTokens) { this.maxAllowedTokens maxAllowedTokens; } /** * 每次与模型交互后累加 Token 消耗并校验预算 */ public void trackUsage(Usage usage) throws TokenBudgetExceededException { if (usage ! null usage.getTotalTokens() ! null) { totalConsumedTokens usage.getTotalTokens(); log.debug(Agent 累计消耗 Token: {} / 预算: {}, totalConsumedTokens, maxAllowedTokens); if (totalConsumedTokens maxAllowedTokens) { log.warn(Agent 单次任务 Token 消耗超出预算上限消耗: {}, 预算: {}, totalConsumedTokens, maxAllowedTokens); throw new TokenBudgetExceededException(任务执行已达 Token 预算上限触发保护熔断); } } } public long getTotalConsumedTokens() { return totalConsumedTokens; } }四、生产级安全 Agent 循环执行器实现我们将两重防线整合进 Spring AI 的 Agent 调度引擎中构建一个健壮的SafeAgentLoopExecutorpackage com.yali.ai.agent; import com.yali.ai.agent.guard.AgentExecutionStepGuard; import com.yali.ai.agent.guard.AgentLoopExceededException; import com.yali.ai.agent.guard.TokenBudgetBreaker; import com.yali.ai.agent.guard.TokenBudgetExceededException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.stereotype.Service; import java.util.List; Slf4j Service RequiredArgsConstructor public class SafeAgentLoopExecutor { private final ChatModel chatModel; // 生产推荐硬上限单次交互最多允许 5 轮工具交互单次最大 16,000 Token private static final int DEFAULT_MAX_STEPS 5; private static final long DEFAULT_TOKEN_BUDGET 16000L; /** * 安全执行多轮 Agent 任务杜绝死循环与账单失控 */ public String executeTask(String userGoal) { AgentExecutionStepGuard stepGuard new AgentExecutionStepGuard(DEFAULT_MAX_STEPS); TokenBudgetBreaker tokenBreaker new TokenBudgetBreaker(DEFAULT_TOKEN_BUDGET); String currentContext userGoal; boolean taskFinished false; while (!taskFinished) { try { // 1. 步数防御校验 stepGuard.step(); // 2. 调用大模型进行下一步思考与决策 Prompt prompt new Prompt(currentContext); ChatResponse response chatModel.call(prompt); // 3. Token 预算防御校验 tokenBreaker.trackUsage(response.getMetadata().getUsage()); // 4. 判定是否有 Tool 触发 var toolCalls extractToolCalls(response); if (toolCalls.isEmpty()) { // 没有需要调用的 Tool说明模型已经得出最终结论 taskFinished true; return response.getResult().getOutput().getContent(); } // 5. 执行具体 Tool 并收集结果更新上下文 currentContext executeToolsAndUpdateContext(currentContext, toolCalls); } catch (AgentLoopExceededException | TokenBudgetExceededException e) { // 触发熔断时的优雅降级输出绝不向用户裸抛异常 log.error(Agent 执行触发安全熔断, 原因: {}, e.getMessage()); return 抱歉由于任务复杂度过高或链路依赖响应异常当前无法完全执行完毕。已为您保留当前已处理进度请联系人工客服协助。; } } return 任务处理完成; } private ListObject extractToolCalls(ChatResponse response) { // 解析 Spring AI Response 中的 Tool Call 元数据 return List.of(); } private String executeToolsAndUpdateContext(String context, ListObject toolCalls) { // 执行工具调用并组装回当前上下文 return context; } }五、老鸭梨的架构心法做 AI 应用开发最忌讳的是把大模型当作 100% 严谨的确定性代码来信任。大模型本质上是一个不可预测的概率机器。赋予它多大的自主行动权就必须为它配备多结实的物理安全缰绳。给循环加上计步器给账本加上熔断器把错误处理转化为明确的业务引导这才是合格的资深架构师在面对前沿技术时该有的成熟与敬畏。在生产系统上线前我们团队还专门建立了一套“Agent 极限对抗演练用例集”模拟各种畸形网络返回、死循环 Prompt 攻击以及超长报文灌入。只有亲眼看到所有的计步熔断器和预算熔断器在毫秒内准确切断、并且优雅输出安抚话术我们才能在双 11 狂风暴雨般的真实流量面前踏踏实实地安睡到天亮。