Проект: Доработка модуля генерации отчётов в существующем Java/Spring Boot бэкенде.

1. Общее описание
   Целью является создание "Агента-синтезатора" внутри бэкенда. Этот агент будет использовать существующий сервис OllamaAnalyticsService для взаимодействия с локально развёрнутой языковой моделью Ollama. Задача агента — принять "сырые" тезисы (learnings) от сервиса deep-research и с помощью Ollama сгенерировать из них связный, структурированный и стилистически выверенный текстовый отчёт в формате Markdown.

2. Требования к реализации
   2.1. Создание нового сервиса: ReportSynthesisService

Необходимо создать новый Spring-сервис (@Service).

Этот сервис будет зависеть от уже существующего OllamaAnalyticsService и должен инжектировать его (@Autowired).

Сервис должен иметь один публичный метод:

Java

public Mono<String> synthesizeReport(String originalQuery, List<String> learnings, String lang)
Вход:

originalQuery: Исходная тема исследования.

learnings: Список "сырых" тезисов от deep-research.

lang: Целевой язык отчёта.

Выход: Mono<String>, содержащий готовый отчёт в формате Markdown.

2.2. Логика работы ReportSynthesisService

Метод synthesizeReport получает на вход данные.

Он объединяет список learnings в единый блок текста, где каждый тезис начинается с новой строки (например, с помощью String.join("\n- ", learnings)).

Он формирует промпт (инструкцию) для модели Ollama (см. п. 2.3).

Ключевой шаг: Он вызывает метод generateWithInstruction из вашего существующего OllamaAnalyticsService:

Java

// Преобразуем асинхронный вызов в Mono
Mono<String> synthesizedReportMono = Mono.fromCallable(() ->
ollamaAnalyticsService.generateWithInstruction(aggregatedLearnings, prompt)
);
Метод возвращает synthesizedReportMono.

2.3. Разработка промпта для Ollama

Промпт — это сердце качественного результата. Он должен быть чётким и структурированным. Этот промпт будет реализован как форматированная строка внутри ReportSynthesisService.

Пример промпта для Ollama (модели типа Llama3, Gemma):

Ты — профессиональный AI-аналитик. Твоя задача — написать подробный и структурированный отчёт на основе предоставленных данных. Не добавляй никаких предисловий или комментариев от себя, просто верни готовый отчёт.

### Тема исследования:

${originalQuery}

### Собранные данные (тезисы):

- ${learnings_as_string}

### Твоя задача:

1.  **Язык:** Напиши отчёт полностью на ${lang} языке.
2.  **Структура:** Отчёт должен иметь чёткую структуру: введение, основная часть с несколькими разделами и подразделами, и заключение.
3.  **Форматирование:** Используй Markdown. Для главных заголовков разделов используй `##`, для подзаголовков — `###`. Для списков используй `-`.
4.  **Стиль:** Преврати разрозненные тезисы в единую, связную и хорошо написанную статью. Не просто перечисляй факты, а анализируй и обобщай их.
5.  **Ограничение:** Используй только ту информацию, которая дана в собранных данных. Не придумывай ничего от себя.

Начинай отчёт с главного заголовка.
2.4. Интеграция в существующий асинхронный процесс

Необходимо модифицировать ваш существующий метод в контроллере, который запускает весь процесс. Вместо вызова OpenAI, теперь будет вызываться ваш новый ReportSynthesisService.

Примерная схема интеграции (на основе вашего кода):

Java

// ...
// Инжектируем новый сервис
@Autowired
private ReportSynthesisService reportSynthesisService;

// ... в вашем методе generateResearchReport
deepResearchService.generateReportAsync(deepRequest)
.publishOn(Schedulers.boundedElastic())
.flatMap(researchResponse -> { // Используем flatMap для асинхронной цепочки
if (researchResponse == null || researchResponse.getLearnings() == null || researchResponse.getLearnings().isEmpty()) {
return Mono.error(new RuntimeException("Empty research response content"));
}
// НОВЫЙ ШАГ: Вызываем наш сервис синтеза с Ollama
return reportSynthesisService.synthesizeReport(
request.getQuery(),
researchResponse.getLearnings(),
request.getLang()
).map(synthesizedText -> new SynthesizedReport(researchResponse, synthesizedText)); // Объединяем результаты
})
.map(synthesizedReport -> {
// Здесь мы используем synthesizedText для генерации PDF
// Этот код остаётся почти без изменений
byte[] pdfBytes = researchPdfService.generatePdfReport(
request.getQuery(),
synthesizedReport.getOriginalResponse(),
synthesizedReport.getSynthesizedMarkdown() // Передаем новый красивый текст от Ollama
);
String filename = "research*report*" + System.currentTimeMillis() + ".pdf";
saveResearchReportToHistory(request, filename, pdfBytes, synthesizedReport.getOriginalResponse());
return true;
})
.doOnError(e -> {
System.err.println("Error in full research chain: " + e.getMessage());
})
.subscribe();
// ...
Вам также понадобится немного изменить researchPdfService.generatePdfReport, чтобы он принимал новый синтезированный Markdown-текст в качестве основного контента.

3. Конфигурация
   Вся конфигурация для Ollama уже есть в вашем OllamaAnalyticsService. Новых настроек добавлять не нужно.

Важная рекомендация: Убедитесь, что в вашем application.properties (или yml) для ollama.model указана достаточно мощная модель, способная следовать сложным инструкциям. Модель gemma3:1b слишком слабая для такой задачи. Рекомендуется использовать:

llama3:8b-instruct (хороший баланс)

gemma:7b-it

mistral или mixtral

Пример:

Properties

ollama.host=http://185.35.223.45:11434
ollama.model=llama3:8b-instruct
ollama.timeoutMs=180000 # Увеличьте таймаут до 3 минут, т.к. генерация отчёта - долгая задача 4. Ожидаемый результат
После реализации бэкенд будет использовать ваш собственный, локально развёрнутый Ollama для создания высококачественных, структурированных PDF-отчётов, полностью контролируя весь процесс и не завися от внешних платных API для синтеза текста.
