Добавьте API AppFunctions в свое приложение.

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

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

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

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

Зависимости

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

# Add this to your app module at the top level. For multi module applications,
# you only need to specify this once.
ksp {
  arg("appfunctions:aggregateAppFunctions", "true")
}

dependencies {
  implementation("androidx.appfunctions:appfunctions:1.0.0-alpha09")
  implementation("androidx.appfunctions:appfunctions-service:1.0.0-alpha09")
  // 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-alpha09")
}

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

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

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

package com.example.android.appfunctions


import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionContext
import androidx.appfunctions.AppFunctionElementNotFoundException
import androidx.appfunctions.AppFunctionInvalidArgumentException
import androidx.appfunctions.service.AppFunction
import javax.inject.Inject
...

// Developers can provide additional parameters in the constructor if needed.
// This requires a custom factory setup in the next step.
class TaskFunctions @Inject constructor(
  private val taskRepository: TaskRepository
) {
  /** The parameter to create the task. */
  @AppFunctionSerializable(isDescribedByKDoc = true)
  data class CreateTaskParams(
    /** The title of the task. */
    val title: String,
    /** The content of the task. */
     val content: String
  )

  /** The user-created task. */
  @AppFunctionSerializable(isDescribedByKDoc = true)
  data class Task(
    /** The ID of the task. */
    val id: String,
    /** The title of the task. */
    val title: String,
    /** The content of the task. */
    val content: String
  )

  /**
   * Creates a task based on [createTaskParams].
   *
   * @param createTaskParams The parameter to describe how to create the task.
   */
  @AppFunction(isDescribedByKDoc = true)
  suspend fun createTask(
    appFunctionContext: AppFunctionContext,
    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 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 выполняется в потоке пользовательского интерфейса Android. Поэтому длительная операция должна выполнять следующие действия:
    • Объявите AppFunction как функцию приостановки.
    • Переключитесь на подходящий диспетчер сопрограмм, если операция может заблокировать поток.
  • Если isDescribedByKDoc установлен в true , описание функции или сериализуемое описание кодируется как часть AppFunctionMetadata , чтобы помочь агенту понять, как использовать AppFunction приложения.

Необязательно: используйте Hilt для предоставления собственной фабрики AppFunction.

Если ваш класс реализации AppFunction требует наличия зависимостей в конструкторе (как в TaskRepository в предыдущем примере), вам необходимо предоставить пользовательскую фабрику, чтобы система знала, как её создать. Это необязательный шаг, необходимый только в том случае, если ваш класс функции имеет параметры конструктора. В этом примере показано, как создать пользовательскую AppFunctionFactory и настроить её в классе Application , используя Hilt для внедрения зависимостей.

import android.app.Application
import androidx.appfunctions.service.AppFunctionConfiguration
import com.example.android.appfunctions.TaskFunctions
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject

@HiltAndroidApp
class TodoApplication : Application(), AppFunctionConfiguration.Provider {
  @Inject lateinit var taskFunctions: TaskFunctions
  override fun onCreate() {
    super.onCreate()
  }
  // This shows how AppFunctions works with Hilt.
  override val appFunctionConfiguration: AppFunctionConfiguration
    get() =
      AppFunctionConfiguration.Builder()
        .addEnclosingClassFactory(TaskFunctions::class.java) { taskFunctions }
        .build()
}

Необязательно: Включение/выключение доступности функций приложения во время выполнения.

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

Для безопасного доступа к функциям приложения, требующим определенного состояния учетной записи, выполните два шага:

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

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

@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(...) { ... }

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

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

import androidx.appfunctions.AppFunctionManager
// Assuming there is a hook API to observe user state or feature flags
suspend fun onFeatureEnabled() {
    try {
        AppFunctionManager.getInstance(context)
            .setAppFunctionEnabled(
                // Function ID is generated for developer to get
                TaskFunctionsIds.CREATE_TASK_ID,
                AppFunctionManagerCompat.APP_FUNCTION_STATE_ENABLED,
            )
    } catch (e: Exception) {
        // Handle exception: AppFunctions indexation may not be fully completed
        // upon initial app startup.
    }
}

suspend fun onFeatureDisabled() {
    AppFunctionManagerCompat.getInstance(context)
        .setAppFunctionEnabled(
            TaskFunctionsIds.CREATE_TASK_ID,
            AppFunctionManagerCompat.APP_FUNCTION_STATE_DISABLED,
        )
}

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

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

Для обеспечения удобного пользовательского интерфейса и предотвращения разглашения конфиденциальной информации мы рекомендуем следовать этим рекомендациям:

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

Проверьте интеграцию AppFunction.

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

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

В Gemini в Android Studio или в других агентах по вашему выбору введите запрос, подобный следующему.

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.