Как добавить в приложение API AppFunctions

В этом руководстве объясняется, как интегрировать AppFunctions API в приложение Android, реализовать логику функции и проверить, правильно ли работает интеграция.

Совместимость версий

Для этой реализации требуется, чтобы в вашем проекте compileSdk был установлен уровень API 36 или выше.

Приложению не нужно проверять, поддерживаются ли AppFunctions. Это автоматически выполняется в библиотеке Jetpack AppFunctions. AppFunctionManager возвращает экземпляр, если функция поддерживается, и null, если нет.

Связанные запросы

Добавьте необходимые зависимости библиотек в файл build.gradle.kts (или build.gradle) модуля и настройте плагин KSP в модуле приложения верхнего уровня, как показано ниже.

dependencies {
  implementation("androidx.appfunctions:appfunctions:1.0.0-alpha10")
  // If this project uses any Kotlin source, use Kotlin Symbol Processing (KSP)
  // See Add the KSP plugin to your project
  ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha10")
}

Реализуйте логику AppFunctions

Чтобы реализовать AppFunction для приложения Android, создайте класс, реализующий логику AppFunctions. Для этого нужно создать сериализуемые классы данных для параметров и ответов, а затем добавить основную логику в метод функции.

В примере кода ниже показано, как создать задачу в приложении TODO, в том числе как определить специальные параметры и типы ответов, а также основную логику функции с помощью репозитория.

@RequiresApi(36)
@AndroidEntryPoint
@AppFunctionServiceEntryPoint(
    serviceName = "TaskAppFunctionService",
    appFunctionXmlFileName = "task_app_function_service",
)
abstract class BaseTaskAppFunctionService : AppFunctionService() {
    @Inject internal lateinit var taskRepository: TaskRepository

    /**
     * Creates a task based on [createTaskParams].
     *
     * @param createTaskParams The parameter to describe how to create the task.
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun createTask(
        createTaskParams: CreateTaskParams,
    ): Task = withContext(Dispatchers.IO) {
        // Developers can use predefined exceptions to let the agent know
        // why it failed.
        if (createTaskParams.title == null && createTaskParams.content == null) {
            throw AppFunctionInvalidArgumentException("Title or content should be non-null")
        }

        val id = taskRepository.createTask(
            createTaskParams.title,
            createTaskParams.content
        )

        return@withContext taskRepository
            .getTask(id)
            ?.toTask()
            ?: throw AppFunctionElementNotFoundException("Task not found for ID = $id")
    }

    // Maps internal TaskEntity
    private fun TaskEntity.toTask() = Task(id = id, title = title, content = description)
}

Важные сведения о коде

  • По умолчанию реализация AppFunction выполняется в потоке UI Android. Поэтому продолжительная операция должна выполнять следующие действия:
    • Объявите AppFunction как функцию приостановки.
    • Переключитесь на подходящий диспетчер сопрограмм, если операция может заблокировать поток.
  • Если для параметра isDescribedByKDoc задано значение true, описание функции или сериализуемое описание кодируется как часть AppFunctionMetadata, чтобы помочь агенту понять, как использовать AppFunction приложения.

Объявите сервис AppFunction в манифесте

Зарегистрируйте сгенерированное KSP объявление сервиса и свойство app_metadata в манифесте модуля, например в src/main/AndroidManifest.xml. Компилятор KSP генерирует конкретный класс сервиса (TaskAppFunctionService), расширяющий ваш абстрактный класс точки входа, вместе с соответствующей XML-схемой в каталоге assets/.

<service
    android:name="com.example.snippets.ai.TaskAppFunctionService"
    android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
    android:exported="true"
    tools:targetApi="36">
    <property
        android:name="android.app.appfunctions.schema"
        android:value="app_functions_schema.xsd" />
    <property
        android:name="android.app.appfunctions.v2"
        android:value="task_app_function_service.xml" />
    <intent-filter>
        <action android:name="android.app.appfunctions.AppFunctionService" />
    </intent-filter>
</service>
<property
    android:name="android.app.appfunctions.app_metadata"
    android:resource="@xml/app_metadata" />

Как включить или отключить доступность AppFunction во время выполнения (необязательно)

Используйте API AppFunctionManager, чтобы явно включать или отключать функции при ограничении доступа к AppFunctions. Это может быть полезно, если некоторые функции приложения доступны не всем пользователям. Динамически включая или отключая AppFunctions, интеллектуальная система точно знает, какие функции доступны пользователю в любой момент времени.

Чтобы безопасно ограничить доступ к AppFunctions, требующим определенного состояния аккаунта, выполните двухэтапный процесс:

Шаг 1. Отключить функцию по умолчанию

Чтобы функция не была доступна до проверки флага функции, задайте для параметра isEnabled аннотации @AppFunction значение false.

@AppFunctionDeclaration(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(
    createTaskParams: CreateTaskParams,
): Task = TODO()

Шаг 2. Динамически включать функцию во время выполнения

Для каждого класса AppFunction компилятор генерирует соответствующий класс, содержащий константы идентификаторов функций (с суффиксом Ids). Вы можете использовать эти сгенерированные константы идентификаторов вместе с методом setAppFunctionEnabled из AppFunctionManagerCompat, чтобы изменить состояние включения функции во время выполнения.

suspend fun onFeatureEnabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_ENABLED,
            )
    } catch (e: Exception) {
        // Handle exception: AppFunctions indexation may not be fully completed
        // upon initial app startup.
    }
}

suspend fun onFeatureDisabled(context: Context) {
    try {
        AppFunctionManager.getInstance(context)
            ?.setAppFunctionEnabled(
                BaseTaskAppFunctionServiceIds.CREATE_TASK_ID,
                AppFunctionManager.APP_FUNCTION_STATE_DISABLED,
            )
    } catch (e: Exception) {
        // Handle exception
    }
}

Рекомендации по выбору функций, которые следует сделать доступными

Безопасность всегда имеет первостепенное значение. Выбирая, какие возможности приложения сделать доступными в качестве AppFunctions, важно помнить, что системные агенты могут обрабатывать запросы пользователей на сервере, чтобы использовать расширенные возможности LLM.

Чтобы обеспечить удобство для пользователей и избежать раскрытия конфиденциальной информации, рекомендуем следовать приведенным ниже инструкциям.

  • Функции, которые выигрывают от использования естественного языка. Сделайте доступными задачи, которые пользователю проще выразить в разговоре, чем через ручную навигацию по интерфейсу.
  • Ограничьте доступ. Создайте AppFunctions, которые предоставляют агенту доступ только к данным и действиям, необходимым для выполнения конкретного запроса пользователя.
  • Неконфиденциальная информация. Передавайте только те данные, которые не являются личными или конфиденциальными, или те, на передачу которых пользователь дал явное согласие в контексте действия.
  • Однозначное подтверждение для любых деструктивных действий. Будьте крайне осторожны с функциями, которые выполняют деструктивные действия, например удаляют данные. Даже если агент вызывает их, в вашем приложении должен быть предусмотрен собственный этап подтверждения с четкими и однозначными формулировками. Также полезно добавить несколько шагов подтверждения, чтобы пользователь точно понимал, что от него требуется.

Проверка интеграции AppFunction

Чтобы проверить, правильно ли вы интегрировали AppFunctions, используйте adb shell cmd app_function.

Используйте adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName, чтобы посмотреть подробные сведения о функциях AppFunctions, которые предоставляет ваше приложение.

Вы также можете выполнить AppFunction напрямую из командной строки, используя явный идентификатор ("$enclosingClassName#$methodName"):

adb shell "cmd app_function execute-app-function \
  --package com.example.android.appfunctions \
  --function 'com.example.android.appfunctions.BaseTaskAppFunctionService#createTask' \
  --parameters '{\"createTaskParams\": {\"title\": \"Buy milk\", \"content\": \"From grocery store\"}}'"

Чтобы посмотреть, как работает Android MCP, и проверить сквозные рабочие процессы без подсказок, установите и запустите на устройстве приложение Android AppFunctions testing agent.

Если вы проверяете интеграцию с помощью чат-ботов, таких как Gemini в Android Studio, используйте навык разработки AppFunctions или введите запрос, например:

Execute `adb shell cmd app_function` to learn how the tool works, then act as a
chat agent aiming to invoke AppFunctions to fulfil user prompts for this app.
Rely on the AppFunction description as instructions.

Переход с более ранних версий API

В версии 1.0.0-alpha10 в AppFunctions появилась архитектура @AppFunctionServiceEntryPoint, которая объединяет зависимости библиотек и заменяет устаревшие поставщики конфигураций (AppFunctionConfiguration.Provider).

Если в вашем приложении используется более ранняя версия AppFunctions (например, 1.0.0-alpha09), вы можете автоматизировать перенос с помощью навыка агента AppFunctions в ИИ-среде разработки, например Gemini в Android Studio. В навыке есть специальные правила переноса, которые помогают агенту объединить зависимости сборки, создать необходимую оболочку сервиса @AppFunctionServiceEntryPoint, разделить параметры контекста и обновить объявления манифеста.