Alat memberi agen di perangkat Anda kemampuan untuk mengakses data dalam aplikasi, berkomunikasi dengan layanan backend, dan melakukan tindakan atas nama pengguna.
Cara kerja alat
Saat pengguna mengirimkan perintah, class AgentRunner akan menjalankan
agent loop dalam empat tahap:
- Mengamati: Gemini Nano menganalisis perintah pengguna dan konteks percakapan saat ini berdasarkan deklarasi alat yang terdaftar (ruang tindakan agen).
- Menalar: Model menentukan apakah alat dalam aplikasi diperlukan. Jika alat diperlukan, Gemini Nano akan memunculkan pemanggilan alat terstruktur yang berisi nama fungsi dan argumen yang diketik.
- Act: Instance
AgentRunnerakan otomatis menangkap pemanggilan ini dan menjalankan fungsi Kotlin yang sesuai di aplikasi Anda. - Mengevaluasi masukan: Output alat dimasukkan kembali ke Gemini Nano. Model
mengevaluasi masukan dan menjawab pengguna atau memanggil alat
lain hingga kondisi penghentian terpenuhi (menyelesaikan sasaran atau mencapai
batas
maxTurns).
Alat deklaratif dengan AppFunctions Android
Cara yang direkomendasikan untuk menentukan alat adalah secara deklaratif menggunakan Android
AppFunctions (androidx.appfunctions). AppFunctions menyediakan model platform
standar untuk mendeklarasikan kemampuan dalam aplikasi yang diketik dengan pembuatan skema
otomatis.
1. Menambahkan dependensi Gradle
Tambahkan artefak AppFunctions bersama genai-agent di file build.gradle.kts modul Anda:
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. Anotasikan metode alat dengan anotasi @AppFunctionDeclaration
Anotasikan metode alat publik dengan
anotasi @AppFunctionDeclaration(isDescribedByKDoc = true). Deskripsi parameter didokumentasikan dalam tag @param KDoc standar.
AppFunction memerlukan nilai yang ditampilkan berupa jenis primitif (seperti
String, Int, atau Boolean) atau class data terstruktur yang dianotasi dengan
anotasi @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."
}
)
}
}
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>
3. Membatasi akses pemanggil dengan konstanta ACCESS_LEVEL_SELF
AppFunctions tidak harus bersifat publik untuk sistem atau asisten eksternal. Di Android, kontrol akses dikelola melalui tingkat akses pemanggil, bukan pemeriksaan identitas pemanggil manual.
Secara default (menggunakan konstanta ACCESS_LEVEL_ANDROID_TRUSTED), AppFunction
dapat ditemukan dan dieksekusi oleh asisten di perangkat yang disertifikasi platform
yang memiliki izin sistem EXECUTE_APP_FUNCTIONS. Namun, saat Anda
membangun kemampuan yang ditujukan khusus untuk agen internal aplikasi Anda (seperti
memeriksa keranjang belanja pengguna, mengakses token lokal, atau melakukan tindakan
dalam aplikasi), Anda dapat membatasi akses secara eksklusif ke aplikasi Anda sendiri 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() {
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."
)
}
}
Platform ini mendukung tiga tingkat akses pemanggil:
ACCESS_LEVEL_SELF(100): Sangat dibatasi hanya untuk aplikasi host itu sendiri. Asisten eksternal, platform sistem, dan aplikasi lain tidak dapat menemukan atau memanggil fungsi melalui IPC. Gunakan level ini untuk operasi atau alat sensitif yang hanya boleh melayani agen internal aplikasi Anda.ACCESS_LEVEL_SYSTEM(200): Dibatasi untuk pemanggil sistem dengan hak istimewa yang memiliki izinandroid.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM(seperti Peluncur sistem atau Terbaru) dan aplikasi host itu sendiri.ACCESS_LEVEL_ANDROID_TRUSTED(300, default): Terbuka untuk agen di perangkat yang disertifikasi platform yang memiliki izinEXECUTE_APP_FUNCTIONS, platform sistem, dan aplikasi host.
4. Mendaftarkan alat ke agen
Anda dapat mendaftarkan AppFunction ke agen secara otomatis menggunakan
metode enableAppFunctions(), atau secara terprogram menggunakan
metode AppFunctionAgentTools.discover():
Penemuan otomatis dengan metode enableAppFunctions()
Untuk aplikasi sederhana yang AppFunctions-nya dijalankan secara otomatis saat dipanggil oleh model:
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)
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.
Penemuan terprogram dengan metode AppFunctionAgentTools.discover()
Saat aplikasi Anda perlu memeriksa AppFunction yang ditemukan, memfilternya, atau mendekorasinya sebelum mendaftar ke agen:
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)
Pola dekorator dan human in the loop
Operasi sensitif, seperti pembatalan pesanan, pembayaran, atau penghapusan data
pengguna, harus tetap memerlukan interaksi manusia (HITL) dengan mewajibkan konfirmasi
pengguna. Semua alat yang ditambahkan ke class Agent berasal dari Android AppFunctions,
dan class AgentTool menyediakan konstruktor pendelegasian AgentTool(delegate) sehingga Anda
dapat menggabungkan alat AppFunction yang ditemukan menggunakan pola 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)
}
}
Praktik terbaik untuk desain alat
- Tentukan cakupan ruang tindakan: Jaga agar ruang tindakan agen (set alat dan izin yang diekspos ke agen) cukup besar untuk tugasnya menggunakan metode
addAppFunctionIds()dan konstantaACCESS_LEVEL_SELF. Ruang tindakan yang terlalu besar membuat model di perangkat lebih rentan terhadap error dan menggunakan token jendela konteks. - Berikan deskripsi yang jelas: Gemini Nano mengandalkan deskripsi alat dan parameter untuk memutuskan kapan harus memanggil alat. Jelaskan tujuan alat dan sebutkan format argumen yang diharapkan (seperti tanggal atau ID).
- Menampilkan peta error terstruktur: Menampilkan peta error deskriptif seperti
mapOf("error" to "Order not found")saat terjadi kegagalan yang diperkirakan. Hal ini memungkinkan Gemini Nano menjelaskan masalah dengan baik kepada pengguna. Jika pengecualian yang tidak terduga ditampilkan oleh alat, runtime akan otomatis menangkapnya dan menampilkan pesan error kembali ke model untuk koreksi mandiri. - Pastikan payload yang ditampilkan tetap fokus: Model di perangkat memiliki jendela konteks yang lebih kecil daripada model server. Hanya tampilkan kolom yang relevan untuk menjawab kueri pengguna, bukan menampilkan seluruh baris database atau respons jaringan mentah.
- Jalankan I/O di dispatcher
Dispatchers.IO: Selalu pastikan permintaan jaringan dan kueri database Room dalam alat dijalankan di dispatcherDispatchers.IOuntuk mencegah pemblokiran pemanggil coroutine.
Langkah berikutnya
- Pelajari cara mempertahankan konteks percakapan di seluruh rotasi aplikasi dan membersihkan sesi di Mengelola sesi dan siklus proses.
- Menangani token teks streaming dan status eksekusi alat di Peristiwa streaming dan integrasi UI.