Ajouter l'API AppFunctions à votre application

Ce guide explique comment intégrer l'API AppFunctions à votre application Android, implémenter la logique d'une fonction et vérifier que l'intégration fonctionne correctement.

Compatibilité des versions

Cette implémentation nécessite que le compileSdk de votre projet soit défini sur le niveau d'API 36 ou supérieur.

Votre application n'a pas besoin de vérifier si les AppFunctions sont compatibles. Cette opération est gérée automatiquement dans la bibliothèque Jetpack AppFunctions. AppFunctionManager renvoie une instance si la fonctionnalité est prise en charge, et la valeur "null" dans le cas contraire.

Dépendances

Ajoutez les dépendances de bibliothèque requises au fichier build.gradle.kts (ou build.gradle) de votre module, et configurez le plug-in KSP dans le module d'application de premier niveau comme indiqué :

# 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")
}

Implémenter la logique AppFunctions

Pour implémenter une AppFunction pour votre application Android, créez une classe qui implémente la logique AppFunctions spécifique. Cela implique de créer des classes de données sérialisables pour les paramètres et les réponses, puis de fournir la logique de base dans la méthode de fonction.

Le code suivant montre un exemple d'implémentation pour créer une tâche dans l'application TODO, y compris la définition de paramètres et de types de réponse personnalisés, ainsi que la logique de la fonction principale à l'aide d'un dépôt.

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)
}

Points clés concernant le code

  • Par défaut, une implémentation AppFunction s'exécute dans le thread UI Android. Par conséquent, une opération de longue durée doit effectuer les opérations suivantes :
    • Déclarez AppFunction comme fonction de suspension.
    • Passez à un répartiteur de coroutine approprié lorsque l'opération peut bloquer le thread.
  • Lorsque isDescribedByKDoc est défini sur true, la description de la fonction ou la description sérialisable est encodée dans AppFunctionMetadata pour aider l'agent à comprendre comment utiliser AppFunction de l'application.

Facultatif : Utilisez Hilt pour fournir une fabrique AppFunction personnalisée

Si votre classe d'implémentation AppFunction nécessite des dépendances dans son constructeur (comme dans TaskRepository dans l'exemple précédent), vous devez fournir une fabrique personnalisée pour que le système sache comment l'instancier. Cette étape est facultative et n'est nécessaire que si votre classe de fonction comporte des paramètres de constructeur. Cet exemple montre comment créer un AppFunctionFactory personnalisé et le configurer dans votre classe Application, en utilisant Hilt pour l'injection de dépendances.

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()
}

Facultatif : Activer/Désactiver la disponibilité d'AppFunction au moment de l'exécution

Utilisez l'API AppFunctionManager pour activer ou désactiver explicitement les fonctions lorsque vous contrôlez l'accès à vos AppFunctions. Le gating peut être utile lorsque certaines fonctionnalités de votre application ne sont pas disponibles pour tous les utilisateurs. En activant ou en désactivant dynamiquement les AppFunctions, le système d'intelligence sait exactement quelles fonctionnalités sont disponibles pour votre utilisateur à tout moment.

Pour protéger les AppFunctions qui nécessitent un état de compte spécifique, suivez une procédure en deux étapes :

Étape 1 : Désactiver la fonction par défaut

Pour empêcher l'accès à la fonction avant la validation de votre flag de fonctionnalité, définissez le paramètre isEnabled de votre annotation @AppFunction sur false.

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

Étape 2 : Activer dynamiquement la fonction lors de l'exécution

Pour chaque classe AppFunction, le compilateur génère une classe correspondante contenant des constantes d'ID de fonction (avec un suffixe Ids). Vous pouvez utiliser ces constantes d'ID générées avec la méthode setAppFunctionEnabled de AppFunctionManagerCompat pour modifier l'état activé d'une fonction au moment de l'exécution.

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,
        )
}

Considérations sur les types de fonctionnalités à rendre disponibles

La sécurité est toujours primordiale. Lorsque vous choisissez les fonctionnalités de votre application à rendre disponibles en tant qu'AppFunctions, il est important de se rappeler que les agents système peuvent traiter les requêtes des utilisateurs sur le serveur pour exploiter les fonctionnalités avancées du LLM.

Pour offrir une expérience utilisateur optimale tout en évitant d'exposer des informations sensibles, nous vous recommandons de suivre ces consignes :

  • Fonctionnalités qui bénéficient du langage naturel : proposez des tâches qu'un utilisateur peut exprimer plus facilement dans une conversation que par le biais d'une navigation manuelle dans l'UI.
  • Accès limité : créez des AppFunctions qui n'accordent à l'agent que l'accès aux données et aux actions nécessaires pour répondre à la demande spécifique de l'utilisateur.
  • Informations non sensibles : ne partagez que les données qui ne sont pas très personnelles ni confidentielles, ou les données que l'utilisateur accepte explicitement de partager dans le contexte de l'action.
  • Confirmation non ambiguë pour toute action destructive : soyez extrêmement prudent avec les fonctions qui effectuent des actions destructives (comme la suppression de données). Bien que l'agent puisse les appeler, votre application doit inclure sa propre étape de confirmation et utiliser un langage clair et non ambigu concernant les intentions. Il est également utile d'ajouter plusieurs étapes de confirmation pour s'assurer que l'utilisateur est conscient de ce qui lui est demandé.

Vérifier l'intégration d'AppFunction

Pour vérifier si vous avez correctement intégré AppFunctions, vous pouvez utiliser adb shell cmd app_function.

Utilisez adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName pour afficher les détails des AppFunctions fournies par votre application.

Dans Gemini dans Android Studio ou d'autres agents de votre choix, fournissez une requête telle que celle ci-dessous.

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.