用Spring AI的CallAroundAdvisor拦截器配合Java 21 Record Patterns,在运行时层面检测ReAct Agent的执行环,避免无限调用消耗预算。
依赖硬迭代上限:在编排器中设置 max-iterations=10 只是推迟了账单峰值,并没有真正解决确定性的工具循环问题。
依赖朴素字符串匹配:检查 LLM 原始文本输出会漏掉循环,因为工具参数中包含时间戳或临时追踪 ID 这类动态值。
依赖提示词级别的护栏:恳求模型("不要重复执行同一工具")在重上下文窗口下的失败概率是很高的。
通过将自定义 Spring AI CallAroundAdvisor 与 Java 21 record patterns 配对,在一个滚动的滑动窗口上拦截,可以在运行时级别捕获执行循环。
在 Advisor 边界拦截:在 Spring AI 将状态分发回模型提供商之前,拦截 AdvisedRequest 载荷。
用 Record Patterns 解构工具调用:使用 Java 21 模式匹配(case ToolCall(String name, Map args))干净地提取工具签名和参数。
滑动窗口指纹:对清理后的工具签名在 5 步滚动窗口上做哈希,快速捕获 $A \rightarrow B \rightarrow A$ 这种循环工具执行。
快速短路执行:立即抛出带类型的运行时异常,在产生下一个 API token 费用之前打破循环。
我在准备 senior 职位时搭建了 javalld.com——完整的 LLD 问题及执行 trace,不只是理论。
public record ToolCall(String name, Map<String, Object> args) {}
public class CycleDetectionAdvisor implements CallAroundAdvisor {
private final SlidingWindowCache<Integer> window = new SlidingWindowCache<>(5);
@Override
public AdvisedResponse around(AdvisedRequest req, CallAroundAdvisorChain chain) {
if (req.attributes().get("last_tool") instanceof ToolCall(String name, var args)) {
int signatureHash = Objects.hash(name, sanitize(args));
if (window.containsAndAdd(signatureHash)) {
throw new AgentCycleDetectedException("Loop detected for tool: " + name);
}
}
return chain.nextAround(req);
}
}
用代码而非提示词来强制护栏:LLM 是概率性的,但后端流水线中的循环检测必须是严格确定性的。
利用 Java 21 Record Patterns:无需繁琐的反射或冗长的 instanceof 检查,即可干净地解构 agent 状态。
在框架边界快速失败:在触发不必要的 LLM token 调用之前,在 Spring AI 的 CallAroundAdvisor 内部捕获工具循环。