Les outils permettent à votre agent sur l'appareil d'accéder aux données de l'application, de communiquer avec les services de backend et d'effectuer des actions au nom de l'utilisateur.
Comment fonctionnent les outils ?
Lorsqu'un utilisateur envoie un prompt, la classe AgentRunner exécute une boucle d'agent en quatre étapes :
- Observer : Gemini Nano analyse la requête de l'utilisateur et le contexte de la conversation en cours par rapport aux déclarations d'outils enregistrées (l'espace d'action de l'agent).
- Raisonner : le modèle détermine si un outil intégré à l'application est nécessaire. Si un outil est nécessaire, Gemini Nano émet un appel d'outil structuré contenant le nom de la fonction et les arguments typés.
- Agir : l'instance
AgentRunnerintercepte automatiquement cet appel et exécute la fonction Kotlin correspondante dans votre application. - Évaluation des commentaires : la sortie de l'outil est renvoyée à Gemini Nano. Le modèle évalue les commentaires et répond à l'utilisateur ou appelle un autre outil jusqu'à ce qu'une condition d'arrêt soit remplie (atteinte de l'objectif ou de la limite
maxTurns).
Outils déclaratifs avec Android AppFunctions
La méthode recommandée pour définir des outils est d'utiliser de manière déclarative les AppFunctions Android (androidx.appfunctions). Les AppFunctions fournissent un modèle de plate-forme standardisé pour déclarer des fonctionnalités typées dans l'application avec une génération automatique de schéma.
1. Ajouter des dépendances Gradle
Ajoutez les artefacts AppFunctions à côté de genai-agent dans le fichier build.gradle.kts de votre module :
dependencies {
implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13")
}
2. Annotez les méthodes d'outil avec l'annotation @AppFunctionDeclaration.
Annotez les méthodes d'outils publiques avec l'annotation @AppFunctionDeclaration(isDescribedByKDoc = true). Les descriptions des paramètres sont documentées dans les tags KDoc @param standards.
Les AppFunctions exigent que les valeurs renvoyées soient des types primitifs (comme String, Int ou Boolean) ou des classes de données structurées annotées avec l'annotation @AppFunctionSerializable.
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
/**
* Structured details for an order.
*/
@AppFunctionSerializable
data class OrderDetails(
val orderId: String,
val status: String,
val estimatedDelivery: String?,
val itemCount: Int,
)
/**
* Result returned when cancelling an order.
*/
@AppFunctionSerializable
data class CancelOrderResult(
val orderId: String,
val cancelled: Boolean,
val message: String,
)
@AppFunctionServiceEntryPoint(
serviceName = "OrderAppFunctionService",
appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {
private val orderRepository = OrderRepository()
/**
* Fetches details and delivery status for a customer order by ID.
*
* @param orderId The alphanumeric order identifier (for example, ORD-8492)
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun fetch_order_details(
orderId: String
): OrderDetails = withContext(Dispatchers.IO) {
val order = orderRepository.findOrder(orderId)
if (order != null) {
OrderDetails(
orderId = order.id,
status = order.status,
estimatedDelivery = order.deliveryEstimate,
itemCount = order.items.size,
)
} else {
OrderDetails(
orderId = orderId,
status = "NOT_FOUND",
estimatedDelivery = null,
itemCount = 0,
)
}
}
/**
* Requests a cancellation or return for an active order.
*
* @param orderId The order identifier to cancel
* @param reason The reason provided by the user for cancellation
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
suspend fun cancel_order(
orderId: String,
reason: String? = null
): CancelOrderResult = withContext(Dispatchers.IO) {
val success = orderRepository.cancelOrder(orderId, reason)
CancelOrderResult(
orderId = orderId,
cancelled = success,
message = if (success) {
"Order cancellation processed."
} else {
"Order cannot be cancelled at this stage."
}
)
}
}
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>
3. 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 pour les assistants externes. Dans Android, le contrôle des accès est géré via les niveaux d'accès de l'appelant plutôt que par des vérifications manuelles de l'identité de l'appelant.
Par défaut (à l'aide de la constante ACCESS_LEVEL_ANDROID_TRUSTED), une AppFunction peut être découverte et exécutée par des assistants sur l'appareil certifiés par la plate-forme et disposant de l'autorisation système EXECUTE_APP_FUNCTIONS. Toutefois, lorsque vous créez des fonctionnalités destinées strictement à l'agent interne de votre application (par exemple, inspecter les paniers des utilisateurs, accéder aux jetons locaux ou effectuer des actions dans l'application), vous pouvez limiter l'accès exclusivement à votre propre application à 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() {
private val orderRepository = OrderRepository()
/**
* Cancels an order. Restricted exclusively to this app's internal agent.
*/
@OptIn(ExperimentalAppFunctionsApi::class)
@AppFunctionDeclaration(isDescribedByKDoc = true)
@AppFunctionAccessLevel(
level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
isCompatEnforcementEnabled = true,
)
suspend fun cancel_order(orderId: String): CancelOrderResult {
// Only your app's internal agent can invoke this function
val success = orderRepository.cancelOrder(orderId)
return CancelOrderResult(
orderId = orderId,
cancelled = success,
message = if (success) "Cancelled." else "Failed."
)
}
}
La plate-forme accepte trois niveaux d'accès pour les appelants :
ACCESS_LEVEL_SELF(100) : strictement limité à l'application hôte elle-même. Les assistants externes, les surfaces système et les autres applications ne peuvent pas détecter ni appeler la fonction via l'IPC. Utilisez ce niveau pour les opérations ou les outils sensibles qui ne doivent servir que l'agent interne de votre application.ACCESS_LEVEL_SYSTEM(200) : réservé aux appelants système privilégiés disposant de l'autorisationandroid.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM(comme le lanceur d'applications système ou les applications récentes) et à l'application hôte elle-même.ACCESS_LEVEL_ANDROID_TRUSTED(300, par défaut) : ouvert aux agents sur l'appareil certifiés par la plate-forme disposant de l'autorisationEXECUTE_APP_FUNCTIONS, aux surfaces système et à l'application hôte.
4. Enregistrer des outils auprès de l'agent
Vous pouvez enregistrer des AppFunctions auprès de l'agent automatiquement à l'aide de la méthode enableAppFunctions() ou de manière programmatique à l'aide de la méthode AppFunctionAgentTools.discover() :
Détection automatique avec la méthode enableAppFunctions()
Pour les applications simples où les AppFunctions s'exécutent automatiquement lorsque le modèle les appelle :
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
val agent = Agent.builder()
.setName("OrderAssistant")
.setInstructions("Assist users with order tracking.")
.enableAppFunctions(true) // Automatically discovers all AppFunctions in your app package
.setMaxTurns(10)
.build()
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.
Découverte programmatique avec la méthode AppFunctionAgentTools.discover()
Lorsque votre application doit inspecter les AppFunctions découvertes, les filtrer ou les décorer avant de les enregistrer auprès de l'agent :
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AppFunctionAgentTools
val discoveredTools = AppFunctionAgentTools.discover(
enabledAppFunctionIds = listOf("fetch_order_details", "cancel_order") // Optional ID filter
)
val agent = Agent.builder()
.setName("OrderAssistant")
.setInstructions("Assist users with order tracking.")
.addTools(discoveredTools)
.build()
val runner = AgentRunner(agent)
Modèle de décorateur et human-in-the-loop
Les opérations sensibles, telles que les annulations de commande, les paiements ou la suppression de données utilisateur, doivent conserver une human-in-the-loop (avec intervention humaine) en exigeant la confirmation de l'utilisateur. Tous les outils ajoutés à la classe Agent proviennent d'AppFunctions Android, et la classe AgentTool fournit un constructeur de délégation AgentTool(delegate) pour que vous puissiez encapsuler les outils AppFunction découverts à l'aide du modèle Decorator :
import com.google.mlkit.genai.agent.AgentTool
class HitlConfirmationTool(
delegate: AgentTool,
private val onConfirm: suspend (toolName: String, args: Map<String, Any>) -> Boolean
) : AgentTool(delegate) {
override suspend fun execute(args: Map<String, Any>): Map<String, Any?> {
val confirmed = onConfirm(name, args)
if (!confirmed) {
return mapOf(
"status" to "CANCELLED_BY_USER",
"message" to "Action was declined by the user."
)
}
return checkNotNull(delegate).execute(args)
}
}
Bonnes pratiques pour la conception d'outils
- Définir le champ d'action : limitez le champ d'action de l'agent (l'ensemble des outils et des autorisations exposés à l'agent) à ce qui est nécessaire pour sa tâche à l'aide de la méthode
addAppFunctionIds()et de la constanteACCESS_LEVEL_SELF. Un espace d'action trop grand rend un modèle sur l'appareil plus sujet aux erreurs et consomme des jetons de la fenêtre de contexte. - Soyez explicite dans les descriptions : Gemini Nano s'appuie sur les descriptions des outils et des paramètres pour décider quand appeler un outil. Soyez clair sur l'objectif de l'outil et mentionnez les formats d'arguments attendus (comme les dates ou les ID).
- Renvoyer des mappages d'erreurs structurés : renvoie des mappages d'erreurs descriptifs tels que
mapOf("error" to "Order not found")lorsqu'un échec attendu se produit. Cela permet à Gemini Nano d'expliquer le problème à l'utilisateur de manière appropriée. Si une exception inattendue est générée par un outil, l'environnement d'exécution la détecte automatiquement et renvoie le message d'erreur au modèle pour autocorrection. - Limitez les charges utiles de retour : les modèles sur l'appareil ont des fenêtres de contexte plus petites que les modèles de serveur. Ne renvoyez que les champs pertinents pour répondre à la requête de l'utilisateur, au lieu de vider des lignes entières de la base de données ou des réponses réseau brutes.
- Exécuter les E/S sur le coordinateur
Dispatchers.IO: assurez-vous toujours que les requêtes réseau et les requêtes de base de données Room dans les outils s'exécutent sur le coordinateurDispatchers.IOpour éviter de bloquer l'appelant de la coroutine.
Étapes suivantes
- Découvrez comment conserver le contexte de la conversation lors des rotations de l'application et nettoyer les sessions dans Gérer les sessions et le cycle de vie.
- Gérez les jetons de texte en streaming et les états d'exécution des outils dans Événements de flux et intégration de l'interface utilisateur.