Grâce aux workflows agentiques sur l'appareil de ML Kit (com.google.mlkit:genai-agent, disponibles via le programme de preview développeur AICore), vous pouvez créer des agents de conversation autonomes et multitours optimisés par Gemini Nano et le service système Android AICore.
Basée sur la classe GenerativeModel, qui fournit un accès direct au modèle, la classe AgentRunner automatise les boucles agentiques complètes : planification d'actions en plusieurs étapes, appel d'outils typés (comme l'interrogation d'un dépôt d'applications, l'appel d'une API de backend ou la lecture d'une base de données locale), évaluation des résultats des outils et gestion du contexte conversationnel localement sur l'appareil, sans latence réseau et dans le respect total de la confidentialité de l'utilisateur. Bien que la classe GenerativeModel soit compatible avec l'historique multitour géré par le client à l'aide d'un objet List<Content> (avec les rôles USER et MODEL) et d'une valeur sessionId, la classe AgentRunner fournit des sessions multitours gérées (à l'aide de l'interface AgentSession) qui enregistrent automatiquement l'historique des conversations en mémoire d'un tour à l'autre, de sorte que vous n'avez pas à gérer les transcriptions manuellement. Les déclarations d'outils statiques et les instructions système sont également mises en cache automatiquement sur l'appareil, ce qui réduit la latence du délai avant le premier jeton sans configuration supplémentaire.
Composants principaux
- La classe
Agent: déclare les instructions, les outils disponibles et les garde-fous d'exécution à l'aide de la méthodeAgent.builder(). La valeur par défaut estmaxTurns = 10automatiquement. - La classe
AgentRunner: basée sur la classeGenerativeModel, elle orchestre les boucles de raisonnement autonomes et les sessions gérées avec des modèles sur l'appareil. Main-safe par défaut surDispatchers.IO, avectemperaturedéfini par défaut surnull(qui utilise la valeur par défaut du modèle ; transmetteztemperature = 0.0fpour le décodage glouton). - L'interface
AgentSession: handle de session de conversation multitour limité obtenu à l'aide de la méthoderunner.getOrCreateSession(sessionId)qui enregistre automatiquement l'historique des conversations en mémoire entre les tours, avec les méthodesrunAsync(content)etclose(). - L'interface
AgentEvent: flux KotlinFlowréactif émettant des jetons de texte et des mises à jour d'outils. - La classe
AgentTool: fonctions typées pouvant être appelées par le modèle, définies à l'aide d'AppFunctions Android (@AppFunctionDeclaration). - Classe
GenAiException: hiérarchie d'exceptions typées (com.google.mlkit.genai.common.GenAiException) signalant des états d'échec tels queMAX_TURNS_EXCEEDED,NOT_AVAILABLEouBUSY.
Compétences Android
Afficher sur GitHubUtiliser l'API ML Kit Agent
android skills add mlkit-agent-apiPrérequis et dépendances
Avant d'intégrer l'API Agent, assurez-vous que votre environnement de développement et votre configuration Gradle répondent aux exigences suivantes.
Exigences liées aux plates-formes et au matériel
- Version d'Android : Android 14 (niveau d'API 34) ou version ultérieure, inscrit au programme Preview développeur AICore.
- Appareils compatibles : pour obtenir la liste des appareils compatibles et de leurs versions du modèle Gemini Nano, consultez Compatibilité des appareils avec la génération personnalisée et les agents.
- SDK minimum :
minSdk 26ou version ultérieure. - Kotlin : Kotlin 2.3 ou version ultérieure.
Configuration de Gradle
Ajoutez la dépendance genai-agent au fichier build.gradle.kts de votre module :
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")
}
Bien que genai-agent ne nécessite pas de processeur d'annotation ni de plug-in de compilateur, la définition d'outils avec androidx.appfunctions nécessite le plug-in KSP appfunctions-compiler.
Guide de démarrage rapide : Initialiser un agent
L'exemple de code suivant montre comment instancier un agent et un exécuteur sur l'appareil :
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)
Définir les outils
Le modèle peut appeler des outils typés pour interroger les données de l'application, communiquer avec les services de backend ou déclencher des actions dans l'application. Les outils sont définis de manière déclarative à l'aide des AppFunctions Android (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",
)
}
}
Enregistrez le service généré dans le fichier AndroidManifest.xml de votre application :
<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>
Restreindre l'accès des appelants avec la constante ACCESS_LEVEL_SELF
Les AppFunctions n'ont pas besoin d'être publiques pour le système ou les appelants externes. Dans Android, le contrôle des accès est géré via les niveaux d'accès de l'appelant plutôt qu'en inspectant les identités des appelants.
Par défaut (à l'aide de la constante ACCESS_LEVEL_ANDROID_TRUSTED), les AppFunctions peuvent être appelées par des assistants sur l'appareil certifiés par la plate-forme et disposant de l'autorisation système EXECUTE_APP_FUNCTIONS. Si votre outil est strictement destiné à l'agent interne de votre application (par exemple, pour gérer les paniers des utilisateurs ou les enregistrements de base de données privés), limitez l'accès des appelants à l'aide de la constante 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."
}
}
Lorsqu'elle est limitée par la constante ACCESS_LEVEL_SELF, la plate-forme empêche les surfaces externes et les assistants système de découvrir ou d'exécuter la fonction via l'IPC.
Configurer l'agent
Configurez une instance Agent avec des instructions système et activez AppFunctions, puis initialisez l'instance 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)
Vous pouvez également appeler la méthode addAppFunctionIds("fetch_order_details", "cancel_order") pour limiter la découverte à des ID AppFunction spécifiques au lieu d'exposer toutes les fonctions intégrées à l'application.
Paramètres par défaut de l'agent
- Limites de tours : la classe
Agent.Builderutilise par défautmaxTurns = 10automatiquement comme condition d'arrêt pour éviter les boucles d'appel d'outils infinies. Vous n'avez pas besoin d'appeler manuellement la méthodesetMaxTurns(10)pour les workflows standards de conversation ou d'appel d'outils. - Décodage glouton déterministe : la classe
AgentRunnerdéfinit le paramètretemperaturepar défaut surnull, en se reportant à la valeur par défaut du modèle sous-jacent. Pour une sélection déterministe des outils et la conformité au schéma JSON sur l'appareil, transmetteztemperature = 0.0f(temperature) au constructeurAgentRunner. Évitez de définir des températures de style cloud (> 0,4f), qui augmentent les risques d'hallucination et de dégradation du schéma sur les modèles mobiles.
Exécution multitour et événements de streaming
Obtenez une instance AgentSession à l'aide de la méthode runner.getOrCreateSession(sessionId) dans une classe ViewModel Android. La session renvoie un flux Flow<AgentEvent> qui émet des jetons de texte en temps réel et des mises à jour d'exécution des outils :
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()
}
}
Diffuser en continu des jetons de texte directement
Pour les interfaces de conversation où les détails d'exécution de l'outil n'ont pas besoin d'être affichés séparément dans l'interface utilisateur, utilisez la fonction d'extension textTokens() sur l'interface 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)
}
Exécution en un seul tour
Pour les tâches ponctuelles où l'historique des dialogues ne doit pas être conservé en RAM (par exemple, l'ajout de tags aux photos ou la synthèse de texte éphémère), appelez directement la méthode 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)
}
Lorsque vous appelez directement la méthode runner.runSingleTurnAsync(content), l'instance AgentRunner alloue un ID de session éphémère en interne et supprime automatiquement l'état de la conversation de la mémoire une fois l'opération terminée, ce qui évite les fuites de mémoire.
Cycle de vie de la session et gestion de la mémoire
L'historique des dialogues multitours est stocké en mémoire et indexé par sa valeur sessionId :
- Modifications de configuration : conserver l'instance
AgentSessiondans une classe AndroidViewModelpermet de préserver automatiquement l'historique des conversations lors des rotations d'écran, sans avoir à gérer les chaînes à chaque rotation. - Éviction de session : appelez la méthode
session.delete()pour évincer une session de manière synchrone lorsqu'un fil de conversation est fermé, ou appelez la méthodesession.close()sans suspension dans la méthodeViewModel.onCleared()pour libérer de la RAM. - Éviction groupée : appelez la méthode
runner.deleteSession(sessionId)pour libérer une session spécifique ou la méthoderunner.clearAllSessions()pour supprimer toutes les conversations mises en cache. - Cycle de vie de l'exécuteur : la classe
AgentRunnerimplémente l'interfaceAutoCloseable. Lorsque vous partagez un exécuteur sur plusieurs écrans, fermez les poignéesAgentSessionindividuelles dans la méthodeViewModel.onCleared()de chaque écran, puis appelez la méthoderunner.close()lorsque vous arrêtez complètement l'exécuteur. - Sessions simultanées : une seule instance
AgentRunnerpeut héberger plusieurs sessions distinctes simultanément. Chaque valeursessionIdconserve un historique de conversation isolé.
Gestion des exceptions avec la classe GenAiException
Les échecs d'exécution lors de l'exécution de l'agent, par exemple lorsque l'agent atteint le nombre maximal de tours d'outil sans générer de réponse ou lorsque l'IA sur l'appareil est temporairement indisponible, sont émis ou générés en tant qu'exception GenAiException (com.google.mlkit.genai.common.GenAiException). Gérez les échecs de manière réactive à l'aide de l'opérateur catch de flux de coroutines Kotlin :
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
}
Principaux codes d'erreur
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112) : l'agent a dépassé la limite maximale de tours (la limitemaxTurns) sans générer de réponse finale.GenAiException.ErrorCode.NOT_AVAILABLE(8) : le modèle Gemini Nano ou le service système AICore n'est pas disponible ou prêt.GenAiException.ErrorCode.BUSY(9) : le système est occupé ou l'application appelante a dépassé son quota.