Команда Spring АйО перевела туториал, в котором рассматриваются возможности Spring AI для интеграции с LLM.
Вы узнаете, как использовать API Function Calling для выполнения задач на естественном языке, генерировать ответы в JSON-формате и сохранять контекст диалога.
Обзор
В этом туториале мы рассмотрим концепции Spring AI, которые помогут создать AI-ассистента с использованием LLM, таких как ChatGPT, Ollama, Mistreal и других.
Компании все чаще внедряют AI-ассистентов для улучшения UX в самых разных бизнес-задачах:
Ответы на вопросы пользователей
Выполнение операций на основе пользовательского ввода
Суммирование длинных предложений и документов
Хотя это лишь несколько базовых возможностей LLM, их потенциал значительно шире.
Возможности Spring AI
Фреймворк Spring AI предлагает множество интересных функций для реализации AI-ориентированных возможностей:
интерфейсы, которые могут бесшовно интегрироваться с базовыми LLM-сервисами и Vector DB
генерация ответов с учетом контекста и выполнение действий с использованием RAG и Function Calling API
преобразование ответов LLM в POJO или машиночитаемые форматы, такие как JSON, с помощью структурированных конвертеров
обогащение промптов и применение ограничений через перехватчики (интерсепторы), предоставляемые Advisor API
улучшение взаимодействия с пользователем за счет сохранения состояния диалога
Мы также можем это визуализировать:
Чтобы продемонстрировать некоторые из этих функций, мы создадим чат-бота для легаси системы управления заказами (Order Management System, OMS).
Типичные функции OMS включают:
Создание заказа
Получение заказов пользователя
Предварительные требования
Во-первых, нам понадобится подписка на OpenAI (комментарий от команды Spring АйО: может использоваться другая модель) для использования его LLM-сервиса. Затем в приложении Spring Boot мы добавим Maven-зависимости для библиотек Spring AI. Мы уже подробно рассмотрели предварительные требования в других статьях, поэтому не будем углубляться в эту тему.
Кроме того, для быстрого старта мы будем использовать in-memory базу данных HSQLDB. Давайте создадим необходимые таблицы и добавим в них данные:
CREATE TABLE User_Order (
order_id BIGINT NOT NULL PRIMARY KEY,
user_id VARCHAR(20) NOT NULL,
quantity INT
);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (1, 'Jenny', 2);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (2, 'Mary', 5);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (3, 'Alex', 1);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (4, 'John', 3);
INSERT INTO User_Order (order_id, user_id, quantity) VALUES (5, 'Sophia', 4);
--and so on..
Мы используем несколько стандартных свойств конфигурации, связанных с инициализацией HSQLDB и клиента OpenAI, в файле application.properties
Spring:
spring.datasource.driver-class-name=org.hsqldb.jdbc.JDBCDriver
spring.datasource.url=jdbc:hsqldb:mem:testdb;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=none
spring.ai.openai.chat.options.model=gpt-4o-mini
spring.ai.openai.api-key=xxxxxxx
Комментарий от команды Spring АйО
Значение данных настроек может меняться в зависимости от выбранной вами LLM модели.
Выбор подходящей модели для конкретного сценария использования — это сложный итеративный процесс, включающий множество проб и ошибок. Тем не менее, для простого демонстрационного приложения в рамках этой статьи подойдет экономичная GPT-4o mini модель.
API вызова функций (Function Call API)
Эта фича является одной из основ популярной агентной концепции в приложениях на основе LLM. Она позволяет приложениям выполнять сложный набор конкретных задач и принимать решения самостоятельно.
Например, это может существенно помочь в создании чат-бота для легаси приложения управления заказами. Чат-бот сможет помогать пользователям создавать запросы на заказы, получать историю заказов и выполнять другие действия описанные на естественном языке.
Эти специализированные навыки обеспечиваются одной или несколькими функциями приложения. Мы определяем алгоритм в промпте, отправляемом LLM вместе со схемой поддерживающих функций. LLM получает эту схему и определяет правильную функцию для выполнения запрошенного действия. Затем LLM сама вызывает выбранную функцию у приложения.
Наконец, приложение выполняет функцию и отправляет дополнительную информацию обратно в LLM:
Сначала давайте рассмотрим основные компоненты легаси приложения:
Класс OrderManagementService
содержит две важные функции: создание заказов и получение истории заказов пользователя. Обе функции используют бин OrderRepository
для интеграции с базой данных:
@Service
public class OrderManagementService {
@Autowired
private OrderRepository orderRepository;
public Long createOrder(OrderInfo orderInfo) {
return orderRepository.save(orderInfo).getOrderID();
}
public Optional<List<OrderInfo>> getAllUserOrders(String userID) {
return orderRepository.findByUserID(userID);
}
}
Теперь давайте посмотрим, как Spring AI может помочь в реализации чат-бота в легаси приложении:
На диаграмме классов OmAiAssistantConfiguration
представлен как конфигурационный бин Spring. Он регистрирует бины обратного вызова функций: createOrderFn
и getUserOrderFn
:
@Configuration
public class OmAiAssistantConfiguration {
@Bean
@Description("Create an order. The Order ID is identified with orderID. "
+ "The order quantity is identified by orderQuantity."
+ "The user is identified by userID. "
+ "The order quantity should be a positive whole number."
+ "If any of the parameters like user id and the order quantity is missing"
+ "then ask the user to provide the missing information.")
public Function<CreateOrderRequest, Long> createOrderFn(OrderManagementService orderManagementService) {
return createOrderRequest -> orderManagementService.createOrder(createOrderRequest.orderInfo());
}
@Bean
@Description("get all the orders of an user. The user ID is identified with userID.")
public Function<GetOrderRequest, List<OrderInfo>> getUserOrdersFn(OrderManagementService orderManagementService) {
return getOrderRequest -> orderManagementService.getAllUserOrders(getOrderRequest.userID()).get();
}
}
Аннотация @Description
помогает сгенерировать схему функции. Затем приложение отправляет эту схему в LLM как часть промпта.
Кроме того, CreateOrderRequest
и GetOrderRequests
— это Record
-классы, которые помогают Spring AI создавать POJO для вызовов downstream-сервисов:
record GetOrderRequest(String userID) {}
record CreateOrderRequest(OrderInfo orderInfo) {}
Наконец, давайте рассмотрим новый класс OrderManagementAIAssistant
, который будет отправлять запросы пользователей в сервис LLM:
@Service
public class OrderManagementAIAssistant {
@Autowired
private ChatModel chatClient;
public ChatResponse callChatClient(Set<String> functionNames, String promptString) {
Prompt prompt = new Prompt(promptString, OpenAiChatOptions
.builder()
.withFunctions(functionNames)
.build()
);
return chatClient.call(prompt);
}
}
Метод callChatClient()
помогает зарегистрировать функции в объекте Prompt
. Затем он вызывает метод ChatModel#call()
, чтобы получить ответ от сервиса LLM.
Сценарии вызова функций
Для пользовательского запроса или инструкции, переданной AI-ассистенту, мы рассмотрим несколько базовых сценариев:
LLM принимает решение и определяет одну или несколько функций для выполнения.
LLM сообщает о недостаточной информации для выполнения функции.
LLM выполняет инструкции в зависимости от определенных условий.
Мы уже обсудили основные концепции, поэтому далее будем использовать эту функцию для создания чат-бота.
5.1. Выполнение функции обратного вызова один или несколько раз
Теперь давайте изучим поведение LLM, когда мы вызываем его с промптом, содержащим запрос пользователя и схему функции.
Начнем с примера, где создается заказ:
void whenOrderInfoProvided_thenSaveInDB(String promptString) {
ChatResponse response = this.orderManagementAIAssistant
.callChatClient(Set.of("createOrderFn"), promptString);
String resultContent = response.getResult().getOutput().getContent();
logger.info("The response from the LLM service: {}", resultContent);
}
Удивительно, но с использованием естественного языка мы получаем желаемые результаты:
Промпт |
LLM Запрос |
Наблюдение |
Создайте заказ с количеством 20 для пользователя с идентификатором Jenny и случайным образом сгенерируйте положительное целое число для идентификатора заказа. |
Заказ был успешно создан со следующими данными: – ID заказа: 123456 – ID пользователя: Jenny – Количество: 20 |
Программа создает заказ с использованием информации, предоставленной в промпте. |
Создайте два заказа. Первый заказ для пользователя с идентификатором Sophia с количеством 30. Второй заказ для пользователя с идентификатором Mary с количеством 40. Случайным образом сгенерируйте положительные целые числа для идентификаторов заказов. |
Заказы были успешно созданы: Заказ для Sophia: ID заказа 1, количество 30. Заказ для Mary: ID заказа 2, количество 40. Если вам нужно что-то еще, не стесняйтесь обращаться! |
Программа создает два заказа. LLM оказался достаточно умным, чтобы запросить выполнение функции дважды. |
Продолжая, давайте проверим, сможет ли LLM понять промпт, запрашивающий получение данных о заказах пользователя:
void whenUserIDProvided_thenFetchUserOrders(String promptString) {
ChatResponse response = this.orderManagementAIAssistant
.callChatClient(Set.of("getUserOrdersFn"), promptString);
String resultContent = response.getResult().getOutput().getContent();
logger.info("The response from the LLM service: {}", resultContent);
}
Результаты аналогичны предыдущему случаю. LLM успешно идентифицирует зарегистрированную функцию для выполнения. Затем приложение Spring Boot вызывает запрошенную функцию и возвращает данные о заказах одного или нескольких пользователей.
Комментарий от команды Spring АйО
Возможно, тут ожидалось изображение с результатами, но, к сожалению, в оригинальной статье оно отсутствует.
5.2. Условное выполнение функций обратного вызова
Рассмотрим программу, которая создает заказ только при выполнении определенных критериев:
void whenUserIDProvided_thenCreateOrderIfUserHasLessThanTwoOrders(String promptString) {
ChatResponse response = this.orderManagementAIAssistant
.callChatClient(Set.of("getUserOrdersFn", "createOrderFn"), promptString);
String resultContent = response.getResult()
.getOutput()
.getContent();
logger.info("The response from the LLM service: {}", resultContent);
}
Мы зарегистрировали функции getUserOrderFn
и CreateOrderFn
в бине ChatModel
. Далее LLM должен определить, какая функция обеспечит наилучший результат для запрошенного действия.
Как и ранее, результаты обнадеживают:
Промпт |
Вывод |
Наблюдение |
Создайте заказ для пользователя с идентификатором Alex с количеством 25. Не создавайте заказ, если у пользователя уже более 2 заказов. При создании заказа случайным образом сгенерируйте положительное целое число для идентификатора заказа. |
У пользователя Alex уже более 2 заказов (в частности, 4 заказа). Поэтому я не буду создавать новый заказ. |
LLM может условно вызывать функции downstream на основе вывода предыдущих функций. |
Создайте заказ для пользователя с идентификатором David с количеством 25. Не создавайте заказ, если у пользователя уже более 2 заказов. При создании заказа случайным образом сгенерируйте положительное целое число для идентификатора заказа. |
Заказ успешно создан для пользователя с идентификатором "David" с количеством 25, и идентификатор заказа — 42. |
На этот раз |
Создайте заказ с количеством 20. |
Пожалуйста, укажите ваш идентификатор пользователя для создания заказа. |
LLM сразу определил, что отсутствует идентификатор пользователя, и прекратил дальнейшую обработку. |
API Advisors в Spring AI
В предыдущих разделах мы обсудили функциональные аспекты приложения. Однако существуют некоторые общие проблемы, которые затрагивают все функциональности, например:
Предотвращение ввода пользователями конфиденциальной информации
Логирование и аудит запросов пользователей
Поддержание состояния диалога
Обогащение промптов
К счастью, API Advisors позволяет последовательно решать эти проблемы. Мы уже подробно объясняли это в одной из наших статей.
API структурированного вывода Spring AI и Spring AI RAG
LLM, как правило, генерируют ответы на запросы пользователей в виде сообщений на естественном языке. Однако downstream-сервисы чаще всего работают с данными в машиночитаемых форматах, таких как POJO, JSON и т. д. Именно здесь возможность Spring AI генерировать структурированный вывод играет важную роль.
Как и в случае с API Advisors, мы подробно объясняли это в одной из наших статей. Поэтому не будем углубляться в эту тему и перейдем к следующему разделу.
Комментарий от команды Spring АйО
По нашему мнению, речь идет о данной статье.
Иногда приложениям необходимо обращаться к Vector DB для выполнения семантического поиска по сохраненным данным и получения дополнительной информации. Затем полученные результаты из Vector DB используются в подсказке, чтобы предоставить LLM контекстную информацию. Этот подход называется техникой RAG, которую также можно реализовать с помощью Spring AI.
Заключение
В этой статье мы рассмотрели ключевые возможности Spring AI, которые могут помочь в создании AI-ассистента. Spring AI активно развивается, предлагая множество готовых функций. Однако правильный выбор базового LLM-сервиса и Vector DB имеет решающее значение, независимо от используемого программного фреймворка. Кроме того, достижение оптимальной конфигурации этих сервисов может быть сложным и требовать значительных усилий. Тем не менее, это важно для широкого распространения приложения.
Как обычно, исходный код, использованный в этой статье, доступен на GitHub.
Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.