This commit is contained in:
root
2025-10-06 19:39:54 +05:00
parent e6e4f6b809
commit dd7953acf0
@@ -13,10 +13,21 @@ import java.time.Duration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; 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 @Service
public class OpenAiChartService { public class OpenAiChartService {
// 1. Инициализируем логгер для этого класса
private static final Logger logger = LoggerFactory.getLogger(OpenAiChartService.class);
private final WebClient webClient; private final WebClient webClient;
@Value("${openai.model.name:gpt-4o-mini}") @Value("${openai.model.name:gpt-4o-mini}")
@@ -28,6 +39,11 @@ public class OpenAiChartService {
public OpenAiChartService( public OpenAiChartService(
@Value("${openai.api.url:https://api.openai.com/v1/chat/completions}") String apiUrl, @Value("${openai.api.url:https://api.openai.com/v1/chat/completions}") String apiUrl,
@Value("${openai.api.key:}") String apiKey) { @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(); HttpClient httpClient = HttpClient.create();
this.webClient = WebClient.builder() this.webClient = WebClient.builder()
.baseUrl(apiUrl) .baseUrl(apiUrl)
@@ -37,22 +53,30 @@ public class OpenAiChartService {
} }
public Mono<String> getChartDataJson(String aggregatedLearnings) { public Mono<String> getChartDataJson(String aggregatedLearnings) {
// 2. Логируем начало важной операции
logger.info("Requesting JSON data for charts from OpenAI...");
String prompt = buildChartDataPrompt(aggregatedLearnings); String prompt = buildChartDataPrompt(aggregatedLearnings);
return callChatCompletions(prompt); return callChatCompletions("Chart Data Extraction", prompt);
} }
public Mono<String> getChartSvg(String jsonData) { public Mono<String> 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); String prompt = buildSvgPrompt(jsonData);
return callChatCompletions(prompt); return callChatCompletions("SVG Generation", prompt);
} }
private Mono<String> callChatCompletions(String prompt) { private Mono<String> callChatCompletions(String taskName, String prompt) {
Map<String, Object> body = new HashMap<>(); Map<String, Object> body = new HashMap<>();
body.put("model", modelName); body.put("model", modelName);
body.put("temperature", 0); body.put("temperature", 0);
body.put("messages", List.of( body.put("messages", List.of(
Map.of("role", "user", "content", prompt))); 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() return this.webClient.post()
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON)
@@ -61,21 +85,51 @@ public class OpenAiChartService {
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() { .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}) })
.timeout(Duration.ofMillis(timeoutMs)) .timeout(Duration.ofMillis(timeoutMs))
.doOnSuccess(response -> {
// 4. Логируем успешный, но еще не обработанный ответ
logger.info("Raw OpenAI response for task '{}': {}", taskName, response);
})
.map(resp -> { .map(resp -> {
try { try {
List<Map<String, Object>> choices = (List<Map<String, Object>>) resp.get("choices"); List<Map<String, Object>> choices = (List<Map<String, Object>>) 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; return null;
Map<String, Object> choice0 = choices.get(0); }
Map<String, Object> message = (Map<String, Object>) choice0.get("message"); Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
if (message == null) if (message == null) {
logger.warn("OpenAI choice for task '{}' contained no 'message'.", taskName);
return null; return null;
}
Object content = message.get("content"); 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) { } catch (Exception e) {
// 6. Логируем ошибку парсинга, если структура ответа неожиданная
logger.error("Failed to parse OpenAI response for task '{}'. Response body: {}", taskName, resp,
e);
return null; 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) { private String buildChartDataPrompt(String learningsAsString) {
@@ -87,4 +141,4 @@ public class OpenAiChartService {
return "Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. SVG должен быть стильным и читаемым, с подписями на русском языке. Не добавляй никаких комментариев, верни ТОЛЬКО SVG-код. JSON с данными:\n---\n" return "Ты — эксперт по визуализации данных. На основе следующего JSON, сгенерируй полный и валидный SVG-код для диаграммы. SVG должен быть стильным и читаемым, с подписями на русском языке. Не добавляй никаких комментариев, верни ТОЛЬКО SVG-код. JSON с данными:\n---\n"
+ json + "\n---"; + json + "\n---";
} }
} }