.
This commit is contained in:
@@ -3730,15 +3730,19 @@ public class MarketingAnalysisService {
|
||||
Map<String, Object> analysisJson = generateJsonAnalysis(request);
|
||||
|
||||
if (analysisJson == null || analysisJson.isEmpty()) {
|
||||
logger.error("generateMarketingReport: Failed to generate JSON analysis");
|
||||
logger.error(
|
||||
"generateMarketingReport: Failed to generate JSON analysis - likely due to OpenAI API rate limits");
|
||||
// При ошибке возвращаем базовую структуру с сообщением об ошибке
|
||||
report.put("analysisTypes", analysisTypeNames);
|
||||
report.put("detailLevel", detailLevelName);
|
||||
report.put("fullAnalysis", "Не удалось сгенерировать анализ. Пожалуйста, попробуйте позже.");
|
||||
report.put("summary", "Маркетинговый анализ для " + request.getProduct());
|
||||
report.put("fullAnalysis", "Не удалось сгенерировать анализ из-за превышения лимита запросов к OpenAI API. "
|
||||
+
|
||||
"Система автоматически повторит попытку с задержкой. Пожалуйста, попробуйте позже или уменьшите частоту запросов.");
|
||||
report.put("summary", "Маркетинговый анализ для " + request.getProduct() +
|
||||
" (временно недоступен из-за лимита API)");
|
||||
report.put("chartsData", new HashMap<>());
|
||||
|
||||
logger.warn("generateMarketingReport: Returning report with error message");
|
||||
logger.warn("generateMarketingReport: Returning report with rate limit error message");
|
||||
} else {
|
||||
logger.info("generateMarketingReport: JSON analysis generated successfully. Keys: {}",
|
||||
analysisJson.keySet());
|
||||
|
||||
@@ -25,6 +25,7 @@ import reactor.util.retry.Retry;
|
||||
import reactor.util.retry.RetryBackoffSpec;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
|
||||
@Service
|
||||
@@ -55,6 +56,15 @@ public class OpenAIAnalyticsService {
|
||||
@Value("${openai.retry.multiplier:2.0}")
|
||||
private double retryMultiplier;
|
||||
|
||||
@Value("${openai.retry.rateLimitMaxAttempts:10}")
|
||||
private int rateLimitMaxAttempts;
|
||||
|
||||
@Value("${openai.retry.rateLimitInitialDelayMs:5000}")
|
||||
private long rateLimitInitialDelayMs;
|
||||
|
||||
@Value("${openai.retry.rateLimitMaxDelayMs:300000}")
|
||||
private long rateLimitMaxDelayMs;
|
||||
|
||||
@Value("${openai.rateLimit.maxConcurrentRequests:2}")
|
||||
private int maxConcurrentRequests;
|
||||
|
||||
@@ -395,18 +405,49 @@ public class OpenAIAnalyticsService {
|
||||
}
|
||||
|
||||
private RetryBackoffSpec createRetrySpec(String operation) {
|
||||
return Retry.backoff(maxRetryAttempts, Duration.ofMillis(initialRetryDelayMs))
|
||||
// Store Retry-After duration for 429 errors
|
||||
AtomicReference<Duration> retryAfterDuration = new AtomicReference<>(null);
|
||||
|
||||
// Use the higher max attempts to handle rate limit errors better
|
||||
// This will apply to all retries, but rate limit errors need more attempts
|
||||
int effectiveMaxAttempts = Math.max(maxRetryAttempts, rateLimitMaxAttempts);
|
||||
|
||||
return Retry.backoff(effectiveMaxAttempts, Duration.ofMillis(initialRetryDelayMs))
|
||||
.maxBackoff(Duration.ofMillis(maxRetryDelayMs))
|
||||
.multiplier(retryMultiplier)
|
||||
.filter(throwable -> {
|
||||
if (throwable instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) throwable;
|
||||
int statusCode = wcre.getStatusCode().value();
|
||||
|
||||
if (statusCode == 429) {
|
||||
// Extract Retry-After header for 429 errors
|
||||
String retryAfterHeader = wcre.getHeaders().getFirst("Retry-After");
|
||||
if (retryAfterHeader != null) {
|
||||
try {
|
||||
int seconds = Integer.parseInt(retryAfterHeader);
|
||||
// Add 10% buffer to be safe, but cap at max delay
|
||||
int delaySeconds = (int) Math.min(seconds * 1.1, rateLimitMaxDelayMs / 1000);
|
||||
retryAfterDuration.set(Duration.ofSeconds(delaySeconds));
|
||||
logger.warn(
|
||||
"OpenAI API returned 429 for operation '{}'. Retry-After: {} seconds. Will wait {}ms before retry.",
|
||||
operation, seconds, retryAfterDuration.get().toMillis());
|
||||
} catch (NumberFormatException e) {
|
||||
// Ignore if header is not a number
|
||||
retryAfterDuration.set(null);
|
||||
}
|
||||
} else {
|
||||
retryAfterDuration.set(null);
|
||||
}
|
||||
} else {
|
||||
retryAfterDuration.set(null);
|
||||
}
|
||||
|
||||
// Only retry on 429 (rate limit) and 5xx (server errors)
|
||||
// Don't retry on 401, 403, etc.
|
||||
return statusCode == 429 || statusCode >= 500;
|
||||
}
|
||||
// Retry on network errors (timeouts, connection issues)
|
||||
retryAfterDuration.set(null);
|
||||
return throwable instanceof java.util.concurrent.TimeoutException
|
||||
|| throwable instanceof java.net.ConnectException
|
||||
|| throwable instanceof java.io.IOException;
|
||||
@@ -414,36 +455,56 @@ public class OpenAIAnalyticsService {
|
||||
.doBeforeRetry(retrySignal -> {
|
||||
long attempt = retrySignal.totalRetries() + 1;
|
||||
Throwable failure = retrySignal.failure();
|
||||
|
||||
Duration retryAfter = null;
|
||||
if (failure instanceof WebClientResponseException) {
|
||||
|
||||
// If we have a Retry-After duration for 429 errors, sleep for that duration
|
||||
// This ensures we respect the API's requested delay before Reactor applies its backoff
|
||||
Duration customDelay = retryAfterDuration.get();
|
||||
if (customDelay != null && failure instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) failure;
|
||||
if (wcre.getStatusCode().value() == 429) {
|
||||
// Try to extract Retry-After header
|
||||
String retryAfterHeader = wcre.getHeaders().getFirst("Retry-After");
|
||||
if (retryAfterHeader != null) {
|
||||
try {
|
||||
int seconds = Integer.parseInt(retryAfterHeader);
|
||||
retryAfter = Duration.ofSeconds(seconds);
|
||||
logger.warn(
|
||||
"OpenAI API returned 429 for operation '{}'. Retry-After: {} seconds. Will retry in {}ms (attempt {}/{})",
|
||||
operation, seconds, retryAfter.toMillis(), attempt, maxRetryAttempts);
|
||||
} catch (NumberFormatException e) {
|
||||
// Ignore if header is not a number
|
||||
}
|
||||
try {
|
||||
logger.warn(
|
||||
"Respecting Retry-After header: sleeping for {}ms (attempt {}/{})",
|
||||
customDelay.toMillis(), attempt, rateLimitMaxAttempts);
|
||||
Thread.sleep(customDelay.toMillis());
|
||||
retryAfterDuration.set(null); // Reset after use
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.warn("Interrupted while waiting for Retry-After delay");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (retryAfter == null) {
|
||||
logger.warn("OpenAI API returned error for operation '{}'. Will retry (attempt {}/{})",
|
||||
|
||||
// Log retry attempt
|
||||
if (failure instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) failure;
|
||||
int statusCode = wcre.getStatusCode().value();
|
||||
int maxAttempts = statusCode == 429 ? rateLimitMaxAttempts : maxRetryAttempts;
|
||||
if (statusCode == 429 && customDelay == null) {
|
||||
logger.warn("OpenAI API returned 429 for operation '{}'. Will retry with exponential backoff (attempt {}/{})",
|
||||
operation, attempt, maxAttempts);
|
||||
} else if (statusCode != 429) {
|
||||
logger.warn("OpenAI API returned {} for operation '{}'. Will retry (attempt {}/{})",
|
||||
statusCode, operation, attempt, maxAttempts);
|
||||
}
|
||||
} else {
|
||||
logger.warn("OpenAI API network error for operation '{}'. Will retry (attempt {}/{})",
|
||||
operation, attempt, maxRetryAttempts);
|
||||
}
|
||||
})
|
||||
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) -> {
|
||||
Throwable failure = retrySignal.failure();
|
||||
int maxAttempts = maxRetryAttempts;
|
||||
if (failure instanceof WebClientResponseException) {
|
||||
WebClientResponseException wcre = (WebClientResponseException) failure;
|
||||
if (wcre.getStatusCode().value() == 429) {
|
||||
maxAttempts = rateLimitMaxAttempts;
|
||||
}
|
||||
}
|
||||
logger.error("OpenAI request for operation '{}' exhausted all {} retry attempts. Giving up.",
|
||||
operation, maxRetryAttempts);
|
||||
operation, maxAttempts);
|
||||
return retrySignal.failure();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ openai.retry.maxAttempts=3
|
||||
openai.retry.initialDelayMs=2000
|
||||
openai.retry.maxDelayMs=60000
|
||||
openai.retry.multiplier=2.0
|
||||
openai.retry.rateLimitMaxAttempts=10
|
||||
openai.retry.rateLimitInitialDelayMs=5000
|
||||
openai.retry.rateLimitMaxDelayMs=300000
|
||||
openai.rateLimit.maxConcurrentRequests=2
|
||||
|
||||
# Serper.dev Web Search Configuration
|
||||
|
||||
Reference in New Issue
Block a user