Команда 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.

На этот раз getUserOrderFn вернул менее 2 заказов для пользователя David. Поэтому LLM принял решение запросить выполнение функции createOrderFn.

Создайте заказ с количеством 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 и всего, что с ним связано.

Комментарии (0)