.
This commit is contained in:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user