Con gli Agentic Workflows on-device di ML Kit (com.google.mlkit:genai-agent, disponibili tramite il programma Anteprima per gli sviluppatori di AICore), puoi creare agenti di conversazione autonomi a più turni basati su Gemini Nano e sul servizio di sistema Android AICore.
Basata sulla classe GenerativeModel, che fornisce l'accesso diretto al modello, la classe AgentRunner automatizza i loop degli agenti completi: pianificazione di azioni in più passaggi, richiamo di strumenti tipizzati (come l'esecuzione di query su un repository di app, la chiamata di un'API di backend o la lettura di un database locale), valutazione dei risultati degli strumenti e gestione del contesto di conversazione localmente on-device con latenza di rete pari a zero e privacy dell'utente completa. Sebbene la classe GenerativeModel supporti la cronologia multi-turn gestita dal client utilizzando un oggetto List<Content> (con ruoli USER e MODEL) e un valore sessionId, la classe AgentRunner fornisce sessioni multi-turn gestite (utilizzando l'interfaccia AgentSession) che registrano automaticamente la cronologia delle conversazioni in memoria tra i turni, in modo da non dover gestire manualmente le trascrizioni. Anche le dichiarazioni degli strumenti statici e le istruzioni di sistema vengono memorizzate automaticamente nella cache on-device, riducendo la latenza del tempo al primo token senza configurazioni aggiuntive.
Componenti principali
- La classe
Agent: dichiara istruzioni, strumenti disponibili e guardrail di esecuzione utilizzando il metodoAgent.builder(). Il valore predefinito èmaxTurns = 10automaticamente. - La classe
AgentRunner: basata sulla classeGenerativeModel, orchestra i cicli di ragionamento autonomi e le sessioni gestite con i modelli on-device. Main-safe per impostazione predefinita suDispatchers.IO, contemperatureimpostato sunull(che rimanda al valore predefinito del modello; passatemperature = 0.0fper la decodifica greedy). - L'interfaccia
AgentSession: handle della sessione di conversazione multi-turno con ambito ottenuto utilizzando il metodorunner.getOrCreateSession(sessionId)che registra automaticamente la cronologia delle conversazioni in memoria tra i turni, con i metodirunAsync(content)eclose(). - L'interfaccia
AgentEvent: lo stream KotlinFlowreattivo che emette token di testo e aggiornamenti degli strumenti. - La classe
AgentTool: funzioni tipizzate richiamabili dal modello, definite utilizzando AppFunctions di Android (@AppFunctionDeclaration). - La
GenAiExceptionclasse: gerarchia di eccezioni tipizzate (com.google.mlkit.genai.common.GenAiException) che segnala stati di errore comeMAX_TURNS_EXCEEDED,NOT_AVAILABLEoBUSY.
Competenze Android
Visualizza su GitHubUtilizza l'API ML Kit Agent
android skills add mlkit-agent-apiPrerequisiti e dipendenze
Prima di integrare l'API Agent, assicurati che l'ambiente di sviluppo e la configurazione di Gradle soddisfino i seguenti requisiti.
Requisiti della piattaforma e dell'hardware
- Versione di Android: Android 14 (livello API 34) o versioni successive registrate al programma Anteprima per gli sviluppatori AICore.
- Dispositivi supportati: per un elenco dei dispositivi supportati e delle relative versioni del modello Gemini Nano, consulta Supporto dei dispositivi per agenti e generazione personalizzata.
- SDK minimo:
minSdk 26o versioni successive. - Kotlin: Kotlin 2.3 o versioni successive.
Configurazione di Gradle
Aggiungi la dipendenza genai-agent al file build.gradle.kts del tuo modulo:
dependencies {
// Gemini Nano agentic workflows
implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
// Optional: Only required when defining tools using Android AppFunctions
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13")
}
Sebbene genai-agent non richieda un processore di annotazioni o un plug-in del compilatore, la definizione degli strumenti con androidx.appfunctions richiede il plug-in KSP appfunctions-compiler.
Guida rapida: inizializza un agente
Il seguente esempio di codice mostra come creare un'istanza di un runner e di un agente on-device:
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
// Configure the agent with system instructions
val agent = Agent.builder()
.setInstructions("You are a helpful on-device assistant.")
.build()
// Create the runner to execute prompts
val runner = AgentRunner(agent)
Definisci gli strumenti
Il modello può chiamare strumenti tipizzati per eseguire query sui dati dell'app, comunicare con i servizi di backend o attivare azioni in-app. Gli strumenti vengono definiti in modo dichiarativo utilizzando
Android AppFunctions (androidx.appfunctions):
package com.example.ai.agent.tools
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@AppFunctionSerializable
data class OrderDetails(
val orderId: String,
val status: String,
val eta: String,
)
@AppFunctionServiceEntryPoint(
serviceName = "OrderAppFunctionService",
appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {
/**
* Fetches details and delivery status for an in-app order.
*
* @param orderId The alphanumeric order identifier
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun fetch_order_details(
orderId: String
): OrderDetails = withContext(Dispatchers.IO) {
// Query local database or call your backend API
OrderDetails(
orderId = orderId,
status = "Out for delivery",
eta = "2:30 PM",
)
}
}
Registra il servizio generato nel file AndroidManifest.xml dell'app:
<service
android:name=".OrderAppFunctionService"
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
android:exported="true">
<property
android:name="android.app.appfunctions.schema"
android:value="app_functions_schema.xsd" />
<property
android:name="android.app.appfunctions.v2"
android:value="order_app_functions.xml" />
<intent-filter>
<action android:name="android.app.appfunctions.AppFunctionService" />
</intent-filter>
</service>
Limita l'accesso del chiamante con la costante ACCESS_LEVEL_SELF
Le AppFunction non devono essere pubbliche per il sistema o per i chiamanti esterni. In Android, il controllo degli accessi viene gestito tramite i livelli di accesso del chiamante anziché ispezionando le identità dei chiamanti.
Per impostazione predefinita (utilizzando la costante ACCESS_LEVEL_ANDROID_TRUSTED), le AppFunction possono essere chiamate dagli assistenti on-device certificati dalla piattaforma che dispongono dell'autorizzazione di sistema EXECUTE_APP_FUNCTIONS. Se lo strumento è destinato esclusivamente all'agente interno della tua app (ad esempio, per la gestione dei carrelli degli utenti o dei record di database privati), limita l'accesso del chiamante utilizzando la costante ACCESS_LEVEL_SELF:
import androidx.appfunctions.AppFunctionAccessLevel
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionService
import androidx.appfunctions.AppFunctionServiceEntryPoint
import androidx.appfunctions.ExperimentalAppFunctionsApi
import androidx.appfunctions.metadata.AppFunctionMetadata
@AppFunctionServiceEntryPoint(
serviceName = "OrderAppFunctionService",
appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {
/**
* Cancels an order. Restricted strictly to this app's internal agent.
*/
@OptIn(ExperimentalAppFunctionsApi::class)
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
fun cancel_order(
orderId: String
): String {
// Only your app's internal agent can invoke this function
return "Order $orderId cancelled successfully."
}
}
Se limitata con la costante ACCESS_LEVEL_SELF, la piattaforma impedisce alle piattaforme esterne e agli assistenti di sistema di rilevare o eseguire la funzione tramite IPC.
Configura l'agente
Configura un'istanza Agent con le istruzioni di sistema e abilita AppFunctions, quindi inizializza l'istanza AgentRunner:
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
// Configure the agent with system instructions and enable AppFunctions
val agent = Agent.builder()
.setName("OrderAssistant")
.setInstructions(
"You are a helpful assistant for looking up store orders. " +
"Use available tools to fetch order information."
)
.enableAppFunctions(true) // Automatically discovers all registered AppFunctions in your app
.build()
// Initialize the runner
val runner = AgentRunner(agent)
Facoltativamente, chiama il metodo addAppFunctionIds("fetch_order_details", "cancel_order") per limitare il rilevamento esclusivamente a ID AppFunction specifici anziché esporre tutte le funzioni in-app.
Impostazioni predefinite dell'agente
- Limiti di turni: la classe
Agent.Builderimposta automaticamentemaxTurns = 10come condizione di terminazione per evitare loop infiniti di chiamate agli strumenti. Non è necessario chiamare manualmente il metodosetMaxTurns(10)per i workflow standard di conversazione o di chiamata allo strumento. - Decodifica greedy deterministica: la classe
AgentRunnerimposta il parametrotemperaturepredefinito sunull, rimandando al valore predefinito del modello sottostante. Per la selezione deterministica degli strumenti e la conformità allo schema JSON on-device, passatemperature = 0.0f(temperature) al costruttoreAgentRunner. Evita di impostare temperature in stile cloud (> 0,4f), che aumentano i rischi di allucinazioni e di degradazione dello schema sui modelli mobile.
Esecuzione in più turni ed eventi di streaming
Ottieni un'istanza AgentSession utilizzando il metodo runner.getOrCreateSession(sessionId) all'interno di una classe ViewModel Android. La sessione restituisce uno stream Flow<AgentEvent> che emette token di testo in tempo reale e aggiornamenti sull'esecuzione degli strumenti:
package com.example.ai.agent
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.mlkit.genai.agent.AgentEvent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AgentSession
import com.google.mlkit.genai.common.GenAiException
import com.google.mlkit.genai.prompt.Content
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
data class AgentUiState(
val messages: List<String> = emptyList(),
val currentResponse: String = "",
val activeToolStatus: String? = null,
val isLoading: Boolean = false,
val errorMessage: String? = null
)
class ChatViewModel(
private val runner: AgentRunner,
private val sessionId: String = "chat_session"
) : ViewModel() {
// Scoped session preserves conversation history across configuration changes (screen rotations)
private val session: AgentSession = runner.getOrCreateSession(sessionId)
private val _uiState = MutableStateFlow(AgentUiState())
val uiState: StateFlow<AgentUiState> = _uiState.asStateFlow()
fun sendMessage(userText: String) {
_uiState.update {
it.copy(
messages = it.messages + userText,
currentResponse = "",
activeToolStatus = null,
isLoading = true,
errorMessage = null
)
}
viewModelScope.launch {
val content = Content.builder().text(userText).build()
session.runAsync(content)
.catch { e ->
val message = when {
e is GenAiException && e.errorCode == GenAiException.ErrorCode.MAX_TURNS_EXCEEDED ->
"Agent exceeded the maximum reasoning turns allowed."
e is GenAiException && e.errorCode == GenAiException.ErrorCode.NOT_AVAILABLE ->
"On-device AI is currently unavailable."
e is GenAiException && e.errorCode == GenAiException.ErrorCode.BUSY ->
"On-device AI is busy. Please try again."
else -> e.localizedMessage ?: "Agent error"
}
_uiState.update {
it.copy(
isLoading = false,
activeToolStatus = null,
errorMessage = message
)
}
}
.collect { event ->
when (event) {
is AgentEvent.TextMessage -> {
_uiState.update {
it.copy(
currentResponse = if (event.isPartial) {
it.currentResponse + event.text
} else {
event.text.ifEmpty { it.currentResponse }
},
activeToolStatus = null
)
}
}
is AgentEvent.FunctionCall -> {
_uiState.update {
it.copy(activeToolStatus = "Executing ${event.name}...")
}
}
is AgentEvent.FunctionResponse -> {
_uiState.update {
it.copy(activeToolStatus = "Received data from ${event.name}")
}
}
else -> {}
}
}
// Finalize message once stream completes
_uiState.update {
it.copy(
messages = it.messages + it.currentResponse,
currentResponse = "",
activeToolStatus = null,
isLoading = false
)
}
}
}
override fun onCleared() {
super.onCleared()
// Cleanly evicts conversation memory from RAM when ViewModel is destroyed
session.close()
}
}
Trasmetti in streaming i token di testo direttamente
Per le interfacce conversazionali in cui i dettagli di esecuzione dello strumento non devono essere visualizzati separatamente nell'interfaccia utente, utilizza la funzione di estensione textTokens() nell'interfaccia Flow<AgentEvent>:
import com.google.mlkit.genai.agent.textTokens
import com.google.mlkit.genai.prompt.Content
val content = Content.builder().text("What is my order status?").build()
session.runAsync(content)
.textTokens()
.collect { token ->
print(token)
}
Esecuzione a turno singolo
Per le attività una tantum in cui la cronologia della conversazione non deve essere conservata nella RAM (ad esempio, il tagging delle foto o la sintesi di testo temporaneo), chiama direttamente il metodo runner.runSingleTurnAsync(content):
// Ephemeral single-turn: cleans up session state from RAM automatically upon completion
val content = Content.builder()
.text("Summarize the order history for customer #1042: ...")
.build()
runner.runSingleTurnAsync(content)
.textTokens()
.collect { token ->
displaySummary(token)
}
Quando richiami direttamente il metodo runner.runSingleTurnAsync(content), l'istanza AgentRunner alloca internamente un ID sessione temporaneo ed elimina automaticamente lo stato della conversazione dalla memoria al termine, evitando perdite di memoria.
Ciclo di vita della sessione e gestione della memoria
La cronologia dei dialoghi multi-turn viene archiviata in memoria e indicizzata in base al valore sessionId:
- Modifiche alla configurazione: la conservazione dell'istanza
AgentSessionall'interno di una classeViewModelAndroid conserva automaticamente la cronologia delle conversazioni durante le rotazioni dello schermo senza dover inserire stringhe a ogni turno. - Espulsione della sessione: chiama il metodo
session.delete()per espellere in modo sincrono una sessione quando un thread di conversazione viene ignorato oppure chiama il metodosession.close()non sospeso nel metodoViewModel.onCleared()per liberare la RAM. - Espulsione collettiva: chiama il metodo
runner.deleteSession(sessionId)per liberare una sessione specifica o il metodorunner.clearAllSessions()per espellere tutte le conversazioni memorizzate nella cache. - Ciclo di vita del runner: la classe
AgentRunnerimplementa l'interfacciaAutoCloseable. Quando condividi un runner tra più schermate, chiudi i singoli handleAgentSessionnel metodoViewModel.onCleared()di ogni schermata e chiama il metodorunner.close()quando arresti completamente il runner. - Sessioni simultanee: una singola istanza
AgentRunnerpuò ospitare più sessioni distinte contemporaneamente. Ogni valoresessionIdmantiene una cronologia delle conversazioni isolata.
Gestione degli errori con la classe GenAiException
Gli errori di runtime durante l'esecuzione dell'agente, ad esempio quando l'agente raggiunge il numero massimo di turni dello strumento senza generare una risposta o quando l'AI on-device non è temporaneamente disponibile, vengono emessi o generati come eccezione GenAiException (com.google.mlkit.genai.common.GenAiException). Gestisci gli errori in modo reattivo utilizzando l'operatore catch di Kotlin Coroutines Flow:
session.runAsync(content)
.catch { e ->
if (e is GenAiException) {
when (e.errorCode) {
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED -> {
// Turn limit exceeded
}
GenAiException.ErrorCode.NOT_AVAILABLE -> {
// Gemini Nano or AICore unavailable
}
GenAiException.ErrorCode.BUSY -> {
// System busy or rate limit reached
}
else -> {
// Other GenAiException errors
}
}
}
}
.collect { event ->
// Process response events
}
Codici di errore principali
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112): l'agente ha superato il limite massimo di turni (il limitemaxTurns) senza generare una risposta finale.GenAiException.ErrorCode.NOT_AVAILABLE(8): il modello Gemini Nano o il servizio di sistema AICore non è disponibile o pronto.GenAiException.ErrorCode.BUSY(9): il sistema è occupato o l'app di chiamate ha superato la sua quota.