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ứcAgent.builder(). Tự động đặt mặc định làmaxTurns = 10. - Lớp
AgentRunner: Được xây dựng dựa trên lớpGenerativeModel, đ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ênDispatchers.IO, vớitemperaturemặc định lànull(tuân theo giá trị mặc định của mô hình; truyềntemperature = 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ứcrunner.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ứcrunAsync(content)vàclose(). - Giao diện
AgentEvent: LuồngFlowKotlin 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_AVAILABLEhoặcBUSY.
Kỹ năng Android
Xem trên GitHubSử dụng API Tác nhân Bộ công cụ học máy
android skills add mlkit-agent-apiĐ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
- Phiên bản Android: Android 14 (cấp độ API 34) trở lên đã đăng ký tham gia chương trình Bản dùng trước cho nhà phát triển AICore.
- Thiết bị được hỗ trợ: Để biết danh sách các thiết bị được hỗ trợ và phiên bản mô hình Gemini Nano của các thiết bị đó, hãy xem phần Hỗ trợ thiết bị cho tác nhân và tạo tuỳ chỉnh.
- SDK tối thiểu:
minSdk 26trở lên. - Kotlin: Kotlin 2.3 trở lên.
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.Buildermặc địnhmaxTurns = 10tự độ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ứcsetMaxTurns(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ốtemperaturemặc định thànhnull, 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ềntemperature = 0.0f(temperature) đến hàm khởi tạoAgentRunner. 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ể
AgentSessionbên trong một lớpViewModelcủ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ứcsession.close()không tạm ngưng trong phương thứcViewModel.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ứcrunner.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
AgentRunnertriển khai giao diệnAutoCloseable. Khi chia sẻ trình chạy trên các màn hình, hãy đóng các tay điều khiểnAgentSessionriêng lẻ trong phương thứcViewModel.onCleared()của mỗi màn hình và gọi phương thứcrunner.close()khi tắt hoàn toàn trình chạy. - Phiên đồng thời: Một thực thể
AgentRunnercó thể lưu trữ nhiều phiên riêng biệt cùng một lúc. Mỗi giá trịsessionIdduy 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ạnmaxTurns) 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.