alibaba/spring-ai-alibaba · error · ToolCallLimitExceededException

Tool call limits exceeded: ${threadCount}/${threadLimit} thr

Error message

Tool call limits exceeded: ${threadCount}/${threadLimit} thread, ${runCount}/${runLimit} run, tool: ${toolName}

What it means

ToolCallLimitHook with ExitBehavior.ERROR throws ToolCallLimitExceededException in beforeModel when the configured thread limit and/or run limit on tool calls has been reached. The message embeds counts like '5/5 thread, 12/10 run, tool: web_search' so you can see which limit tripped and for which tool. This is the intended hard-stop that prevents the agent from continuing to burn tool invocations.

Source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/hook/toolcalllimit/ToolCallLimitHook.java:84

	private String getRunCountKey() {
		String trackKey = toolName != null ? toolName : "__all__";
		return RUN_COUNT_KEY_PREFIX + "_" + trackKey;
	}

	@Override
	public CompletableFuture<Map<String, Object>> beforeModel(OverAllState state, RunnableConfig config) {
		// Read current counts from context
		int threadCount = config.context().containsKey(getThreadCountKey())
				? (int) config.context().get(getThreadCountKey()) : 0;
		int runCount = config.context().containsKey(getRunCountKey())
				? (int) config.context().get(getRunCountKey()) : 0;

		boolean threadLimitExceeded = threadLimit != null && threadCount >= threadLimit;
		boolean runLimitExceeded = runLimit != null && runCount >= runLimit;

		if (threadLimitExceeded || runLimitExceeded) {
			if (exitBehavior == ExitBehavior.ERROR) {
				throw new ToolCallLimitExceededException(
						threadCount,
						runCount,
						threadLimit,
						runLimit,
						toolName
				);
			}
			else if (exitBehavior == ExitBehavior.END) {
				String message = buildLimitExceededMessage(threadCount, runCount, threadLimit, runLimit, toolName);

				// Do not copy old messages
				List<Message> messages = new ArrayList<>();
				// This new message will be appended to the messages list
				messages.add(new AssistantMessage(message));

				Map<String, Object> updates = new HashMap<>();
				updates.put("messages", messages);
				updates.put("jump_to", JumpTo.end);

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Raise threadLimit/runLimit in the ToolCallLimitHook builder to a level suited to the task.
  2. Catch ToolCallLimitExceededException and handle it (e.g. summarize progress and end the run gracefully) or switch exitBehavior to a non-ERROR mode.
  3. Fix the underlying loop causing excessive tool calls (e.g. retry without progress) so limits are not reached.

Example fix

// before
ToolCallLimitHook hook = ToolCallLimitHook.builder().threadLimit(3).exitBehavior(ExitBehavior.ERROR).build();
// after
ToolCallLimitHook hook = ToolCallLimitHook.builder().threadLimit(20).exitBehavior(ExitBehavior.ERROR).build();
Defensive patterns

Strategy: try-catch

Validate before calling

// before running the agent, size the limits to expected usage
int expectedCalls = estimateToolCalls(task);
if (expectedCalls >= configuredThreadLimit) { raise or adjust limits; }

Try / catch

try { agent.invoke(input); } catch (ToolCallLimitExceededException e) {
    log.warn("Tool limit hit: {}", e.getMessage());
    return partialResultOrSummary();
}

Prevention

When it happens

Trigger: Within one thread/run, the agent makes as many tool calls as threadLimit or runLimit for a given toolName (or all tools if unscoped), and the next model request arrives; hook configured with exitBehavior(ERROR).

Common situations: Agent loops retrying a failing tool until the limit trips; limits set too low for a legitimately long workflow; a single hook instance limiting all tools when per-tool limits were intended.

Related errors


AI-assisted analysis of alibaba/spring-ai-alibaba@f82da0b50f (2026-09-09). Data as JSON: /api/errors/38e79d444709ed75. Report an issue: GitHub.