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

Pour cette implémentation, votre projet compileSdk doit être 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. Cela est géré automatiquement dans la bibliothèque Jetpack AppFunctions. AppFunctionManager renvoie une instance si la fonctionnalité est compatible, et une valeur nulle si ce n'est pas le cas.

Dépendances

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

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

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. Pour ce faire, vous devez créer des classes de données sérialisables pour les paramètres et les réponses, puis fournir la logique de base dans la méthode de la 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 personnalisés et de types de réponses, ainsi que la logique de la fonction principale à l'aide d'un dépôt.

@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.
     */
    @AppFunction(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)
}

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 l'AppFunction en tant que fonction de suspension.
    • Passez à un coordinateur de coroutines approprié lorsque l'opération risque de bloquer le thread.
  • Lorsque isDescribedByKDoc est défini sur true, la description de la fonction ou la description sérialisable est encodée dans le AppFunctionMetadata pour aider l'agent à comprendre comment utiliser l'AppFunction de l'application.

Déclarer le service AppFunction dans votre fichier manifeste

Enregistrez la déclaration de service générée par KSP et la propriété app_metadata dans le fichier manifeste de votre module, par exemple dans src/main/AndroidManifest.xml. Le compilateur KSP génère la classe de service concrète (TaskAppFunctionService) qui étend votre classe de point d'entrée abstraite, ainsi que le schéma XML correspondant dans votre répertoire 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" />

Facultatif : Activez ou désactivez la disponibilité d'AppFunction au moment de l'exécution

Utilisez l'API AppFunctionManager pour activer ou désactiver explicitement des fonctions lorsque vous contrôlez 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 AppFunctions de manière dynamique, le système d'intelligence sait exactement quelles fonctionnalités sont disponibles pour votre utilisateur à un moment donné.

Pour sécuriser les AppFunctions qui nécessitent un état de compte spécifique, suivez un processus en deux étapes :

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

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

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

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

Pour chaque classe AppFunction, le compilateur génère une classe correspondante contenant des constantes d'ID de fonction (à l'aide d'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 d'activation d'une fonction au moment de l'exécution.

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

Considérations sur les types de fonctionnalités à mettre à disposition

La sécurité est toujours primordiale. Lorsque vous choisissez les fonctionnalités de votre application à rendre disponibles en tant qu'AppFunctions, n'oubliez pas que les agents système peuvent traiter les requêtes des utilisateurs sur le serveur pour exploiter les fonctionnalités avancées des 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 : rendez disponibles les tâches qu'un utilisateur peut exprimer plus facilement dans une conversation que par une navigation manuelle dans l'interface utilisateur.
  • Accès restreint : créez des AppFunctions qui ne donnent à l'agent l'accès qu'aux données et aux actions nécessaires pour répondre à la requête spécifique de l'utilisateur.
  • Informations non sensibles : ne partagez que des données qui ne sont pas hautement personnelles ou confidentielles, ou des données que l'utilisateur accepte explicitement de partager dans le contexte de l'action.
  • Confirmation sans ambiguïté pour toute action destructive : soyez extrêmement prudent avec les fonctions qui effectuent des actions destructives (comme la suppression de données). Même si l'agent peut les appeler, votre application doit inclure sa propre étape de confirmation et utiliser un langage clair et sans ambiguïté concernant les intentions. Il est également utile d'ajouter plusieurs étapes de confirmation pour vous assurer que l'utilisateur est bien conscient de ce qu'on lui demande de faire.

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.

Vous pouvez également exécuter une AppFunction directement à partir de la ligne de commande à l'aide de son identifiant explicite ("$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\"}}'"

Pour découvrir Android MCP en action et vérifier les workflows de bout en bout sans avoir besoin de prompts, installez et exécutez l'application Android AppFunctions testing agent sur votre appareil.

Si vous validez votre intégration à l'aide d'assistants basés sur le chat comme Gemini dans Android Studio, utilisez la compétence de développement AppFunctions ou fournissez un prompt comme celui-ci :

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.

Migrer depuis des versions d'API antérieures

Dans la version 1.0.0-alpha10, AppFunctions a introduit une architecture @AppFunctionServiceEntryPoint au moment de la compilation qui consolide les dépendances de bibliothèque et remplace les anciens fournisseurs de configuration (AppFunctionConfiguration.Provider).

Si votre application utilise actuellement une version antérieure d'AppFunctions (comme la version 1.0.0-alpha09), vous pouvez automatiser votre migration à l'aide de la compétence d'agent AppFunctions dans un IDE d'IA comme Gemini dans Android Studio. La compétence contient des règles de migration dédiées qui guident un agent pour consolider vos dépendances de compilation, créer le wrapper de service @AppFunctionServiceEntryPoint requis, dissocier les paramètres de contexte et mettre à jour vos déclarations de fichier manifeste.