Инструменты позволяют агенту на устройстве получать доступ к данным приложения, взаимодействовать с бэкенд-службами и выполнять действия от имени пользователя.
Как работают инструменты
Когда пользователь отправляет запрос, класс AgentRunner выполняет цикл агента, состоящий из четырех этапов:
- Наблюдение. Gemini Nano анализирует запрос пользователя и текущий контекст разговора, сопоставляя их с зарегистрированными объявлениями инструментов (пространством действий агента).
- Причина. Модель определяет, требуется ли инструмент в приложении. Если инструмент необходим, Gemini Nano выдает структурированный вызов инструмента, содержащий имя функции и типизированные аргументы.
- Действие. Экземпляр
AgentRunnerавтоматически перехватывает этот вызов и выполняет соответствующую функцию Kotlin в вашем приложении. - Оценка отзыва. Результат работы инструмента передается обратно в Gemini Nano. Модель оценивает отзыв и либо отвечает пользователю, либо вызывает другой инструмент, пока не будет выполнено условие завершения (достижение цели или достижение лимита
maxTurns).
Декларативные инструменты с Android AppFunctions
Рекомендуемый способ определения инструментов – декларативный, с использованием Android AppFunctions (androidx.appfunctions). AppFunctions предоставляет стандартизированную модель платформы для объявления типизированных возможностей приложения с автоматическим созданием схемы.
1. Добавьте зависимости Gradle
Добавьте артефакты AppFunctions вместе с genai-agent в файл build.gradle.kts модуля:
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. Добавьте к методам инструментов аннотацию @AppFunctionDeclaration.
Добавьте к методам общедоступных инструментов аннотацию @AppFunctionDeclaration(isDescribedByKDoc = true). Описания параметров задокументированы в стандартных тегах KDoc @param.
AppFunctions требуют, чтобы возвращаемые значения были либо примитивными типами (такими как String, Int или Boolean), либо классами структурированных данных, аннотированными с помощью аннотации @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."
}
)
}
}
Зарегистрируйте сгенерированный сервис в файле AndroidManifest.xml приложения:
<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. Ограничение доступа вызывающего абонента с помощью константы ACCESS_LEVEL_SELF
AppFunctions не обязательно должны быть общедоступными для системы или внешних помощников. В Android контроль доступа осуществляется с помощью уровней доступа вызывающего абонента, а не ручной проверки его идентификационных данных.
По умолчанию (с использованием константы ACCESS_LEVEL_ANDROID_TRUSTED) AppFunction может быть обнаружена и выполнена сертифицированными платформой помощниками на устройстве, у которых есть системное разрешение EXECUTE_APP_FUNCTIONS. Однако если вы создаете функции, предназначенные исключительно для внутреннего агента вашего приложения (например, для проверки корзин пользователей, доступа к локальным токенам или выполнения действий в приложении), вы можете ограничить доступ только своим приложением, используя константу 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."
)
}
}
Платформа поддерживает три уровня доступа для звонящих:
ACCESS_LEVEL_SELF(100). Строго ограничено самим хост-приложением. Сторонние помощники, системные интерфейсы и другие приложения не могут обнаруживать или вызывать функцию через IPC. Используйте этот уровень для конфиденциальных операций или инструментов, которые должны обслуживать только внутреннего агента вашего приложения.ACCESS_LEVEL_SYSTEM(200). Доступно только для системных вызывающих абонентов с разрешениемandroid.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM(например, системного средства запуска или списка недавно использованных приложений) и самого хост-приложения.ACCESS_LEVEL_ANDROID_TRUSTED(300, по умолчанию). Доступно для сертифицированных платформой агентов на устройстве с разрешениемEXECUTE_APP_FUNCTIONS, системных интерфейсов и хост-приложения.
4. Как зарегистрировать инструменты в агенте
Зарегистрировать AppFunctions в агенте можно автоматически с помощью метода enableAppFunctions() или программно с помощью метода AppFunctionAgentTools.discover().
Автоматическое обнаружение с помощью метода enableAppFunctions()
Для простых приложений, в которых AppFunctions выполняются автоматически, когда модель вызывает их:
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)
При необходимости вызовите метод addAppFunctionIds("fetch_order_details", "cancel_order"), чтобы ограничить поиск определенными идентификаторами AppFunction, а не всеми функциями приложения.
Автоматическое обнаружение с помощью метода AppFunctionAgentTools.discover()
Если приложению нужно проверить обнаруженные AppFunctions, отфильтровать или декорировать их перед регистрацией в агенте:
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)
Шаблон "Декоратор" и человек в контуре
Для выполнения конфиденциальных операций, таких как отмена заказа, оплата или удаление пользовательских данных, требуется подтверждение пользователя. Это называется человек в контуре. Все инструменты, добавленные в класс Agent, происходят из Android AppFunctions, а класс AgentTool предоставляет делегирующий конструктор AgentTool(delegate), чтобы вы могли обернуть обнаруженные инструменты AppFunction с помощью шаблона 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)
}
}
Рекомендации по разработке инструментов
- Ограничьте пространство действий. Пространство действий агента (набор инструментов и разрешений, доступных ему) должно быть достаточным для выполнения задачи. Для этого используйте метод
addAppFunctionIds()и константуACCESS_LEVEL_SELF. Слишком большое пространство действий повышает вероятность ошибок локальной модели и расходует токены окна контекста. - Будьте точны в описаниях. Gemini Nano использует описания инструментов и параметров, чтобы решать, когда вызывать инструмент. Четко опишите назначение инструмента и укажите ожидаемые форматы аргументов (например, даты или идентификаторы).
- Возвращать структурированные карты ошибок. Возвращать описательные карты ошибок, например
mapOf("error" to "Order not found"), когда происходит ожидаемый сбой. Это позволяет Gemini Nano корректно объяснить пользователю, в чем проблема. Если инструмент выдает непредвиденное исключение, среда выполнения автоматически перехватывает его и возвращает сообщение об ошибке модели для самокоррекции. - Ограничивайте объем возвращаемых данных. У моделей на устройстве окна контекста меньше, чем у серверных моделей. Возвращайте только те поля, которые имеют отношение к ответу на запрос пользователя, а не все строки базы данных или необработанные ответы сети.
- Выполняйте операции ввода-вывода в диспетчере
Dispatchers.IO. Сетевые запросы и запросы к базе данных Room в инструментах должны выполняться в диспетчереDispatchers.IO, чтобы не блокировать вызывающий объект сопрограммы.
Дальнейшие действия
- Узнайте, как сохранять контекст разговора при повороте экрана и очищать сеансы, в статье Управление сеансами и жизненным циклом.
- Обрабатывайте токены потокового текста и состояния выполнения инструментов в разделе События потока и интеграция с интерфейсом.