Dzięki Agentic Workflows ML Kit na urządzeniu (com.google.mlkit:genai-agent, dostępnym w ramach programu wersji przedpremierowej dla programistów AICore) możesz tworzyć autonomiczne, wieloetapowe agenty konwersacyjne oparte na Gemini Nano i usłudze systemowej Android AICore.
Klasa AgentRunner, która jest oparta na klasie GenerativeModel zapewniającej bezpośredni dostęp do modelu, automatyzuje całe pętle agenta: planowanie wieloetapowych działań, wywoływanie wpisanych narzędzi (takich jak wysyłanie zapytania do repozytorium aplikacji, wywoływanie interfejsu API backendu lub odczytywanie lokalnej bazy danych), ocenianie wyników narzędzi i zarządzanie kontekstem rozmowy lokalnie na urządzeniu bez opóźnień sieci i z zachowaniem pełnej prywatności użytkownika. Klasa GenerativeModel obsługuje zarządzaną przez klienta historię wieloetapową za pomocą obiektu List<Content> (z rolami USER i MODEL) i wartości sessionId, natomiast klasa AgentRunner udostępnia zarządzane sesje wieloetapowe (za pomocą interfejsu AgentSession), które automatycznie rejestrują historię rozmów w pamięci, dzięki czemu nie musisz ręcznie zarządzać transkrypcjami. Statyczne deklaracje narzędzi i instrukcje systemowe są również automatycznie zapisywane w pamięci podręcznej na urządzeniu, co skraca czas oczekiwania na pierwszy token bez konieczności dodatkowej konfiguracji.
Podstawowe komponenty
- Klasa
Agent: deklaruje instrukcje, dostępne narzędzia i ograniczenia wykonywania za pomocą metodyAgent.builder(). Domyślnie automatycznie ustawiana jest wartośćmaxTurns = 10. - Klasa
AgentRunner: oparta na klasieGenerativeModel, która koordynuje autonomiczne pętle rozumowania i zarządzane sesje z modelami na urządzeniu. Domyślnie bezpieczne dla wątku głównego wDispatchers.IO, przy czymtemperaturedomyślnie ma wartośćnull(która odnosi się do domyślnej wartości modelu; przekażtemperature = 0.0f, aby użyć dekodowania zachłannego). - Interfejs
AgentSession: uchwyt sesji rozmowy wieloetapowej o określonym zakresie uzyskany za pomocą metodyrunner.getOrCreateSession(sessionId), która automatycznie rejestruje historię rozmowy w pamięci w ramach poszczególnych etapów, za pomocą metodrunAsync(content)iclose(). - Interfejs
AgentEvent: reaktywny strumieńFloww języku Kotlin, który emituje tokeny tekstowe i aktualizacje narzędzi. - Klasa
AgentTool: funkcje typowane, które mogą być wywoływane przez model, zdefiniowane za pomocą funkcji aplikacji na Androida (@AppFunctionDeclaration). - Klasa
GenAiException: typowana hierarchia wyjątków (com.google.mlkit.genai.common.GenAiException) sygnalizująca stany niepowodzenia, takie jakMAX_TURNS_EXCEEDED,NOT_AVAILABLElubBUSY.
Umiejętności związane z Androidem
Wyświetl na GitHubieKorzystanie z interfejsu ML Kit Agent API
android skills add mlkit-agent-apiWymagania wstępne i zależności
Zanim zintegrujesz interfejs Agent API, upewnij się, że środowisko programistyczne i konfiguracja Gradle spełniają te wymagania.
Wymagania dotyczące platformy i sprzętu
- Wersja Androida: Android 14 (poziom interfejsu API 34) lub nowszy zarejestrowany w programie wersji przedpremierowej AICore dla deweloperów.
- Obsługiwane urządzenia: listę obsługiwanych urządzeń i wersji modelu Gemini Nano znajdziesz w artykule Obsługa niestandardowego generowania i agentów na urządzeniach.
- Minimalna wersja pakietu SDK:
minSdk 26lub nowsza. - Kotlin Kotlin 2.3 lub nowszy.
Konfiguracja Gradle
Dodaj zależność genai-agent do pliku build.gradle.kts modułu:
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")
}
Chociaż genai-agent nie wymaga procesora adnotacji ani wtyczki kompilatora, definiowanie narzędzi za pomocą androidx.appfunctions wymaga wtyczki appfunctions-compiler KSP.
Krótkie wprowadzenie: inicjowanie agenta
Poniższy przykładowy kod pokazuje, jak utworzyć instancję agenta i modułu uruchamiającego na urządzeniu:
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)
Definiowanie narzędzi
Model może wywoływać narzędzia z określonym typem, aby wysyłać zapytania o dane aplikacji, komunikować się z usługami backendu lub wywoływać działania w aplikacji. Narzędzia są definiowane deklaratywnie za pomocą funkcji aplikacji na Androida (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",
)
}
}
Zarejestruj wygenerowaną usługę w pliku AndroidManifest.xml aplikacji:
<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>
Ograniczanie dostępu elementu wywołującego za pomocą stałej ACCESS_LEVEL_SELF
Funkcje AppFunctions nie muszą być publiczne dla systemu ani rozmówców zewnętrznych. W Androidzie kontrola dostępu jest obsługiwana przez poziomy dostępu elementu wywołującego, a nie przez sprawdzanie tożsamości elementu wywołującego.
Domyślnie (przy użyciu stałej ACCESS_LEVEL_ANDROID_TRUSTED) funkcje AppFunctions mogą być wywoływane przez certyfikowanych przez platformę asystentów na urządzeniu, którzy mają uprawnienia systemowe EXECUTE_APP_FUNCTIONS. Jeśli narzędzie jest przeznaczone wyłącznie dla agenta wewnętrznego aplikacji (np. do zarządzania koszykami użytkowników lub prywatnymi rekordami bazy danych), ogranicz dostęp wywołującego za pomocą stałej 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."
}
}
W przypadku ograniczenia za pomocą stałej ACCESS_LEVEL_SELF platforma blokuje platformom zewnętrznym i asystentom systemowym możliwość wykrywania lub wykonywania funkcji przez IPC.
Konfigurowanie agenta
Skonfiguruj instancję Agent z instrukcjami systemowymi i włącz AppFunctions, a następnie zainicjuj instancję 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)
Opcjonalnie możesz wywołać metodę addAppFunctionIds("fetch_order_details", "cancel_order"), aby ograniczyć wykrywanie wyłącznie do określonych identyfikatorów AppFunction, zamiast udostępniać wszystkie funkcje w aplikacji.
Ustawienia domyślne agenta
- Limity tur: klasa
Agent.Builderdomyślnie ustawiamaxTurns = 10jako warunek zakończenia, aby zapobiec nieskończonym pętlom wywoływania narzędzi. W przypadku standardowych przepływów pracy związanych z rozmową lub wywoływaniem narzędzi nie musisz ręcznie wywoływać metodysetMaxTurns(10). - Deterministyczne zachłanne dekodowanie: klasa
AgentRunnerustawia domyślny parametrtemperaturenanull, odwołując się do domyślnego modelu bazowego. Aby zapewnić deterministyczny wybór narzędzi i zgodność ze schematem JSON na urządzeniu, przekażtemperature = 0.0f(temperature) do konstruktoraAgentRunner. Unikaj ustawiania temperatur w stylu chmury (> 0,4f), które zwiększają ryzyko halucynacji i pogorszenia schematu w modelach mobilnych.
Wieloetapowe wykonywanie i zdarzenia strumieniowe
Uzyskaj instancję AgentSession za pomocą metody runner.getOrCreateSession(sessionId) w klasie ViewModel Androida. Sesja zwraca strumień Flow<AgentEvent>, który emituje w czasie rzeczywistym tokeny tekstowe i aktualizacje dotyczące wykonywania narzędzi:
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()
}
}
Przesyłanie strumieniowe tokenów tekstowych bezpośrednio
W przypadku interfejsów konwersacyjnych, w których szczegóły wykonywania narzędzia nie muszą być wyświetlane osobno w interfejsie, użyj funkcji rozszerzenia textTokens() w interfejsie 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)
}
Wykonanie jednoetapowe
W przypadku jednorazowych zadań, w których historia dialogu nie powinna być przechowywana w pamięci RAM (np. tagowanie zdjęć lub tymczasowe podsumowanie tekstu), wywołaj bezpośrednio metodę 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)
}
Gdy wywołujesz metodę runner.runSingleTurnAsync(content) bezpośrednio, instancja AgentRunner wewnętrznie przydziela tymczasowy identyfikator sesji i automatycznie usuwa stan rozmowy z pamięci po zakończeniu, co zapobiega wyciekom pamięci.
Cykl życia sesji i zarządzanie pamięcią
Historia wieloetapowego dialogu jest przechowywana w pamięci i indeksowana według wartości sessionId:
- Zmiany konfiguracji: zachowanie instancji
AgentSessionw klasieViewModelAndroida pozwala automatycznie zachować historię rozmów podczas obracania ekranu bez konieczności przekazywania ciągów znaków przy każdym obrocie. - Usuwanie sesji: wywołaj metodę
session.delete(), aby synchronicznie usunąć sesję, gdy wątek rozmowy zostanie odrzucony, lub wywołaj metodęsession.close(), która nie zawiesza działania, w metodzieViewModel.onCleared(), aby zwolnić pamięć RAM. - Zbiorcze usuwanie: wywołaj metodę
runner.deleteSession(sessionId), aby zwolnić konkretną sesję, lub metodęrunner.clearAllSessions(), aby usunąć wszystkie rozmowy z pamięci podręcznej. - Cykl życia modułu uruchamiającego: klasa
AgentRunnerimplementuje interfejsAutoCloseable. Jeśli udostępniasz moduł uruchamiający na różnych ekranach, zamknij poszczególne uchwytyAgentSessionw metodzieViewModel.onCleared()każdego ekranu i wywołaj metodęrunner.close(), gdy całkowicie wyłączasz moduł uruchamiający. - Sesje równoczesne: pojedyncza instancja
AgentRunnermoże jednocześnie obsługiwać wiele różnych sesji. Każda wartośćsessionIdzachowuje odrębną historię rozmów.
Obsługa błędów za pomocą klasy GenAiException
Błędy czasu działania podczas wykonywania agenta, np. gdy agent osiągnie maksymalną liczbę tur narzędzia bez wygenerowania odpowiedzi lub gdy AI na urządzeniu jest tymczasowo niedostępna, są emitowane lub zgłaszane jako wyjątek GenAiException (com.google.mlkit.genai.common.GenAiException). Reaguj na błędy, używając operatora 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
}
Kluczowe kody błędów
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112): agent przekroczył maksymalny limit tur (limitmaxTurns) bez wygenerowania ostatecznej odpowiedzi.GenAiException.ErrorCode.NOT_AVAILABLE(8): model Gemini Nano lub usługa systemowa AICore jest niedostępna lub niegotowa.GenAiException.ErrorCode.BUSY(9): system jest zajęty lub aplikacja do dzwonienia przekroczyła limit.