.
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
---
|
||||
name: DetailLevel prompts
|
||||
overview: Адаптировать генерацию промптов и выбор модели/лимитов по уровню DetailLevel для JSON-аналитики (get*Analysis) и текстовых секций (genSection*), сохранив текущий режим СТАНДАРТНО как baseline.
|
||||
todos:
|
||||
- id: inspect-json-methods
|
||||
content: Пройти по getAudienceAnalysis/getCompetitorAnalysis/getSeasonalityAndMarket/getStrategyAndFunnel и определить точки для вставки detailLevel-ветвления + вынесения общих частей промпта
|
||||
status: pending
|
||||
- id: update-text-directives
|
||||
content: Обновить appendDetailLevelDirectives/buildSystemPromptForSection под новые требования КРАТКО (150-250, bullet-only, soften placeholders) и ПОДРОБНО (1000-1500, академичность, 3 примера, смежные рынки, прогноз 3-5 лет, LaTeX формулы)
|
||||
status: pending
|
||||
- id: model-routing
|
||||
content: "В genSection* и get*Analysis внедрить выбор модели по DetailLevel: КРАТКО→mini, ПОДРОБНО→text, СТАНДАРТНО→как сейчас"
|
||||
status: pending
|
||||
dependencies:
|
||||
- update-text-directives
|
||||
- inspect-json-methods
|
||||
- id: max-tokens-detailed
|
||||
content: Скорректировать maxTokensOverride для ПОДРОБНО в generateWithRetryForSection и добавить override в JSON get*Analysis вызовы (8000-12000, например 12000)
|
||||
status: pending
|
||||
dependencies:
|
||||
- model-routing
|
||||
- id: smoketest-3-levels
|
||||
content: "Сделать короткий ручной smoketest: один запрос, три уровня, проверить объем/сегменты/плейсхолдеры/модель"
|
||||
status: pending
|
||||
dependencies:
|
||||
- max-tokens-detailed
|
||||
---
|
||||
|
||||
# План внедрения DetailLevel (КРАТКО/СТАНДАРТНО/ПОДРОБНО)
|
||||
|
||||
## Цели
|
||||
|
||||
- **КРАТКО**: экономим токены — краткие инструкции, меньше сегментов в JSON, текст 150–250 слов, только bullet points, модель всегда `miniModelName`.
|
||||
- **СТАНДАРТНО**: **без изменений** (текущие промпты/модели/объемы принимаем baseline).
|
||||
- **ПОДРОБНО**: максимальная глубина — больше сегментов в JSON + доп. поля (гипотезы/мини-кейсы), текст 1000–1500 слов, академические требования, больше формул LaTeX, модель всегда `textModelName`, увеличенный `maxTokens`.
|
||||
|
||||
## Где менять код
|
||||
|
||||
- Основная логика промптов и вызовов модели: [src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java](/Users/magzhanzhumabaev/Desktop/kt%20cloud/parser/src/main/java/kz/konturai/parser/service/MarketingAnalysisService.java)
|
||||
- API вызовы и параметры `max_tokens`: [src/main/java/kz/konturai/parser/service/OpenAIAnalyticsService.java](/Users/magzhanzhumabaev/Desktop/kt%20cloud/parser/src/main/java/kz/konturai/parser/service/OpenAIAnalyticsService.java)
|
||||
- Источник уровня: `MarketingAnalysisRequest.getDetailLevelEnum()` в [src/main/java/kz/konturai/parser/dto/MarketingAnalysisRequest.java](/Users/magzhanzhumabaev/Desktop/kt%20cloud/parser/src/main/java/kz/konturai/parser/dto/MarketingAnalysisRequest.java)
|
||||
|
||||
## Изменения для текстовых секций (genSection*)
|
||||
|
||||
- **Централизовать требования по уровню** в `MarketingAnalysisService.appendDetailLevelDirectives(...)`:
|
||||
- **КРАТКО**: добавить явное требование **150–250 слов**, **только bullet points**, запрет вводных/«консалтинговых оборотов», и правило по плейсхолдерам: **вставлять `[[CHART_...]]/[[TABLE_...]] только если в JSON реально есть данные/раздел этого требует**.
|
||||
- **ПОДРОБНО**: добавить жесткие требования:
|
||||
- объем **1000–1500 слов**;
|
||||
- «глубокий академический анализ»;
|
||||
- **для каждого вывода — 3 конкретных примера реализации**;
|
||||
- **смежные рынки** и **прогноз 3–5 лет**;
|
||||
- больше расчетов в LaTeX: **CAC, LTV, ROI, Payback Period**.
|
||||
- **Выбор модели по уровню** в каждом `genSection*` (там, где сейчас жестко задан `miniModelName` или mix):
|
||||
- **КРАТКО → всегда `miniModelName`**
|
||||
- **ПОДРОБНО → всегда `textModelName`**
|
||||
- **СТАНДАРТНО → оставить текущую модельную логику как есть** (например, `genSectionSummary/product` сейчас на `miniModelName`, `market/audience/competitors/strategy` — на `textModelName`).
|
||||
- Технически: добавить небольшой helper вроде `selectModelForTextSection(detailLevel, currentPrimaryModel, currentFallbackModel)` и использовать его при вызове `generateWithRetryForSection(...)`.
|
||||
|
||||
## Изменения для JSON-методов (get*Analysis)
|
||||
|
||||
Методы: `getAudienceAnalysis`, `getCompetitorAnalysis`, `getSeasonalityAndMarket`, `getStrategyAndFunnel`.
|
||||
|
||||
- Добавить `DetailLevel detailLevel = request.getDetailLevelEnum()` и ветвление (switch/if):
|
||||
- **КРАТКО**:
|
||||
- сократить инструкции;
|
||||
- заменить «минимум 4 сегмента» → **«2–3 ключевых сегмента»**;
|
||||
- заменить требования к полям (motivation/triggers/barriers/contentStrategy и т.п.) с 3–4 предложений → **1 емкое предложение**;
|
||||
- модель **всегда `miniModelName`**.
|
||||
- **СТАНДАРТНО**: оставить текущий prompt builder без изменений.
|
||||
- **ПОДРОБНО**:
|
||||
- требовать **5–6 сегментов** (где применимо) и более глубокую детализацию;
|
||||
- расширить JSON-схему (без ломания текущих полей) добавив:
|
||||
- top-level: `hypotheses[]`, `miniCases[]` внутри соответствующей секции (например `audienceAnalysis.hypotheses`, `competitorAnalysis.miniCases`);
|
||||
- per-item: например `segments[].hypothesis`, `segments[].miniCase`, `competitorsDetails[].miniCase`;
|
||||
- модель **всегда `textModelName`**.
|
||||
- **Вынести базовые части промптов** в общие методы/константы, чтобы не дублировать текст:
|
||||
- базовый «Ты — ...», правила «только JSON», блок входных данных;
|
||||
- отдельные функции для добавления level-specific требований (segment count / sentence counts / extra fields).
|
||||
|
||||
## Max tokens для ПОДРОБНО
|
||||
|
||||
- Сейчас в `generateWithRetryForSection(...)` уже есть `maxTokensOverride` для `DetailLevel.ПОДРОБНО`, но он равен `REPORT_BASE_MAX_TOKENS * 2`.
|
||||
- Привести к требованию: **установить override в диапазоне 8000–12000** (например, **12000**) для ПОДРОБНО, чтобы ответы не обрезались и не упираться в потенциальные лимиты модели.
|
||||
- Для JSON-методов, которые вызывают `openAIAnalyticsService.generateWithInstructionWithModel(...)` без override, добавить `maxTokensOverride` (минимум для ПОДРОБНО), чтобы подробный JSON не резался.
|
||||
|
||||
## Проверка совместимости
|
||||
|
||||
- Убедиться, что существующие поля, которые используются в `genSection*` (например `audienceAnalysis.ageGroups`, `competitorAnalysis.marketShareChart`) **не меняются**, а новые поля добавляются как опциональные.
|
||||
|
||||
## Быстрый smoketest (локально)
|
||||
|
||||
- Прогнать генерацию отчета для 3 режимов на одном и том же входе и сравнить:
|
||||
- модель (`gpt-4o-mini` vs `gpt-4o`),
|
||||
- длину ответа (word count приблизительно),
|
||||
@@ -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("- Объем: 150–250 слов.\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("- Объем: 1000–1500 слов.\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;
|
||||
|
||||
Reference in New Issue
Block a user