This commit is contained in:
root
2025-12-20 00:25:46 +05:00
parent 2f834770a7
commit e5d7e5fb77
@@ -1428,6 +1428,17 @@ public class MarketingAnalysisService {
String lowerResult = result.toLowerCase().trim();
// Явные "заглушки" о провале генерации (не считаем валидным ответом модели)
// Важно: это нужно, чтобы такие ответы не считались "успешной генерацией" при
// сборке отчёта.
if (lowerResult.contains("не удалось сгенерировать раздел") ||
lowerResult.contains("ошибка при генерации раздела") ||
lowerResult.startsWith("ошибка при генерации") ||
lowerResult.startsWith("error generating") ||
lowerResult.startsWith("error:")) {
return false;
}
// Проверяем на типичные ответы отказа от OpenAI
if (lowerResult.contains("i'm sorry") ||
lowerResult.contains("i cannot") ||
@@ -1453,6 +1464,51 @@ public class MarketingAnalysisService {
return true;
}
private boolean hasNonEmptyValue(Map<String, Object> map, String key) {
if (map == null || key == null) {
return false;
}
Object v = map.get(key);
return !isEmptyChartData(v);
}
private String generateWithRetry(String jsonContext, String prompt, String lang, String primaryModel,
String fallbackModel, int attempts) {
int maxAttempts = Math.max(1, attempts);
String last = null;
for (int i = 1; i <= maxAttempts; i++) {
String modelToUse = primaryModel;
// На последней попытке можно переключиться на запасную модель (если задана)
if (i == maxAttempts && fallbackModel != null && !fallbackModel.isBlank()) {
modelToUse = fallbackModel;
}
try {
last = openAIAnalyticsService.generateWithInstructionWithModel(
jsonContext, prompt, lang, modelToUse);
} catch (Exception e) {
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;
}
/**
* Удаляет сообщения об ошибках и отказах AI из текста отчета
*
@@ -1654,9 +1710,27 @@ public class MarketingAnalysisService {
}
break;
case "content":
// Если contentRecommendations отсутствует, всё равно можно построить
// контент-стратегию
// на основе аудитории/воронки/каналов/сезонности/конкурентов.
if (fullJson.containsKey("contentRecommendations")) {
context.put("contentRecommendations", fullJson.get("contentRecommendations"));
}
if (fullJson.containsKey("audienceAnalysis")) {
context.put("audienceAnalysis", fullJson.get("audienceAnalysis"));
}
if (fullJson.containsKey("competitorAnalysis")) {
context.put("competitorAnalysis", fullJson.get("competitorAnalysis"));
}
if (fullJson.containsKey("seasonality")) {
context.put("seasonality", fullJson.get("seasonality"));
}
if (fullJson.containsKey("market")) {
context.put("market", fullJson.get("market"));
}
if (fullJson.containsKey("strategyAnalysis")) {
context.put("strategyAnalysis", fullJson.get("strategyAnalysis"));
}
break;
case "strategy":
// Для итоговой стратегии включаем все ключевые данные
@@ -1851,6 +1925,15 @@ public class MarketingAnalysisService {
*/
private String genSectionAudience(Map<String, Object> contextJson) {
try {
// Если нет ключевых данных не дергаем модель, чтобы не получать "короткие"
// ответы
boolean hasAudienceAnalysis = hasNonEmptyValue(contextJson, "audienceAnalysis");
boolean hasAudienceSegments = hasNonEmptyValue(contextJson, "audienceSegments");
if (!hasAudienceAnalysis && !hasAudienceSegments) {
return "IV. Анализ целевой аудитории\n\n"
+ "Раздел пропущен: отсутствуют данные аудитории в JSON (audienceAnalysis/audienceSegments).";
}
String jsonString = objectMapper.writeValueAsString(contextJson);
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("Ты — высокооплачиваемый бизнес-консультант и маркетинговый аналитик.\n\n");
@@ -1901,8 +1984,8 @@ public class MarketingAnalysisService {
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
String result = openAIAnalyticsService.generateWithInstructionWithModel(
jsonString, promptBuilder.toString(), "ru", textModelName);
String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", textModelName, miniModelName,
2);
if (!isValidAiResponse(result)) {
logger.warn("genSectionAudience: AI returned invalid or refusal response");
@@ -1997,6 +2080,11 @@ public class MarketingAnalysisService {
*/
private String genSectionSWOT(Map<String, Object> contextJson) {
try {
if (!hasNonEmptyValue(contextJson, "swot")) {
return "VI. SWOT-анализ\n\n"
+ "Раздел пропущен: отсутствуют данные SWOT в JSON (swot / strategyAnalysis.globalSwot).";
}
String jsonString = objectMapper.writeValueAsString(contextJson);
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("Ты — высокооплачиваемый бизнес-консультант и маркетинговый аналитик.\n\n");
@@ -2033,8 +2121,8 @@ public class MarketingAnalysisService {
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
String result = openAIAnalyticsService.generateWithInstructionWithModel(
jsonString, promptBuilder.toString(), "ru", textModelName);
String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", textModelName, miniModelName,
2);
if (!isValidAiResponse(result)) {
logger.warn("genSectionSWOT: AI returned invalid or refusal response");
@@ -2110,6 +2198,13 @@ public class MarketingAnalysisService {
*/
private String genSectionFunnel(Map<String, Object> contextJson) {
try {
boolean hasFunnel = hasNonEmptyValue(contextJson, "funnel");
boolean hasConversionFunnel = hasNonEmptyValue(contextJson, "conversionFunnel");
if (!hasFunnel && !hasConversionFunnel) {
return "VIII. Воронка спроса\n\n"
+ "Раздел пропущен: отсутствуют данные воронки в JSON (funnel / strategyAnalysis.funnel / conversionFunnel).";
}
String jsonString = objectMapper.writeValueAsString(contextJson);
StringBuilder promptBuilder = new StringBuilder();
promptBuilder.append("Ты — высокооплачиваемый бизнес-консультант и маркетинговый аналитик.\n\n");
@@ -2145,8 +2240,8 @@ public class MarketingAnalysisService {
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
String result = openAIAnalyticsService.generateWithInstructionWithModel(
jsonString, promptBuilder.toString(), "ru", textModelName);
String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", textModelName, miniModelName,
2);
if (!isValidAiResponse(result)) {
logger.warn("genSectionFunnel: AI returned invalid or refusal response");
@@ -2228,7 +2323,10 @@ public class MarketingAnalysisService {
promptBuilder.append("СТРУКТУРА РАЗДЕЛА:\n");
promptBuilder.append("X. Рекомендации по контенту\n\n");
promptBuilder.append("Детально проанализируй:\n");
promptBuilder.append("- Типы контента для разных целей (из contentRecommendations)\n");
promptBuilder.append(
"- Если в JSON есть contentRecommendations: используй их как основу и доработай/структурируй\n");
promptBuilder.append(
"- Если contentRecommendations НЕТ: разработай контент-стратегию, опираясь на audienceAnalysis, strategyAnalysis (funnel/channelsPotential), seasonality и competitorAnalysis\n");
promptBuilder.append("- Форматы контента для разных каналов продвижения\n");
promptBuilder.append("- Контент для разных этапов воронки (осведомленность, интерес, решение, действие)\n");
promptBuilder.append("- Контент для разных сегментов целевой аудитории\n");
@@ -2243,8 +2341,8 @@ public class MarketingAnalysisService {
promptBuilder.append("JSON ДАННЫЕ:\n");
promptBuilder.append(jsonString);
String result = openAIAnalyticsService.generateWithInstructionWithModel(
jsonString, promptBuilder.toString(), "ru", miniModelName);
String result = generateWithRetry(jsonString, promptBuilder.toString(), "ru", miniModelName, textModelName,
2);
if (!isValidAiResponse(result)) {
logger.warn("genSectionContent: AI returned invalid or refusal response");