Con los flujos de trabajo de agentes integrados en el dispositivo del ML Kit (com.google.mlkit:genai-agent, disponibles a través del programa de versión preliminar para desarrolladores de AICore), puedes crear agentes conversacionales autónomos de varios turnos con la tecnología de Gemini Nano y el servicio del sistema Android AICore.
Creada sobre la clase GenerativeModel, que proporciona
acceso directo al modelo, la clase AgentRunner automatiza
bucles de agente completos: planifica acciones de varios pasos, invoca herramientas con tipo (como
consultar un repositorio de apps, llamar a una API de backend o leer una base de datos local),
evalúa los resultados de las herramientas y administra el contexto de la conversación de forma integrado en el dispositivo
con cero latencia de red y total privacidad del usuario. Mientras que la clase GenerativeModel
admite el historial de varios turnos administrado por el cliente con un objeto List<Content> (con
roles USER y MODEL) y un valor sessionId, la clase AgentRunner
proporciona sesiones de varios turnos administradas (con la interfaz AgentSession) que
registran el historial de conversaciones en la memoria automáticamente en todos los turnos para que no
tengas que administrar las transcripciones de forma manual. Las declaraciones de herramientas estáticas y las instrucciones del sistema también se almacenan en caché automáticamente integrado en el dispositivo, lo que reduce la latencia del tiempo hasta el primer token sin configuración adicional.
Componentes centrales
- La clase
Agent: Declara instrucciones, herramientas disponibles y protecciones de ejecución con el métodoAgent.builder(). Se establece de forma predeterminada enmaxTurns = 10automáticamente. - La clase
AgentRunner: Se basa en la claseGenerativeModely organiza bucles de razonamiento autónomos y sesiones administradas con modelos integrados en el dispositivo. Seguro para el subproceso principal de forma predeterminada enDispatchers.IO, contemperaturede forma predeterminada ennull(que se ajusta al valor predeterminado del modelo; pasatemperature = 0.0fpara la decodificación voraz). - La interfaz
AgentSession: Es el controlador de sesión de conversación de varios turnos con alcance que se obtiene con el métodorunner.getOrCreateSession(sessionId)que registra automáticamente el historial de conversaciones en la memoria en todos los turnos, con los métodosrunAsync(content)yclose(). - La interfaz
AgentEvent: El flujo reactivo de KotlinFlowque emite tokens de texto y actualizaciones de herramientas. - La clase
AgentTool: Funciones con tipo que puede llamar el modelo, definidas con AppFunctions de Android (@AppFunctionDeclaration). - La clase
GenAiException: Es la jerarquía de excepciones con tipo (com.google.mlkit.genai.common.GenAiException) que indica estados de falla, comoMAX_TURNS_EXCEEDED,NOT_AVAILABLEoBUSY.
Habilidades de Android
Ver en GitHubUsa la API de Agent de ML Kit
android skills add mlkit-agent-apiRequisitos previos y dependencias
Asegúrate de que tu entorno de desarrollo y la configuración de Gradle cumplan con los siguientes requisitos antes de integrar la API de Agent.
Requisitos de plataforma y hardware
- Versión de Android: Android 14 (nivel de API 34) o versiones posteriores inscritas en el programa de versión preliminar para desarrolladores de AICore.
- Dispositivos compatibles: Para obtener una lista de los dispositivos compatibles y sus versiones del modelo de Gemini Nano, consulta Compatibilidad con dispositivos de agentes y generación personalizada.
- Minimum SDK:
minSdk 26o una versión posterior - Kotlin: Kotlin 2.3 o una versión posterior
Configuración de Gradle
Agrega la dependencia genai-agent al archivo build.gradle.kts de tu módulo:
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")
}
Si bien genai-agent en sí no requiere un procesador de anotaciones ni un complemento de compilador, definir herramientas con androidx.appfunctions requiere el complemento de KSP de appfunctions-compiler.
Guía de inicio rápido: Inicializa un agente
En la siguiente muestra de código, se indica cómo crear una instancia de un agente y un ejecutor integrados en el dispositivo:
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)
Define herramientas
El modelo puede llamar a herramientas con tipo para consultar datos de la app, comunicarse con servicios de backend o activar acciones en la app. Las herramientas se definen de forma declarativa con las AppFunctions de 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",
)
}
}
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>
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 emisores externos. En Android, el control de acceso se controla a través de los niveles de acceso del emisor, en lugar de inspeccionar las identidades 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 llamar a las AppFunctions. Si tu herramienta está destinada estrictamente al
agente interno de tu app (por ejemplo, para administrar carritos de usuarios o registros de bases de datos
privadas), restringe el acceso de los emisores 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() {
/**
* 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."
}
}
Cuando se restringe con la constante ACCESS_LEVEL_SELF, la plataforma impide que las plataformas externas y los asistentes del sistema descubran o ejecuten la función a través de IPC.
Configura el agente
Configura una instancia de Agent con instrucciones del sistema y habilita AppFunctions.
Luego, inicializa la instancia de 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)
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.
Configuración predeterminada del agente
- Límites de turnos: La clase
Agent.Builderestablece de forma predeterminadamaxTurns = 10automáticamente como una condición de finalización para evitar bucles infinitos de llamadas a herramientas. No es necesario que llames al métodosetMaxTurns(10)de forma manual para los flujos de trabajo estándar de conversación o de llamada a herramientas. - Decodificación codiciosa determinística: La clase
AgentRunnerestablece el parámetro predeterminadotemperatureennull, lo que se difiere al valor predeterminado del modelo subyacente. Para la selección determinística de herramientas y el cumplimiento del esquema JSON integrado en el dispositivo, pasatemperature = 0.0f(temperature) al constructorAgentRunner. Evita establecer temperaturas de estilo de nube (> 0.4f), que aumentan los riesgos de alucinación y la degradación del esquema en los modelos para dispositivos móviles.
Ejecución de varios turnos y eventos de transmisión
Obtén una instancia de AgentSession con el método runner.getOrCreateSession(sessionId) dentro de una clase ViewModel de Android. La sesión devuelve un flujo de Flow<AgentEvent> que emite tokens de texto
en tiempo real y actualizaciones de ejecución de herramientas:
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()
}
}
Transmite tokens de texto directamente
Para las interfaces conversacionales en las que no es necesario mostrar los detalles de la ejecución de la herramienta por separado en la IU, usa la función de extensión textTokens() en la interfaz 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)
}
Ejecución de un solo turno
Para tareas únicas en las que el historial de diálogo no se debe retener en la RAM (por
ejemplo, etiquetado de fotos o resumen de texto efímero), llama al
método runner.runSingleTurnAsync(content) directamente:
// 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)
}
Cuando se invoca el método runner.runSingleTurnAsync(content) directamente, la instancia de AgentRunner asigna un ID de sesión efímero de forma interna y borra automáticamente el estado de la conversación de la memoria cuando se completa, lo que evita fugas de memoria.
Ciclo de vida de la sesión y administración de la memoria
El historial de diálogos de varios turnos se almacena en la memoria y se indexa por su valor
sessionId:
- Cambios de configuración: Conservar la instancia de
AgentSessiondentro de una claseViewModelde Android preserva el historial de conversaciones en las rotaciones de pantalla de forma automática sin necesidad de canalizar cadenas en cada turno. - Expulsión de sesiones: Llama al método
session.delete()para expulsar de forma síncrona una sesión cuando se descarta un hilo de conversación, o llama al métodosession.close()que no se suspende en el métodoViewModel.onCleared()para liberar RAM. - Expulsión masiva: Llama al método
runner.deleteSession(sessionId)para liberar una sesión específica o al métodorunner.clearAllSessions()para expulsar todas las conversaciones almacenadas en caché. - Ciclo de vida del ejecutor: La clase
AgentRunnerimplementa la interfazAutoCloseable. Cuando compartas un ejecutor entre pantallas, cierra los controladoresAgentSessionindividuales en el métodoViewModel.onCleared()de cada pantalla y llama al métodorunner.close()cuando cierres el ejecutor por completo. - Sesiones simultáneas: Una sola instancia de
AgentRunnerpuede alojar varias sesiones distintas de forma simultánea. Cada valor desessionIdmantiene un historial de conversación aislado.
Manejo de errores con la clase GenAiException
Los errores de tiempo de ejecución durante la ejecución del agente, como cuando el agente alcanza el máximo
de turnos de herramientas sin generar una respuesta o cuando la IA integrada en el dispositivo no está disponible
temporalmente, se emiten o se arrojan como una excepción GenAiException
(com.google.mlkit.genai.common.GenAiException). Controla los errores de forma reactiva
con el operador catch de flujo de corrutinas de 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
}
Códigos de error clave
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112): El agente superó el límite máximo de turnos (el límite demaxTurns) sin generar una respuesta final.GenAiException.ErrorCode.NOT_AVAILABLE(8): El modelo de Gemini Nano o el servicio del sistema de AICore no están disponibles o listos.GenAiException.ErrorCode.BUSY(9): El sistema está ocupado o la app que realiza la llamada superó su cuota.