### **Техническое Задание: Внедрение гибридной AI-модели (OpenAI для диаграмм, Ollama для текста)**

#### **1. Общее описание**

Целью является доработка AI-агента для использования гибридной модели генерации. Основной синтез текстового отчёта по-прежнему будет выполняться с помощью локального сервиса **Ollama**. Для опциональной и более сложной задачи — извлечения структурированных данных и генерации диаграмм — будет использоваться API **OpenAI**.

Этот подход позволяет использовать сильные стороны каждой модели:

- **Ollama:** Экономичная и быстрая генерация основного текста отчёта.
- **OpenAI:** Высокая точность в следовании инструкциям для генерации идеально отформатированного JSON и SVG-кода для диаграмм.

#### **2. Обновлённая схема работы**

1.  **Сбор данных:** Бэкенд получает `learnings` от `deep-research` API.
2.  **Параллельный запуск двух AI-задач:**
    - **Ветка A (Текст -\> Ollama):** `learnings` отправляются в ваш существующий `OllamaAnalyticsService` для синтеза основного текста отчёта в Markdown.
    - **Ветка B (Диаграммы -\> OpenAI):** `learnings` отправляются в **новый сервис** (`OpenAiChartService`), который делает два последовательных вызова к **API OpenAI**:
      1.  Извлечь из текста данные для диаграмм в формате **JSON**.
      2.  Превратить этот JSON в **SVG-код** диаграммы.
3.  **Конвертация SVG:** SVG-код, полученный от OpenAI, конвертируется в PNG-изображение на стороне Java.
4.  **Финальная сборка PDF:** `ResearchPdfService` объединяет текст отчёта (от Ollama) и изображения диаграмм (от OpenAI) в единый PDF-файл.

#### **3. Требования к реализации**

**3.1. Конфигурация**

В ваш файл `application.properties` (или `application.yml`) необходимо добавить API-ключ для OpenAI:

```properties
# application.properties

# ... ваши существующие настройки Ollama ...
ollama.model=llama3:8b-instruct

# --- НОВЫЕ НАСТРОЙКИ ДЛЯ OPENAI ---
openai.api.key=sk-...(ваш API ключ от OpenAI)...
openai.api.url=https://api.openai.com/v1/chat/completions
openai.model.name=gpt-4-turbo # Рекомендуемая модель для работы с JSON и SVG
```

**3.2. Создание нового сервиса: `OpenAiChartService`**

Необходимо создать новый Spring-сервис, отвечающий за взаимодействие с OpenAI.

- **`WebClient`:** Сервис должен содержать собственный `WebClient`, настроенный на `openai.api.url` и автоматически добавляющий заголовок `Authorization: Bearer ${openai.api.key}` ко всем запросам.
- **Методы:**
  1.  `public Mono<String> getChartDataJson(List<String> learnings)`: Принимает `learnings`, использует **Промпт №1** (для извлечения данных) и возвращает `Mono` со строкой, содержащей JSON-массив.
  2.  `public Mono<String> getChartSvg(String jsonData)`: Принимает JSON с данными для одной диаграммы, использует **Промпт №2** (для генерации SVG) и возвращает `Mono` со строкой SVG-кода.

**3.3. Модификация оркестратора: `ReportSynthesisService`**

Этот сервис теперь будет управлять вызовами к обоим AI-сервисам.

- **Зависимости:** `ReportSynthesisService` теперь должен инжектировать (`@Autowired`) и `OllamaAnalyticsService`, и новый `OpenAiChartService`.
- **Метод `synthesizeReportAndCharts`:**
  - **Ветка текста (`textMono`)** по-прежнему вызывает `ollamaAnalyticsService` для генерации отчёта.
  - **Ветка диаграмм (`chartsMono`)** теперь будет вызывать `openAiChartService`.

**Пример обновлённой логики для `chartsMono`:**

```java
// ... внутри ReportSynthesisService ...

// Ветка B: Генерация диаграмм через OpenAI
Mono<List<byte[]>> chartsMono = openAiChartService.getChartDataJson(aggregatedLearnings)
    .flatMap(jsonArrayString -> {
        String cleanJsonArray = extractJsonArray(jsonArrayString);
        if (cleanJsonArray == null || cleanJsonArray.equals("[]")) {
            return Mono.just(Collections.emptyList()); // Если данных нет, возвращаем пустой список
        }

        try {
            List<ChartData> chartDataList = objectMapper.readValue(cleanJsonArray, new TypeReference<List<ChartData>>() {});

            // Превращаем список задач в поток (Flux) и выполняем их последовательно
            return Flux.fromIterable(chartDataList)
                .concatMap(chartData ->
                    openAiChartService.getChartSvg(objectMapper.writeValueAsString(chartData))
                )
                .map(this::extractSvgCode)
                .map(SvgToPngConverter::convert)
                .filter(Objects::nonNull)
                .collectList();

        } catch (IOException e) {
            return Mono.error(e);
        }
    });
```

**3.4. Промпты для OpenAI**

Промпты остаются теми же, что и в предыдущем ТЗ, так как модели OpenAI отлично их понимают.

- **Промпт №1 (Извлечение данных):** Просит вернуть **JSON-массив** объектов с данными для диаграмм или пустой массив `[]`, если данных нет.
- **Промпт №2 (Генерация SVG):** Просит на основе одного JSON-объекта вернуть **только SVG-код**.

#### **4. План действий**

1.  **Добавить `openai.api.key`** в ваш `application.properties`.
2.  **Создать новый класс `OpenAiChartService.java`**. Он будет похож на `OllamaAnalyticsService`, но настроен для работы с API OpenAI (другой URL, заголовок авторизации, другая структура JSON-запроса).
3.  **Обновить `ReportSynthesisService.java`**, чтобы он использовал `OllamaAnalyticsService` для текста и `OpenAiChartService` для диаграмм, как показано в примере выше.
4.  **Перезапустить** ваше Java-приложение.
