alibaba/spring-ai-alibaba · warning

Tool '{}' failed (attempt {}/{}), retrying in {}ms: {}

Error message

Tool '{}' failed (attempt {}/{}), retrying in {}ms: {}

What it means

ToolRetryInterceptor logs this warning each time a tool call fails but is still eligible for another attempt. It reports the tool name, attempt count out of maxAttempts, the computed backoff delay, and the failure message. Tool execution continues after the sleep; only non-retryable exceptions or exhausted attempts terminate the loop.

Source

Thrown at spring-ai-alibaba-agent-framework/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/toolretry/ToolRetryInterceptor.java:112

				throw new RuntimeException(result);
			}
			catch (Exception e) {
				lastException = e;

				// Check if we should retry this exception
				if (!retryOn.test(e)) {
					log.debug("Exception {} not configured for retry, re-throwing", e.getClass().getSimpleName());
					throw e;
				}

				// Last attempt failed: Stop the operation
				if (attempt >= maxAttempts - 1) {
					break;
				}

				// Calculate delay
				long delay = calculateDelay(attempt);
				log.warn("Tool '{}' failed (attempt {}/{}), retrying in {}ms: {}",
						toolName, attempt + 1, maxAttempts, delay, e.getMessage());

				try {
					Thread.sleep(delay);
				}
				catch (InterruptedException ie) {
					Thread.currentThread().interrupt();
					throw new RuntimeException("Retry interrupted", ie);
				}
			}
		}

		// All retries exhausted
		if (onFailure == OnFailureBehavior.RAISE) {
			throw new RuntimeException("Tool call failed after " + maxAttempts + " attempts", lastException);
		}
		else {
			// Return error message as tool response

View on GitHub (pinned to f82da0b50f)

Solutions

  1. Read the exception message/detail in the warning to fix the underlying tool failure (endpoint down, timeout too low, dependency missing).
  2. Tune maxAttempts and the backoff (calculateDelay) so retries stop earlier for persistently failing tools.
  3. Configure a non-retryable exception predicate so deterministic failures (bad tool arguments, permission errors) rethrow immediately instead of retrying.
  4. Add resilience inside the tool itself (timeouts, circuit breaker) rather than relying on interceptor-level retries.

Example fix

// before
new ToolRetryInterceptor(5) // retries everything 5 times
// after
new ToolRetryInterceptor(3)
    .retryableExceptionPredicate(e -> e instanceof java.io.IOException || e instanceof java.util.concurrent.TimeoutException)
Defensive patterns

Strategy: retry

Validate before calling

if (maxAttempts < 1 || maxAttempts > 10) {
    throw new IllegalArgumentException("maxAttempts must be between 1 and 10");
}

Try / catch

try {
    return interceptToolCall(req, handler);
} catch (RuntimeException e) {
    log.warn("Tool {} exhausted retries: {}", req.getToolName(), e.getMessage());
    return ToolCallResponse.builder().status("error").content("Tool unavailable").build();
}

Prevention

When it happens

Trigger: interceptToolCall catches an exception from the underlying ToolCallback, attempt < maxAttempts - 1, and the exception passes the retryable check; the warning fires before Thread.sleep(delay) for each failed intermediate attempt.

Common situations: 1) A tool invoking a flaky HTTP endpoint that intermittently times out. 2) A database-backed tool hitting transient connection pool exhaustion. 3) A shell/MCP tool crashing intermittently under load. 4) maxAttempts set high so users see this warning repeatedly while an endpoint is down.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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