Spring AI в корпоративному середовищі: агент підтримки з інструментами, RAG та оглядом
Spring AI для асистента з триажу інцидентів: запити як контракти, структурований вихід, виклик інструментів, відкриття MCP, Qdrant RAG, відстеження та обмежені цикли агента.
Джерела: Leveraging Spring AI for building the next generation of enterprise applications
Заява на підтримку як тестовий випадок
Внутрішній майстер-клас з розробки ШІ для інженерів програмного забезпечення у компанії з фінансових технологій виник через ситуацію, з якою майже кожен розробник стикається рано чи пізно. Надходить заява на підтримку. Журнали подій містять лише часткові докази. Відповідний код знаходиться у внутрішньому репозиторії. Той, хто перебуває на чергуванні, потребує цілеспрямованого початкового аналізу, а не універсальної відповіді.
Spring AI ідеально підходить для таких завдань, оскільки він вводить функції ШІ в екосистему, у якій щодня працюють розробники Java та Spring. Він пропонує абстракції для моделей, запитів, структурованих результатів, виклику та отримання інструментів, які легко інтегруються в існуючу архітектуру додатків без значних труднощів.
Виклик LLM з Java-додатку — це не найцікавіша частина. Важливо робити це, дотримуючись принципів, яких очікують у сфері фінтеху: чіткі контракти, інструменти з мінімальними правами, можливість відстеження та людський огляд у всіх випадках, коли рішення має значення.
Тож сфера застосування тут виходить за межі семінару. Проєкт „Агент підтримки“ є постійним прикладом того, як під’єднати ШІ до існуючих систем, де має знаходитися модель у архітектурі, як вона надає можливості та доступ до даних корпорації, а також як навколишні системи забезпечують її корисну, прогнозовану та контрольовану роботу.
Проблема інтеграції, а не проблема моделі
Інтеграція ШІ в корпоративні системи — це переважно робота з інтеграцією, і вона майже не пов’язана з вибором моделі. Команди зазвичай усвідомлюють це після створення першого прототипу. Надсилання запиту до моделі є простим, а результати можуть здаватися вражаючими. Однак як тільки ШІ потрапляє в систему для експлуатації, з’являються базові проблеми.
Корпоративне програмне забезпечення будується на принципі детермінізму. Воно залежить від суворих умов, передбачуваної затримки та чітко визначеного потоку даних. Певний вхідний даний має забезпечувати послідовний вихідний результат, а поведінку можна простежити через код.
Системи ШІ функціонують інакше. Вони за своєю природою є ймовірнісними, часто приймають природну мову замість структурованих даних вхіду та спираються на зовнішніх постачальників. Один і той самий запит може призводити до різних відповідей, а частина логіки знаходиться всередині моделі, поза контролем системи.
Результатом є структурна неузгодженість: недетерміністичний компонент, розташований усередині детерміністичної архітектури. Звичні припущення щодо тестування, моніторингу та надійності більше не діють. Складність полягає не у створенні розумних відповідей, а у їх інтеграції в системи, які вимагають контролю.
Spring AI реагує на це, розглядаючи штучний інтелект як ще одну проблему інтеграції в екосистемі Spring, а не як особливий випадок. Spring вже абстрагує бази даних, системи обміну повідомленнями та запити REST, а Spring AI додає аналогічні абстракції для мовних моделей, сховищ векторів та робочих процесів, керованих штучним інтелектом. Метою є не повне приховування складності, а її організація так, щоб вона відповідала існуючим архітектурним патернам.
У типовій конфігурації штучний інтелект не знаходиться у окремому сервісі — він вбудований у вигляді коду інтеграції у рівень додатку.
Логіка проекту працює наступним чином. Надходить запит, і бізнес-запити відхиляються. Справжні інциденти отримують тикет. Інструмент Spring AI перевіряє журнали подій, а діагноз перетворюється на запис у форматі Java. Відповідний вихідний код завантажується з векторного сховища, підтримуваного Qdrant, генерується пропозиція щодо виправлення, і тикет оновлюється, щоб людина могла його переглянути.
Для розробника ШІ стає ще однією залежністю, а додаток зберігає контроль над процесом роботи. Моделі налаштовуються як будь-яка інша частина інфраструктури, а взаємодія відбувається через чітко визначені API, а не через спорадичні HTTP-запити.
У проекті SupportAgentController отримує вхідний запит у вигляді SupportTicketRequest. Він передає логіку обробки до SupportAnalysisService, зберігає стан через SupportTicketService, завантажує вихідний код за допомогою GithubRagService та зберігає створене рішення у тикеті.
Варто зазначити, що бізнес-логіка залишається незалежною від постачальника моделей. Зміна постачальників чи додавання альтернативних рішень не впливає на основний код. У довгостроковій перспективі це стосується скоріше збереження архітектурного контролю, ніж гнучкості. Якщо до моделі немає доступу, наведений нижче ендпоїнт все одно має створити тикет, позначити аналіз ШІ як недоступний та надіслати справу до черги для людини.
@PostMapping("/requests")
public ResponseEntity<?> submitSupportRequest(@Valid @RequestBody SupportTicketRequest request) {
boolean isBusiness = supportAnalysisService.isItBusinessRequest(request);
if (isBusiness) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.businessRequest());
}
SupportTicket ticket = ticketService.createTicket(request);
LogCauseReport logCauseReport = supportAnalysisService.analyzeLogs(request);
String classCode = ragService.findRelevantClass(null, logCauseReport.repoName(), null, logCauseReport.className()).stream()
.map(SupportAgentController::stripMetadataLines)
.filter(content -> !content.isBlank())
.collect(Collectors.joining(System.lineSeparator() + System.lineSeparator()));
String solution = supportAnalysisService.suggestFix(classCode, logCauseReport.causeOfProblem());
ticketService.updateTicket(ticket.getId(), SupportStatus.SUBMITTED,
"Cause of the problem: " + logCauseReport.causeOfProblem() + "\n\nSuggested fix: " + solution);
return ResponseEntity.status(HttpStatus.CREATED).body(SupportTicketResponse.from(ticket));
}
ChatClient та запити як контракти
Spring AI базується на API ChatClient — структурованому способі взаємодії з мовними моделями. Замість того, щоб вручну створювати HTTP-запити, розробники використовують зручний API, який об’єднує процеси формування запитів, їх виконання та обробки відповідей.
Перша спроба створення класифікатора заявок у проекті Support Agent виглядала ось так.
String result = chatClient.prompt("Analyze if incoming support ticket is about some kind of issue or is it a business request. "
+ "If it's a business request, the text will ask you to create something new or ask for estimation, if it's a support request "
+ "the text will say that something is not working properly, something is down or needs fixing. Answer with true if it's a "
+ "business request and false if it's a support request" + request.description())
.call()
.content();
Це схоже на звичайний API, за винятком того, що контракт визначається запитом, а не схемою JSON.
Чи буде він забезпечувати точні результати в умовах реальної роботи? Ймовірно, ні. Він поєднує постійні інструкції з ненадійними даними від користувача в один рядок, не розділяє контент запиту та припускає, що модель поверне щось, що Java зможе розпарсити як логічну величину. Версія, призначена для реальної роботи, більше нагадує контракт додатку, де інструкції на рівні системи розміщуються окремо від даних від користувача.
String result = chatClient.prompt()
.system("""
You classify incoming tickets for an L3 support workflow.
Return exactly one token: true or false.
Return true when the request asks for new functionality, estimation, planning, or project work.
Return false when the request reports broken, degraded, unavailable, or incorrect production behavior.
Do not explain your answer.
""")
.user(u -> u.text("""
Ticket title: {title}
Ticket source: {source}
Ticket description:
---
{description}
---
""")
.param("title", request.title())
.param("source", request.source())
.param("description", request.description()))
.call()
.content();
Інструкції системи визначають її поведінку та обмеження, тоді як повідомлення користувача містить змінні дані вхіду. Запити стають частиною самої структури дизайну, фіксуючи припущення, визначаючи тон та впливаючи на спосіб обробки запитів.
Це призводить до появи нової галузі — інженерії запитів. Запит більше не є просто тимчасовим рядком тексту; це об’єкт, який потрібно версіонувати, тестувати та підтримувати, і саме такий підхід є необхідним для стабільних систем. Довідкові сторінки Spring AI про запити та про ChatClient є наступним логічним джерелом інформації.
Структурований вихід
Однією з перших проблем під час впровадження ШІ в бекенд-системи є неструктурована відповідь. Моделі природним чином генерують текст у вільній формі, тоді як корпоративні додатки потребують структурованих даних.
Spring AI заповнює цю прогалину, перетворюючи відповіді безпосередньо на об’єкти Java. Це усуває необхідність ручного аналізу та зменшує можливості неоднозначності у сприйнятті результатів.
Замість отримання абзацу з повідомленням про збій, співробітник підтримки використовує запис Java, щоб вказати, який репозиторій та клас, ймовірно, беруть участь у інциденті.
@Service
@RequiredArgsConstructor
public class SupportAnalysisService {
private final ChatClient chatClient;
private final LogRetrievalTool logRetrievalTool;
public LogCauseReport analyzeLogs(SupportTicketRequest request) {
var converter = new BeanOutputConverter<>(LogCauseReport.class);
var format = converter.getFormat();
String response = chatClient.prompt("Analyze the support problem and provide solution, use available MCP tools to access logs. "
+ "Incoming support request description: " + request.description()
+ " \n provide response in this format " + format).tools(logRetrievalTool).call().content();
LogCauseReport report = converter.convert(response);
return report;
}
}
public record LogCauseReport(String causeOfProblem, String repoName, String className) {
}
Тут модель керується так, щоб генерувати відповідь, яку можна безпосередньо перетворити на об’єкт із суворо визначеним типом, що переносить інтеграцію від неформалізованої обробки тексту до чітких контрактів.
Переваги значні. Неструктурований вихід змушує використовувати крихку обробку, яка ламається при незначних змінах формулювань. Структурований вихід можна перевіряти, фіксувати та передавати, як будь-який інший тип даних. У офіційній документації описаний підхід з використанням перетворювачів.
Новіші версії Spring AI також пропонують мапування типових ентитетів та, у деяких постачальників, нативний структурований вихід — обидва варіанти варто розглянути. Забезпечення дотримання контракту безпосередньо біля моделі означає меншу кількість коду для обробки для додатку.
Виклик інструменту
Генерація тексту корисна, але справжня цінність проявляється тоді, коли ШІ може взаємодіяти з системою шляхом отримання даних, запуску робочих процесів чи виконання бізнес-логіки.
Інструменти виклику уможливлюють це, надаючи доступ до функцій додатку, які модель може використовувати на основі своїх міркувань. Модель не слід розглядати як оператора з прямим доступом до інфраструктури. Вона запитує певну можливість, а додаток вирішує, чи існує ця можливість, як перевіряються аргументи, які дані повертаються та як фіксується виконання.
У межах проекту SupportAnalysisService надає доступ до LogRetrievalTool для кроку аналізу журналів. Для визначення інструменту використовується анотація @Tool, а параметр опису допомагає моделі вирішити, коли викликати відповідну функцію.
@Tool(name = "analyze_logs", description = "Analyzes provided logs and summarizes potential issues")
public LogCauseReport analyzeLogs(
@ToolParam(description = "Application name") String applicationName) {
var converter = new BeanOutputConverter<>(LogCauseReport.class);
var format = converter.getFormat();
String prompt = """
You are a support AI specialized in analyzing application logs to identify potential issues.
Given the application name "%s", analyze the relevant logs and provide a structured report.
Respond ONLY with a single JSON object that strictly matches this format:
%s
Do NOT return the schema itself, only valid JSON with values filled in.
""".formatted(applicationName, format);
String jsonResponse = chatClient.prompt()
.user(prompt)
.call()
.content();
return converter.convert(jsonResponse);
}
Коли модель вирішує, що їй потрібна додаткова інформація, вона викликає відповідну функцію, обробляє отриманий результат та продовжує генерацію. Фактично модель стає організатором можливостей системи.
Однак у міру зростання систем жорстко закодовані інтеграції стають обмежуючими, адже кожен новий інструмент додає зв’язок між моделлю та додатком.
Відкриття інструментів за допомогою MCP
Протокол Model Context Protocol вирішує цю проблему. MCP стандартизує спосіб, у який інструменти та сервіси відкриваються для моделей, щоб їх можна було динамічно знаходити та не потрібно було вбудовувати безпосередньо в додаток.
MCP перетворює фіксовані інтеграції на динамічну екосистему, де можна додавати чи змінювати функціонал без змін у основному додатку, що є базовим кроком до масштабованих архітектур на основі агентів. Документація Spring AI щодо виклику інструментів описує модель програмування та потік виконання. Коли інструменти потребують динамічного пошуку чи мають бути спільними між процесами, підтримка MCP є логічним наступним кроком.
Генерація з підсиленням за допомогою пошуку
Моделі є потужними, але не мають жодної інформації про дані ваших систем. Клієнти, продукти та внутрішні процеси залишаються невідомими, якщо інформація не надається.
RAG вирішує цю проблему шляхом додавання кроку пошуку перед генерацією. Відповідні документи отримуються з векторного сховища та включаються до запиту, що надає результатам основу у вигляді корпоративних даних, таких як інструкції, журнали роботи та код додатків.
Для агента підтримки RAG надає кодове рішення для конкретної проблеми в продакшені, використовуючи клас Java, зазначений у журналах застосунку.
Першим кроком є отримання фрагментів вихідного коду за допомогою SupportAgentController через GithubRagService, який працює на основі LogCauseReport, що містить ім’я підозрюваного репозиторію та класу.
String classCode = ragService.findRelevantClass(null, logCauseReport.repoName(), null, logCauseReport.className()).stream()
.map(SupportAgentController::stripMetadataLines)
.filter(content -> !content.isBlank())
.collect(Collectors.joining(System.lineSeparator() + System.lineSeparator()));
На другому етапі сервіс пошуку створює фільтри метаданих та проводить пошук за схожістю у векторному сховищі Qdrant.
SearchRequest.Builder searchRequestBuilder = SearchRequest.builder();
if (expressions != null) {
searchRequestBuilder = searchRequestBuilder.filterExpression(expressions.build());
}
Set<String> results = Objects.requireNonNull(vectorStore.similaritySearch(searchRequestBuilder.topK(10).build()))
.stream()
.map(Document::getFormattedContent)
.collect(Collectors.toSet());
Третій крок — це вставка отриманого коду в запит, у якому проситься виправлення.
String prompt = """
You are a Senior Java Developer. You have the following class code:
%s
The code has the following problem: %s
Provide a detailed suggestion on how to fix the problem in the code.
Format your response in markdown with clear sections.
""".formatted(classCode, problemDescription);
String suggestion = chatClient.prompt(prompt).call().content();
Цей підхід дозволяє перетворити модель зі звичайного асистента на таку, яка розуміє конкретну галузь. RAG переважно зменшує галюцинації та забезпечує, щоб відповіді відповідали внутрішнім знанням, але це супроводжується збільшенням складності.
Він додає моделі ембеддингу, векторні бази даних та логіку пошуку, які всі потребують керування та налаштування. Для корпоративного використання така складність часто є неминучою, оскільки якість відповідей залежить від кількох факторів: свіжості отриманих даних, якості метаданих, стратегії розділення на частини, авторизації та відстеження доказів. Документація Spring AI щодо RAG, векторних сховищ та Qdrant охоплює концепції, зазначені в цьому розділі.
Спостережуваність, оцінка та операційний контроль
Оперативне спостереження для ШІ функціонує інакше. Традиційне логування та моніторинг ґрунтуються на детерміністичній поведінці, проте ШІ потребує додаткових параметрів для відстеження. Відстеження часу відповіді та частоти помилок є недостатнім, тож командам також необхідно бачити запити, результати роботи моделей, використання токенів та взаємодію з інструментами.
Агент підтримки використовує Micrometer як свою систему оперативного спостереження, яка фіксує:
- ідентифікатори та контекст кожного запиту, такі як номер замовлення, особа, що звертається, та джерельна система
- постачальника та назву моделі, версію запиту, затримку, використання токенів та причину завершення роботи
- чи вдалося перетворити дані на структурований формат
- кожен виклик інструменту з нормалізованими аргументами, розміром результату та затримкою
- які репозиторії, файли, векторні оцінки та фрагменти даних були отримані
- кінцевий статус замовлення та чи прийняв чи змінив людський рецензент пропозицію
Такий рівень деталізації є критично важливим для виправлення помилок та оптимізації, оскільки він пояснює, чому система поводиться саме так. У продакшн-застосунках чутливі запити та результати не слід записувати буквально. Кращою альтернативою є структуроване обліковування подій із ідентифікатором запиту, ідентифікатором моделі, кількістю токенів та результатом застосування правил.
Оцінка результатів додає ще один рівень контролю. Системи можуть оцінювати якість відповідей за заздалегідь визначеними критеріями та не змушені припускати, що відповіді є правильними. Це дозволяє використовувати такі підходи, як цикли оцінювача-оптимізатора, коли відповідь удосконалюється перед тим, як надійти користувачеві.
Це також змінює спосіб розвитку системи. Кожного разу, коли змінюються запити, моделі для генерації ембеддингів, підходи до розділення даних на частини чи версії моделей, результат можна порівняти з відомими випадками, щоб команда могла визначити, чи покращує ця зміна робочий процес, і чи не залежить вона від кількох вражаючих прикладів.
Перш ніж відкрити асистента для інших команд, для проекту було розглянуто низку операційних механізмів контролю:
- жорсткі таймаути для викликів до моделі, інструментів та сховища векторів
- бюджети токенів на запит та на користувача, що обмежує витрати та можливі проблеми
- повторні спроби лише там, де це безпечно та обмежено
- режими резервного функціонування у разі несправності моделі, інструменту чи Qdrant
- замаскування конфіденційної інформації, облікових даних та особистих даних перед надсиланням запитів та записуванням журналів
- запис контенту за замовчуванням або лише за наявності схвалених механізмів контролю доступу
- чіткі правила зберігання запитів, результатів, ембеддингів та вихідних даних інструментів
- панелі керування та сповіщення про невдачі конвертації, порожні результати пошуку, помилки інструментів та відхилення рецензентами
Рівень відхилення рекомендацій рецензентами найкраще відображає їхнє фактичне використання. Коли інженери регулярно переписують або відкидають генеровані рекомендації, це додає зусиль асистенту. Система для продакшну має враховувати цей сигнал. Автоматична налаштування для всього — не найкращий підхід. Кращим шляхом є оперативний: покращувати запити, інструменти, механізми пошуку та правила на основі об’єктивних даних. Документація Spring AI щодо можливостей спостереження охоплює вбудовані інструменти.
Проектування обмежених агентних робочих процесів
Якщо поєднати все вищезазначене, з’являється нова модель: агентна система.
Традиційні додатки дотримуються заздалегідь визначених робочих процесів. Агентні додатки працюють у ітеративному циклі спостереження, міркувань та дій. Вони зберігають стан, використовують пам’ять та динамічно вирішують, які дії виконати.
Стан, пам’ять та автономія стають ключовими аспектами, і кожен з них відповідає певній функціональності, яку вже надає Spring AI:
- Стан — це те, що відоме під час поточної взаємодії
- Пам’ять — це те, що зберігається між взаємодіями
- Інструменти визначають, які дії дозволені
- Обмеження позначають те, що заборонено або потребує підтвердження
- Критерії завершення вказують, коли цикл має закінчитися
Це змінює роль додатку порівняно з класичним корпоративним робочим процесом. Замість того, щоб визначати кожен крок, система визначає можливості та обмеження та дозволяє моделі організовувати поведінку в межах цих обмежень.
У моделі підтримки агент починає свою роботу з запиту. Він аналізує його, використовує інструменти та отримує код. Потім він складає рекомендацію та оновлює заявку. Це не автономний оператор виробництва, і він не повинен ним ставати. У фінансових технологіях саме цей кордон визначає, які дії може виконувати агент у певному контексті. Моделі можна довіряти у стисненні журналів подій задовго до того, як можна буде довіряти їй у модифікації заявок.
Створення ефективних агентів є складним завданням, а документація Spring AI з цієї теми є гарною відправною точкою.
Від експериментів до корпоративних систем
Штучний інтелект — це не просто ще одна функція. Він додає новий рівень до корпоративних систем, рівень, який є ймовірнісним, динамічним та тісно пов’язаним із зовнішніми залежностями. Без належної структури цей рівень швидко призводить до крихкої та непередбачуваної поведінки.
Spring AI пропонує спосіб його інтеграції, зберігаючи інженерні практики, які роблять корпоративні системи надійними. Знайомі абстракції та чітко визначені шаблони інтеграції дозволяють командам впроваджувати ШІ, не відмовляючись від чітких умов, контрольованих взаємодій та прогнозованої поведінки.
Отже, зміни стосуються не лише додавання функцій ШІ. Йдеться про еволюцію систем так, щоб ШІ став корисною, керованою та прозорою частиною архітектури. Наступна хвиля корпоративних додатків буде оцінюватися за тим, наскільки добре вони інтегрують ШІ, а спосіб його використання матиме менше значення.
Пов’язана література
- Практичні нотатки: RAG вже не актуальний. LLM Wiki — ідея Андрея Карпати — це те, що буде далі — Детальний посібник з практичних нотаток: RAG вже не актуальний. LLM Wiki — ідея Андрея Карпати — це те, що буде далі: контракти, перевірки та шаблони коду для команд, які розробляють RAG.
- Проєкт RAG — це не один величезний файл Python: структура папок, яка підлаштовується під зростання — Розділіть демонстраційний додаток RAG на компоненти: завантаження даних, розділення на частини, створення ембеддингів, зберігання векторів, пошук, формулювання запитів, клієнт LLM, API, тести та журнали, щоб проєкт міг розвиватися за межі початкової стадії.