Как стать автором
Обновить
71.71

Spring AI научился видеть! Показываю, как заставить GPT находить бананы на картинках

Уровень сложностиПростой
Время на прочтение8 мин
Количество просмотров1.2K
Автор оригинала: Piotr Minkowski

Новый перевод от команды Spring АйО расскажет вам о работе с мультимодальностью при работе со Spring AI и о различных вариантах работы с графическими изображениями с использованием искусственного интеллекта.


Эта статья научит вас, как создать Spring Boot приложение, которое работает с графикой и текстом, используя мультимодальность в Spring AI. Мультимодальность - это способность понимать и обрабатывать информацию одновременно из разных источников. Это определение охватывает текст, графические изображения, аудиофайлы и другие форматы данных. 

Исходный код

Вы можете использовать мой исходный код, если хотите попробовать все сами. Для этого вам необходимо клонировать мой GitHub репозиторий с примером. Дальше остается лишь следовать моим инструкциям 

Мотивация для мультимодальности со Spring AI

Функциональность, заложенная в мультимодальную большую языковую модель (LLM), позволяет ей обрабатывать и генерировать текст, равно как и другие модальности, включая изображения, аудиофайлы и видео. Эта функциональность включает use case, для которого мы хотим, чтобы LLM обнаружила какую-то специфическую часть внутри изображения и описала его содержимое. Давайте предположим, что у нас есть список введенных в программу графических файлов. Мы хотим найти в этом списке изображение, которое соответствует нашему описанию. Например, это описание может попросить модель найди картинку, содержащую тот или иной предмет. Spring AI Message API предоставляет все необходимые элементы для поддержки мультимодальных LLM. Приведенная далее диаграмма иллюстрирует наш сценарий.

Использование мультимодальности со Spring AI

Нам не надо включать никаких специфических библиотек кроме Spring AI стартера для конкретной ИИ модели. Опция по умолчанию — spring-ai-openai-spring-boot-starter. Наше приложение использует изображения, сохраненные в каталоге src/main/resources/images. Поддержка мультимодальности в Spring AI требует, чтобы графическое изображение передавалось внутри объекта Media. Мы загружаем все картинки из classpath внутри конструктора.

Распознавание предметов внутри изображения

Команда GET /images/find/{object} пытается найти изображение, содержащее предмет, заданный в переменной пути к объекту. ИИ модель должна вернуть позицию на рисунке во вводимом списке. Чтобы достичь этого, мы создаем объект UserMessage, который содержит запрос от пользователя и список объектов типа Media. Как только модель возвращает позицию, эндпоинт считывает изображение из списка и возвращает его содержимое в формате image/png.

@RestController
@RequestMapping("/images")
public class ImageController {

    private final static Logger LOG = LoggerFactory
        .getLogger(ImageController.class);

    private final ChatClient chatClient;
    private List<Media> images;
    private List<Media> dynamicImages = new ArrayList<>();

    public ImageController(ChatClient.Builder chatClientBuilder) {
        this.chatClient = chatClientBuilder
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
        this.images = List.of(
                Media.builder().id("fruits").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/fruits.png")).build(),
                Media.builder().id("fruits-2").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/fruits-2.png")).build(),
                Media.builder().id("fruits-3").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/fruits-3.png")).build(),
                Media.builder().id("fruits-4").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/fruits-4.png")).build(),
                Media.builder().id("fruits-5").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/fruits-5.png")).build(),
                Media.builder().id("animals").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/animals.png")).build(),
                Media.builder().id("animals-2").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/animals-2.png")).build(),
                Media.builder().id("animals-3").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/animals-3.png")).build(),
                Media.builder().id("animals-4").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/animals-4.png")).build(),
                Media.builder().id("animals-5").mimeType(MimeTypeUtils.IMAGE_PNG).data(new ClassPathResource("images/animals-5.png")).build()
        );
    }

    @GetMapping(value = "/find/{object}", produces = MediaType.IMAGE_PNG_VALUE)
    @ResponseBody byte[] analyze(@PathVariable String object) {
        String msg = """
        Which picture contains %s.
        Return only a single picture.
        Return only the number that indicates its position in the media list.
        """.formatted(object);
        LOG.info(msg);

        UserMessage um = new UserMessage(msg, images);

        String content = this.chatClient.prompt(new Prompt(um))
                .call()
                .content();

        assert content != null;
        return images.get(Integer.parseInt(content)-1).getDataAsByteArray();
    }

}

Давайте сделаем тестовый вызов. Мы будем искать картинку, содержащую банан. Вот так выглядит ответ ИИ модели после вызова http://localhost:8080/images/find/banana. Вы можете попробовать сделать другие тестовые вызовы и найти картинку, содержащую, например, апельсин или помидор.

spring-ai-multimodality-find-object
spring-ai-multimodality-find-object

Описание содержимого картинки

С другой стороны, мы можем попросить ИИ модель сгенерировать короткое описание всех изображений, включенных как содержимое Media объектов. Эндпоинт GET /images/describe объединяет два списка изображений.

@GetMapping("/describe")
String[] describe() {
   UserMessage um = new UserMessage("Explain what do you see on each image.",
            List.copyOf(Stream.concat(images.stream(), dynamicImages.stream()).toList()));
      return this.chatClient.prompt(new Prompt(um))
              .call()
              .entity(String[].class);
}

Когда мы вызываем URL http://localhost:8080/images/describe , мы получаем сжатое описание всех введенных изображений. Два выделенных описания были сгенерированы для изображений из списка dynamicImages. Эти изображения были сгенерированы моделью ИИ изображений. Мы обсудим это в следующем разделе.

spring-ai-multimodality-image-desc
spring-ai-multimodality-image-desc

Генерация изображений при помощи ИИ модели 

Чтобы сгенерировать изображение, используя AI API мы должны заинжектировать бин ImageModel. Он предоставляет один метод call, который позволяет нам взаимодействовать с ИИ моделями, предназначенными для генерации изображений. Этот метод получает объект типа ImagePrompt в качестве аргумента. Как правило, мы используем конструктор ImagePrompt, который получает инструкции по генерации изображения и опции, которые настраивают высоту, ширину и количество изображений. Мы сгенерируем одно (N=1) изображение на 1024 пикселя как по ширине, так и по высоте. ИИ модель возвращает URL изображения (responseFormat). Как только изображение сгенерировалось, мы создаем объект UrlResource, создаем объект типа Media и кладем его в dynamicImages список. Эндпоинт GET /images/generate/{object} возвращает массив байтов, который представляет собой объект-изображение.

@RestController
@RequestMapping("/images")
public class ImageController {

    private final ChatClient chatClient;
    private final ImageModel imageModel;
    private List<Media> images;
    private List<Media> dynamicImages = new ArrayList<>();
    
    public ImageController(ChatClient.Builder chatClientBuilder,
                           ImageModel imageModel) {
        this.chatClient = chatClientBuilder
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
        this.imageModel = imageModel;
        // other initializations
    }
    
    @GetMapping(value = "/generate/{object}", produces = MediaType.IMAGE_PNG_VALUE)
    byte[] generate(@PathVariable String object) throws IOException {
        ImageResponse ir = imageModel.call(new ImagePrompt("Generate an image with " + object, ImageOptionsBuilder.builder()
                .height(1024)
                .width(1024)
                .N(1)
                .responseFormat("url")
                .build()));
        UrlResource url = new UrlResource(ir.getResult().getOutput().getUrl());
        LOG.info("Generated URL: {}", ir.getResult().getOutput().getUrl());
        dynamicImages.add(Media.builder()
                .id(UUID.randomUUID().toString())
                .mimeType(MimeTypeUtils.IMAGE_PNG)
                .data(url)
                .build());
        return url.getContentAsByteArray();
    }
    
}

Вы помните описание изображения, возвращенное эндпоинтом GET /images/describe? Ниже приведено наше изображение с клубникой, сгенерированное ИИ моделью после вызова URL  http://localhost:8080/images/generate/strawberry.

А вот похожий тест с входным параметром banana.

Используйте хранилище векторов с мультимодальностью от Spring AI

Давайте посмотрим на то, как мы можем использовать хранилище векторов для этого сценария. Мы не можем вставить представление картинки напрямую в хранилище векторов, поскольку большинство популярных вендоров, такие как OpenAI или Mistral AI не предоставляют эмбеддинг моделей для изображений. Мы могли бы интегрироваться напрямую с моделью clip-vit-base-patch32 для генерации эмбеддингов для изображений, но данная статья не описывает такой сценарий. Вместо этого хранилище векторов может содержать описание изображения и его местоположение (или его название). Эндпоинт GET /images/load предоставляет метод для загрузки описаний изображений в хранилище векторов. Он использует поддержку мультимодальности от Spring AI для генерации компактного описания каждого изображения во входящем списке и затем кладет его в хранилище.

@GetMapping("/load")
    void load() throws JsonProcessingException {
        String msg = """
        Explain what do you see on the image.
        Generate a compact description that explains only what is visible.
        """;
        for (Media image : images) {
            UserMessage um = new UserMessage(msg, image);
            String content = this.chatClient.prompt(new Prompt(um))
                    .call()
                    .content();

            var doc = Document.builder()
                    .id(image.getId())
                    .text(mapper.writeValueAsString(new ImageDescription(image.getId(), content)))
                    .build();
            store.add(List.of(doc));
            LOG.info("Document added: {}", image.getId());
        }
    }

Ну и наконец-то мы можем реализовать другой эндпоинт, который генерирует новую картинку и просит ИИ модель сгенерировать описание изображения. Затем он выполняет поиск на схожесть в хранилище векторов, чтобы найти наиболее похожее изображение, основываясь на текстовом описании.

@GetMapping("/generate-and-match/{object}")
    List<Document> generateAndMatch(@PathVariable String object) throws IOException {
        ImageResponse ir = imageModel.call(new ImagePrompt("Generate an image with " + object, ImageOptionsBuilder.builder()
                .height(1024)
                .width(1024)
                .N(1)
                .responseFormat("url")
                .build()));
        UrlResource url = new UrlResource(ir.getResult().getOutput().getUrl());
        LOG.info("URL: {}", ir.getResult().getOutput().getUrl());

        String msg = """
        Explain what do you see on the image.
        Generate a compact description that explains only what is visible.
        """;

        UserMessage um = new UserMessage(msg, new Media(MimeTypeUtils.IMAGE_PNG, url));
        String content = this.chatClient.prompt(new Prompt(um))
                .call()
                .content();

        SearchRequest searchRequest = SearchRequest.builder()
                .query("Find the most similar description to this: " + content)
                .topK(2)
                .build();

        return store.similaritySearch(searchRequest);
    }

Давайте протестируем эндпоинт GET /images/generate-and-match/{object} с параметром pineapple. Он возвращает описание файла fruits.png из classpath.

spring-ai-multimodality-vector-store
spring-ai-multimodality-vector-store

Кстати, вот изображение fruits.png, находящееся в каталоге /src/main/resources/images.

Финальные размышления

Spring AI предоставляет поддержку мультимодальности и генерации изображений. Все возможности, представленные в этой статье, прекрасно работают с OpenAI, которая . поддерживает как модель изображений, так и мультимодальность. Чтобы узнать больше о том, какую поддержку предоставляют другие модели, вы можете обратиться к документации по моделям чата и изображений Spring AI.

Эта статья показывает, как мы можем использовать Spring AI и ИИ модели для взаимодействия с изображениями тем или иным способом.


Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм - Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.

Теги:
Хабы:
+6
Комментарии1

Публикации

Информация

Сайт
t.me
Дата регистрации
Численность
11–30 человек