Aggiungere l'API AppFunctions alla tua app

Questa guida spiega come integrare l'API AppFunctions nella tua app per Android, implementare la logica per una funzione e verificare che l'integrazione funzioni correttamente.

Compatibilità delle versioni

Questa implementazione richiede che il progetto compileSdk sia impostato sul livello API 37 o superiore. Il progetto deve utilizzare il plug-in Android per Gradle 9.1.0 o versioni successive.

La tua app non è tenuta a verificare se le AppFunctions sono supportate; questa operazione viene gestita automaticamente all'interno della libreria Jetpack AppFunctions. AppFunctionManager restituisce un'istanza se la funzionalità è supportata, altrimenti restituisce null.

Dipendenze

Aggiungi le dipendenze della libreria richieste al file build.gradle.kts (o build.gradle) del modulo e configura il plug-in KSP nel modulo dell'app di primo livello come mostrato:

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

Implementa la logica di AppFunctions

Per implementare un'AppFunction per la tua app per Android, crea una classe che implementi la logica specifica delle AppFunction. Ciò comporta la creazione di classi di dati serializzabili per parametri e risposte, quindi la fornitura della logica di base all'interno del metodo della funzione.

Il codice seguente mostra un'implementazione di esempio per la creazione di un'attività nell'app TODO, inclusa la definizione di parametri personalizzati e tipi di risposta e la logica della funzione principale utilizzando un repository.

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

Punti chiave sul codice

  • Per impostazione predefinita, un'implementazione di AppFunction viene eseguita nel thread dell'interfaccia utente di Android. Pertanto, un'operazione a lunga esecuzione deve eseguire le seguenti operazioni:
    • Dichiara AppFunction come funzione di sospensione.
    • Passa a un coroutine dispatcher adatto quando l'operazione potrebbe bloccare il thread.
  • Quando isDescribedByKDoc è impostato su true, la descrizione della funzione o la descrizione serializzabile viene codificata come parte di AppFunctionMetadata per aiutare l'agente a capire come utilizzare l'AppFunction dell'app. A partire dalla versione 1.0.0-beta01, isDescribedByKDoc non ha un valore predefinito, quindi impostalo esplicitamente.

Dichiara il servizio AppFunction nel manifest

Registra la dichiarazione del servizio generata da KSP e la proprietà app_metadata all'interno del manifest del modulo, ad esempio in src/main/AndroidManifest.xml. Il compilatore KSP genera la classe di servizio concreta (TaskAppFunctionService) che estende la classe del punto di accesso astratto, insieme allo schema XML corrispondente nella directory 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" />

(Facoltativo) Attiva/disattiva la disponibilità di AppFunction in fase di runtime

Utilizza l'API AppFunctionManager per attivare o disattivare esplicitamente le funzioni quando limiti le tue AppFunction. La limitazione può essere utile quando alcune funzionalità della tua app non sono disponibili per tutti gli utenti. Attivando o disattivando dinamicamente AppFunctions, il sistema di intelligence sa esattamente quali funzionalità sono disponibili per l'utente in un determinato momento.

Per proteggere in modo sicuro le AppFunction che richiedono uno stato dell'account specifico, segui una procedura in due passaggi:

Passaggio 1: Disattiva la funzione per impostazione predefinita

Per impedire l'accesso alla funzione prima che il flag di funzionalità venga verificato, imposta il parametro isEnabled dell'annotazione @AppFunctionDeclaration su false.

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

Passaggio 2: Attiva dinamicamente la funzione in fase di runtime

Per ogni classe @AppFunctionServiceEntryPoint, il compilatore genera una classe corrispondente contenente costanti ID funzione (utilizzando un suffisso Ids). Puoi utilizzare queste costanti ID generate insieme al metodo setAppFunctionEnabled di AppFunctionManager per modificare lo stato di attivazione di una funzione in fase di runtime.

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

Considerazioni sui tipi di funzionalità da rendere disponibili

La sicurezza è sempre fondamentale. Quando scegli quali funzionalità della tua app rendere disponibili come AppFunction, è importante ricordare che gli agenti di sistema potrebbero elaborare le query degli utenti sul server per sfruttare le funzionalità avanzate degli LLM.

Per offrire un'esperienza utente ottimale ed evitare di esporre informazioni sensibili, ti consigliamo di seguire queste linee guida:

  • Funzionalità che traggono vantaggio dal linguaggio naturale: rendi disponibili attività che un utente può esprimere più facilmente in una conversazione rispetto alla navigazione manuale nell'interfaccia utente.
  • Accesso limitato: crea AppFunction che consentono all'agente di accedere solo ai dati e alle azioni necessari per soddisfare la richiesta specifica dell'utente.
  • Informazioni non sensibili: condividi solo dati che non sono altamente personali o riservati o dati per i quali l'utente acconsente esplicitamente alla condivisione nel contesto dell'azione.
  • Conferma inequivocabile per qualsiasi azione distruttiva: presta la massima attenzione alle funzioni che eseguono azioni distruttive (come l'eliminazione di dati). Anche se l'agente potrebbe richiamarle, l'app deve includere un proprio passaggio di conferma e utilizzare un linguaggio chiaro e non ambiguo in merito alle intenzioni. È anche utile aggiungere più di un passaggio di conferma per assicurarsi che l'utente sia consapevole di ciò che gli viene chiesto di fare.

Verifica l'integrazione di AppFunction

Per verificare se hai integrato correttamente AppFunctions, puoi utilizzare adb shell cmd app_function.

Utilizza adb shell cmd app_function list-app-functions --package $myPackageName per visualizzare i dettagli delle AppFunctions fornite dalla tua app.

Puoi anche eseguire un'AppFunction direttamente dalla riga di comando utilizzando il suo identificatore esplicito ("$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\"}}'"

Per provare Android MCP in azione e verificare i workflow end-to-end senza bisogno di prompt, installa ed esegui l'app per Android AppFunctions testing agent sul tuo dispositivo.

Se stai verificando l'integrazione utilizzando assistenti basati su chat come Gemini in Android Studio, utilizza la skill di sviluppo AppFunctions o fornisci un prompt come il seguente:

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.
Use the AppFunction descriptions to choose functions and parameters.

Esegui la migrazione da versioni API precedenti

Nella versione 1.0.0-alpha10, AppFunctions ha introdotto un'architettura @AppFunctionServiceEntryPoint in fase di compilazione che consolida le dipendenze della libreria e sostituisce i provider di configurazione legacy (AppFunctionConfiguration.Provider). La versione 1.0.0-alpha12 ha rimosso i provider legacy. La versione 1.0.0-alpha13 ha rinominato l'annotazione @AppFunction in @AppFunctionDeclaration, mentre la versione 1.0.0-beta01 ha rinominato l'attributo appfn:description nel file XML dei metadati dell'app in appfn:instructions.

Se la tua app utilizza una versione precedente di AppFunctions (ad esempio 1.0.0-alpha09 o 1.0.0-alpha10), puoi automatizzare la migrazione utilizzando la skill dell'agente AppFunctions in un IDE AI come Gemini in Android Studio. La skill contiene regole di migrazione dedicate che guidano un agente nel consolidamento delle dipendenze di build, nella creazione del wrapper del servizio @AppFunctionServiceEntryPoint richiesto, nel disaccoppiamento dei parametri di contesto, nell'aggiornamento delle dichiarazioni del manifest e nell'applicazione delle ridenominazioni della versione 1.0.0-beta01.