Ви хочете спланувати подорож до Гоа. У вас є п'ять днів, бюджет у 25 000 рупій і чіткі вподобання щодо пляжів та морепродуктів. Зазвичай це означає відкриття десяти вкладок у браузері, читання застарілих постів на форумах і ручне складання маршруту. Замість цього уявіть, що ви надсилаєте один POST-запит і отримуєте структурований поденний план із пропозиціями щодо харчування, списками активностей та точним розподілом бюджету. Саме це забезпечує цей проєкт.
Ми побудуємо REST API за допомогою Spring Boot та Azure OpenAI. API приймає місце призначення, бюджет, тривалість та інтереси. Він повертає чистий JSON, який фронтенд або мобільний додаток може відобразити миттєво. Жодного скрейпінгу, жодних жорстко закодованих маршрутів. Тільки модель ШІ, якій дали інструкцію діяти як планувальник подорожей.
Що повертає API
Відповідь — це не блок тексту Markdown, який вам доведеться розбирати за допомогою регулярних виразів. Це структурований JSON-об'єкт, що містить щоденні заходи, рекомендації щодо харчування та розподіл бюджету. Для поїздки в Гоа ви можете отримати сегмент для першого дня, який виділяє 500 рупій на сніданок у пляжній закусочці, ранок у Палолемі та вечірню вечерю з морепродуктами в певній місцевості. Кожен день містить часові проміжки, орієнтовну вартість і теги, такі як "beach" або "food". Така структура має значення, тому що сучасні туристичні додатки не хочуть парсити абзаци. Їм потрібні об'єкти, які вони можуть відобразити у RecyclerView або компонентах React.
Стек технологій і чому він підходить
Проєкт використовує Spring Boot 3.5 із Spring AI. Spring AI — це критично важливий елемент. Він надає уніфіковану абстракцію ChatModel, тому вам не потрібно писати сирі HTTP-клієнти для Azure OpenAI. Ви змінюєте залежності та властивості, а не код сервісу.
Вам знадобляться чотири залежності у вашому файлі збірки:
spring-boot-starter-webдля рівня REST.spring-ai-starter-model-azure-openaiдля підключення до LLM через інтерфейс Spring AI.springdoc-openapiдля автоматичної документації Swagger.Lombokдля зменшення кількості шаблонного коду у ваших POJO для запитів та відповідей.
Spring AI знаходиться між вашою бізнес-логікою та провайдером LLM. Таке розташування є навмисним. Воно дозволяє вашим класам @Service залишатися чистими та незалежними від конкретного провайдера.
Промпт-інжиніринг за допомогою PromptTemplates
Жорстке кодування промптів у рядках Java — це швидкий шлях до створення непідтримуваного програмного забезпечення. Якщо команда продукту вирішить, що ШІ має звучати більш невимушено або відмовлятися від оцінки бюджету вище певного порогу, вам не доведеться перекомпілювати свій сервіс.
Spring AI надає PromptTemplate. Ви зберігаєте скелет промпту у файлі ресурсів або в окремому рядку шаблону, залишаючи плейсхолдери для таких змінних, як {destination}, {budget}, {days} та {interests}. Під час виконання сервіс створює об'єкт Prompt і вставляє значення користувача.
Розділяйте системні повідомлення та повідомлення користувача. Використовуйте системне повідомлення для визначення ролі (persona). Наприклад, ви кажете моделі, що вона є планувальником подорожей, що спеціалізується на індійських напрямках, дбає про бюджет і суворо дотримується повернення лише JSON без маркдаун-оформлення. Використовуйте повідомлення користувача для передачі конкретних деталей поїздки. Такий поділ допоможе, коли ви захочете провести A/B тестування різних ролей, не змінюючи контракт API.
Сервісний шар: взаємодія з Azure OpenAI
Клас @Service має одне завдання. Він будує промпт, викликає модель, очищує відповідь і парсить результат.
Вставте (inject) ChatClient або ChatModel із Spring AI. Відрендеріть PromptTemplate із вхідними значеннями запиту, а потім викличте метод чату. Відповідь приходить як String. Саме тут багато туторіалів закінчуються, а починається реальний продакшн-код.
LLM іноді додають ввічливі преамбули. Ви можете отримати відповідь, яка починається з "Ось ваш маршрут", а потім видає JSON, загорнутий у потрійні зворотні лапки. Якщо ви спробуєте десеріалізувати це безпосередньо за допомогою Jackson, ваш додаток впаде. Додайте невеликий допоміжний метод, який сканує сирий рядок, знаходить першу відкриваючу фігурну дужку та останню закриваючу дужку і витягує лише JSON-корисне навантаження. Потім перевірте витягнутий блок. Переконайтеся, що обов'язкові поля існують і що числові значення мають сенс, перш ніж повертати об'єкт у контролер.
Цей захисний парсинг не є опціональним. Це межа між демо-версією та надійним API.
Обробка помилок як у зрілій системі
Зовнішні API можуть давати збої. Azure OpenAI повертатиме помилки ліміту запитів (rate limit), помилки автентифікації або тимчасові помилки 500. Якщо ви дозволите цим помилкам вилітати користувачеві у вигляді стек-трейсів, ви втратите довіру.
Використовуйте @RestControllerAdvice, щоб перехоплювати винятки глобально. Співставте винятки Spring AI, HttpClientErrorException та загальні RuntimeException із послідовними відповідями на помилки. Повертайте JSON-тіло з чітким повідомленням, HTTP-статусом (наприклад, 429 для обмеження частоти запитів) та достатньою кількістю деталей, щоб клієнт міг повторити запит або залогувати проблему. Користувач має бачити щось на кшталт "Service temporarily busy. Please retry in 30 seconds", а не екран, заповнений назвами Java-класів.
Ніколи не зашивайте секрети в код
Ваш ключ Azure OpenAI API не повинен бути в application.properties, який ви перевіряєте в Git. Винесіть його назовні. Використовуйте змінні середовища, на які посилається ваша конфігурація Spring, такі як ${AZURE_OPENAI_KEY} та ${AZURE_OPENAI_ENDPOINT}. Тримайте локальний файл .env для розробки, додайте його до .gitignore і завантажуйте через механізм relaxed binding у Spring Boot. Якщо ключ буде викрадено, ви зможете змінити його в одному місці, а не перезбирати весь артефакт.
Тестування через Swagger
Залежність springdoc-openapi відкриває endpoint Swagger UI під час виконання. Після запуску вашого додатка відкрийте /swagger-ui.html у браузері. Ви можете заповнити приклад для Goa безпосередньо: destination — "Goa", budget — 25000, days — 5, interests — "beaches, food". Натисніть execute і спостерігайте, як з'являється JSON-маршрут. Це дозволяє вам перевіряти зміни в промптах, валідувати серіалізацію та ділитися живим майданчиком для тестування з фронтенд-розробниками ще до того, як хтось із них напише юніт-тест.
Зміна провайдерів без переписування коду
Стартапи змінюють провайдерів. Можливо, закінчаться кредити Azure, або ви захочете запустити інференс на локальному екземплярі Ollama, щоб зменшити витрати. Оскільки Spring AI абстрагує інтерфейс ChatModel, ця заміна є механічною. Змініть Maven-залежність з spring-ai-starter-model-azure-openai на інший starter, оновіть файл властивостей новим endpoint та ключем, і не чіпайте свій сервісний клас. API-контракт, який бачить ваш мобільний додаток, залишиться ідентичним.
Така портативність робить цю архітектуру особливо корисною для реальних продуктів. Ви не прив'язуєте себе до Azure назавжди. Ви використовуєте його як один із двигунів, підключених до чистого Spring-конвеєра.
Головний висновок
AI-модель — це не ваш додаток. Це зовнішній сервіс, який повертає непередбачуваний текст. Ставтеся до неї з тією ж суворістю, з якою ви ставилися б до платіжного шлюзу або стороннього API погоди. Виносьте свої облікові дані назовні. Валідуйте кожну відповідь. Очищуйте корисне навантаження (payload) перед парсингом. Обробляйте помилки глобально, щоб ваші користувачі ніколи не бачили stack trace.
Нехай AI бере на себе творчу роботу зі створення маршруту по Гоа з бюджетом 25 000 рупій. Ви займайтеся інфраструктурою. Коли ці дві частини залишаються розділеними, ви отримуєте систему, яка дійсно працює.
Оригінальний посібник, що надихнув на написання цієї статті, можна знайти тут.
Цікавить обговорення Spring AI та подібних проєктів? Приєднуйтесь до навчальної спільноти GyaanSetu.
