.
This commit is contained in:
@@ -27,11 +27,15 @@ import java.util.*;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class MarketingAnalysisService {
|
public class MarketingAnalysisService {
|
||||||
|
private final Semaphore openAiSemaphore = new Semaphore(3); // Разрешить только 3 активных запроса к OpenAI на всё
|
||||||
|
// приложение
|
||||||
|
|
||||||
private static final String AI_MANDATORY_JSON_USAGE_WARNING = "ВНИМАНИЕ: Если в предоставленном JSON есть данные, ты ОБЯЗАН использовать их и вставить соответствующий тег [[CHART_...]]. "
|
private static final String AI_MANDATORY_JSON_USAGE_WARNING = "ВНИМАНИЕ: Если в предоставленном JSON есть данные, ты ОБЯЗАН использовать их и вставить соответствующий тег [[CHART_...]]. "
|
||||||
+ "Никогда не пиши, что данных нет, если JSON не пустой.\n\n";
|
+ "Никогда не пиши, что данных нет, если JSON не пустой.\n\n";
|
||||||
private static final String AI_ULTIMATE_JSON_PRESENCE_INSTRUCTION = ""
|
private static final String AI_ULTIMATE_JSON_PRESENCE_INSTRUCTION = ""
|
||||||
@@ -1556,42 +1560,52 @@ public class MarketingAnalysisService {
|
|||||||
String fallbackModel,
|
String fallbackModel,
|
||||||
int attempts,
|
int attempts,
|
||||||
String sectionId) {
|
String sectionId) {
|
||||||
int maxAttempts = Math.max(1, attempts);
|
try {
|
||||||
String last = null;
|
|
||||||
DetailLevel detailLevel = resolveDetailLevelFromContext(contextJson);
|
|
||||||
String systemPrompt = buildSystemPromptForSection(detailLevel, sectionId);
|
|
||||||
Integer maxTokensOverride = detailLevel == DetailLevel.ПОДРОБНО ? REPORT_BASE_MAX_TOKENS * 2 : null;
|
|
||||||
|
|
||||||
for (int i = 1; i <= maxAttempts; i++) {
|
openAiSemaphore.acquire();
|
||||||
String modelToUse = primaryModel;
|
int maxAttempts = Math.max(1, attempts);
|
||||||
// На последней попытке можно переключиться на запасную модель (если задана)
|
String last = null;
|
||||||
if (i == maxAttempts && fallbackModel != null && !fallbackModel.isBlank()) {
|
DetailLevel detailLevel = resolveDetailLevelFromContext(contextJson);
|
||||||
modelToUse = fallbackModel;
|
String systemPrompt = buildSystemPromptForSection(detailLevel, sectionId);
|
||||||
}
|
Integer maxTokensOverride = detailLevel == DetailLevel.ПОДРОБНО ? REPORT_BASE_MAX_TOKENS * 2 : null;
|
||||||
try {
|
|
||||||
last = openAIAnalyticsService.generateWithInstructionWithModel(
|
|
||||||
jsonContext, prompt, lang, modelToUse, systemPrompt, maxTokensOverride);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.warn("generateWithRetry: attempt {} failed with exception: {}", i, e.getMessage());
|
|
||||||
last = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidAiResponse(last)) {
|
for (int i = 1; i <= maxAttempts; i++) {
|
||||||
return last;
|
String modelToUse = primaryModel;
|
||||||
}
|
// На последней попытке можно переключиться на запасную модель (если задана)
|
||||||
|
if (i == maxAttempts && fallbackModel != null && !fallbackModel.isBlank()) {
|
||||||
// Небольшая пауза между попытками, чтобы сгладить кратковременные сбои
|
modelToUse = fallbackModel;
|
||||||
if (i < maxAttempts) {
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(250L);
|
last = openAIAnalyticsService.generateWithInstructionWithModel(
|
||||||
} catch (InterruptedException ie) {
|
jsonContext, prompt, lang, modelToUse, systemPrompt, maxTokensOverride);
|
||||||
Thread.currentThread().interrupt();
|
} catch (Exception e) {
|
||||||
break;
|
logger.warn("generateWithRetry: attempt {} failed with exception: {}", i, e.getMessage());
|
||||||
|
last = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidAiResponse(last)) {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Небольшая пауза между попытками, чтобы сгладить кратковременные сбои
|
||||||
|
if (i < maxAttempts) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(250L);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return last;
|
return last;
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("generateWithRetryForSection: Error generating section: {}", e.getMessage(), e);
|
||||||
|
logger.error("generateWithRetryForSection: Stack trace: ", e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
openAiSemaphore.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backward-compatible helper (legacy signature). Kept because file historically
|
// Backward-compatible helper (legacy signature). Kept because file historically
|
||||||
|
|||||||
Reference in New Issue
Block a user