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 superior.
No es necesario que tu app verifique si se admiten AppFunctions. Esto se controla automáticamente dentro de la biblioteca de AppFunctions Jetpack.
AppFunctionManager muestra una instancia si se admite la función y muestra un valor nulo si no se admite.
Dependencias
Agrega las dependencias de biblioteca necesarias 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:
# 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")
}
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 parámetros y respuestas, y luego proporcionar la lógica principal dentro del método de función.
En el siguiente código, se muestra un ejemplo de implementación para crear una tarea en la app de TODO, incluida la definición de parámetros y tipos de respuesta personalizados, y la lógica de la función principal con un repositorio.
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)
}
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:
- Declarar la AppFunction como una función de suspensión
- Cambiar a un distribuidor de corrutinas adecuado cuando la operación pueda bloquear el subproceso
- Cuando
isDescribedByKDocse establece entrue, la descripción de la función o la descripción serializable se codifica como parte deAppFunctionMetadatapara ayudar al agente a comprender cómo usar la AppFunction de la app.
Opcional: Usa Hilt para proporcionar una fábrica de AppFunction personalizada
Si tu clase de implementación de AppFunction requiere dependencias en su constructor (como en TaskRepository en el ejemplo anterior), debes proporcionar una fábrica personalizada para que el sistema sepa cómo crear una instancia. Este es un paso opcional y solo es necesario si tu clase de función tiene parámetros de constructor. En este ejemplo, se muestra cómo crear una AppFunctionFactory personalizada y configurarla dentro de tu clase Application con Hilt para la inyección de dependencias.
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()
}
Opcional: Activa o desactiva la disponibilidad de AppFunction en el tiempo de ejecución
Usa la API de AppFunctionManager para habilitar o inhabilitar funciones de forma explícita cuando limites tus AppFunctions. La limitación puede ser útil cuando ciertas funciones de tu app no están disponibles para todos los usuarios. Si habilitas o inhabilitas AppFunctions de forma dinámica, el sistema de inteligencia sabe exactamente qué funciones están disponibles para tu usuario en un momento determinado.
Para limitar de forma segura las AppFunctions que requieren un estado de cuenta específico, sigue un proceso de dos pasos:
Paso 1: Inhabilita la función de forma predeterminada
Para evitar que se pueda acceder a la función antes de que se verifique tu marca de función, establece el parámetro isEnabled de tu anotación @AppFunction en false.
@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(...) { ... }
Paso 2: Habilita la función de forma dinámica en el tiempo 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 en el tiempo de ejecución.
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,
)
}
Consideraciones sobre los tipos de funcionalidades que se deben poner a disposición
La seguridad es siempre prioritaria. Cuando elijas qué capacidades de tu app poner a disposición como AppFunctions, es importante recordar que los agentes del sistema pueden procesar las consultas de los usuarios en el servidor para aprovechar las capacidades avanzadas de LLM.
Para proporcionar una excelente experiencia del usuario que también evite exponer información sensible, te recomendamos que sigas estas instrucciones:
- Funcionalidad que se beneficia del lenguaje natural: Pon a disposición tareas que sean 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 requieren para cumplir con la solicitud específica del usuario.
- Información no sensible: Solo comparte datos que no sean altamente 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 e inequívoco sobre las intenciones. También es útil agregar más de un paso de confirmación para asegurarte de 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.
En Gemini en Android Studio o en otros agentes de tu elección, 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.