diff --git a/src/main/java/kz/konturai/parser/service/OpenAiChartService.java b/src/main/java/kz/konturai/parser/service/OpenAiChartService.java index afa6e6e..8942229 100644 --- a/src/main/java/kz/konturai/parser/service/OpenAiChartService.java +++ b/src/main/java/kz/konturai/parser/service/OpenAiChartService.java @@ -13,10 +13,21 @@ import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import kz.konturai.parser.dto.ChartData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import java.util.Objects; @Service public class OpenAiChartService { + // 1. Инициализируем логгер для этого класса + private static final Logger logger = LoggerFactory.getLogger(OpenAiChartService.class); + private final WebClient webClient; @Value("${openai.model.name:gpt-4o-mini}") @@ -28,6 +39,11 @@ public class OpenAiChartService { public OpenAiChartService( @Value("${openai.api.url:https://api.openai.com/v1/chat/completions}") String apiUrl, @Value("${openai.api.key:}") String apiKey) { + + if (apiKey == null || apiKey.isBlank()) { + logger.warn("OpenAI API key is not configured. OpenAiChartService will not work."); + } + HttpClient httpClient = HttpClient.create(); this.webClient = WebClient.builder() .baseUrl(apiUrl) @@ -37,22 +53,30 @@ public class OpenAiChartService { } public Mono getChartDataJson(String aggregatedLearnings) { + // 2. Логируем начало важной операции + logger.info("Requesting JSON data for charts from OpenAI..."); String prompt = buildChartDataPrompt(aggregatedLearnings); - return callChatCompletions(prompt); + return callChatCompletions("Chart Data Extraction", prompt); } public Mono getChartSvg(String jsonData) { + // 3. Логируем параметры, с которыми вызывается метод + logger.info("Requesting SVG code for chart from OpenAI..."); + logger.info("SVG generation request based on JSON: {}", jsonData); // DEBUG, чтобы не засорять логи String prompt = buildSvgPrompt(jsonData); - return callChatCompletions(prompt); + return callChatCompletions("SVG Generation", prompt); } - private Mono callChatCompletions(String prompt) { + private Mono callChatCompletions(String taskName, String prompt) { Map body = new HashMap<>(); body.put("model", modelName); body.put("temperature", 0); body.put("messages", List.of( Map.of("role", "user", "content", prompt))); + logger.info("Calling OpenAI API for task '{}' with model '{}'.", taskName, modelName); + logger.info("OpenAI Prompt: {}", prompt); + return this.webClient.post() .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) @@ -61,21 +85,51 @@ public class OpenAiChartService { .bodyToMono(new ParameterizedTypeReference>() { }) .timeout(Duration.ofMillis(timeoutMs)) + .doOnSuccess(response -> { + // 4. Логируем успешный, но еще не обработанный ответ + logger.info("Raw OpenAI response for task '{}': {}", taskName, response); + }) .map(resp -> { try { List> choices = (List>) resp.get("choices"); - if (choices == null || choices.isEmpty()) + if (choices == null || choices.isEmpty()) { + // 5. Логируем аномальные, но не ошибочные ситуации + logger.warn("OpenAI response for task '{}' contained no 'choices'.", taskName); return null; - Map choice0 = choices.get(0); - Map message = (Map) choice0.get("message"); - if (message == null) + } + Map message = (Map) choices.get(0).get("message"); + if (message == null) { + logger.warn("OpenAI choice for task '{}' contained no 'message'.", taskName); return null; + } Object content = message.get("content"); - return content == null ? null : String.valueOf(content); + if (content == null) { + logger.warn("OpenAI message for task '{}' contained no 'content'.", taskName); + return null; + } + String responseContent = String.valueOf(content); + logger.info("Successfully extracted content from OpenAI for task '{}'. Content length: {}", + taskName, responseContent.length()); + return responseContent; } catch (Exception e) { + // 6. Логируем ошибку парсинга, если структура ответа неожиданная + logger.error("Failed to parse OpenAI response for task '{}'. Response body: {}", taskName, resp, + e); return null; } - }); + }) + .doOnError(e -> { + // 7. Логируем ошибки сети или HTTP-статусов (4xx, 5xx) + if (e instanceof WebClientResponseException) { + WebClientResponseException wcre = (WebClientResponseException) e; + logger.error("Error from OpenAI API for task '{}'. Status: {}, Body: {}", + taskName, wcre.getStatusCode(), wcre.getResponseBodyAsString(), e); + } else { + logger.error("Generic WebClient error for task '{}'.", taskName, e); + } + }) + .onErrorResume(e -> Mono.empty()); // В случае любой ошибки возвращаем пустой результат, чтобы не + // прерывать цепочку вызовов } private String buildChartDataPrompt(String learningsAsString) { @@ -87,4 +141,4 @@ public class OpenAiChartService { return "Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. SVG должен быть стильным и читаемым, с подписями на русском языке. Не добавляй никаких комментариев, верни ТОЛЬКО SVG-код. JSON с данными:\n---\n" + json + "\n---"; } -} +} \ No newline at end of file