Xác định công cụ

Công cụ giúp tác nhân trên thiết bị có thể truy cập vào dữ liệu trong ứng dụng, giao tiếp với các dịch vụ phụ trợ và thực hiện hành động thay cho người dùng.

Cách hoạt động của các công cụ

Khi người dùng gửi một câu lệnh, lớp AgentRunner sẽ thực thi một vòng lặp tác nhân qua 4 giai đoạn:

  1. Quan sát: Gemini Nano phân tích câu lệnh của người dùng và bối cảnh trò chuyện hiện tại dựa trên các nội dung khai báo công cụ đã đăng ký (không gian hành động của tác nhân).
  2. Lý do: Mô hình xác định xem có cần công cụ trong ứng dụng hay không. Nếu cần một công cụ, Gemini Nano sẽ phát ra một lệnh gọi công cụ có cấu trúc chứa tên hàm và các đối số được nhập.
  3. Hành động: Thực thể AgentRunner tự động chặn lệnh gọi này và thực thi hàm Kotlin tương ứng trong ứng dụng của bạn.
  4. Đánh giá ý kiến phản hồi: Kết quả của công cụ được đưa trở lại Gemini Nano. Mô hình sẽ đánh giá ý kiến phản hồi và trả lời người dùng hoặc gọi một công cụ khác cho đến khi đáp ứng điều kiện chấm dứt (hoàn thành mục tiêu hoặc đạt đến giới hạn maxTurns).

Công cụ khai báo bằng Android AppFunctions

Bạn nên xác định các công cụ theo cách khai báo bằng cách sử dụng AppFunctions của Android (androidx.appfunctions). AppFunctions cung cấp một mô hình nền tảng chuẩn hoá để khai báo các chức năng được nhập trong ứng dụng bằng tính năng tạo giản đồ tự động.

1. Thêm phần phụ thuộc vào Gradle

Thêm cấu phần phần mềm AppFunctions cùng với genai-agent trong tệp build.gradle.kts của mô-đun:

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. Chú thích các phương thức công cụ bằng chú thích @AppFunctionDeclaration

Chú thích các phương thức công cụ công khai bằng chú thích @AppFunctionDeclaration(isDescribedByKDoc = true). Nội dung mô tả tham số được ghi lại trong các thẻ @param KDoc chuẩn.

AppFunctions yêu cầu giá trị trả về phải là kiểu dữ liệu gốc (chẳng hạn như String, Int hoặc Boolean) hoặc các lớp dữ liệu có cấu trúc được chú thích bằng chú thích @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."
            }
        )
    }
}

Đăng ký dịch vụ đã tạo trong tệp AndroidManifest.xml của ứng dụng:

<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. Hạn chế quyền truy cập của phương thức gọi bằng hằng số ACCESS_LEVEL_SELF

AppFunction không cần phải công khai với hệ thống hoặc với các trợ lý bên ngoài. Trong Android, quyền kiểm soát truy cập được quản lý thông qua các cấp truy cập của phương thức gọi thay vì kiểm tra danh tính phương thức gọi theo cách thủ công.

Theo mặc định (sử dụng hằng số ACCESS_LEVEL_ANDROID_TRUSTED), một AppFunction có thể được phát hiện và thực thi bởi các trợ lý trên thiết bị được nền tảng chứng nhận và có quyền hệ thống EXECUTE_APP_FUNCTIONS. Tuy nhiên, khi xây dựng các chức năng dành riêng cho tác nhân nội bộ của ứng dụng (chẳng hạn như kiểm tra giỏ hàng của người dùng, truy cập vào mã thông báo cục bộ hoặc thực hiện các hành động trong ứng dụng), bạn có thể hạn chế quyền truy cập chỉ cho ứng dụng của riêng mình bằng cách sử dụng hằng số 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."
        )
    }
}

Nền tảng này hỗ trợ 3 cấp truy cập cho người gọi:

  • ACCESS_LEVEL_SELF (100): Chỉ dành riêng cho ứng dụng lưu trữ. Trợ lý bên ngoài, nền tảng hệ thống và các ứng dụng khác không thể khám phá hoặc gọi hàm qua IPC. Hãy sử dụng cấp độ này cho các thao tác hoặc công cụ nhạy cảm chỉ phục vụ tác nhân nội bộ của ứng dụng.
  • ACCESS_LEVEL_SYSTEM (200): Chỉ dành cho phương thức gọi hệ thống đặc quyền có quyền android.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM (chẳng hạn như Trình chạy hệ thống hoặc ứng dụng Gần đây) và chính ứng dụng lưu trữ.
  • ACCESS_LEVEL_ANDROID_TRUSTED (300, mặc định): Mở cho các tác nhân trên thiết bị được nền tảng chứng nhận có quyền EXECUTE_APP_FUNCTIONS, các nền tảng hệ thống và ứng dụng lưu trữ.

4. Đăng ký công cụ với tác nhân

Bạn có thể đăng ký AppFunctions với tác nhân theo cách tự động bằng phương thức enableAppFunctions() hoặc theo phương thức lập trình bằng phương thức AppFunctionAgentTools.discover():

Tự động khám phá bằng phương thức enableAppFunctions()

Đối với các ứng dụng đơn giản, trong đó AppFunctions tự động thực thi khi mô hình gọi chúng:

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)

Nếu muốn, hãy gọi phương thức addAppFunctionIds("fetch_order_details", "cancel_order") để giới hạn phạm vi khám phá chỉ cho các mã AppFunction cụ thể thay vì hiển thị tất cả chức năng trong ứng dụng.

Khám phá có lập trình bằng phương thức AppFunctionAgentTools.discover()

Khi ứng dụng của bạn cần kiểm tra AppFunctions đã phát hiện, lọc hoặc trang trí các hàm đó trước khi đăng ký với tác nhân:

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)

Mẫu trang trí và con người nằm trong quy trình

Đối với các thao tác nhạy cảm, chẳng hạn như huỷ đơn đặt hàng, thanh toán hoặc xoá dữ liệu người dùng, bạn nên áp dụng quy trình con người nằm trong quy trình (HITL) bằng cách yêu cầu người dùng xác nhận. Tất cả công cụ được thêm vào lớp Agent đều bắt nguồn từ Android AppFunctions và lớp AgentTool cung cấp một hàm khởi tạo uỷ quyền AgentTool(delegate) để bạn có thể gói các công cụ AppFunction được khám phá bằng mẫu 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)
    }
}

Các phương pháp hay nhất để thiết kế công cụ

  • Xác định phạm vi không gian hành động: Giữ cho không gian hành động của tác nhân (tập hợp các công cụ và quyền được cung cấp cho tác nhân) vừa đủ lớn cho nhiệm vụ của tác nhân bằng cách sử dụng phương thức addAppFunctionIds() và hằng số ACCESS_LEVEL_SELF. Không gian hành động quá lớn khiến mô hình trên thiết bị dễ gặp lỗi hơn và tiêu tốn token cửa sổ ngữ cảnh.
  • Nêu rõ ràng trong nội dung mô tả: Gemini Nano dựa vào nội dung mô tả về công cụ và tham số để quyết định thời điểm gọi một công cụ. Nêu rõ mục đích của công cụ và đề cập đến các định dạng đối số dự kiến (chẳng hạn như ngày hoặc mã nhận dạng).
  • Trả về bản đồ lỗi có cấu trúc: Trả về bản đồ lỗi mô tả như mapOf("error" to "Order not found") khi xảy ra lỗi dự kiến. Điều này cho phép Gemini Nano giải thích vấn đề một cách dễ hiểu cho người dùng. Nếu một công cụ gửi một ngoại lệ không mong muốn, thì thời gian chạy sẽ tự động phát hiện ngoại lệ đó và phản ánh thông báo lỗi trở lại mô hình để tự sửa lỗi.
  • Giữ cho tải trọng trả về tập trung: Các mô hình trên thiết bị có cửa sổ ngữ cảnh nhỏ hơn so với các mô hình máy chủ. Chỉ trả về các trường liên quan đến việc trả lời câu hỏi của người dùng thay vì kết xuất toàn bộ hàng cơ sở dữ liệu hoặc phản hồi mạng thô.
  • Chạy I/O trên trình điều phối Dispatchers.IO: Luôn đảm bảo các yêu cầu mạng và truy vấn cơ sở dữ liệu Room trong các công cụ thực thi trên trình điều phối Dispatchers.IO để tránh chặn phương thức gọi coroutine.

Các bước tiếp theo