Dengan Agentic Workflows di perangkat ML Kit (com.google.mlkit:genai-agent, tersedia melalui program Pratinjau Developer AICore), Anda dapat membangun agen percakapan multi-turn otonom yang didukung oleh Gemini Nano dan layanan sistem Android AICore.
Dibangun di atas class GenerativeModel, yang menyediakan
akses model langsung, class AgentRunner mengotomatiskan
loop agentic lengkap: merencanakan tindakan multi-langkah, memanggil alat yang diketik (seperti
membuat kueri repositori aplikasi, memanggil API backend, atau membaca database lokal),
mengevaluasi hasil alat, dan mengelola konteks percakapan secara lokal di perangkat
dengan latensi jaringan nol dan privasi pengguna penuh. Meskipun class GenerativeModel
mendukung histori multi-turn yang dikelola klien menggunakan objek List<Content> (dengan
peran USER dan MODEL) dan nilai sessionId, class AgentRunner
menyediakan sesi multi-turn terkelola (menggunakan antarmuka AgentSession) yang
merekam histori percakapan dalam memori secara otomatis di seluruh giliran sehingga Anda
tidak perlu mengelola transkrip secara manual. Deklarasi alat statis dan petunjuk sistem juga otomatis di-cache di perangkat, sehingga mengurangi latensi waktu ke token pertama tanpa konfigurasi tambahan.
Komponen inti
- Class
Agent: Mendeklarasikan petunjuk, alat yang tersedia, dan guardrail eksekusi menggunakan metodeAgent.builder(). Default-nya adalahmaxTurns = 10secara otomatis. - Class
AgentRunner: Dibangun di atas classGenerativeModel, mengatur reasoning loop otonom dan sesi terkelola dengan model di perangkat. Main-safe secara default diDispatchers.IO, dengantemperatureditetapkan secara default kenull(yang mengikuti default model; teruskantemperature = 0.0funtuk decoding greedy). - Antarmuka
AgentSession: Handle sesi percakapan multi-turn tercakup yang diperoleh menggunakan metoderunner.getOrCreateSession(sessionId)yang otomatis mencatat histori percakapan dalam memori di seluruh giliran, dengan metoderunAsync(content)danclose(). - Antarmuka
AgentEvent: Aliran dataFlowKotlin reaktif yang memunculkan token teks dan update alat. - Class
AgentTool: Fungsi yang diketik yang dapat dipanggil oleh model, yang ditentukan menggunakan AppFunctions Android (@AppFunctionDeclaration). - Class
GenAiException: Hierarki pengecualian yang diketik (com.google.mlkit.genai.common.GenAiException) yang menandakan status kegagalan sepertiMAX_TURNS_EXCEEDED,NOT_AVAILABLE, atauBUSY.
Keterampilan Android
Lihat di GitHubMenggunakan ML Kit Agent API
android skills add mlkit-agent-apiPrasyarat dan dependensi
Pastikan lingkungan pengembangan dan konfigurasi Gradle Anda memenuhi persyaratan berikut sebelum mengintegrasikan Agent API.
Persyaratan platform dan hardware
- Versi Android: Android 14 (level API 34) atau yang lebih tinggi yang terdaftar dalam program Pratinjau Developer AICore.
- Perangkat yang didukung: Untuk mengetahui daftar perangkat yang didukung dan versi model Gemini Nano-nya, lihat Dukungan perangkat agen dan pembuatan kustom.
- Minimum SDK:
minSdk 26atau yang lebih tinggi. - Kotlin: Kotlin 2.3 atau yang lebih baru.
Konfigurasi Gradle
Tambahkan dependensi genai-agent ke file build.gradle.kts modul Anda:
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")
}
Meskipun genai-agent sendiri tidak memerlukan pemroses anotasi atau plugin
compiler, menentukan alat dengan androidx.appfunctions memerlukan
plugin KSP appfunctions-compiler.
Panduan memulai: Melakukan inisialisasi agen
Contoh kode berikut menunjukkan cara membuat instance agen dan runner di perangkat:
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)
Menentukan alat
Model ini dapat memanggil alat yang diketik untuk mengkueri data aplikasi, berkomunikasi dengan layanan backend, atau memicu tindakan dalam aplikasi. Alat ditentukan secara deklaratif menggunakan
AppFunctions 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",
)
}
}
Daftarkan layanan yang dihasilkan dalam file AndroidManifest.xml aplikasi Anda:
<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>
Membatasi akses pemanggil dengan konstanta ACCESS_LEVEL_SELF
AppFunctions tidak harus bersifat publik untuk sistem atau pemanggil eksternal. Di Android, kontrol akses ditangani melalui tingkat akses pemanggil, bukan memeriksa identitas pemanggil.
Secara default (menggunakan konstanta ACCESS_LEVEL_ANDROID_TRUSTED), AppFunction dapat
dipanggil oleh asisten di perangkat yang disertifikasi platform yang memiliki
izin sistem EXECUTE_APP_FUNCTIONS. Jika alat Anda benar-benar ditujukan untuk
agen internal aplikasi (misalnya, mengelola keranjang pengguna atau data database
pribadi), batasi akses pemanggil menggunakan konstanta 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."
}
}
Jika dibatasi dengan konstanta ACCESS_LEVEL_SELF, platform akan memblokir
platform eksternal dan asisten sistem agar tidak menemukan atau menjalankan
fungsi melalui IPC.
Mengonfigurasi agen
Konfigurasikan instance Agent dengan petunjuk sistem dan aktifkan AppFunctions,
lalu lakukan inisialisasi instance 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)
Secara opsional, panggil metode addAppFunctionIds("fetch_order_details", "cancel_order")
untuk membatasi penemuan secara ketat ke ID AppFunction tertentu, bukan
mengekspos semua fungsi dalam aplikasi.
Default agen
- Batas giliran: Class
Agent.BuildermenetapkanmaxTurns = 10secara default sebagai kondisi penghentian untuk mencegah loop panggilan alat yang tak terbatas. Anda tidak perlu memanggil metodesetMaxTurns(10)secara manual untuk alur kerja percakapan atau panggilan alat standar. - Decoding greedy deterministik: Class
AgentRunnermenetapkan parametertemperaturedefault kenull, yang mengacu pada default model yang mendasarinya. Untuk pemilihan alat deterministik dan kepatuhan skema JSON di perangkat, teruskantemperature = 0.0f(temperature) ke konstruktorAgentRunner. Hindari menyetel suhu bergaya cloud (> 0,4f), yang meningkatkan risiko halusinasi dan penurunan kualitas skema pada model seluler.
Eksekusi multi-turn dan peristiwa streaming
Dapatkan instance AgentSession menggunakan metode runner.getOrCreateSession(sessionId) di dalam
class ViewModel Android. Sesi ini menampilkan streaming Flow<AgentEvent> yang memunculkan token teks real-time dan update eksekusi alat:
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()
}
}
Men-streaming token teks secara langsung
Untuk antarmuka percakapan yang detail eksekusi alatnya tidak perlu
ditampilkan secara terpisah di UI, gunakan fungsi ekstensi textTokens() di
antarmuka 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)
}
Eksekusi satu giliran
Untuk tugas satu kali yang histori dialognya tidak boleh disimpan dalam RAM (misalnya, pemberian tag foto atau ringkasan teks sementara), panggil metode runner.runSingleTurnAsync(content) secara langsung:
// 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)
}
Saat memanggil metode runner.runSingleTurnAsync(content) secara langsung, instance AgentRunner
mengalokasikan ID sesi sementara secara internal dan otomatis menghapus
status percakapan dari memori setelah selesai, sehingga mencegah kebocoran memori.
Siklus proses sesi dan manajemen memori
Histori dialog multi-turn disimpan dalam memori dan diindeks berdasarkan nilai sessionId-nya:
- Perubahan konfigurasi: Mempertahankan instance
AgentSessiondi dalam classViewModelAndroid akan mempertahankan histori percakapan di seluruh rotasi layar secara otomatis tanpa perlu menghubungkan string di setiap giliran. - Pengeluaran sesi: Panggil metode
session.delete()untuk mengeluarkan sesi secara sinkron saat rangkaian percakapan ditutup, atau panggil metodesession.close()yang tidak ditangguhkan dalam metodeViewModel.onCleared()untuk mengosongkan RAM. - Pengusiran massal: Panggil metode
runner.deleteSession(sessionId)untuk mengosongkan sesi tertentu, atau metoderunner.clearAllSessions()untuk mengusir semua percakapan yang di-cache. - Siklus proses runner: Class
AgentRunnermengimplementasikan antarmukaAutoCloseable. Saat membagikan runner di seluruh layar, tutup setiap handleAgentSessiondi metodeViewModel.onCleared()setiap layar, dan panggil metoderunner.close()saat menonaktifkan runner sepenuhnya. - Sesi serentak: Satu instance
AgentRunnerdapat menghosting beberapa sesi yang berbeda secara bersamaan. Setiap nilaisessionIdmempertahankan histori percakapan yang terisolasi.
Penanganan error dengan class GenAiException
Kegagalan runtime selama eksekusi agen, seperti saat agen mencapai giliran alat maksimum tanpa menghasilkan respons atau saat AI di perangkat tidak tersedia untuk sementara, akan ditampilkan atau ditampilkan sebagai pengecualian GenAiException (com.google.mlkit.genai.common.GenAiException). Tangani kegagalan secara reaktif menggunakan operator catch Flow Coroutine 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
}
Kode error utama
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112): Agen melebihi batas giliran maksimum (batasmaxTurns) tanpa menghasilkan respons akhir.GenAiException.ErrorCode.NOT_AVAILABLE(8): Model Gemini Nano atau layanan sistem AICore tidak tersedia atau belum siap.GenAiException.ErrorCode.BUSY(9): Sistem sedang sibuk atau aplikasi panggilan telah melebihi kuotanya.