Xây dựng quy trình làm việc dựa trên tác nhân bằng Gemini Nano

Với quy trình làm việc dựa trên tác nhân trên thiết bị của Bộ công cụ học máy (com.google.mlkit:genai-agent, có trong chương trình Bản dùng trước cho nhà phát triển AICore), bạn có thể xây dựng các tác nhân trò chuyện nhiều lượt, tự động, dựa trên Gemini Nano và dịch vụ hệ thống Android AICore.

Được xây dựng dựa trên lớp GenerativeModel (cung cấp quyền truy cập trực tiếp vào mô hình), lớp AgentRunner tự động hoá hoàn toàn các vòng lặp tác nhân: lập kế hoạch cho các hành động gồm nhiều bước, gọi các công cụ được nhập (chẳng hạn như truy vấn kho lưu trữ ứng dụng, gọi API phụ trợ hoặc đọc cơ sở dữ liệu cục bộ), đánh giá kết quả của công cụ và quản lý bối cảnh trò chuyện cục bộ trên thiết bị mà không có độ trễ mạng và đảm bảo quyền riêng tư của người dùng. Mặc dù lớp GenerativeModel hỗ trợ nhật ký nhiều lượt do ứng dụng quản lý bằng đối tượng List<Content> (với vai trò USER và MODEL) và giá trị sessionId, nhưng lớp AgentRunner cung cấp các phiên nhiều lượt được quản lý (bằng giao diện AgentSession) tự động ghi lại nhật ký cuộc trò chuyện trong bộ nhớ qua các lượt để bạn không phải quản lý bản chép lời theo cách thủ công. Khai báo công cụ tĩnh và hướng dẫn hệ thống cũng được tự động lưu vào bộ nhớ đệm trên thiết bị, giúp giảm độ trễ thời gian đến mã thông báo đầu tiên mà không cần cấu hình thêm.

Thành phần cốt lõi

  • Lớp Agent: Khai báo hướng dẫn, công cụ có sẵn và rào chắn thực thi bằng phương thức Agent.builder(). Tự động đặt mặc định là maxTurns = 10.
  • Lớp AgentRunner: Được xây dựng dựa trên lớp GenerativeModel, điều phối các vòng lặp suy luận tự động và các phiên được quản lý bằng các mô hình trên thiết bị. An toàn cho luồng chính theo mặc định trên Dispatchers.IO, với temperature mặc định là null (tuân theo giá trị mặc định của mô hình; truyền temperature = 0.0f để giải mã tham lam).
  • Giao diện AgentSession: Trình xử lý phiên trò chuyện nhiều lượt trong phạm vi được lấy bằng phương thức runner.getOrCreateSession(sessionId), tự động ghi lại nhật ký trò chuyện trong bộ nhớ qua các lượt, bằng phương thức runAsync(content) và close().
  • Giao diện AgentEvent: Luồng Flow Kotlin phản ứng phát ra mã thông báo văn bản và thông tin cập nhật về công cụ.
  • Lớp AgentTool: Các hàm được nhập mà mô hình có thể gọi, được xác định bằng AppFunctions của Android (@AppFunctionDeclaration).
  • Lớp GenAiException: Hệ phân cấp ngoại lệ được nhập (com.google.mlkit.genai.common.GenAiException) báo hiệu trạng thái không thành công, chẳng hạn như MAX_TURNS_EXCEEDED, NOT_AVAILABLE hoặc BUSY.

Điều kiện tiên quyết và phần phụ thuộc

Đảm bảo môi trường phát triển và cấu hình Gradle của bạn đáp ứng các yêu cầu sau trước khi tích hợp Agent API.

Yêu cầu về nền tảng và phần cứng

Cấu hình Gradle

Thêm phần phụ thuộc genai-agent vào tệp build.gradle.kts của mô-đun:

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")
}

Mặc dù bản thân genai-agent không yêu cầu trình xử lý chú thích hoặc trình bổ trợ trình biên dịch, nhưng việc xác định các công cụ bằng androidx.appfunctions đòi hỏi phải có trình bổ trợ KSP appfunctions-compiler.

Bắt đầu nhanh: Khởi chạy tác nhân

Mã mẫu sau đây cho biết cách tạo thực thể cho một trình chạy và tác nhân trên thiết bị:

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)

Xác định công cụ

Mô hình có thể gọi các công cụ đã nhập để truy vấn dữ liệu ứng dụng, giao tiếp với các dịch vụ phụ trợ hoặc kích hoạt các hành động trong ứng dụng. Các công cụ được xác định theo cách khai báo bằng Android AppFunctions (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",
        )
    }
}

Đă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>

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 phương thức gọi bên ngoài. Trong Android, việc kiểm soát quyền truy cập được xử 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 của phương thức gọi.

Theo mặc định (sử dụng hằng số ACCESS_LEVEL_ANDROID_TRUSTED), AppFunctions có thể được gọi 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. Nếu công cụ của bạn chỉ dành cho tác nhân nội bộ của ứng dụng (ví dụ: quản lý giỏ hàng của người dùng hoặc bản ghi cơ sở dữ liệu riêng tư), hãy hạn chế quyền truy cập của phương thức gọi bằ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() {

    /**
     * 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."
    }
}

Khi bị hạn chế bằng hằng số ACCESS_LEVEL_SELF, nền tảng sẽ chặn các nền tảng bên ngoài và trợ lý hệ thống khám phá hoặc thực thi hàm qua IPC.

Định cấu hình tác nhân

Định cấu hình một thực thể Agent bằng các lệnh hệ thống và bật AppFunctions, sau đó khởi động thực thể 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)

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.

Chế độ mặc định cho tác nhân

  • Giới hạn lượt: Lớp Agent.Builder mặc định maxTurns = 10 tự động làm điều kiện kết thúc để ngăn vòng lặp gọi công cụ vô hạn. Bạn không cần gọi phương thức setMaxTurns(10) theo cách thủ công cho quy trình trò chuyện hoặc gọi công cụ thông thường.
  • Giải mã tham lam tất định: Lớp AgentRunner đặt tham số temperature mặc định thành null, tuân theo giá trị mặc định của mô hình cơ bản. Để chọn công cụ tất định và tuân thủ giản đồ JSON trên thiết bị, hãy truyền temperature = 0.0f (temperature) đến hàm khởi tạo AgentRunner. Tránh đặt nhiệt độ kiểu đám mây (> 0,4f), vì điều này làm tăng nguy cơ ảo tưởng và làm giảm chất lượng giản đồ trên các mô hình di động.

Sự kiện thực thi và truyền trực tuyến nhiều lượt

Lấy một thực thể AgentSession bằng phương thức runner.getOrCreateSession(sessionId) bên trong một lớp ViewModel của Android. Phiên này trả về một luồng Flow<AgentEvent> phát ra các mã thông báo văn bản theo thời gian thực và thông tin cập nhật về việc thực thi công cụ:

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()
    }
}

Truyền trực tiếp mã thông báo văn bản

Đối với giao diện trò chuyện mà bạn không cần hiển thị riêng thông tin chi tiết về việc thực thi công cụ trong giao diện người dùng, hãy sử dụng hàm mở rộng textTokens() trên giao diện 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)
    }

Thực thi một lượt

Đối với các tác vụ một lần mà nhật ký cuộc trò chuyện không được lưu giữ trong RAM (ví dụ: gắn thẻ ảnh hoặc tóm tắt văn bản tạm thời), hãy gọi trực tiếp phương thức 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)
    }

Khi gọi trực tiếp phương thức runner.runSingleTurnAsync(content), thực thể AgentRunner sẽ phân bổ mã phiên tạm thời trong nội bộ và tự động xoá trạng thái cuộc trò chuyện khỏi bộ nhớ sau khi hoàn tất, giúp ngăn chặn rò rỉ bộ nhớ.

Vòng đời phiên và quản lý bộ nhớ

Nhật ký cuộc trò chuyện nhiều lượt được lưu trữ trong bộ nhớ và được lập chỉ mục theo giá trị sessionId:

  • Thay đổi về cấu hình: Việc giữ lại thực thể AgentSession bên trong một lớp ViewModel của Android sẽ tự động lưu giữ nhật ký trò chuyện khi xoay màn hình mà không cần phải kết nối các chuỗi mỗi lần xoay.
  • Loại bỏ phiên: Gọi phương thức session.delete() để loại bỏ đồng bộ một phiên khi một chuỗi trò chuyện bị đóng, hoặc gọi phương thức session.close() không tạm ngưng trong phương thức ViewModel.onCleared() để giải phóng RAM.
  • Xoá hàng loạt: Gọi phương thức runner.deleteSession(sessionId) để giải phóng một phiên cụ thể hoặc phương thức runner.clearAllSessions() để xoá tất cả cuộc trò chuyện đã lưu vào bộ nhớ đệm.
  • Vòng đời của trình chạy: Lớp AgentRunner triển khai giao diện AutoCloseable. Khi chia sẻ trình chạy trên các màn hình, hãy đóng các tay điều khiển AgentSession riêng lẻ trong phương thức ViewModel.onCleared() của mỗi màn hình và gọi phương thức runner.close() khi tắt hoàn toàn trình chạy.
  • Phiên đồng thời: Một thực thể AgentRunner có thể lưu trữ nhiều phiên riêng biệt cùng một lúc. Mỗi giá trị sessionId duy trì một nhật ký trò chuyện riêng biệt.

Xử lý lỗi bằng lớp GenAiException

Lỗi thời gian chạy trong quá trình thực thi tác nhân, chẳng hạn như khi tác nhân đạt đến số lượt tối đa của công cụ mà không tạo ra phản hồi hoặc khi AI trên thiết bị tạm thời không hoạt động, sẽ được phát ra hoặc gửi dưới dạng ngoại lệ GenAiException (com.google.mlkit.genai.common.GenAiException). Xử lý lỗi một cách linh hoạt bằng toán tử catch của Luồng 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
    }

Mã lỗi chính

  • GenAiException.ErrorCode.MAX_TURNS_EXCEEDED (-112): Tác nhân đã vượt quá giới hạn lượt tối đa (giới hạn maxTurns) mà không tạo ra câu trả lời cuối cùng.
  • GenAiException.ErrorCode.NOT_AVAILABLE (8): Mô hình Gemini Nano hoặc dịch vụ hệ thống AICore không hoạt động hoặc chưa sẵn sàng.
  • GenAiException.ErrorCode.BUSY (9): Hệ thống đang bận hoặc ứng dụng gọi điện đã vượt quá hạn mức.