Las herramientas le brindan a tu agente integrado en el dispositivo la capacidad de acceder a los datos de la app, comunicarse con los servicios de backend y realizar acciones en nombre del usuario.
Cómo funcionan las herramientas
Cuando un usuario envía una instrucción, la clase AgentRunner ejecuta un
bucle de agente en cuatro etapas:
- Observar: Gemini Nano analiza la instrucción del usuario y el contexto de la conversación actual en función de las declaraciones de herramientas registradas (el espacio de acción del agente).
- Motivo: El modelo determina si se requiere una herramienta integrada en la app. Si se necesita una herramienta, Gemini Nano emite una invocación de herramienta estructurada que contiene el nombre de la función y los argumentos con tipo.
- Act: La instancia de
AgentRunnerintercepta automáticamente esta invocación y ejecuta la función de Kotlin correspondiente en tu app. - Evalúa los comentarios: El resultado de la herramienta se vuelve a enviar a Gemini Nano. El
modelo evalúa los comentarios y responde al usuario o llama a otra
herramienta hasta que se cumple una condición de finalización (completar el objetivo o alcanzar
el límite de
maxTurns).
Herramientas declarativas con AppFunctions de Android
La forma recomendada de definir herramientas es de forma declarativa con AppFunctions de Android (androidx.appfunctions). AppFunctions proporciona un modelo de plataforma estandarizado para declarar capacidades de la app con tipos con generación automática de esquemas.
1. Agrega dependencias de Gradle
Agrega los artefactos de AppFunctions junto con genai-agent en el archivo build.gradle.kts de tu módulo:
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. Anota los métodos de la herramienta con la anotación @AppFunctionDeclaration.
Anota los métodos públicos de la herramienta con la anotación @AppFunctionDeclaration(isDescribedByKDoc = true). Las descripciones de los parámetros se documentan en etiquetas @param de KDoc estándar.
Las AppFunctions requieren que los valores que se muestran sean tipos primitivos (como
String, Int o Boolean) o clases de datos estructurados anotadas con la
anotación @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."
}
)
}
}
Registra el servicio generado en el archivo AndroidManifest.xml de tu 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>
3. Restringe el acceso de los emisores con la constante ACCESS_LEVEL_SELF
Las AppFunctions no tienen que ser públicas para el sistema ni para los asistentes externos. En Android, el control de acceso se administra a través de los niveles de acceso del emisor, en lugar de las verificaciones manuales de identidad del emisor.
De forma predeterminada (con la constante ACCESS_LEVEL_ANDROID_TRUSTED), los asistentes integrados en el dispositivo certificados por la plataforma que tengan el permiso del sistema EXECUTE_APP_FUNCTIONS pueden descubrir y ejecutar una AppFunction. Sin embargo, cuando
creas funciones destinadas estrictamente al agente interno de tu app (como
inspeccionar los carritos de compras de los usuarios, acceder a tokens locales o realizar acciones
en la app), puedes restringir el acceso exclusivamente a tu propia aplicación con 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 plataforma admite tres niveles de acceso de los emisores:
ACCESS_LEVEL_SELF(100): Se restringe estrictamente a la aplicación host en sí. Los asistentes externos, las plataformas del sistema y otras apps no pueden descubrir ni invocar la función a través de IPC. Usa este nivel para operaciones o herramientas sensibles que solo deberían entregar el agente interno de tu app.ACCESS_LEVEL_SYSTEM(200): Se restringe a los emisores del sistema con privilegios que tienen el permisoandroid.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM(como el Selector del sistema o Recientes) y la app host.ACCESS_LEVEL_ANDROID_TRUSTED(300, predeterminado): Se abre para los agentes integrados en el dispositivo certificados por la plataforma que tienen el permisoEXECUTE_APP_FUNCTIONS, las plataformas del sistema y la app host.
4. Registra herramientas con el agente
Puedes registrar AppFunctions con el agente de forma automática con el método enableAppFunctions() o de forma programática con el método AppFunctionAgentTools.discover():
Descubrimiento automático con el método enableAppFunctions()
Para apps sencillas en las que las AppFunctions se ejecutan automáticamente cuando el modelo las llama, haz lo siguiente:
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)
De manera opcional, llama al método addAppFunctionIds("fetch_order_details", "cancel_order") para limitar el descubrimiento estrictamente a IDs de AppFunction específicos en lugar de exponer todas las funciones de la app.
Descubrimiento programático con el método AppFunctionAgentTools.discover()
Cuando tu app necesite inspeccionar las AppFunctions descubiertas, filtrarlas o decorarlas antes de registrarlas con el agente, haz lo siguiente:
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)
Patrón de decorador y con intervención humana
Las operaciones sensibles, como las cancelaciones de pedidos, los pagos o la eliminación de datos de los usuarios, deben mantener una intervención humana (HITL), ya que requieren la confirmación del usuario. Todas las herramientas que se agregan a la clase Agent se originan en las AppFunctions de Android,
y la clase AgentTool proporciona un constructor de delegación AgentTool(delegate) para que
puedas unir las herramientas de AppFunction descubiertas con el patrón 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)
}
}
Prácticas recomendadas para el diseño de herramientas
- Define el alcance del espacio de acción: Mantén el espacio de acción del agente (el conjunto de herramientas y permisos expuestos al agente) lo suficientemente grande para su tarea con el método
addAppFunctionIds()y la constanteACCESS_LEVEL_SELF. Un espacio de acción demasiado grande hace que un modelo integrado en el dispositivo sea más propenso a errores y consuma tokens de la ventana de contexto. - Sé explícito en las descripciones: Gemini Nano se basa en las descripciones de las herramientas y los parámetros para decidir cuándo llamar a una herramienta. Sé claro sobre el propósito de la herramienta y menciona los formatos de argumentos esperados (como fechas o IDs).
- Devuelve mapas de errores estructurados: Devuelve mapas de errores descriptivos, como
mapOf("error" to "Order not found"), cuando se produce una falla esperada. Esto permite que Gemini Nano le explique el problema al usuario de forma clara. Si una herramienta arroja una excepción inesperada, el entorno de ejecución la detecta automáticamente y refleja el mensaje de error en el modelo para la autocorrección. - Mantén enfocadas las cargas útiles de devolución: Los modelos integrados en el dispositivo tienen ventanas de contexto más pequeñas que los modelos de servidor. Muestra solo los campos relevantes para responder la consulta del usuario en lugar de volcar filas completas de la base de datos o respuestas de red sin procesar.
- Ejecuta operaciones de E/S en el despachador
Dispatchers.IO: Asegúrate siempre de que las solicitudes de red y las consultas de la base de datos de Room dentro de las herramientas se ejecuten en el despachadorDispatchers.IOpara evitar que se bloquee el emisor de la corrutina.
Próximos pasos
- Obtén información para mantener el contexto de la conversación en las rotaciones de la app y limpiar las sesiones en Administra las sesiones y el ciclo de vida.
- Controla los tokens de texto de transmisión y los estados de ejecución de las herramientas en Eventos de transmisión y de integración de la IU.