As ferramentas dão ao agente no dispositivo a capacidade de acessar dados do app, se comunicar com serviços de back-end e realizar ações em nome do usuário.
Como as ferramentas funcionam
Quando um usuário envia um comando, a classe AgentRunner executa um
loop do agente em quatro etapas:
- Observação: o Gemini Nano analisa o comando do usuário e o contexto da conversa atual em relação às declarações de ferramentas registradas (o espaço de ação do agente).
- Motivo: o modelo determina se uma ferramenta no app é necessária. Se uma ferramenta for necessária, o Gemini Nano vai emitir uma invocação de ferramenta estruturada contendo o nome da função e os argumentos digitados.
- Ação: a instância
AgentRunnerintercepta automaticamente essa invocação e executa a função Kotlin correspondente no app. - Avaliar o feedback: a saída da ferramenta é enviada de volta ao Gemini Nano. O
modelo avalia o feedback e responde ao usuário ou chama outra
ferramenta até que uma condição de encerramento seja atendida (conclusão da meta ou alcance
do limite de
maxTurns).
Ferramentas declarativas com Android AppFunctions
A maneira recomendada de definir ferramentas é de forma declarativa usando as AppFunctions do Android (androidx.appfunctions). Elas oferecem um modelo de plataforma padronizado para declarar recursos tipados no app com geração automática de esquema.
1. Adicionar dependências do Gradle
Adicione os artefatos do AppFunctions com genai-agent no arquivo build.gradle.kts do seu 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. Adicionar a anotação @AppFunctionDeclaration aos métodos de ferramenta
Anote os métodos de ferramenta públicos com a
anotação @AppFunctionDeclaration(isDescribedByKDoc = true). As descrições
de parâmetros são documentadas em tags @param padrão do KDoc.
As AppFunctions exigem que os valores de retorno sejam tipos primitivos (como
String, Int ou Boolean) ou classes de dados estruturados anotadas com a
anotação @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."
}
)
}
}
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>
3. 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 assistentes externos. No Android, o controle de acesso é gerenciado por níveis de acesso do autor da chamada, em vez de verificações manuais de identidade.
Por padrão (usando a constante ACCESS_LEVEL_ANDROID_TRUSTED), uma AppFunction
pode ser descoberta e executada por assistentes no dispositivo certificados pela plataforma
que tenham a permissão do sistema EXECUTE_APP_FUNCTIONS. No entanto, ao
criar recursos destinados estritamente ao agente interno do app (como
inspecionar carrinhos de compras do usuário, acessar tokens locais ou realizar ações
no app), é possível restringir o acesso exclusivamente ao seu aplicativo 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() {
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."
)
}
}
A plataforma oferece três níveis de acesso para os autores de chamadas:
ACCESS_LEVEL_SELF(100): estritamente restrito ao próprio aplicativo host. Assistentes externos, plataformas do sistema e outros apps não podem descobrir ou invocar a função por IPC. Use esse nível para operações ou ferramentas sensíveis que devem atender apenas ao agente interno do app.ACCESS_LEVEL_SYSTEM(200): restrito a autores de chamada do sistema privilegiados que têm a permissãoandroid.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM(como a tela de início do sistema ou os apps recentes) e o próprio app host.ACCESS_LEVEL_ANDROID_TRUSTED(300, padrão): aberto para agentes no dispositivo certificados pela plataforma que têm a permissãoEXECUTE_APP_FUNCTIONS, as plataformas do sistema e o app host.
4. Registrar ferramentas com o agente
É possível registrar AppFunctions com o agente de forma automática usando o método
enableAppFunctions() ou de forma programática usando o método
AppFunctionAgentTools.discover():
Descoberta automática com o método enableAppFunctions()
Para apps simples em que as AppFunctions são executadas automaticamente quando o modelo as chama:
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)
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.
Descoberta programática com o método AppFunctionAgentTools.discover()
Quando o app precisa inspecionar as AppFunctions descobertas, filtrá-las ou decorá-las antes de registrar com o agente:
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)
Padrão de decorador e human in the loop
Operações sensíveis, como cancelamentos de pedidos, pagamentos ou exclusão de dados do usuário, precisam manter um human in the loop (HITL), exigindo a confirmação do usuário. Todas as ferramentas adicionadas à classe Agent são originadas do Android AppFunctions,
e a classe AgentTool fornece um construtor de delegação AgentTool(delegate) para que você
possa encapsular as ferramentas AppFunction descobertas usando o padrão 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áticas recomendadas para design de ferramentas
- Defina o escopo do espaço de ação: mantenha o espaço de ação do agente (o conjunto de ferramentas e permissões expostas ao agente) apenas o suficiente para a tarefa usando o método
addAppFunctionIds()e a constanteACCESS_LEVEL_SELF. Um espaço de ação excessivamente grande torna um modelo no dispositivo mais propenso a erros e consome tokens de janela de contexto. - Seja explícito nas descrições: o Gemini Nano depende das descrições de ferramentas e parâmetros para decidir quando chamar uma ferramenta. Seja claro sobre a finalidade da ferramenta e mencione os formatos de argumento esperados (como datas ou IDs).
- Retornar mapas de erros estruturados: retorne mapas de erros descritivos, como
mapOf("error" to "Order not found"), quando ocorrer uma falha esperada. Isso permite que o Gemini Nano explique o problema de forma clara para o usuário. Se uma exceção inesperada for gerada por uma ferramenta, o ambiente de execução vai capturá-la automaticamente e refletir a mensagem de erro de volta ao modelo para autocorreção. - Mantenha os payloads de retorno focados: os modelos no dispositivo têm janelas de contexto menores do que os modelos de servidor. Retorne apenas os campos relevantes para responder à consulta do usuário, em vez de despejar linhas inteiras do banco de dados ou respostas de rede brutas.
- Executar E/S no agente
Dispatchers.IO: sempre garanta que as solicitações de rede e as consultas do banco de dados do Room nas ferramentas sejam executadas no agenteDispatchers.IOpara evitar o bloqueio do autor da chamada da corrotina.
Próximas etapas
- Saiba como manter o contexto da conversa em rotações de apps e limpar sessões em Gerenciar sessões e ciclo de vida.
- Processar tokens de texto de streaming e estados de execução de ferramentas em Eventos de stream e integração de interface.