This commit is contained in:
arys
2026-02-21 00:38:29 +05:00
parent 63a1e39563
commit 0e68c69fad
@@ -1,6 +1,7 @@
package kz.konturai.parser.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import kz.konturai.parser.dto.*;
import kz.konturai.parser.model.MarketingAnalysisV3Document;
@@ -29,6 +30,8 @@ public class MarketingAnalysisV3Service {
private String highIntelligenceModel;
public String createAndStartAnalysis(MarketingAnalysisV3Request request, String userId) {
log.info("Creating new Analysis V3 for user: {}", userId);
MarketingAnalysisV3Document doc = new MarketingAnalysisV3Document();
doc.setUserId(userId);
doc.setRequestData(request);
@@ -37,6 +40,7 @@ public class MarketingAnalysisV3Service {
doc.setUpdatedAt(LocalDateTime.now());
doc = repository.save(doc);
log.info("Analysis document saved with ID: {}", doc.getId());
processAnalysisAsync(doc.getId(), request);
return doc.getId();
@@ -45,21 +49,28 @@ public class MarketingAnalysisV3Service {
@Async("reportGenerationExecutor")
public void processAnalysisAsync(String docId, MarketingAnalysisV3Request request) {
try {
log.info("[Analysis ID: {}] Started async processing", docId);
updateStatus(docId, "PROCESSING");
log.info("[Analysis ID: {}] Step 1/3: Executing Deep Research", docId);
Map<String, Object> researchPack = executeDeepResearch(request);
saveResearchMetaData(docId, researchPack);
log.info("[Analysis ID: {}] Step 2/3: Building Prompts", docId);
String systemPrompt = buildKazakhstanSystemPrompt();
String userPrompt = buildDataDrivenUserPrompt(request, researchPack);
String jsonResponse = generateAiResponseWithRetry(userPrompt, systemPrompt);
log.info("[Analysis ID: {}] Step 3/3: Calling AI Model ({})", docId, highIntelligenceModel);
String jsonResponse = generateAiResponseWithRetry(docId, userPrompt, systemPrompt);
log.info("[Analysis ID: {}] Parsing and validating AI result", docId);
MarketingAnalysisV3Result result = parseAndValidateResult(jsonResponse);
completeAnalysis(docId, result);
log.info("[Analysis ID: {}] Analysis successfully completed!", docId);
} catch (Exception e) {
log.error("Analysis V3 Failed for ID {}: {}", docId, e.getMessage(), e);
log.error("[Analysis ID: {}] FAILED with error: {}", docId, e.getMessage(), e);
failAnalysis(docId, e.getMessage());
}
}
@@ -91,10 +102,16 @@ public class MarketingAnalysisV3Service {
queries.add(String.format("кейс продвижение SMM %s казахстан", niche));
queries.parallelStream().forEach(q -> {
// КРИТИЧЕСКИ ВАЖНО: MongoDB не поддерживает точки (.) в названиях ключей!
// Поэтому заменяем точки на нижнее подчеркивание перед сохранением в Map.
String safeMongoKey = q.replace(".", "_").replace("$", "");
try {
pack.put(q, searchService.search(q));
log.debug("Executing search query: {}", q);
pack.put(safeMongoKey, searchService.search(q));
} catch (Exception e) {
pack.put(q, Map.of("error", e.getMessage(), "status", "ERROR"));
log.warn("Search failed for query: {}. Error: {}", q, e.getMessage());
pack.put(safeMongoKey, Map.of("error", e.getMessage(), "status", "ERROR"));
}
});
@@ -102,13 +119,14 @@ public class MarketingAnalysisV3Service {
return pack;
}
private String generateAiResponseWithRetry(String userPrompt, String systemPrompt) {
private String generateAiResponseWithRetry(String docId, String userPrompt, String systemPrompt) {
int attempts = 0;
int maxAttempts = 3;
String lastError = "";
while (attempts < maxAttempts) {
try {
log.info("[Analysis ID: {}] AI Generation attempt {}/{}", docId, attempts + 1, maxAttempts);
String response = aiService.generateWithInstructionWithModel(
"{}",
userPrompt,
@@ -121,24 +139,31 @@ public class MarketingAnalysisV3Service {
String cleaned = cleanJson(response);
if (cleaned != null && cleaned.startsWith("{") && cleaned.endsWith("}")) {
// Проверка валидности JSON
objectMapper.readTree(cleaned);
return cleaned;
} else {
lastError = "Response is not a valid JSON";
lastError = "Response is not a valid JSON structure";
log.warn("[Analysis ID: {}] Invalid JSON received from AI", docId);
}
} catch (JsonProcessingException e) {
lastError = "JSON Parse Error: " + e.getMessage();
log.warn("[Analysis ID: {}] Failed to parse AI JSON response", docId);
} catch (Exception e) {
lastError = "API Error: " + e.getMessage();
log.warn("[Analysis ID: {}] AI API Call failed: {}", docId, e.getMessage());
}
attempts++;
try { Thread.sleep(2500L * attempts); } catch (InterruptedException ignored) {}
try { Thread.sleep(3000L * attempts); } catch (InterruptedException ignored) {}
}
throw new RuntimeException("Failed to generate valid JSON after " + maxAttempts + " attempts. Last error: " + lastError);
}
private MarketingAnalysisV3Result parseAndValidateResult(String json) throws Exception {
return objectMapper.readValue(json, MarketingAnalysisV3Result.class);
// Создаем безопасную копию маппера, чтобы он не падал, если ИИ придумает новые поля
ObjectMapper safeMapper = this.objectMapper.copy();
safeMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return safeMapper.readValue(json, MarketingAnalysisV3Result.class);
}
private String cleanJson(String response) {
@@ -163,10 +188,16 @@ public class MarketingAnalysisV3Service {
}
private void saveResearchMetaData(String id, Map<String, Object> researchPack) {
repository.findById(id).ifPresent(doc -> {
doc.setResearchMetaData(researchPack);
repository.save(doc);
});
try {
repository.findById(id).ifPresent(doc -> {
doc.setResearchMetaData(researchPack);
repository.save(doc);
log.debug("[Analysis ID: {}] Research metadata successfully saved to DB", id);
});
} catch (Exception e) {
log.error("[Analysis ID: {}] CRITICAL: Failed to save research metadata to MongoDB. Check for invalid characters in keys. Error: {}", id, e.getMessage());
throw e; // Пробрасываем дальше, чтобы уйти в статус FAILED
}
}
private void completeAnalysis(String id, MarketingAnalysisV3Result result) {
@@ -308,28 +339,6 @@ public class MarketingAnalysisV3Service {
"reviews": 0,
"strengths": ["string"],
"weaknesses": ["string"]
},
{
"name": "РЕАЛЬНОЕ НАЗВАНИЕ БРЕНДА 2",
"platform": "string",
"followers": 0,
"postsPerMonth": 0,
"er": 0.0,
"rating": 0.0,
"reviews": 0,
"strengths": ["string"],
"weaknesses": ["string"]
},
{
"name": "РЕАЛЬНОЕ НАЗВАНИЕ БРЕНДА 3",
"platform": "string",
"followers": 0,
"postsPerMonth": 0,
"er": 0.0,
"rating": 0.0,
"reviews": 0,
"strengths": ["string"],
"weaknesses": ["string"]
}
],
"4_content_profile": {