Искусственный интеллект в корпоративной среде: агент поддержки с инструментами, 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, тестов и логов, чтобы проект мог развиваться за пределы этапа простого прототипа.