Агентные рабочие процессы ML Kit на устройстве (com.google.mlkit:genai-agent, доступные в рамках программы предварительного тестирования AICore для разработчиков) позволяют создавать автономных многоэтапных разговорных агентов на базе Gemini Nano и системного сервиса Android AICore.
Класс AgentRunner, созданный на основе класса GenerativeModel, который обеспечивает прямой доступ к модели, автоматизирует полные циклы агентов: планирование многоэтапных действий, вызов типизированных инструментов (например, запрос к репозиторию приложений, вызов API бэкенда или чтение локальной базы данных), оценку результатов работы инструментов и управление контекстом разговора локально на устройстве с нулевой задержкой сети и полной конфиденциальностью пользователя. Класс GenerativeModel поддерживает управляемую клиентом историю многоэтапного диалога с помощью объекта List<Content> (с ролями USER и MODEL) и значения sessionId, а класс AgentRunner предоставляет управляемые многоэтапные сеансы (с помощью интерфейса AgentSession), которые автоматически записывают историю разговора в памяти на всех этапах, поэтому вам не нужно управлять расшифровками вручную. Статические объявления инструментов и системные инструкции также автоматически кешируются на устройстве, что сокращает время до первого токена без дополнительной настройки.
Основные разделы
- Класс
Agentобъявляет инструкции, доступные инструменты и ограничения на выполнение с помощью методаAgent.builder(). По умолчанию автоматически устанавливается значениеmaxTurns = 10. - Класс
AgentRunner. Создан на основе классаGenerativeModel. Он управляет автономными циклами рассуждений и сеансами с моделями на устройстве. По умолчанию безопасно для основного потока вDispatchers.IO, аtemperatureпо умолчанию имеет значениеnull(которое зависит от значения по умолчанию для модели; передайтеtemperature = 0.0fдля жадного декодирования). - Интерфейс
AgentSession. Дескриптор сеанса многоэтапного разговора, полученный с помощью методаrunner.getOrCreateSession(sessionId), который автоматически записывает историю разговора в памяти на разных этапах с помощью методовrunAsync(content)иclose(). - Интерфейс
AgentEvent. Реактивный потокFlowKotlin, который передает текстовые токены и обновления инструментов. - Класс
AgentTool. Типизированные функции, вызываемые моделью и определенные с помощью Android AppFunctions (@AppFunctionDeclaration). - Класс
GenAiException. Иерархия типизированных исключений (com.google.mlkit.genai.common.GenAiException), сигнализирующих о сбоях, таких какMAX_TURNS_EXCEEDED,NOT_AVAILABLEилиBUSY.
Навыки работы с Android
Посмотреть на GitHubКак использовать ML Kit Agent API
android skills add mlkit-agent-apiПредварительные требования и зависимости
Прежде чем интегрировать Agent API, убедитесь, что ваша среда разработки и конфигурация Gradle соответствуют следующим требованиям.
Требования к платформе и оборудованию
- Версия Android. Android 14 (уровень API 34) или более поздняя версия, зарегистрированная в программе предварительной версии AICore для разработчиков.
- Поддерживаемые устройства. Список поддерживаемых устройств и версий модели Gemini Nano, которые на них используются, можно найти в статье Поддержка устройств для агентов и пользовательской генерации.
- Минимальная версия SDK:
minSdk 26или выше. - Kotlin Kotlin 2.3 или более поздней версии.
Конфигурация Gradle
Добавьте зависимость genai-agent в файл build.gradle.kts модуля:
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")
}
Хотя для самой библиотеки genai-agent не требуется инструмент обработки аннотаций или плагин компилятора, для определения инструментов с помощью androidx.appfunctions необходим плагин appfunctions-compiler KSP.
Быстрый старт: инициализация агента
В следующем примере кода показано, как создать экземпляр агента и исполнителя на устройстве:
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)
Определите инструменты
Модель может вызывать типизированные инструменты для запроса данных приложения, связи с бэкенд-службами или запуска действий в приложении. Инструменты определяются декларативно с помощью Android AppFunctions (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",
)
}
}
Зарегистрируйте сгенерированный сервис в файле 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>
Ограничение доступа вызывающего абонента с помощью константы ACCESS_LEVEL_SELF
Функции AppFunctions не обязательно должны быть общедоступными для системы или внешних вызывающих объектов. В Android контроль доступа осуществляется через уровни доступа вызывающего абонента, а не через проверку его идентификационных данных.
По умолчанию (с использованием константы ACCESS_LEVEL_ANDROID_TRUSTED) AppFunctions могут вызываться сертифицированными платформой помощниками на устройстве, у которых есть системное разрешение 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() {
/**
* 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."
}
}
Если ограничить ее с помощью константы ACCESS_LEVEL_SELF, платформа заблокирует внешним интерфейсам и системным помощникам возможность обнаруживать или выполнять функцию через IPC.
Настройте агент
Настройте экземпляр Agent с помощью системных инструкций и включите AppFunctions, а затем инициализируйте экземпляр 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)
При необходимости вызовите метод addAppFunctionIds("fetch_order_details", "cancel_order"), чтобы ограничить поиск определенными идентификаторами AppFunction, а не всеми функциями приложения.
Настройки агента по умолчанию
- Ограничение количества ходов. В классе
Agent.Builderпо умолчанию используетсяmaxTurns = 10в качестве условия завершения, чтобы предотвратить бесконечные циклы вызова инструментов. Для стандартных рабочих процессов, связанных с разговорами или вызовом инструментов, вызывать методsetMaxTurns(10)вручную не нужно. - Детерминированное жадное декодирование. Класс
AgentRunnerзадает для параметраtemperatureзначение по умолчаниюnull, которое соответствует значению по умолчанию базовой модели. Чтобы обеспечить детерминированный выбор инструментов и соответствие схеме JSON на устройстве, передайтеtemperature = 0.0f(temperature) в конструкторAgentRunner. Не устанавливайте температуру, характерную для облачных моделей (> 0,4f), поскольку это повышает риск галлюцинаций и ухудшения схемы в мобильных моделях.
Многоэтапное выполнение и потоковая передача событий
Получите экземпляр AgentSession с помощью метода runner.getOrCreateSession(sessionId) в классе ViewModel Android. Сеанс возвращает поток Flow<AgentEvent>, который в реальном времени передает текстовые токены и обновления выполнения инструментов.
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()
}
}
Прямая передача текстовых токенов
Для диалоговых интерфейсов, в которых не нужно отдельно показывать сведения о выполнении инструмента, используйте функцию расширения textTokens() в интерфейсе 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)
}
Выполнение за один шаг
Для разовых задач, когда историю диалога не нужно сохранять в ОЗУ (например, для добавления тегов к фотографиям или краткого пересказа текста), вызывайте метод runner.runSingleTurnAsync(content) напрямую:
// 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)
}
При прямом вызове метода runner.runSingleTurnAsync(content) экземпляр AgentRunner выделяет временный идентификатор сеанса и автоматически удаляет состояние разговора из памяти по завершении, предотвращая утечки памяти.
Жизненный цикл сеанса и управление памятью
История диалога хранится в памяти и индексируется по значению sessionId:
- Изменения конфигурации. Сохранение экземпляра
AgentSessionв классеViewModelAndroid позволяет автоматически сохранять историю разговора при повороте экрана без необходимости передавать строки при каждом повороте. - Вытеснение сеанса. Вызовите метод
session.delete(), чтобы синхронно вытеснить сеанс, когда цепочка чата закрыта, или вызовите методsession.close()без приостановки в методеViewModel.onCleared(), чтобы освободить ОЗУ. - Массовое удаление. Вызовите метод
runner.deleteSession(sessionId), чтобы освободить определенный сеанс, или методrunner.clearAllSessions(), чтобы удалить все кешированные чаты. - Жизненный цикл исполнителя. Класс
AgentRunnerреализует интерфейсAutoCloseable. Если вы используете один и тот же объект runner на нескольких экранах, закрывайте отдельные дескрипторыAgentSessionв методеViewModel.onCleared()каждого экрана и вызывайте методrunner.close()при полном завершении работы объекта runner. - Параллельные сеансы. Один экземпляр
AgentRunnerможет одновременно поддерживать несколько отдельных сеансов. Для каждого значенияsessionIdсохраняется отдельная история чата.
Обработка ошибок с помощью класса GenAiException
Сбои во время выполнения агента, например когда он достигает максимального количества обращений к инструментам, не генерируя ответ, или когда ИИ на устройстве временно недоступен, генерируются или выдаются как исключение GenAiException (com.google.mlkit.genai.common.GenAiException). Обрабатывайте сбои реактивно с помощью оператора catch Kotlin Coroutines Flow:
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
}
Основные коды ошибок
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112): агент превысил максимальное количество реплик (maxTurns), не сгенерировав окончательный ответ.GenAiException.ErrorCode.NOT_AVAILABLE(8): модель Gemini Nano или системный сервис AICore недоступны или не готовы.GenAiException.ErrorCode.BUSY(9): система занята или приложение для звонков превысило квоту.