This commit is contained in:
root
2025-12-28 10:13:08 +05:00
parent 7983a4b352
commit 8b73fbcc2c
2 changed files with 147 additions and 16 deletions
@@ -43,6 +43,8 @@ public class MarketingAnalysisService {
+ "ГРАФИКИ (ОБЯЗАТЕЛЬНО): располагай теги графиков (например, [[CHART_...]]) непосредственно внутри или сразу после описания конкретных данных/цифр, к которым относится график.\n\n";
private static final int REPORT_BASE_MAX_TOKENS = 12000;
private static final int REPORT_DETAILED_MAX_TOKENS = 12000;
private static final int JSON_DETAILED_MAX_TOKENS = 12000;
private DetailLevel resolveDetailLevelFromContext(Map<String, Object> contextJson) {
if (contextJson == null) {
@@ -62,12 +64,13 @@ public class MarketingAnalysisService {
if (detailLevel != DetailLevel.ПОДРОБНО) {
return null;
}
// Требование пользователя + уточнение (hybrid): 500+ слов строго для ключевых
// разделов.
return "Твоя задача — провести глубокий академический анализ. Используй профессиональную терминологию (LTV, CAC, PESTEL, Porter's Five Forces). "
+ "Каждый подраздел должен содержать не менее 500 слов и включать конкретные тактические рекомендации.\n"
+ "Примечание: правило «не менее 500 слов» применяй строго для ключевых разделов IV (ЦА), V (Конкуренты), XI (Стратегия). "
+ "Для остальных разделов избегай искусственного раздувания, но сохраняй академическую глубину и практическую направленность.";
return "РЕЖИМ ПОДРОБНО (SYSTEM): Проведи глубокий академический анализ с причинно-следственной логикой.\n"
+ "- Объем: 1000–1500 слов (если раздел ключевой — ближе к верхней границе).\n"
+ "- Для КАЖДОГО вывода приводи по 3 конкретных примера реализации.\n"
+ "- Обязательно включи анализ смежных рынков и долгосрочные прогнозы на 3–5 лет.\n"
+ "- Включай расчеты в LaTeX там, где уместно: CAC, LTV, ROI, Payback Period. Если не хватает чисел — делай обоснованные допущения и помечай как \"Экстраполяция\".\n"
+ "- Используй фреймворки, когда релевантно: PESTEL и Porter's Five Forces.\n"
+ "- Всегда опирайся на JSON-контекст, не выдумывай факты.";
}
private void appendDetailLevelDirectives(StringBuilder promptBuilder, DetailLevel detailLevel, String sectionId) {
@@ -81,11 +84,14 @@ public class MarketingAnalysisService {
if (detailLevel == DetailLevel.КРАТКО) {
promptBuilder.append("РЕЖИМ КРАТКО (ОБЯЗАТЕЛЬНО):\n");
promptBuilder.append("- Игнорируй любые требования по «минимум N слов», если они есть выше.\n");
promptBuilder.append("- Дай только самое важное в виде bullet points.\n");
promptBuilder.append("- Максимум 10–14 буллетов; каждый буллет 1–2 строки.\n");
promptBuilder.append("- Без длинных абзацев, без воды, без повторов.\n");
promptBuilder.append("- Объем: 150250 слов.\n");
promptBuilder.append(
"- Если выше требуется вставка [[CHART_...]]/[[TABLE_...]] — вставь эти плейсхолдеры в подходящие буллеты рядом с конкретными цифрами.\n");
"- Формат: оставь обязательный заголовок раздела (если он требуется выше), далее используй ТОЛЬКО списки (bullet points).\n");
promptBuilder.append(
"- Пиши максимально лаконично, используй только bullet points, избегай вводных слов и консалтинговых оборотов.\n");
promptBuilder.append("- Максимум 10–14 буллетов; каждый буллет 1–2 строки.\n");
promptBuilder.append(
"- Плейсхолдеры [[CHART_...]]/[[TABLE_...]]: вставляй ТОЛЬКО если в JSON реально есть данные для соответствующего графика/таблицы и раздел это обсуждает. Если данных нет — не вставляй тег и не упоминай его.\n");
return;
}
@@ -94,6 +100,11 @@ public class MarketingAnalysisService {
promptBuilder.append("- Дай глубокий академический анализ с причинно-следственной логикой.\n");
promptBuilder
.append("- Используй терминологию (LTV, CAC, PESTEL, Porter's Five Forces) там, где уместно.\n");
promptBuilder.append("- Объем: 10001500 слов.\n");
promptBuilder.append("- Для КАЖДОГО вывода приводи по 3 конкретных примера реализации.\n");
promptBuilder.append("- Обязательно включи анализ смежных рынков и долгосрочные прогнозы на 3–5 лет.\n");
promptBuilder.append(
"- Включай расчеты в LaTeX там, где уместно: CAC, LTV, ROI, Payback Period. Если не хватает цифр — делай обоснованные допущения и помечай блок как \"Экстраполяция\".\n");
if ("market".equalsIgnoreCase(sectionId) || "strategy".equalsIgnoreCase(sectionId)) {
promptBuilder.append(
"- ОБЯЗАТЕЛЬНО ВКЛЮЧИ ФРЕЙМВОРКИ: сделай отдельные подблоки PESTEL и «5 сил Портера» (не просто упоминание терминов).\n");
@@ -102,14 +113,40 @@ public class MarketingAnalysisService {
"- ОБЯЗАТЕЛЬНО ВКЛЮЧИ «5 сил Портера» (минимум: Rivalry/Threat of New Entrants/Substitutes/Buyer Power/Supplier Power) применительно к этой нише в Казахстане.\n");
}
promptBuilder.append("- После каждого крупного вывода добавляй конкретные тактические рекомендации.\n");
if (isKeyVerboseSectionForHybridRule(sectionId)) {
promptBuilder.append("- ОБЪЕМ (КЛЮЧЕВОЙ РАЗДЕЛ): не менее 500 слов.\n");
} else {
promptBuilder.append("- ОБЪЕМ: подробно, но без искусственного раздувания.\n");
}
}
}
private String selectPrimaryModelForTextSection(DetailLevel detailLevel, String standardPrimaryModel) {
if (detailLevel == null) {
return standardPrimaryModel;
}
if (detailLevel == DetailLevel.КРАТКО) {
return miniModelName;
}
if (detailLevel == DetailLevel.ПОДРОБНО) {
return textModelName;
}
return standardPrimaryModel;
}
private String selectFallbackModelForTextSection(DetailLevel detailLevel, String standardFallbackModel) {
if (detailLevel == null) {
return standardFallbackModel;
}
if (detailLevel == DetailLevel.КРАТКО || detailLevel == DetailLevel.ПОДРОБНО) {
return null;
}
return standardFallbackModel;
}
private String selectModelForJsonAnalysis(DetailLevel detailLevel) {
if (detailLevel == DetailLevel.ПОДРОБНО) {
return textModelName;
}
// КРАТКО и СТАНДАРТНО: всегда mini
return miniModelName;
}
private Map<String, Object> buildResearchNotesForSection(Map<String, Object> contextJson, String sectionId) {
if (contextJson == null) {
return new HashMap<>();
@@ -1679,7 +1716,7 @@ public class MarketingAnalysisService {
String last = null;
DetailLevel detailLevel = resolveDetailLevelFromContext(contextJson);
String systemPrompt = buildSystemPromptForSection(detailLevel, sectionId);
Integer maxTokensOverride = detailLevel == DetailLevel.ПОДРОБНО ? REPORT_BASE_MAX_TOKENS * 2 : null;
Integer maxTokensOverride = detailLevel == DetailLevel.ПОДРОБНО ? REPORT_DETAILED_MAX_TOKENS : null;
for (int i = 1; i <= maxAttempts; i++) {
String modelToUse = primaryModel;