Agrega la API de AppFunctions a tu app

En esta guía, se explica cómo integrar la API de AppFunctions en tu app para Android, implementar la lógica de una función y verificar que la integración funcione correctamente.

Compatibilidad de versiones

Esta implementación requiere que tu proyecto compileSdk se establezca en el nivel de API 36 o uno posterior.

No es necesario que tu app verifique si se admiten las AppFunctions, ya que esto se controla automáticamente en la biblioteca de Jetpack de AppFunctions. AppFunctionManager devuelve una instancia si se admite la función y, de lo contrario, devuelve un valor nulo.

Dependencias

Agrega las dependencias de biblioteca requeridas al archivo build.gradle.kts (o build.gradle) de tu módulo y configura el complemento de KSP en el módulo de la app de nivel superior, como se muestra a continuación:

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

Implementa la lógica de AppFunctions

Para implementar una AppFunction en tu app para Android, crea una clase que implemente la lógica específica de AppFunctions. Esto implica crear clases de datos serializables para los parámetros y las respuestas y, luego, proporcionar la lógica principal dentro del método de la función.

En el siguiente código, se muestra un ejemplo de implementación para crear una tarea en la app de tareas pendientes, incluida la definición de parámetros personalizados y tipos de respuesta, y la lógica de la función principal con un repositorio.

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

Puntos clave sobre el código

  • De forma predeterminada, una implementación de AppFunction se ejecuta en el subproceso de IU de Android. Por lo tanto, una operación de larga duración debe hacer lo siguiente:
    • Declara la AppFunction como una función de suspensión.
    • Cambia a un despachador de corrutinas adecuado cuando la operación pueda bloquear el subproceso.
  • Cuando isDescribedByKDoc se establece en true, la descripción de la función o la descripción serializable se codifica como parte de AppFunctionMetadata para ayudar al agente a comprender cómo usar la AppFunction de la app.

Declara el servicio de AppFunction en tu manifiesto

Registra la declaración de servicio generada por KSP y la propiedad app_metadata dentro del manifiesto del módulo, por ejemplo, en src/main/AndroidManifest.xml. El compilador de KSP genera la clase de servicio concreta (TaskAppFunctionService) que extiende tu clase de punto de entrada abstracta, junto con el esquema XML correspondiente en tu directorio 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" />

Opcional: Activa o desactiva la disponibilidad de AppFunction en el entorno de ejecución

Usa la API de AppFunctionManager para habilitar o inhabilitar funciones de forma explícita cuando restrinjas tus AppFunctions. La restricción puede ser útil cuando algunas funciones de tu app no están disponibles para todos los usuarios. Cuando se habilitan o inhabilitan de forma dinámica las AppFunctions, el sistema de inteligencia sabe exactamente qué funciones están disponibles para el usuario en un momento determinado.

Para controlar de forma segura las AppFunctions que requieren un estado de cuenta específico, sigue un proceso de dos pasos:

Paso 1: Inhabilitar la función de forma predeterminada

Para evitar que se pueda acceder a la función antes de que se verifique la marca de función, establece el parámetro isEnabled de la anotación @AppFunction en false.

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

Paso 2: Habilitar la función de forma dinámica en el entorno de ejecución

Para cada clase AppFunction, el compilador genera una clase correspondiente que contiene constantes de ID de función (con un sufijo Ids). Puedes usar estas constantes de ID generadas junto con el método setAppFunctionEnabled de AppFunctionManagerCompat para cambiar el estado habilitado de una función durante el tiempo de ejecución.

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

Consideraciones sobre los tipos de funciones que se deben poner a disposición

La seguridad siempre es primordial. Cuando elijas qué funciones de tu app estarán disponibles como AppFunctions, es importante recordar que los agentes del sistema pueden procesar las consultas de los usuarios en el servidor para aprovechar las funciones avanzadas de LLM.

Para proporcionar una excelente experiencia del usuario que también evite exponer información sensible, te recomendamos que sigas estos lineamientos:

  • Funcionalidad que se beneficia del lenguaje natural: Haz que estén disponibles las tareas que son más fáciles de expresar para un usuario en una conversación que a través de la navegación manual por la IU.
  • Acceso limitado: Crea AppFunctions que solo le den al agente acceso a los datos y las acciones que se necesitan para completar la solicitud específica del usuario.
  • Información no sensible: Solo comparte datos que no sean muy personales o confidenciales, o datos que el usuario acepte compartir de forma explícita en el contexto de la acción.
  • Confirmación inequívoca para cualquier acción destructiva: Ten mucho cuidado con las funciones que realizan acciones destructivas (como borrar datos). Si bien el agente puede invocarlas, tu app debe incluir su propio paso de confirmación y usar un lenguaje claro y sin ambigüedades sobre las intenciones. También es útil agregar más de un paso de confirmación para garantizar que el usuario sepa lo que se le pide que haga.

Verifica la integración de AppFunction

Para verificar si integraste correctamente AppFunctions, puedes usar adb shell cmd app_function.

Usa adb shell cmd app_function list-app-functions | grep --after-context 10 $myPackageName para ver los detalles de las AppFunctions que proporciona tu app.

También puedes ejecutar una AppFunction directamente desde la línea de comandos con su identificador explícito ("$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\"}}'"

Para experimentar el MCP de Android en acción y verificar los flujos de trabajo de extremo a extremo sin necesitar instrucciones, instala y ejecuta la app para Android del agente de pruebas de AppFunctions en tu dispositivo.

Si verificas tu integración con asistentes basados en chat, como Gemini en Android Studio, usa la habilidad de desarrollo de AppFunctions o proporciona una instrucción como la siguiente:

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.

Migra desde versiones de API anteriores

En la versión 1.0.0-alpha10, AppFunctions introdujo una arquitectura de @AppFunctionServiceEntryPoint en tiempo de compilación que consolida las dependencias de la biblioteca y reemplaza los proveedores de configuración heredados (AppFunctionConfiguration.Provider).

Si tu app usa actualmente una versión anterior de AppFunctions (como 1.0.0-alpha09), puedes automatizar la migración con la habilidad del agente de AppFunctions en un IDE de IA, como Gemini en Android Studio. La habilidad contiene reglas de migración dedicadas que guían a un agente para consolidar tus dependencias de compilación, crear el wrapper de servicio de @AppFunctionServiceEntryPoint requerido, desvincular los parámetros de contexto y actualizar tus declaraciones de manifiesto.