Проект: Доработка модуля генерации отчётов в существующем Java/Spring Boot бэкенде. 1. Общее описание Целью является создание "Агента-синтезатора" внутри бэкенда. Этот агент будет использовать существующий сервис OllamaAnalyticsService для взаимодействия с локально развёрнутой языковой моделью Ollama. Задача агента — принять "сырые" тезисы (learnings) от сервиса deep-research и с помощью Ollama сгенерировать из них связный, структурированный и стилистически выверенный текстовый отчёт в формате Markdown. 2. Требования к реализации 2.1. Создание нового сервиса: ReportSynthesisService Необходимо создать новый Spring-сервис (@Service). Этот сервис будет зависеть от уже существующего OllamaAnalyticsService и должен инжектировать его (@Autowired). Сервис должен иметь один публичный метод: Java public Mono synthesizeReport(String originalQuery, List learnings, String lang) Вход: originalQuery: Исходная тема исследования. learnings: Список "сырых" тезисов от deep-research. lang: Целевой язык отчёта. Выход: Mono, содержащий готовый отчёт в формате Markdown. 2.2. Логика работы ReportSynthesisService Метод synthesizeReport получает на вход данные. Он объединяет список learnings в единый блок текста, где каждый тезис начинается с новой строки (например, с помощью String.join("\n- ", learnings)). Он формирует промпт (инструкцию) для модели Ollama (см. п. 2.3). Ключевой шаг: Он вызывает метод generateWithInstruction из вашего существующего OllamaAnalyticsService: Java // Преобразуем асинхронный вызов в Mono Mono 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 для синтеза текста.