.
This commit is contained in:
@@ -19,6 +19,9 @@ import java.util.Map;
|
||||
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Service
|
||||
@@ -34,6 +37,18 @@ public class OpenAIAnalyticsService {
|
||||
@Value("${openai.timeoutMs:90000}")
|
||||
private long timeoutMs;
|
||||
|
||||
@Value("${openai.retry.maxAttempts:5}")
|
||||
private int maxRetryAttempts;
|
||||
|
||||
@Value("${openai.retry.initialDelayMs:1000}")
|
||||
private long initialRetryDelayMs;
|
||||
|
||||
@Value("${openai.retry.maxDelayMs:60000}")
|
||||
private long maxRetryDelayMs;
|
||||
|
||||
@Value("${openai.retry.multiplier:2.0}")
|
||||
private double retryMultiplier;
|
||||
|
||||
@Value("${openai.api.key}")
|
||||
private String apiKey;
|
||||
|
||||
@@ -156,16 +171,20 @@ public class OpenAIAnalyticsService {
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
})
|
||||
.retryWhen(createRetrySpec("generate"))
|
||||
.onErrorResume(err -> {
|
||||
logger.error("OpenAI request failed: {} - {}", err.getMessage(),
|
||||
logger.error("OpenAI request failed after retries: {} - {}", err.getMessage(),
|
||||
err.getClass().getSimpleName());
|
||||
if (err.getMessage().contains("401")) {
|
||||
logger.error(
|
||||
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
|
||||
} else if (err.getMessage().contains("429")) {
|
||||
logger.error("OpenAI API rate limit exceeded. Please try again later.");
|
||||
} else if (err.getMessage().contains("500")) {
|
||||
logger.error("OpenAI API server error. Please try again later.");
|
||||
if (err instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) err;
|
||||
if (wcre.getStatusCode().value() == 401) {
|
||||
logger.error(
|
||||
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
|
||||
} else if (wcre.getStatusCode().value() == 429) {
|
||||
logger.error("OpenAI API rate limit exceeded after all retry attempts.");
|
||||
} else if (wcre.getStatusCode().value() >= 500) {
|
||||
logger.error("OpenAI API server error after all retry attempts.");
|
||||
}
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
@@ -217,16 +236,20 @@ public class OpenAIAnalyticsService {
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
})
|
||||
.retryWhen(createRetrySpec("generateWithInstruction"))
|
||||
.onErrorResume(err -> {
|
||||
logger.error("OpenAI request failed: {} - {}", err.getMessage(),
|
||||
logger.error("OpenAI request failed after retries: {} - {}", err.getMessage(),
|
||||
err.getClass().getSimpleName());
|
||||
if (err.getMessage().contains("401")) {
|
||||
logger.error(
|
||||
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
|
||||
} else if (err.getMessage().contains("429")) {
|
||||
logger.error("OpenAI API rate limit exceeded. Please try again later.");
|
||||
} else if (err.getMessage().contains("500")) {
|
||||
logger.error("OpenAI API server error. Please try again later.");
|
||||
if (err instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) err;
|
||||
if (wcre.getStatusCode().value() == 401) {
|
||||
logger.error(
|
||||
"OpenAI API key is invalid or expired. Please check your openai.api.key configuration.");
|
||||
} else if (wcre.getStatusCode().value() == 429) {
|
||||
logger.error("OpenAI API rate limit exceeded after all retry attempts.");
|
||||
} else if (wcre.getStatusCode().value() >= 500) {
|
||||
logger.error("OpenAI API server error after all retry attempts.");
|
||||
}
|
||||
}
|
||||
return Mono.empty();
|
||||
})
|
||||
@@ -254,4 +277,37 @@ public class OpenAIAnalyticsService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private RetryBackoffSpec createRetrySpec(String operation) {
|
||||
return Retry.backoff(maxRetryAttempts, Duration.ofMillis(initialRetryDelayMs))
|
||||
.maxBackoff(Duration.ofMillis(maxRetryDelayMs))
|
||||
.multiplier(retryMultiplier)
|
||||
.filter(throwable -> {
|
||||
if (throwable instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) throwable;
|
||||
int statusCode = wcre.getStatusCode().value();
|
||||
// Retry on 429 (rate limit) and 5xx (server errors)
|
||||
boolean shouldRetry = statusCode == 429 || statusCode >= 500;
|
||||
if (shouldRetry) {
|
||||
logger.warn("OpenAI API returned {} for operation '{}'. Will retry...", statusCode,
|
||||
operation);
|
||||
}
|
||||
return shouldRetry;
|
||||
}
|
||||
// Retry on network errors (timeouts, connection issues)
|
||||
return throwable instanceof java.util.concurrent.TimeoutException
|
||||
|| throwable instanceof java.net.ConnectException
|
||||
|| throwable instanceof java.io.IOException;
|
||||
})
|
||||
.doBeforeRetry(retrySignal -> {
|
||||
long attempt = retrySignal.totalRetries() + 1;
|
||||
logger.info("Retrying OpenAI request for operation '{}' (attempt {}/{})",
|
||||
operation, attempt, maxRetryAttempts);
|
||||
})
|
||||
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
|
||||
logger.error("OpenAI request for operation '{}' exhausted all {} retry attempts",
|
||||
operation, maxRetryAttempts);
|
||||
return retrySignal.failure();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.reactive.function.client.WebClientResponseException;
|
||||
import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
|
||||
@Service
|
||||
public class OpenAiChartService {
|
||||
@@ -31,6 +33,18 @@ public class OpenAiChartService {
|
||||
@Value("${openai.timeoutMs:90000}")
|
||||
private long timeoutMs;
|
||||
|
||||
@Value("${openai.retry.maxAttempts:5}")
|
||||
private int maxRetryAttempts;
|
||||
|
||||
@Value("${openai.retry.initialDelayMs:1000}")
|
||||
private long initialRetryDelayMs;
|
||||
|
||||
@Value("${openai.retry.maxDelayMs:60000}")
|
||||
private long maxRetryDelayMs;
|
||||
|
||||
@Value("${openai.retry.multiplier:2.0}")
|
||||
private double retryMultiplier;
|
||||
|
||||
public OpenAiChartService(
|
||||
@Value("${openai.api.url:https://api.openai.com/v1/chat/completions}") String apiUrl,
|
||||
@Value("${openai.api.key:}") String apiKey) {
|
||||
@@ -93,6 +107,7 @@ public class OpenAiChartService {
|
||||
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
|
||||
})
|
||||
.timeout(Duration.ofMillis(timeoutMs))
|
||||
.retryWhen(createRetrySpec(taskName))
|
||||
.doOnSuccess(response -> {
|
||||
// 4. Логируем успешный, но еще не обработанный ответ
|
||||
logger.info("Raw OpenAI response for task '{}': {}", taskName, response);
|
||||
@@ -242,4 +257,37 @@ public class OpenAiChartService {
|
||||
return buildSvgPrompt(json, "ru");
|
||||
}
|
||||
|
||||
private RetryBackoffSpec createRetrySpec(String operation) {
|
||||
return Retry.backoff(maxRetryAttempts, Duration.ofMillis(initialRetryDelayMs))
|
||||
.maxBackoff(Duration.ofMillis(maxRetryDelayMs))
|
||||
.multiplier(retryMultiplier)
|
||||
.filter(throwable -> {
|
||||
if (throwable instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) throwable;
|
||||
int statusCode = wcre.getStatusCode().value();
|
||||
// Retry on 429 (rate limit) and 5xx (server errors)
|
||||
boolean shouldRetry = statusCode == 429 || statusCode >= 500;
|
||||
if (shouldRetry) {
|
||||
logger.warn("OpenAI API returned {} for operation '{}'. Will retry...", statusCode,
|
||||
operation);
|
||||
}
|
||||
return shouldRetry;
|
||||
}
|
||||
// Retry on network errors (timeouts, connection issues)
|
||||
return throwable instanceof java.util.concurrent.TimeoutException
|
||||
|| throwable instanceof java.net.ConnectException
|
||||
|| throwable instanceof java.io.IOException;
|
||||
})
|
||||
.doBeforeRetry(retrySignal -> {
|
||||
long attempt = retrySignal.totalRetries() + 1;
|
||||
logger.info("Retrying OpenAI request for operation '{}' (attempt {}/{})",
|
||||
operation, attempt, maxRetryAttempts);
|
||||
})
|
||||
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
|
||||
logger.error("OpenAI request for operation '{}' exhausted all {} retry attempts",
|
||||
operation, maxRetryAttempts);
|
||||
return retrySignal.failure();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,6 +58,10 @@ openai.api.key=sk-proj-zEsggv3MCgvZ2aQbRhpCfyZPKn-isyylNhO16dekXGpM3yzi9H4g0zSjp
|
||||
openai.api.url=https://api.openai.com/v1/chat/completions
|
||||
openai.model.name=gpt-4o-mini
|
||||
openai.timeoutMs=90000
|
||||
openai.retry.maxAttempts=5
|
||||
openai.retry.initialDelayMs=1000
|
||||
openai.retry.maxDelayMs=60000
|
||||
openai.retry.multiplier=2.0
|
||||
|
||||
# Email Configuration
|
||||
spring.mail.host=smtp.gmail.com
|
||||
|
||||
Reference in New Issue
Block a user