Allow Retry on choosen status codes

- Add a new autoconfig property spring.ai.retry.on-http-codes that list status codes (scuh as 429)
   for which the retry should be attemptd.
 - Updated the docs.

 Resolves #433
This commit is contained in:
Christian Tzolov
2024-03-22 14:50:00 +01:00
parent 7a55d66d77
commit c66ed36073
10 changed files with 33 additions and 11 deletions

View File

@@ -50,7 +50,7 @@ import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(classes = AnthropicTestConfiguration.class)
@SpringBootTest(classes = AnthropicTestConfiguration.class, properties = "spring.ai.retry.on-http-codes=429")
@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".+")
class AnthropicChatClientIT {

View File

@@ -67,7 +67,8 @@ The prefix `spring.ai.retry` is used as the property prefix that lets you config
| spring.ai.retry.backoff.multiplier | Backoff interval multiplier. | 5
| spring.ai.retry.backoff.max-interval | Maximum backoff duration. | 3 min.
| spring.ai.retry.on-client-errors | If false, throw a NonTransientAiException, and do not attempt retry for `4xx` client error codes | false
| spring.ai.retry.exclude-on-http-codes | List of HTTP status codes that should not trigger a retry (e.g. to throw NonTransientAiException). | empty
| spring.ai.retry.exclude-on-http-codes | List of HTTP status codes that should NOT trigger a retry (e.g. to throw NonTransientAiException). | empty
| spring.ai.retry.on-http-codes | List of HTTP status codes that should trigger a retry (e.g. to throw TransientAiException). | empty
|====
NOTE: currently the retry policies are not applicable for the streaming API.

View File

@@ -63,6 +63,7 @@ The prefix `spring.ai.retry` is used as the property prefix that lets you config
| spring.ai.retry.backoff.max-interval | Maximum backoff duration. | 3 min.
| spring.ai.retry.on-client-errors | If false, throw a NonTransientAiException, and do not attempt retry for `4xx` client error codes | false
| spring.ai.retry.exclude-on-http-codes | List of HTTP status codes that should not trigger a retry (e.g. to throw NonTransientAiException). | empty
| spring.ai.retry.on-http-codes | List of HTTP status codes that should trigger a retry (e.g. to throw TransientAiException). | empty
|====
==== Connection Properties

View File

@@ -63,6 +63,7 @@ The prefix `spring.ai.retry` is used as the property prefix that lets you config
| spring.ai.retry.backoff.max-interval | Maximum backoff duration. | 3 min.
| spring.ai.retry.on-client-errors | If false, throw a NonTransientAiException, and do not attempt retry for `4xx` client error codes | false
| spring.ai.retry.exclude-on-http-codes | List of HTTP status codes that should not trigger a retry (e.g. to throw NonTransientAiException). | empty
| spring.ai.retry.on-http-codes | List of HTTP status codes that should trigger a retry (e.g. to throw TransientAiException). | empty
|====
==== Connection Properties

View File

@@ -63,6 +63,7 @@ The prefix `spring.ai.retry` is used as the property prefix that lets you config
| spring.ai.retry.backoff.max-interval | Maximum backoff duration. | 3 min.
| spring.ai.retry.on-client-errors | If false, throw a NonTransientAiException, and do not attempt retry for `4xx` client error codes | false
| spring.ai.retry.exclude-on-http-codes | List of HTTP status codes that should not trigger a retry (e.g. to throw NonTransientAiException). | empty
| spring.ai.retry.on-http-codes | List of HTTP status codes that should trigger a retry (e.g. to throw TransientAiException). | empty
|====
==== Connection Properties

View File

@@ -64,6 +64,7 @@ The prefix `spring.ai.retry` is used as the property prefix that lets you config
| spring.ai.retry.backoff.max-interval | Maximum backoff duration. | 3 min.
| spring.ai.retry.on-client-errors | If false, throw a NonTransientAiException, and do not attempt retry for `4xx` client error codes | false
| spring.ai.retry.exclude-on-http-codes | List of HTTP status codes that should not trigger a retry (e.g. to throw NonTransientAiException). | empty
| spring.ai.retry.on-http-codes | List of HTTP status codes that should trigger a retry (e.g. to throw TransientAiException). | empty
|====
==== Connection Properties

View File

@@ -89,6 +89,7 @@ The prefix `spring.ai.retry` is used as the property prefix that lets you config
| spring.ai.retry.backoff.max-interval | Maximum backoff duration. | 3 min.
| spring.ai.retry.on-client-errors | If false, throw a NonTransientAiException, and do not attempt retry for `4xx` client error codes | false
| spring.ai.retry.exclude-on-http-codes | List of HTTP status codes that should not trigger a retry (e.g. to throw NonTransientAiException). | empty
| spring.ai.retry.on-http-codes | List of HTTP status codes that should trigger a retry (e.g. to throw TransientAiException). | empty
|====

View File

@@ -82,18 +82,19 @@ public class SpringAiRetryAutoConfiguration {
if (response.getStatusCode().isError()) {
String error = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
String message = String.format("%s - %s", response.getStatusCode().value(), error);
/**
* Thrown on 4xx client errors, such as 401 - Incorrect API key
* provided, 401 - You must be a member of an organization to use the
* API, 429 - Rate limit reached for requests, 429 - You exceeded your
* current quota , please check your plan and billing details.
*/
// Explicitly configured transient codes
if (properties.getOnHttpCodes().contains(response.getStatusCode().value())) {
throw new TransientAiException(message);
}
// onClientErrors - If true, do not throw a NonTransientAiException,
// and do not attempt retry for 4xx client error codes, false by
// default.
if (!properties.isOnClientErrors() && response.getStatusCode().is4xxClientError()) {
throw new NonTransientAiException(message);
}
// Explicitly configured non-transient codes
if (!CollectionUtils.isEmpty(properties.getExcludeOnHttpCodes())
&& properties.getExcludeOnHttpCodes().contains(response.getStatusCode().value())) {

View File

@@ -54,6 +54,11 @@ public class SpringAiRetryProperties {
*/
private List<Integer> excludeOnHttpCodes = new ArrayList<>();
/**
* List of HTTP status codes that should trigger a retry.
*/
private List<Integer> onHttpCodes = new ArrayList<>();
/**
* Exponential Backoff properties.
*/
@@ -128,4 +133,12 @@ public class SpringAiRetryProperties {
this.onClientErrors = onClientErrors;
}
public List<Integer> getOnHttpCodes() {
return this.onHttpCodes;
}
public void setOnHttpCodes(List<Integer> onHttpCodes) {
this.onHttpCodes = onHttpCodes;
}
}

View File

@@ -37,10 +37,10 @@ public class SpringAiRetryPropertiesTests {
var retryProperties = context.getBean(SpringAiRetryProperties.class);
assertThat(retryProperties.getMaxAttempts()).isEqualTo(10);
assertThat(retryProperties.isOnClientErrors()).isFalse(); // do not retry
// on 4xx
// errors
// do not retry on 4xx errors
assertThat(retryProperties.isOnClientErrors()).isFalse();
assertThat(retryProperties.getExcludeOnHttpCodes()).isEmpty();
assertThat(retryProperties.getOnHttpCodes()).isEmpty();
assertThat(retryProperties.getBackoff().getInitialInterval().toMillis()).isEqualTo(2000);
assertThat(retryProperties.getBackoff().getMultiplier()).isEqualTo(5);
assertThat(retryProperties.getBackoff().getMaxInterval().toMillis()).isEqualTo(3 * 60000);
@@ -55,6 +55,7 @@ public class SpringAiRetryPropertiesTests {
"spring.ai.retry.max-attempts=100",
"spring.ai.retry.on-client-errors=false",
"spring.ai.retry.exclude-on-http-codes=404,500",
"spring.ai.retry.on-http-codes=429",
"spring.ai.retry.backoff.initial-interval=1000",
"spring.ai.retry.backoff.multiplier=2",
"spring.ai.retry.backoff.max-interval=60000" )
@@ -66,6 +67,7 @@ public class SpringAiRetryPropertiesTests {
assertThat(retryProperties.getMaxAttempts()).isEqualTo(100);
assertThat(retryProperties.isOnClientErrors()).isFalse();
assertThat(retryProperties.getExcludeOnHttpCodes()).containsExactly(404, 500);
assertThat(retryProperties.getOnHttpCodes()).containsExactly(429);
assertThat(retryProperties.getBackoff().getInitialInterval().toMillis()).isEqualTo(1000);
assertThat(retryProperties.getBackoff().getMultiplier()).isEqualTo(2);
assertThat(retryProperties.getBackoff().getMaxInterval().toMillis()).isEqualTo(60000);