A2UI переходит от веб-компонентов к нативным компонентам Android. Новое средство отрисовки Jetpack Compose для A2UI версии 0.9 позволяет разработчикам отрисовывать динамический интерфейс, созданный LLM, нативно. Вместо компонентов WebView в нем используется фреймворк Actor на основе сопрограмм, жизненные циклы запечатанных классов и нативная интеграция Material 3.
На этом занятии мы рассмотрим новую архитектуру, объясним, как деревья компонентов, управляемые сервером, преобразуются в реактивные узлы Compose и как встроенные переходы макета плавно обрабатывают структурные обновления, не блокируя пользовательский интерфейс.
Расшифровка
Здравствуйте! Сегодня мы поговорим о том, как генерировать интерфейс в приложениях для Android с помощью нашей новой библиотеки A2UI.
Меня зовут Ник Бутчер, я менеджер по продукту Jetpack Compose.
Всем привет! Меня зовут Антон, я инженер в команде Compose.
Я работаю над новым рендерером Jetpack Compose A2UI, о котором мы сегодня расскажем.
Да, сейчас, когда возможности ИИ растут, многие разработчики приложений ищут способы использовать его, чтобы улучшить свои продукты или создать новые.
Один из интересных вариантов, который сейчас рассматривают, – это генеративный интерфейс. В этом случае агентные бэкенды могут создавать совершенно новые интерфейсы для пользователей во время выполнения.
Но, к сожалению, сейчас ответы многих агентных бэкендов заперты в стенах текста, как здесь.
Вот пример, в котором я попросил агента создать новую функцию для бронирования поездки.
Вот запрос.
Как видите, ответ вполне приличный, но он заперт в этой стене текста, с которой мне не очень удобно взаимодействовать, и он совсем не похож на остальную часть моего приложения.
Генеративный пользовательский интерфейс позволяет создавать новые интерфейсы на лету в ответ на динамический ввод, открывая возможности для большей настройки или новых возможностей.
Вот пример пользовательского интерфейса, созданного с использованием стандартных нативных компонентов.
Так что вы можете создать такое приложение уже сегодня, но вам придется самостоятельно определить формат обмена данными с сервером, поток событий, функции и т. д.
A2UI (Agent-to-UI) – это открытый протокол в формате JSON, который позволяет определять все эти элементы.
Мне нравится, что A2UI – это декларативный формат.
Агент просто возвращает JSON-представление интерфейса для отрисовки.
Он не возвращает код, который клиент должен выполнить, что может привести к уязвимостям в системе безопасности.
Это просто декларативный подход.
Итак, какие ещё концепции нам нужно знать об A2UI?
Вам нужно знать всего два понятия: каталоги и платформы.
В каталоге есть такие компоненты, как текст, кнопка, строка и столбец, которые агент может использовать для создания интерфейса.
В каталоге также есть функции, которые агент может использовать для форматирования текста, проверки пользовательского ввода или открытия URL.
Каталог – это контракт, который определяет, какие компоненты и функции поддерживает ваше приложение. Агент может использовать только их.
Это означает, что, как упомянул Ник, агент никогда не выполняет произвольный код в вашем приложении.
Он создает пользовательский интерфейс из разработанных вами и надежных строительных блоков.
Ещё одно важное понятие – поверхность.
Поверхность – это часть интерфейса, которой управляет агент, например планировщик поездок на слайде.
Агент описывает интерфейс для поверхности в формате JSON, используя сообщения протокола A2UI, такие как createSurface, updateComponents, а также компоненты и функции, определенные в вашем каталоге.
Каталог – это фундаментальная концепция A2UI, поскольку он определяет, какие компоненты агент может использовать для создания интерфейса.
В спецификации A2UI определен так называемый базовый каталог – набор общих компонентов, которые можно использовать для создания большинства пользовательских интерфейсов.
Рендерер Jetpack Compose A2UI предоставляет реализацию базового каталога с использованием компонентов Material 3.
Большинство компонентов каталога готовы к использованию сразу после установки.
В шаблоне есть три медиакомпонента: изображение, видео и аудиопроигрыватель. Для каждого из них нужно указать библиотеку.
Например, вы можете выбрать Coil для загрузки изображений и Media3 для видео- и аудиоплеера.
Но главное преимущество A2UI и нашей новой библиотеки рендеринга заключается в том, что вы можете создавать собственные компоненты и каталоги и полностью контролировать, что агент может использовать для создания пользовательского интерфейса.
К каталогам мы вернемся позже.
Давайте посмотрим, как это выглядит.
Давайте переключимся на демонстрационный компьютер.
Это наше демоприложение, которое мы будем использовать. Мы создадим средство визуализации A2UI, чтобы показать, насколько это просто.
Это набор запросов, которые можно отправить агенту. Они показаны на чипах в середине экрана.
Если мы нажмем на одну из подсказок, она будет отправлена реальному агенту. Скрестим пальцы, чтобы все прошло хорошо. Кроме того, мы добавим некоторые системные инструкции.
Итак, мы сообщаем серверной части о протоколе A2UI, о каталоге компонентов, в данном случае о базовом каталоге, который мы используем прямо здесь, который он может использовать для создания пользовательского интерфейса, и мы даем ему несколько примеров того, как это сделать.
Реализация сеанса чата в нашем демоприложении уже умеет извлекать необработанные сообщения A2UI в формате JSON из ответов агента.
В этом случае агент ответил текстом, за которым следовали сообщения протокола A2UI: createSurface, updateComponents.
Но в данный момент наше приложение просто показывает этот необработанный JSON и пока не отображает его как пользовательский интерфейс.
Прежде чем мы перейдем к реализации кода, давайте на секунду вернемся к слайдам и поговорим о дизайне системы, который мы будем использовать для реализации.
О, вот и оно.
Да, вот план.
Итак, ИИ-агент передает ответ в наше приложение, которое получает класс ChatSession.
ChatSession извлекает необработанные сообщения JSON A2UI из ответов агента по мере их поступления и передает их обработчику сообщений A2UI.
Обработчик сообщений настроен с помощью синтаксического анализатора, который преобразует необработанные сообщения JSON в семантическое представление.
Затем обработчик проверяет сообщения на соответствие определению каталога и сопоставляет их с набором моделей интерфейса.
Модели поверхности определяют, как выглядит пользовательский интерфейс и какими его частями управляет агент.
Затем эти модели поверхностей отображаются как нативный интерфейс Compose с помощью composable-функции поверхности A2UI, предоставляемой нашей библиотекой.
Обмен данными между агентом и интерфейсом двунаправленный, поэтому, когда пользователь взаимодействует с интерфейсом, действия пользователя и ошибки передаются обратно агенту.
Итак, у нас есть три основных компонента: каталог, обработчик сообщений и платформа.
Давайте напишем для него код.
Да, давайте построим его, если сможем переключить машины.
Итак, самый первый шаг – добавление зависимости.
Как видите, мы уже добавили зависимость Material 3 A2UI и синхронизировали проект, чтобы не ждать Gradle.
Это зависимость верхнего уровня, которая определяет реализацию базового каталога с использованием этих компонентов Material 3, но под ней есть и другие, более детализированные зависимости.
Если вы не хотите использовать базовый каталог (почему – мы расскажем позже), вы можете использовать более детализированные.
Но это приведет к тому, что остальные будут подтянуты транзитивно.
Окей.
Итак, мы сказали, что есть три вещи.
Первым шагом было определение каталога.
Итак, в нашем приложении мы собираемся создать фабричную функцию, которая поможет создать экземпляр этой реализации Material 3 базового каталога.
Поэтому мы используем здесь эту библиотечную функцию, базовый каталог материалов A2UI, который также имеет версию.
Как мы уже говорили, в нем есть все необходимые компоненты, но мы не хотим заставлять вас использовать определенные библиотеки для загрузки изображений или рендеринга медиаконтента, поэтому вам нужно предоставить их самостоятельно.
Например, Антон уже создал обертку вокруг composable-функции AsyncImage из библиотеки Coil, которую мы будем использовать для загрузки изображений, а также добавил немного логики для поиска изображений в Википедии.
И поэтому мы можем очень легко подключить это к компонентам каталога.
Аналогично, у нас есть похожие реализации для рендеринга, видео и аудио, где мы будем использовать библиотеку Media3 для их рендеринга с помощью ExoPlayer.
Вот так просто мы можем подключить к нему наши собственные компоненты.
Следующее, что нужно каталогу, это набор стандартных функций.
Итак, это функции, которые может предоставить клиент.
Мы поставляем реализацию этого вместе с базовым каталогом, но нам нужно предоставить ему контекст, который вы будете использовать для выполнения определенных действий, например открытия URI, если пользователь нажимает на элемент.
Вот и всё, первый шаг сделан.
Мы создали каталог.
Следующим шагом, который мы должны сделать, была настройка сообщения, процессора.
Итак, это то, что на самом деле анализирует ответы и так далее.
Итак, мы переключимся на модель представления нашего приложения, где мы сначала создадим экземпляр каталога, который мы настроили ранее, потому что он нам понадобится.
Затем мы создадим синтаксический анализатор сообщений, объект обработчика сообщений, которому нужен доступ к каталогу, чтобы проверять ответы на соответствие компонентам, которые мы хотим использовать.
Ещё один компонент, который нам нужно создать, – это синтаксический анализатор, который фактически является объектом, ответственным за синтаксический анализ сообщений JSON по мере их возврата.
Итак, здесь мы создаем объект ChatSession.
Как я уже говорил, ChatSession извлекает необработанные сообщения A2UI из ответов агента.
Когда новое сообщение A2UI готово, вызывается обратный вызов onA2uiMessage.
Поэтому нам нужно лишь передавать каждое из этих сообщений обработчику по мере их поступления.
Мы вызываем функцию processInput, передаем ей синтаксический анализатор и сообщение.
Таким образом, функция processInput анализирует каждое сообщение, а затем ставит в очередь работу для процессора, чтобы проверить его по каталогу и обновить модели поверхности.
Таким образом, процессор предоставляет текущий набор активных моделей поверхности в виде activeSurfaces StateFlow.
В ChatSession есть логика, которая добавляет модели Surface в состояния пользовательского интерфейса ответов агента для рендеринга.
Поэтому мы просто передаем этот activeSurfaces StateFlow в качестве параметра в ChatSession.
Как уже упоминалось, функция processInput ставит задачи в очередь для процессора.
Но чтобы фактически начать обработку, нам нужно запустить сопрограмму в фоновом потоке.
Поэтому мы будем использовать для этого область модели представления и установим диспетчер по умолчанию.
И в этой сопрограмме нам просто нужно вызвать processor.collectMessages.
Пока эта сопрограмма активна, процессор будет обрабатывать сообщения из очереди.
Итак, ещё одна вещь, которую нам нужно сделать, это когда пользователь очищает историю чата, нам также нужно очистить текущие поверхности.
Поэтому мы вызываем clearSurfaces.
Окей.
Итак, мы выполнили первые два шага.
Мы создали каталог, мы создали процессор для анализа сообщений.
Следующий шаг, который нам нужно сделать, – это фактически отобразить эти ответы в нашем пользовательском интерфейсе.
Поэтому мы собираем эти поверхности по мере их анализа синтаксическим анализатором.
Теперь в нашем пользовательском интерфейсе мы передадим их в этот объект ответа агента и просто переберем их.
Затем мы используем composable-функцию A2UI и передадим ей каждую из моделей, чтобы отобразить контент в нужном месте.
Обратите внимание, что агент может использовать список, если ему нужно показать большой объем контента.
Поэтому нам нужно задать ограниченную высоту – максимальную высоту этого компонента, чтобы он отображался правильно.
Итак, мы реализовали базовый рендерер A2UI.
Теперь, если мы вернемся в приложение, перезапустим его и снова введем запрос, агент должен использовать этот инструмент, чтобы, да, вот, отобразить настоящий интерфейс.
Поэтому мы просто пишем код в прямом эфире.
Это не просто иллюзия.
Но да, он использует реальные изображения.
Оно использует вкладки, диалоги, окна выбора даты, ползунки – все стандартные компоненты Material, которые вы бы использовали для создания приложения, а не просто показывает текстовый ответ.
Поскольку это реальное приложение, использующее реальные компоненты Material, оно делает то, что делают реальные компоненты.
Оно работает так же, как нативное.
Например, он реагирует на тёмную тему.
Это компоненты Material.
Поэтому, если у вас есть внешняя composable-функция тематического оформления Material Design, в которой вы настроили цвета, формы, типографику и т. д., эти компоненты подхватят их, чтобы они выглядели так же, как и остальная часть вашего приложения.
Как упоминал Ник, поскольку это нативные компоненты Compose, они имеют нативную семантику.
Это означает, что поддерживаются все встроенные специальные возможности Compose.
Поэтому такие функции, как TalkBack, масштабирование шрифта и навигация с помощью клавиатуры, работают по умолчанию.
Поскольку вы вызываете агента для создания нового пользовательского интерфейса во время выполнения, может возникнуть некоторая задержка.
Возможно, вам придется подождать ответа специалиста.
Библиотека делает все возможное, чтобы минимизировать эту задержку.
Поэтому мы добавим задержки, чтобы показать вам, как мы минимизируем это.
Да, поскольку агенту может потребоваться некоторое время, чтобы передать полный ответ, мы используем так называемый прогрессивный рендеринг.
Поэтому, как только поступает корневой компонент, мы начинаем отображать пользовательский интерфейс для поверхности.
Все компоненты контейнера показывают плейсхолдеры загрузки для своих дочерних элементов, если они ещё не загрузились.
Когда дочерние компоненты прибывают, они анимируются с использованием схемы движения Material 3.
Окей.
Давайте перейдем к следующему запросу, чтобы показать ещё одну функцию библиотеки.
Вот ещё один запрос, в котором мы бронируем столик на ужин.
А здесь агент выбирает компонент ползунка. Да, он его использовал.
Как видите, вы можете перетаскивать элементы и взаимодействовать с интерфейсом, который агент создал на лету.
Обратите внимание, что меняется не только число в компоненте, но и строка формата ниже, где оно подсчитывается.
Надеюсь, для этого не нужно отправлять запрос агенту туда и обратно, чтобы обновить его, верно?
Это двусторонняя привязка.
Таким образом, компонент ползунка привязан к модели данных в памяти этой поверхности.
Все компоненты, связанные с одним и тем же путем в модели данных, обновляются автоматически при изменении значения.
Таким образом, ползунок считывает значение для отображения, а также, когда я перетаскиваю ползунок, он записывает его обратно в модель, чтобы обновить его.
То есть все это происходит локально, без обращения к агенту.
Но мы также можем попросить агента обновить данные.
Пусть будет 6 человек.
Обратите внимание на ползунок, когда агент отвечает.
Теперь их шесть.
Давайте посмотрим, что мы получили от агента.
Вместо того чтобы отправлять полное описание интерфейса, агент отправил одно небольшое сообщение updateDataModel, которое обновило путь для ползунка.
Поскольку ползунок привязан к этому пути, он обновился на месте.
Таким образом, модель данных каждой поверхности построена на состоянии снимка Compose, а каждое свойство компонента, привязанное к пути, является производным состоянием.
Поэтому всякий раз, когда пользователь или агент обновляет значение в модели данных, все компоненты, привязанные к одному и тому же значению, автоматически перекомпоновываются.
Окей.
Допустим, мы хотим забронировать столик. Что произойдет, если мы нажмем кнопку "Забронировать"?
Ничего не происходит.
Кнопка отправляет действие, но мы пока не передаем его агенту.
Давайте это исправим.
Итак, как и в случае с обработчиком входных данных, нам нужно подключиться к выходным данным, то есть к действиям, которые пользователь совершает в интерфейсе, созданном агентом.
Для этого мы используем поток outboundEvents, который предоставляет процессор, и собираем данные.
Так что это ваш шанс, когда вы можете подключить любую логику, которую хотите, основываясь на том, когда пользователь нажимает на действие или выполняет что-то с пользовательским интерфейсом.
В этом примере мы отправляем все действия обратно агенту, чтобы он решил, что делать дальше. Но вы также можете решить, вызывать ли собственный бэкенд или перейти на другой экран в приложении.
Антон подключил его, и теперь, когда мы нажимаем кнопку подтверждения бронирования, это действие отправляется обратно агенту.
И здесь, вы знаете, вы могли бы на бэкэнде даже решить вызвать некоторые функции.
Давайте посмотрим, что мы отправляем агенту.
Это сообщение о действии A2UI.
У него есть контекст, который содержит набор свойств, запрошенных агентом для отправки при выполнении этого действия.
Когда я нажимаю кнопку, эти свойства определяются на основе текущих значений в модели данных, и именно это мы отправляем агенту.
Пока что агент был довольно полезен, но мы говорим об ИИ, а ИИ может ошибаться.
Давайте посмотрим, что произойдет, если агент вернет ошибку в интерфейсе, который он нам описывает.
В последнем запросе мы просим агента допустить ошибку, чтобы показать вам, как это работает.
Посмотрим, что произойдет.
Да.
Как видите, один из компонентов помечен как ошибочный.
Теперь все исправлено.
Это цикл самокоррекции агента.
Прежде чем каждый компонент достигнет пользовательского интерфейса, мы проверяем это сообщение на соответствие схеме каталога.
Если компонент не соответствует схеме, он помечается как ошибка.
При базовой реализации каталога ошибка будет показана только в том компоненте, в котором она возникла, а остальной интерфейс будет отображаться.
При этом средство визуализации отправляет агенту сообщение об ошибке, чтобы сообщить, что пошло не так.
В этом случае мы отправили сообщение о том, что тип компонента неизвестен и не найден в каталоге.
Затем агент мог отправить ответ, который обновлял существующий интерфейс, и интерфейс фиксировался на месте.
Да.
В текущей реализации базового каталога Material мы сразу показываем ошибки, но в вашем пользовательском компоненте вы можете решить продолжать показывать индикатор загрузки для компонента, если есть ошибка, ожидая, пока агент ее исправит.
Таким образом, ошибка может быть полностью скрыта от пользователя.
Что касается каталогов и пользовательских компонентов, то до сих пор мы использовали только базовый каталог, который поставляется в комплекте, но вы, конечно, можете настроить его в соответствии со своими потребностями.
Итак, вы видите, что когда мы используем фабричную функцию для создания каталога, они являются значениями по умолчанию для всех различных компонентов.
Поэтому вы можете легко подключить собственные компоненты, если захотите.
Например, если мы хотим подключить нашу собственную систему проектирования в нашем приложении и его собственную кнопку, мы можем легко это сделать.
Поэтому мы просто переопределяем этот параметр при создании экземпляра каталога и создаем это определение компонента.
Если мы создадим новый класс для реализации этого, вы увидите, что здесь есть интерфейс, базовый каталог A2UI кнопки типа.
Поэтому, если мы реализуем это, мы можем подключить наш собственный компонент.
Да.
Поскольку мы переопределяем компонент в базовом каталоге, нам нужно реализовать этот интерфейс кнопки базового каталога A2UI.
Таким образом, этот интерфейс уже содержит некоторую информацию о компоненте, такую как имя, описание и схема свойства.
Поэтому всё, что нам нужно сделать, чтобы реализовать собственную версию, – это реализовать эту composable-функцию.
Он принимает несколько параметров.
Таким образом, эти параметры – значения, полученные от агента после проверки.
Это такие вещи, как идентификатор дочернего элемента, который ссылается на компонент, который будет содержимым кнопки.
У него также есть вариант, который описывает тип кнопки, определение действия и некоторые другие параметры.
В рабочей реализации кнопки нам нужно будет использовать все эти параметры, но для демонстрации нам нужны только дочерний элемент и действие.
Давайте реализуем эту composable-функцию.
Итак, в этой composable-функции мы собираемся создать собственный тип кнопки "Нравится".
Как видите, мы можем использовать параметры, которые были предоставлены и проверены A2UI.
Например, в функции onClick мы можем отправить это действие.
Мы можем использовать этот тип библиотеки DispatchAction и передать ему действие, определенное A2UI.
Мы используем собственную кнопку, но хотим отображать дочерний контент, ярлык или что-то ещё, что решил использовать агент.
Для этого мы используем функцию observeA2uiComponentState и передаем ей childId, чтобы получить дескриптор дочернего контента, который должен быть отрисован.
Затем мы используем composable-функцию A2uiComponent и передадим ей состояние дочернего компонента для отрисовки.
Здесь вы видите, что дочерний контент отображается, только если он загружен успешно. Но вы можете добавить собственную логику для случаев, когда контент загружается или возникает ошибка.
Если мы вернемся к одному из примеров запросов в нашем демо-приложении и заменим кнопку стандартной кнопкой Material.
Надеюсь, вы видите, как это красиво.
Как видите, здесь мы также используем настраиваемую кнопку.
Отлично, теперь вы знаете, как заменить некоторые типы библиотеки. Но что, если вы хотите расширить библиотеку собственными типами?
Это можно сделать очень просто, добавив их в сам каталог.
Поэтому Антон создаст новый класс, чтобы реализовать компонент оценки.
Итак, мы собираемся добавить в наше приложение что-то, что позволит нам оценивать рестораны, пока мы находимся в этой гипотетической поездке.
Поскольку мы не заменяем существующий компонент, мы используем более общий интерфейс, интерфейс компонента A2UI, в котором, как вы видите, есть несколько параметров, которые нам нужно переопределить.
Итак, первые два — это название и описание.
Итак, это описывает, что представляет собой наш компонент и что он делает.
Это очень важно, потому что когда мы сериализуем каталог и отправляем его в бэкенд, чтобы сообщить, что он может использовать для создания пользовательского интерфейса, именно это агент будет использовать для принятия решения о том, использовать ли этот компонент при ответе.
Поэтому вам нужно быть достаточно описательным и сказать ИИ, что вы точно знаете, что это делает.
Следующее свойство, которое нам нужно переопределить, – это свойство properties, которое в основном определяет, с какими данными работает этот компонент.
Итак, мы делаем панель со звёздным рейтингом, чтобы вы могли оценить ресторан, и нам понадобятся два свойства.
Это будет значение, например, сколько звезд выбрал пользователь, которое будет изменяемым свойством, с которым пользователь может взаимодействовать и изменять, и максимальное количество звезд.
Итак, мы видим, что Антон использует это динамическое свойство числового типа для значения.
Это количество выбранных звезд.
У него тоже есть описание, которое объясняет агенту, для чего нужно это значение.
Затем у нас есть свойство статического типа, поэтому мы просто используем это свойство A2UI типа number, чтобы определить максимальное количество звезд, которое может показать этот компонент.
После того, как мы определили эти два свойства, нам просто нужно их вернуть.
Итак, переопределяя это свойство properties, мы создадим список и вернем его.
Так вы сообщаете агенту, с чем работает этот компонент.
Да.
Как упомянул Ник, эти определения свойств отправляются в составе схемы каталога, чтобы агент знал, как использовать этот компонент.
Но это свойство, те же определения свойств также используются для извлечения типизированных значений во время выполнения.
Таким образом, у вас будет единый источник достоверных данных.
Давайте посмотрим, что нужно сделать, чтобы реализовать это.
Итак, во-первых, чтобы получить доступ, скажем, к свойству max, поскольку свойство max является статическим свойством, это означает, что агент отправляет его значение встроенным в объект свойств.
Чтобы получить доступ к нему, нам просто нужно получить его из объекта свойств, сославшись на него, используя объект свойства proper-max в качестве ключа.
Поскольку это свойство определено как число, нам также необходимо преобразовать его в int и установить значение по умолчанию.
Следующий элемент – свойство value.
Поскольку свойство value является динамическим, оно ссылается на что-то в модели данных.
Чтобы получить доступ к нему, нужно использовать API properties.bind и передать объект свойства в качестве ключа как параметр.
Таким образом, он возвращает текущее значение, хранящееся в модели данных для этого свойства, а также подписывается на будущие обновления.
Поэтому при каждом обновлении модели данных этот компонент будет автоматически перекомпоновываться.
Аналогично, мы также преобразовали в int и установили некоторые значения по умолчанию.
Ещё нам нужно настроить двустороннюю привязку, чтобы можно было менять оценку.
Для этого мы используем API bindUpdater.
Мы передаем тот же объект свойства, который определяет свойство, и в результате получаем bindUpdater, который возвращает лямбда-функцию, позволяющую нам изменять значение этого свойства в модели данных.
Теперь мы готовы выпустить наш компонент оценки.
Окей.
Теперь, когда мы определили собственный компонент, нам нужно добавить его в каталог, который мы используем.
Раньше мы использовали базовый каталог как есть.
Теперь нам нужно создать новый каталог, который будет содержать все элементы базового каталога и наш собственный компонент.
Итак, каталог состоит из трёх вещей.
Это идентификатор, набор компонентов и набор функций.
Для идентификатора мы будем использовать URI, чтобы определить, как вы знаете, уникально идентифицирует каталог, а также версию, с которой мы работаем.
Затем мы возьмем все компоненты из базового каталога и добавим к ним созданный нами компонент.
А для функций мы просто будем использовать функции из базового каталога.
Таким образом, мы расширили базовый каталог, добавив в него новый тип пользовательского компонента.
Как видите, создать собственный каталог довольно просто.
Агент узнает, как использовать каталог, из системных инструкций, которые мы ему отправляем.
Поскольку мы создали собственный каталог, давайте также обновим системные инструкции.
Поэтому мы передадим объект каталога в нашу функцию, которая создает системные инструкции.
Вот и всё.
До сих пор мы отправляли статическое определение схемы для базового каталога, поскольку использовали стандартный каталог.
Теперь, когда у нас есть собственный каталог, мы можем динамически генерировать схему с помощью функции toJsonSchemaString.
Это создаст полное представление JSON этого каталога, включая компонент оценки, который мы только что добавили.
Системные инструкции также включают примеры ответов для агента.
Поэтому мы также меняем здесь идентификатор каталога, чтобы примеры ссылались на правильный каталог.
Окей.
Таким образом, мы расширили базовый каталог, добавив собственные типы.
Итак, если мы повторно развернем это и попробуем одну из этих подсказок, которая, надеюсь, даст нам пользовательский интерфейс рейтинга.
Затем агент отвечает.
И здесь вы можете видеть, что этот компонент звезды – это совершенно новый компонент, который агент никогда не видел, пока мы его не реализовали прямо сейчас.
Это не часть базового каталога, мы только что создали это.
Когда я нажимаю на звездочку, вызывается лямбда-функция bindUpdater и значение обновляется в модели данных.
Если мы отправим оценки, то сможем посмотреть действие, и в контексте действия будут значения, которые мы только что установили.
Хорошо, теперь мы можем вернуться к слайдам.
Мы быстро рассмотрели много информации, но, надеемся, смогли показать, как легко перейти от агентов, отвечающих стеной текста, к созданию реальных нативных интерфейсов.
Надеемся, мы показали вам, что интеграция довольно проста.
Вам нужно выполнить три основных действия: создать каталог компонентов, реализовать логику синтаксического анализа, а затем отобразить его в пользовательском интерфейсе.
Надеемся, мы смогли показать, что библиотека делает за вас много работы, например прогрессивный рендеринг, проверку схемы, циклы самокоррекции и обработку ошибок.
Надеемся, что сегодня мы смогли быстро продемонстрировать, насколько легко его настроить.
Мы показали, как базовый каталог уже реагирует на ваше тематическое оформление Material Design, если вы используете Material Design, но расширить или заменить компоненты в каталоге действительно просто.
Но я думаю, что наибольшую пользу вы получите, если создадите собственный каталог.
Если вы хотите полностью контролировать, какие элементы интерфейса может использовать агент, вы можете создать полностью настраиваемые компоненты.
Если вы хотите дать ему, знаете, полную свободу действий, вы можете дать ему очень простые компоненты, такие как текстовые метки и кнопки, и сказать: "Вы создаете пользовательский интерфейс".
Если вы хотите больше контроля, вы можете создать более сложные компоненты и сказать: "Это единственное, что вы можете использовать".
Таким образом, вы контролируете, что агент может делать и показывать пользователям.
Это новая библиотека рендеринга A2UI и библиотека Jetpack, которые мы только что запустили.
implementation("androidx.compose.material3:material3-a2ui:1.0.0-alpha02")Сейчас она находится на этапе альфа-тестирования, и недавно мы выпустили версию Alpha 02.
Перейдите по ссылкам ниже, чтобы узнать больше.
На сайте developer.android.com есть документация и несколько примеров.
У нас есть базовый пример, показывающий базовый каталог, а также более сложный пример и пример приложения JetPacker, которое мы только что создали. Вы снова увидите его в сегодняшнем докладе ADS, демонстрирующем, как можно создавать более сложные компоненты для управления тем, что видит пользователь.
Как я уже сказал, сейчас мы находимся на стадии альфа-тестирования, поэтому самое время попробовать и оставить отзыв, а также запросить новые функции и рассказать нам, как бы вы хотели использовать этот инструмент или что бы вы хотели с его помощью делать.
Поэтому, пожалуйста, загляните в библиотеку и оставьте отзыв.
И вот что у нас получилось.
И мне не терпится увидеть, что вы и ваши агенты создадите с его помощью.
Спасибо.
Спасибо!