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:
- 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).
- 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.
- Hành động: Thực thể
AgentRunnertự độ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. - Đá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ềnandroid.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ềnEXECUTE_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ốiDispatchers.IOđể tránh chặn phương thức gọi coroutine.
Các bước tiếp theo
- Tìm hiểu cách duy trì bối cảnh cuộc trò chuyện trong các lần xoay ứng dụng và dọn dẹp phiên trong phần Quản lý phiên và vòng đời.
- Xử lý mã thông báo văn bản truyền trực tuyến và trạng thái thực thi công cụ trong phần Sự kiện truyền trực tuyến và tích hợp giao diện người dùng.