Menentukan alat

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:

  1. Mengamati: Gemini Nano menganalisis perintah pengguna dan konteks percakapan saat ini berdasarkan deklarasi alat yang terdaftar (ruang tindakan agen).
  2. 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.
  3. Act: Instance AgentRunner akan otomatis menangkap pemanggilan ini dan menjalankan fungsi Kotlin yang sesuai di aplikasi Anda.
  4. 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 izin android.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 izin EXECUTE_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 konstanta ACCESS_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 dispatcher Dispatchers.IO untuk mencegah pemblokiran pemanggil coroutine.

Langkah berikutnya