Java-інтеграція AI-агента: як побудувати фічу, яку захоче бізнес і зрозуміє розробник

💡 Усі статті, обговорення, новини про AI — в одному місці. Приєднуйтесь до AI спільноти!

Привіт! Мене звати Поліна Сергієнко, я Senior Java Software Engineer у Levi9. У цій статті ділюся досвідом інтеграції AI у Java-продакшн — без PhD і тотальної перебудови системи.

Якщо ви Java-розробник, то, швидше за все, вже чули фразу «а давайте зробимо AI-фічу». Наче звучить просто, але на практиці — немає ML-досвіду, код старий, а система працює в продакшні, тому страшно чіпати. І все ж інтегрувати AI реально — навіть без PhD і переписування всього з нуля. Найкраще зрозуміти потенціал AI можна на реальних прикладах — коли він не просто «додає фічу», а вирішує щоденні проблеми.

Наприклад, онлайн-магазин кілька років тому це такий флоу: клієнт питає про доставку, оператор вручну шукає відповідь, черга росте, всі нервують. Сьогодні AI-чатбот самостійно може розрулити 70–80% звернень. Натомість люди займаються тим, що справді потребує їхньої участі. Це дає бізнесу очевидні переваги: 24/7 доступність, оптимізація витрат і зростання задоволеності користувачів.

У цей момент якась бізнес-команда каже розробникам: «А давайте зробимо AI-фічу». Просто ж? Тільки у реальності ситуація така:

  • ML-досвіду немає;
  • команда — Java-розробники, а не Data Science-гуру;
  • система живе в продакшні роками й працює стабільно (тому страшно чіпати);
  • «прикрутити» нову технологію абияк не вийде — доведеться інтегрувати.

І ось тут гарна новина — власну модель будувати не доведеться. Є безліч готових інструментів — LLM API та open-source рішень. І так, ними реально користуватися без спеціальних знань.

Java-екосистема дозволяє інтегрувати AI в enterprise-проєкти без тотальних перебудов. Найзручніше винести AI-функціонал в окремий шар чи сервіс. Тоді:

  • основна бізнес-логіка залишається недоторканною (що працює — краще не чіпати);
  • AI-частина ізольована й зрозуміла;
  • можна додавати й покращувати фічі поступово, без ризику «зламати все».

AI у системі? Цілком реально.

Постановка задачі

Онлайн-магазин захотів інтегрувати AI у свій застосунок. Бізнес-очікування виглядали дуже прямолінійно: менеджери повинні мати змогу вводити звичайні фрази на кшталт «Покажи товари зі знижкою в категорії електроніки» — і отримувати релевантну вибірку продуктів.

При цьому одразу з’явилися обмеження:

  • жодних SQL-заклять для менеджерів;
  • мінімум витрат на імплементацію: без епопейних редизайнів архітектури та без залучення цілої ML-команди.

Існуюча логіка

Система працювала на Java (Spring Boot + PostgreSQL) і вже мала REST-ендпоїнт для пошуку, який виконував запити до БД. Проблема в тому, що цей ендпоїнт був занадто вузьким:

  • пошук — тільки в одній категорії;
  • параметри — тільки чіткі значення;
  • жодних «більше/менше», часткових збігів чи складних умов.

Іншими словами, він працював чітко, але без жодної гнучкості.

При цьому ендпоїнт повертав структурований респонс, який уже споживав фронтенд. І тут була принципова вимога: навіть якщо дані будуть тягнутися через AI, формат відповіді має залишитися тим самим. Це гарантує стабільність клієнтської частини та зворотну сумісність.

Схема БД:

Ендпоїнт: POST /products.

Request body:

{
  "category": "8cacd6cf-37db-4b75-9219-88808ff59e0e",
  "params": [
    {
      "id": "c2a19482-d216-4e6a-abd2-e2443c4f8cfc",
      "values": [
        "12",
        "24",
        "36"
      ]
    },
    {
      "id": "585489b8-f64a-439e-9e5f-2c51fabe8355",
      "values": [
        "сірий"
      ]
    }
  ]
}

Response body:

[
  {
    "id": "ddddddd1-dddd-dddd-dddd-dddddddddddd",
    "name": "Smartphone",
    "categoryId": "11111111-1111-1111-1111-111111111111",
    "price": 699.99,
    "availableAmount": 100,
    "createdAt": "2025-06-22T16:58:51.042661Z",
    "updatedAt": "2025-06-22T16:58:51.042661Z"
  }
]

Рішення

Під час брейншторму з’явилися дві основні ідеї, як реалізувати AI-функціональність:

1. Генерація SQL-запиту за допомогою AI

Суть підходу: передавати текстовий запит користувача в AI-модель і отримувати у відповідь готовий SQL-запит, який можна виконати безпосередньо на базі даних.

Плюси: максимальна гнучкість — можна побудувати довільні запити з умовами, фільтрами, діапазонами тощо.

2. Генерація DTO-об’єкта з тексту

Інший варіант — навчити AI одразу формувати готовий DTO на основі текстового опису користувача. Плюси: існуюча бізнес-логіка лишається недоторканною, а користувачі отримують свободу у формуванні запитів природною мовою.

Загальна архітектура. Для реалізації ми обрали хмарну LLM-модель від OpenAI. Вона вже добре працює з природною мовою і здатна повертати структуровані відповіді, що робить її оптимальним варіантом для нашого сценарію.

Загальна схема виглядає наступним чином:

Першим кроком для обох випадків було створення AI-агента з системним запитом (system prompt), який формулює контекст і правила поведінки моделі. Ми описали, яку саме задачу має виконувати агент, які дані він повинен аналізувати, і що має повертати у відповідь.

Цей промпт формувався ітеративно. Після кожної серії тестів ми помічали, які інструкції модель інтерпретує неоднозначно або де виникають помилки, і поступово уточнювали та розширювали інструкції. Таким чином з кожною ітерацією агент краще «розумів» нашу систему і запити користувачів.

Наступним спільним кроком було додавання тулів. Розглянемо в деталях роботу над двома варіантами нижче.

Генерація SQL-запиту за допомогою AI

Ми вирішили не описувати вручну структуру бази даних у промпті. Натомість дали агенту можливість самостійно звертатися до БД і отримувати потрібну інформацію про структуру.

Для цього реалізували набір спеціалізованих тулів, які роблять службові запити до PostgreSQL:

  • Tool для отримання списку всіх таблиць у схемі.
  • Tool для отримання списку полів конкретної таблиці.

Такий підхід дав кілька переваг:

  • завжди актуальна інформація про структуру БД (жодних «застарілих промптів» після змін у схемі);
  • агент не перевантажений зайвим контекстом — додаткові дані підтягуються лише тоді, коли вони реально потрібні;
  • у system message ми залишили тільки справді неочевидні деталі про структуру даних.

Приклади промптів і коду можна взяти тут:
github.com/...​linaucc/ai-sql-generation

Проблеми на етапі тестування

Одне з перших питань звучало просто: чи варто щоразу ходити в базу, щоб підтягнути структуру таблиць? Відповідь швидко знайшлася: ні. Схема змінюється рідко, а навантаження зростає. Тому ми додали кешування службових запитів — і одразу розвантажили БД.

Далі з’ясувалося, що модель іноді «економить кроки» і будує SQL напряму. У результаті з’являлися вигадані поля або синтаксичні помилки. Вирішення тут одне: чітко прописати правила, коли саме слід звертатися до допоміжних інструментів і що саме вони мають повертати.

Ще один момент стосувався формату відповіді. Запит міг виглядати правильним, але не відповідати нашому DTO. Щоб цього уникнути, ми зробили окремий механізм, який підказує обов’язковий набір полів для кожної сутності.

На рівні агента довелося відмовитися від автоматичного режиму: він викликав усе підряд, що тільки було в контексті. Ми перейшли на EXPLICIT, де самі визначаємо, які інструменти доступні, яка саме модель використовується і чи є пам’ять. Це дало контроль і прибрало хаос.

І навіть після цього траплялися сюрпризи: іноді AI генерував SQL, який PostgreSQL відкидав ще на етапі парсингу. Щоб обійтись без ручних виправлень, ми додали два механізми: пам’ять для збереження контексту та retry-логіку, яка дає моделі шанс спробувати ще раз з урахуванням конкретної помилки.

Приклади:

User input: Please show me all products with weight between 50 and 70 kg​.

Починали ми з максимально простих задач. Просили показати всі продукти вагою від 50 до 70 кг, і загалом кверя згенерувалася робоча. АІ зрозумів найскладнішу частину в базі — це зберігання значень характеристики (у нас це varchar-колонка, яка може містити як цифри «20-50», так і текст типу «сірий»). Тут є певні недоліки, можна було б забрати зайві параметри в квері, але загалом результат непоганий.

User input: I want to receive list of Electronics products with weight between 0.1 and 0.4 tons.

Складність тут полягає в тому, що вага в БД зберігається в кілограмах. Нам важливо було виконати переведення з одиниць виміру користувача до наших.

І у нас це вийшло за допомогою додавання ще одного тула, який ходить в БД і просить одиницю виміру за назвою характеристики. Єдиний нюанс — тули мають завжди щось повертати, бо NULL вважається не валідним значенням.

User input: I want to receive list of Electronics products with voltage suitable for US and power 60W​.

Наступний запит ми трохи ускладнили: попросили згенерувати список продуктів з вольтажем, що відповідає стандартам у США. У нашій базі даних інформації про вольтаж у різних країнах не було, але ШІ самостійно знайшов потрібні значення й додав їх у підсумковий запит.

Саме такого типу завдання виглядають найбільш доречними для використання ШІ.

User input: Please show me all books where author is a woman.

В результаті АІ зробила кверю з пошуком за параметром author gender, якого не було в базі.

Інсайти: генерація SQL-запитів

  1. Якість промптів та описів інструментів критично важлива. Що зрозуміліше вони сформульовані, то менше шансів, що модель «забуде» викликати потрібний інструмент.
  2. Виклики треба логувати. Найпростіше — увімкнути debug-логи в LangChain, і тоді буде видно, коли й з якими параметрами викликався кожен інструмент. Це дуже полегшує аналіз.
  3. Результати роботи інструментів можуть зберігатися в пам’яті й не викликатися повторно. Але є й зворотний випадок: якщо пам’яті замало, а інструментів треба кілька, агент може «зациклитися» і викликати їх по колу без фінального результату.
  4. Важливо пам’ятати: однакові запити не завжди повертають однакові результати. Тут допомагає параметр temperature, який визначає рівень «творчості» моделі. Низьке значення робить відповіді більш передбачуваними — що зручно для генерації коду чи SQL. Високе — додає імпровізації, але важче отримати повторюваний результат, наприклад, після retry.
  5. Усі згенеровані SQL-запити треба перевіряти. Найбезпечніше запускати їх на read-репліці чи спеціально підготовленій базі з обмеженим пулом даних. Ще один варіант — робити санітизацію.

Інсайти: генерація DTO

Для цього підходу ми зробили кілька допоміжних інструментів:

  • два прості — щоб із назви категорії чи параметра отримати відповідний ID (бо наш ендпоїнт працює з ID, а користувачі у тексті їх не вказують);
  • ще один — для формування структури об’єкта, який потрібен у відповіді.

Приклади запитів і згенерованих об’єктів ми супроводжували коментарями.

Що помітили під час роботи:

  1. Варіант виявився більш передбачуваним, бо базувався на нашій існуючій бізнес-логіці.
  2. Помилок для користувача значно менше. Були кейси, коли DTO згенерувався некоректно, але юзер просто не отримував нічого. Це можна сприймати і як мінус, і як плюс.
  3. Ми були впевнені, що виконуються лише SELECT-операції, отже, не потрібно додатково «чистити» запити.
  4. Оцінювати відповіді моделі в цьому випадку було набагато простіше.
  5. Недолік — менша гнучкість. Можна отримати тільки те, що вже передбачено нашою бізнес-логікою.

Порівняня обох варіантів, що можна використати:

На нашу думку, у продакшн-рішеннях із реальними користувачами безпечніше й стабільніше працювати з генерацією DTO. До того ж він дозволяє контролювати перформанс, адже запускаються перевірені нами запити.

Генерацію SQL доцільно використовувати для внутрішніх потреб, коли важлива максимальна гнучкість, а ризики прийнятні.

Що варто пам’ятати

AI — це потужний сучасний інструмент, який можна відносно швидко інтегрувати в існуючі системи, значно розширюючи вже написану логіку. Водночас важливо пам’ятати про неповну передбачуваність результатів, питання безпеки та вартість інтеграції, яка може зростати залежно від інтенсивності використання.

Сподобалась стаття авторки? Підписуйтесь на її акаунт вгорі сторінки, щоб отримувати сповіщення про нові публікації на пошту.

👍ПодобаєтьсяСподобалось8
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Ребята молодцы, пощупали LLM, прикрутили к существующему продукту, наверняка всем понравилось обсуждать разные AI-приколяхи в курилке. Но этож какой-то франкенштейн получился?! Тут работаю, а тут — нет.
Может я не прав, но это со стороны выглядит как смесь ежа и ужа, именно в контексте подхода к реализации, такая себе большая курсовая работа. Может вам всеже стоило нанять одного узкопрофильного специалиста, который понимает как это правильно посторить.

Цікаво, які ще варіанти були, окрім мейнстірімної OpenAI? HugginFace начебто ще даними постачає деякі моделі.
Працювати з лише доступними промтами це плюс, дяка за думку

Водночас важливо пам’ятати про неповну передбачуваність результатів

На жаль, таким чином ви слона не продасте... Але було цікаво, дякую

Які LLM використовували для генерації SQL?

Те що ви зробили це велика проблема для безпеки, бо prompt injection ще ніхто не відміняв. Що буде якщо ллм сгенерує щось тупу DROP TABLE або DELETE, або отримає доступ до данних другого тенанта або юзера?

Зробить для драйвера через котрий ходить ллм окремого read only юзера.

Ще можно парсити SQL який сгенерував агент, та блочити коли там є доступ до несанкціонованих даних або DML стейтменти.

Добре підмітили. Та думаю що використати конкешен з іншим юзером що має урізані права не має бути складно. Тут найбільш веселе це усілякі injection з хитровимаханими read квері 🙂

Тре парсити кверю, та чекати, щоб userID/tenantID був того ж юзера щонайменьше. А ще тiльки select дозовляти, та ще з LIMIT 100 чи щось таке, щоб не було ддос. А якщо щось таке пiдозрiле є, то reject’ати.

Дякую за статтю. А ви дивились у сторону якогось MCP сервера для Postgres щоб не треба було писати щось кастомне ? Умовно MCP дати права на read schema + read data і бізнес юзерам дати готову юайку з чатом ?

Я не тестував контретно цей MCP, але можете спробувати ось цей від Амазона
github.com/...​n/src/postgres-mcp-server

Підписатись на коментарі