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 compileSdk del progetto sia impostato sul livello API 36 o superiore.
Non è necessario che la tua app verifichi se AppFunctions è supportato. Questa operazione viene gestita automaticamente all'interno della libreria Jetpack AppFunctions.
AppFunctionManager restituisce un'istanza se la funzionalità è supportata e restituisce null in caso contrario.
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 di seguito:
# 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")
}
Implementare la logica di AppFunctions
Per implementare un'AppFunction per la tua app per Android, crea una classe che implementi la logica specifica di AppFunctions. Ciò comporta la creazione di classi di dati serializzabili per parametri e risposte e la fornitura della logica di base all'interno del metodo della funzione.
Il seguente codice mostra un'implementazione di esempio per la creazione di un'attività nell' app TODO, inclusa la definizione di parametri e tipi di risposta personalizzati e la logica della funzione principale utilizzando un repository.
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)
}
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:
- Dichiarare AppFunction come funzione di sospensione.
- Passare a un dispatcher di coroutine appropriato quando l'operazione potrebbe bloccare il thread.
- Quando
isDescribedByKDocè impostato sutrue, la descrizione della funzione o la descrizione serializzabile viene codificata come parte diAppFunctionMetadataper aiutare l'agente a capire come utilizzare l'AppFunction dell'app.
(Facoltativo) Utilizzare Hilt per fornire una factory AppFunction personalizzata
Se la classe di implementazione AppFunction richiede dipendenze nel costruttore (come in TaskRepository nell'esempio precedente), devi fornire una factory personalizzata in modo che il sistema sappia come crearla. Questo passaggio è facoltativo ed è necessario solo se la classe della funzione ha parametri del costruttore. Questo esempio mostra come creare una AppFunctionFactory personalizzata e configurarla all'interno della classe Application, utilizzando Hilt per l'iniezione delle dipendenze.
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()
}
(Facoltativo) Attivare/disattivare la disponibilità di AppFunction in fase di runtime
Utilizza l'API AppFunctionManager per attivare o disattivare esplicitamente le funzioni quando limiti le AppFunctions. Il gating può essere utile quando alcune funzionalità dell'app non sono disponibili per tutti gli utenti. Abilitando o disabilitando dinamicamente le AppFunctions, il sistema di intelligence sa esattamente quali funzionalità sono disponibili per l'utente in un determinato momento.
Per limitare in modo sicuro le AppFunctions che richiedono uno stato dell'account specifico, segui una procedura in due passaggi:
Passaggio 1. Disattivare la funzione per impostazione predefinita
Per impedire l'accesso alla funzione prima che venga verificato il flag della funzionalità, imposta il parametro isEnabled dell'annotazione @AppFunction su false.
@AppFunction(isEnabled = false, isDescribedByKDoc = true)
suspend fun createTask(...) { ... }
Passaggio 2. Attivare dinamicamente la funzione in fase di runtime
Per ogni classe AppFunction, 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 AppFunctionManagerCompat per modificare lo stato di attivazione di una funzione in fase di runtime.
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,
)
}
Considerazioni sui tipi di funzionalità da rendere disponibili
La sicurezza è sempre fondamentale. Quando scegli le funzionalità della tua app da rendere disponibili come AppFunctions, è importante ricordare che gli agenti di sistema potrebbero elaborare le query degli utenti sul server per sfruttare le funzionalità avanzate dei LLM.
Per offrire un'esperienza utente ottimale ed evitare di esporre informazioni sensibili, ti consigliamo di seguire queste linee guida:
- Funzionalità che sfruttano il linguaggio naturale: rendi disponibili le attività che un utente può esprimere più facilmente in una conversazione che tramite la navigazione manuale dell'interfaccia utente.
- Accesso limitato: crea AppFunctions che consentano 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 siano altamente personali o riservati oppure dati che l'utente acconsente esplicitamente a condividere nel contesto dell'azione.
- Conferma non ambigua per qualsiasi azione distruttiva: presta estrema attenzione alle funzioni che eseguono azioni distruttive (ad es. l'eliminazione dei dati). Sebbene l'agente possa richiamarle, la tua app deve includere un proprio passaggio di conferma e utilizzare un linguaggio chiaro e non ambiguo sulle 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.
Verificare 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 | grep --after-context 10
$myPackageName per visualizzare i dettagli delle AppFunctions fornite dalla tua app.
In Gemini in Android Studio o in altri agenti di tua scelta, 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.
Rely on the AppFunction description as instructions.