前面两篇,我们把 Agent 压测最容易踩的两个坑讲透了:
第一篇告诉你,只压 HTTP 接口不算压 Agent;第二篇告诉你,真实模型烧钱压测不如先做 Model 层仿真。
这一篇,我们来解决一个更隐蔽、也更致命的问题:Agent 无限循环调用。
很多团队上线 Agent 后,账单不是线性增长,而是突然翻倍、甚至几倍。
一开始大家会怀疑:是不是用户量涨了?是不是 Prompt 写得太长?是不是模型太贵?
查下来往往不是。
真正的原因,经常是 Agent 在某些输入下反复进入“思考—调用工具—再思考—再调用工具”的循环。
循环本身不一定报错。
接口可能一直返回 200,LLM 网关也可能没有明显异常,但 Token、工具调用次数、向量库查询量一直在涨。
等账单告警出来,损失已经发生了。
💡 框架迁移提示:本文熔断与上下文裁剪思路是通用方案,示例基于 Spring AI 实现;如果你使用 LangChain4j、AgentScope 等其他 Java Agent 框架,核心思路一致,只替换为对应框架的中断条件与上下文管理扩展点即可。
一、先搞清楚:Agent 为什么会无限循环?
Agent 和普通接口最大的区别,是它的执行路径不确定。
普通接口:输入固定,逻辑固定,结束条件明确。
Agent:输入同一个问题,可能一轮结束,也可能多轮推理;如果工具返回结果不收敛,模型就可能一直要求继续调用。
常见循环场景有三类:
1. 工具结果无法满足模型预期
比如模型要求“查到结果再回答”,但工具每次都返回“未查到”“请补充条件”。
模型理解为条件不足,于是继续调用同一个工具,进入死循环。
2. 输出格式不稳定
模型第一次调用工具失败,第二次换参数重试,第三次换说法再试。
如果没有次数限制,重试会被不断放大。
3. 上下文越来越长,模型越来越难收敛
每一轮工具结果都塞回上下文,Token 越来越多,模型注意力下降。
到后面,模型可能已经分不清自己有没有查过、有没有回答过,只能继续调用工具。
所以,要治 Agent 循环,不能只靠模型“自觉结束”,必须在工程上加三层边界:
- 第一层:限制推理轮次
- 第二层:限制工具调用次数
- 第三层:限制上下文长度
二、第一层熔断:限制 Agent 最大推理轮次
最有效的第一道防线,是给 Agent 设置一个明确的最大轮次。
很多人觉得“设轮次限制会影响正常能力”。
实际上,绝大多数业务 Agent,正常回答都不需要几十轮。
真正异常的请求,往往会在第 5 轮、第 8 轮还不收敛。
我们可以维护一个轮次计数器,每完成一轮推理就加 1;超过阈值后,主动终止执行。
代码实现:RoundLimitAdvisor
package com.agent.demo.advisor; import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; import org.springframework.ai.chat.client.advisor.api.AdvisedResponse; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; import org.springframework.stereotype.Component; @Component public class RoundLimitAdvisor implements CallAroundAdvisor { private static final int MAX_ROUNDS = 5; @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { Integer round = (Integer) advisedRequest.chatOptions().getMetadata().getOrDefault("round", 1); if (round > MAX_ROUNDS) { throw new IllegalStateException("Agent 超过最大推理轮次,已终止执行"); } advisedRequest.chatOptions().getMetadata().put("round", round + 1); return chain.nextAroundCall(advisedRequest); } @Override public String getName() { return "roundLimitAdvisor"; } @Override public int getOrder() { return 0; } }设计说明:
round存在 metadata 里,跟随整个 Agent 会话传递。- 每调用一次
aroundCall,代表一轮交互。 - 超过
MAX_ROUNDS后直接抛异常,不再继续调用模型。 - 异常会被全局异常处理器捕获,返回明确错误,而不是继续消耗 Token。
推荐阈值
不同业务阈值不同,但建议:
- 普通问答 Agent:3~5 轮
- 工具型 Agent:5~8 轮
- 复杂任务 Agent:8~15 轮
不要一上来就设 20 轮、30 轮。
阈值越高,成本风险越大。先从保守值开始,再根据真实业务观察放开。
三、第二层熔断:限制工具调用次数
有些场景下,Agent 推理轮次还没到上限,但工具调用已经失控。
比如反复调用同一个检索工具、数据库工具、计算工具。
所以第二层熔断,要单独统计工具调用次数。
代码实现:ToolCallLimitAdvisor
package com.agent.demo.advisor; import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; import org.springframework.ai.chat.client.advisor.api.AdvisedResponse; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; import org.springframework.stereotype.Component; @Component public class ToolCallLimitAdvisor implements CallAroundAdvisor { private static final int MAX_TOOL_CALLS = 4; @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { Integer toolCallCount = (Integer) advisedRequest.chatOptions().getMetadata() .getOrDefault("toolCallCount", 0); if (toolCallCount >= MAX_TOOL_CALLS) { throw new IllegalStateException("Agent 超过最大工具调用次数,已终止执行"); } AdvisedResponse response = chain.nextAroundCall(advisedRequest); boolean hasToolCall = response.response().getResults().stream() .anyMatch(result -> result.getMetadata().containsKey("toolCalls")); if (hasToolCall) { toolCallCount++; advisedRequest.chatOptions().getMetadata().put("toolCallCount", toolCallCount); } return response; } @Override public String getName() { return "toolCallLimitAdvisor"; } @Override public int getOrder() { return 1; } }设计说明:
- 工具调用次数单独统计,不与推理轮次混在一起。
- 每轮响应里如果包含
toolCalls,计数加 1。 - 超过阈值后终止,避免 Agent 一直查库、一直检索。
更细一点:按工具类型隔离
如果业务里工具很多,建议不只统计总次数,还要按工具名隔离。
比如:
- 查询类工具:最多 3 次
- 计算类工具:最多 2 次
- 写入类工具:最多 1 次
这样能防止某一个工具被无限打爆。
四、第三层熔断:上下文长度裁剪
Agent 循环调用的另一个放大器,是上下文越来越长。
工具结果、历史对话、中间推理步骤,如果全部保留,Token 会迅速膨胀。
上下文越长,模型越容易分心,越容易继续调用工具。
所以第三层,不是等循环发生再拦,而是主动把上下文“瘦身”。
策略 1:只保留必要上下文
不要把所有历史都塞进每一轮。
很多业务只需要保留:
- 用户原始问题
- 当前任务目标
- 最近一次工具结果
- 必要的约束条件
中间过程、调试信息、重复描述,能不保留就不保留。
策略 2:超过长度自动裁剪
可以设置上下文窗口,超过后只保留最近 N 轮,或保留关键结果。
代码实现:ContextTruncationAdvisor
package com.agent.demo.advisor; import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; import org.springframework.ai.chat.client.advisor.api.AdvisedResponse; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisor; import org.springframework.ai.chat.client.advisor.api.CallAroundAdvisorChain; import org.springframework.ai.chat.messages.Message; import org.springframework.stereotype.Component; import java.util.List; @Component public class ContextTruncationAdvisor implements CallAroundAdvisor { private static final int MAX_HISTORY_ROUNDS = 6; @Override public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) { List<Message> messages = advisedRequest.messages(); if (messages.size() > MAX_HISTORY_ROUNDS) { List<Message> truncated = messages.subList(messages.size() - MAX_HISTORY_ROUNDS, messages.size()); return chain.nextAroundCall(new AdvisedRequest( advisedRequest.userText(), advisedRequest.systemText(), advisedRequest.advisors(), advisedRequest.chatOptions(), advisedRequest.functionNames(), advisedRequest.memoryId(), truncated, advisedRequest.advisorParams() )); } return chain.nextAroundCall(advisedRequest); } @Override public String getName() { return "contextTruncationAdvisor"; } @Override public int getOrder() { return 2; } }设计说明:
- 只保留最近
MAX_HISTORY_ROUNDS条消息。 - 减少无效上下文堆积,降低 Token 消耗,也降低模型“反复纠结”的概率。
- 裁剪策略要按业务调整,不能机械一刀切。
策略 3:工具结果摘要
如果工具返回结果很长,不要原样塞回上下文。
先做摘要,再送入模型。
例如:
- 数据库返回 100 条记录:只保留关键字段和统计结果
- 检索返回 20 个片段:只保留 Top 5 相关片段
- 计算结果很大:只返回最终结论
工具结果摘要,是很多团队容易忽略的成本优化点。
五、三层熔断怎么配合使用?
这三层不是选一个,而是要同时开。
推荐执行顺序:
第一步:先限制轮次和工具次数
这是最快见效的两道硬边界。
只要 Agent 出现异常循环,先被拦在这两层。
第二步:再做上下文裁剪
上下文短了,模型出错概率下降,轮次和工具调用自然也会下降。
第三步:最后加监控告警
配合前两篇的 Agent 可观测体系,监控这几个指标:
- 平均推理轮次
- P95 推理轮次
- 工具调用次数
- 单请求 Token 消耗
- 异常终止率
如果这些指标突然上涨,说明 Agent 正在进入异常模式,需要立刻排查。
六、生产避坑清单
❌ 不要只靠 Prompt 要求模型“结束后不要继续调用工具”
模型不可靠,必须工程兜底。
❌ 不要只做全局异常捕获
异常捕获只能止损,不能阻止循环发生。
❌ 不要把上下文全量保留
上下文越长,成本越高,模型越容易失控。
❌ 不要阈值设置过大
轮次、工具次数、上下文长度,都要从保守值开始。
✅ 必须设置最大推理轮次
✅ 必须设置最大工具调用次数
✅ 必须做上下文裁剪或摘要
✅ 必须配合监控观察指标变化
✅ 异常终止后,记录输入、上下文、工具结果,方便复盘
七、简历描述片段
基于 Spring AI 搭建 Agent 生产稳定性治理体系,通过多层 Advisor 扩展实现最大推理轮次熔断、最大工具调用次数限制与上下文动态裁剪,有效防止 Agent 无限循环调用;配合 Agent 可观测指标监控推理轮次、工具调用频次与 Token 消耗,在保证业务能力的前提下,显著降低大模型成本异常波动,提升多 Agent 生产稳定性。
📦 落地资源推荐
跑Agent的应用,尤其是做压力测试,更吃服务器资源,通常本机是不够用的,建议使用云服务器,能大大减少我们做其他事的精力,提高学习代码的专注力和学习AI的效率,免除后顾之忧,新用户首年特惠+专属渠道,https://www.aliyun.com/daily-act/ecs/activity_selection?userCode=fzoabro9
💡领取完整工程
找到图片上的水印即Java-AI工程师,然后在Java-AI工程师中练习打字【agent-metric】即可,本篇完整熔断与上下文裁剪源码
下期预告
下一篇,我们继续把成本治理往前推一步:前面解决了“异常循环”,下一篇解决“正常请求也很贵”的问题。我们会把缓存、工具结果摘要、模型降级、Token统计归因放到一个方案里,做一套可直接上线的 Agent成本控制清单。