Com os Agentic Workflows no dispositivo do Kit de ML (com.google.mlkit:genai-agent, disponíveis no programa de prévia para desenvolvedores do AICore), é possível criar agentes de conversação autônomos e multiturno com tecnologia do Gemini Nano e do serviço do sistema Android AICore.
Criada com base na classe GenerativeModel, que fornece
acesso direto ao modelo, a classe AgentRunner automatiza loops do agente completos: planejamento de ações em várias etapas, invocação de ferramentas tipadas (como
consultar um repositório de apps, chamar uma API de back-end ou ler um banco de dados local),
avaliação de resultados de ferramentas e gerenciamento de contexto de conversação localmente no dispositivo
com latência de rede zero e privacidade do usuário total. Enquanto a classe GenerativeModel
oferece suporte ao histórico de vários turnos gerenciado pelo cliente usando um objeto List<Content> (com
papéis USER e MODEL) e um valor sessionId, a classe AgentRunner
oferece sessões de vários turnos gerenciadas (usando a interface AgentSession) que
registram o histórico de conversas na memória automaticamente em vários turnos para que você
não precise gerenciar as transcrições manualmente. As declarações de ferramentas estáticas e as instruções do sistema também são armazenadas em cache automaticamente no dispositivo, reduzindo a latência do tempo até o primeiro token sem configuração extra.
Principais componentes
- A classe
Agent: declara instruções, ferramentas disponíveis e proteções de execução usando o métodoAgent.builder(). O padrão émaxTurns = 10automaticamente. - A classe
AgentRunner: criada com base na classeGenerativeModel, ela organiza loops de raciocínio autônomos e sessões gerenciadas com modelos no dispositivo. Seguro para a linha de execução principal por padrão emDispatchers.IO, comtemperaturedefinido comonull(que segue o padrão do modelo; transmitatemperature = 0.0fpara decodificação gananciosa). - A interface
AgentSession: identificador de sessão de conversa de várias interações com escopo definido, obtido usando o métodorunner.getOrCreateSession(sessionId), que registra automaticamente o histórico de conversas na memória em várias interações, com os métodosrunAsync(content)eclose(). - A interface
AgentEvent: o stream reativoFlowdo Kotlin que emite tokens de texto e atualizações de ferramentas. - A classe
AgentTool: funções tipadas que podem ser chamadas pelo modelo, definidas usando as AppFunctions do Android (@AppFunctionDeclaration). - A classe
GenAiException: hierarquia de exceções tipadas (com.google.mlkit.genai.common.GenAiException) que sinaliza estados de falha, comoMAX_TURNS_EXCEEDED,NOT_AVAILABLEouBUSY.
Habilidades do Android
Ver no GitHubUsar a API Agent do Kit de ML
android skills add mlkit-agent-apiPré-requisitos e dependências
Verifique se o ambiente de desenvolvimento e a configuração do Gradle atendem aos seguintes requisitos antes de integrar a API Agent.
Requisitos de plataforma e hardware
- Versão do Android: Android 14 (nível 34 da API) ou mais recente inscrito no programa de prévia para desenvolvedores do AICore.
- Dispositivos compatíveis: para ver uma lista de dispositivos compatíveis e as versões do modelo Gemini Nano, consulte Suporte a dispositivos de geração personalizada e agentes.
- SDK mínimo:
minSdk 26ou mais recente. - Kotlin: Kotlin 2.3 ou mais recente.
Configuração do Gradle
Adicione a dependência genai-agent ao arquivo build.gradle.kts do 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")
}
Embora o genai-agent em si não exija um processador de anotações ou um plug-in de compilador, a definição de ferramentas com androidx.appfunctions exige o
plug-in KSP appfunctions-compiler.
Guia de início rápido: inicializar um agente
O exemplo de código a seguir mostra como instanciar um agente e um executor no 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)
Definir ferramentas
O modelo pode chamar ferramentas tipadas para consultar dados do app, se comunicar com serviços de back-end ou acionar ações no app. As ferramentas são definidas de forma declarativa usando
as AppFunctions do 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",
)
}
}
Registre o serviço gerado no arquivo AndroidManifest.xml do 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>
Restringir o acesso do autor da chamada com a constante ACCESS_LEVEL_SELF
As AppFunctions não precisam ser públicas para o sistema ou para autores de chamada externos. No Android, o controle de acesso é feito por níveis de acesso do autor da chamada, em vez de inspecionar as identidades dele.
Por padrão (usando a constante ACCESS_LEVEL_ANDROID_TRUSTED), as AppFunctions podem
ser chamadas por assistentes no dispositivo certificados pela plataforma que têm a
permissão do sistema EXECUTE_APP_FUNCTIONS. Se a ferramenta for destinada estritamente ao
agente interno do app (por exemplo, gerenciar carrinhos de usuários ou registros de banco de dados
particulares), restrinja o acesso do autor da chamada usando a 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."
}
}
Quando restrita com a constante ACCESS_LEVEL_SELF, a plataforma impede que
superfícies externas e assistentes do sistema descubram ou executem a
função por IPC.
Configurar o agente
Configure uma instância Agent com instruções do sistema e ative as AppFunctions.
Em seguida, inicialize a instância 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)
Se quiser, chame o método addAppFunctionIds("fetch_order_details", "cancel_order")
para limitar a descoberta estritamente a IDs de AppFunction específicos, em vez de
expor todas as funções no app.
Padrões do agente
- Limites de turnos: a classe
Agent.BuilderusamaxTurns = 10como padrão automaticamente como uma condição de encerramento para evitar loops infinitos de chamadas de ferramentas. Não é necessário chamar o métodosetMaxTurns(10)manualmente para fluxos de trabalho padrão de conversação ou chamada de ferramenta. - Decodificação gananciosa determinista: a classe
AgentRunnerdefine o parâmetro padrãotemperaturecomonull, de acordo com o padrão do modelo subjacente. Para seleção de ferramentas determinística e conformidade com o esquema JSON no dispositivo, transmitatemperature = 0.0f(temperatura) para o construtorAgentRunner. Evite definir temperaturas no estilo de nuvem (> 0,4f), que aumentam os riscos de alucinação e a degradação do esquema em modelos para dispositivos móveis.
Execução em vários turnos e eventos de streaming
Consiga uma instância de AgentSession usando o método runner.getOrCreateSession(sessionId) em uma
classe ViewModel do Android. A sessão retorna um stream Flow<AgentEvent> que emite tokens de texto em tempo real e atualizações de execução de ferramentas:
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()
}
}
Fazer streaming de tokens de texto diretamente
Para interfaces de conversação em que os detalhes de execução da ferramenta não precisam ser
mostrados separadamente na interface, use a função de extensão textTokens() na
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)
}
Execução de interação única
Para tarefas únicas em que o histórico de diálogo não deve ser retido na RAM (por
exemplo, marcação de fotos ou resumo de texto temporário), chame o
método runner.runSingleTurnAsync(content) diretamente:
// 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)
}
Ao invocar o método runner.runSingleTurnAsync(content) diretamente, a instância AgentRunner
aloca um ID de sessão temporário internamente e exclui automaticamente o
estado da conversa da memória após a conclusão, evitando vazamentos de memória.
Ciclo de vida da sessão e gerenciamento de memória
O histórico de diálogos de várias interações é armazenado na memória e indexado pelo valor
sessionId:
- Mudanças de configuração: manter a instância
AgentSessiondentro de uma classeViewModeldo Android preserva o histórico de conversas em rotações de tela automaticamente, sem precisar de strings de encadeamento em cada turno. - Remoção de sessão: chame o método
session.delete()para remover uma sessão de forma síncrona quando uma conversa for dispensada ou chame o métodosession.close()não suspenso no métodoViewModel.onCleared()para liberar RAM. - Remoção em massa: chame o método
runner.deleteSession(sessionId)para liberar uma sessão específica ou o métodorunner.clearAllSessions()para remover todas as conversas em cache. - Ciclo de vida do executor: a classe
AgentRunnerimplementa a interfaceAutoCloseable. Ao compartilhar um executor entre telas, feche os identificadoresAgentSessionindividuais no métodoViewModel.onCleared()de cada tela e chame o métodorunner.close()ao encerrar o executor completamente. - Sessões simultâneas: uma única instância do
AgentRunnerpode hospedar várias sessões distintas simultaneamente. Cada valorsessionIdmantém um histórico de conversas isolado.
Tratamento de erros com a classe GenAiException
As falhas de execução durante a execução do agente, como quando o agente atinge o máximo de
turnos de ferramenta sem gerar uma resposta ou quando a IA no dispositivo está temporariamente
indisponível, são emitidas ou geradas como uma exceção GenAiException
(com.google.mlkit.genai.common.GenAiException). Trate as falhas de forma reativa
usando o operador catch do fluxo de corrotinas do 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 erro principais
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112): o agente excedeu o limite máximo de turnos (o limitemaxTurns) sem gerar uma resposta final.GenAiException.ErrorCode.NOT_AVAILABLE(8): o modelo Gemini Nano ou o serviço do sistema AICore não está disponível ou pronto.GenAiException.ErrorCode.BUSY(9): o sistema está ocupado ou o app de ligação excedeu a cota.