工具可讓裝置端代理存取應用程式資料、與後端服務通訊,以及代表使用者執行動作。
工具的運作方式
使用者提交提示詞時,AgentRunner 類別會執行代理迴圈,分為四個階段:
- 觀察:Gemini Nano 會根據已註冊的工具宣告 (代理的動作空間),分析使用者的提示和目前的對話內容。
- 原因:模型會判斷是否需要應用程式內工具。如果需要工具,Gemini Nano 會發出結構化工具叫用,其中包含函式名稱和類型引數。
- 執行:
AgentRunner執行個體會自動攔截這個叫用,並在應用程式中執行對應的 Kotlin 函式。 - 評估意見回饋:工具的輸出內容會回傳給 Gemini Nano。模型會評估意見回饋,並回答使用者或呼叫其他工具,直到符合終止條件 (達成目標或達到
maxTurns限制)。
使用 Android AppFunctions 的宣告式工具
建議使用 Android AppFunctions (androidx.appfunctions) 以宣告方式定義工具。AppFunctions 提供標準化平台模型,可宣告具類型的應用程式內功能,並自動產生結構定義。
1. 新增 Gradle 依附元件
在模組的 build.gradle.kts 檔案中,將 AppFunctions 構件與 genai-agent 一併加入:
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. 使用 @AppFunctionDeclaration 註解為工具方法加上註解
使用 @AppFunctionDeclaration(isDescribedByKDoc = true) 註解為公開工具方法加上註解。參數說明記載於標準 KDoc @param 標記中。
AppFunctions 的傳回值必須是原始類型 (例如 String、Int 或 Boolean),或加上 @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."
}
)
}
}
在應用程式的 AndroidManifest.xml 檔案中註冊產生的服務:
<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. 使用 ACCESS_LEVEL_SELF 常數限制呼叫端存取權
AppFunctions 不必公開給系統或外部助理。在 Android 中,存取控管是透過呼叫端存取層級管理,而非手動檢查呼叫端身分。
根據預設 (使用 ACCESS_LEVEL_ANDROID_TRUSTED 常數),具備 EXECUTE_APP_FUNCTIONS 系統權限的平台認證裝置端助理,可以探索及執行 AppFunction。不過,如果您建構的功能僅供應用程式內部代理程式使用 (例如檢查使用者購物車、存取本機權杖或執行應用程式內動作),可以使用 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."
)
}
}
平台支援三種來電者存取層級:
ACCESS_LEVEL_SELF(100):嚴格限制僅限主機應用程式本身。外部助理、系統介面和其他應用程式無法透過 IPC 探索或叫用函式。如果作業或工具較為敏感,且僅供應用程式的內部代理使用,請採用這個層級。ACCESS_LEVEL_SYSTEM(200):僅限具備android.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM權限的特殊權限系統呼叫端 (例如系統啟動器或「最近」畫面) 和主機應用程式本身。ACCESS_LEVEL_ANDROID_TRUSTED(300,預設):開放給具備EXECUTE_APP_FUNCTIONS權限、系統介面和主機應用程式的平台認證裝置端代理程式。
4. 向代理註冊工具
您可以使用 enableAppFunctions() 方法自動向代理註冊 AppFunction,也可以使用 AppFunctionAgentTools.discover() 方法透過程式輔助方式註冊:
使用 enableAppFunctions() 方法自動探索
如果應用程式較為簡單,AppFunctions 會在模型呼叫時自動執行:
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)
您可以選擇呼叫 addAppFunctionIds("fetch_order_details", "cancel_order") 方法,將探索範圍嚴格限制在特定 AppFunction ID,而非公開所有應用程式內函式。
使用 AppFunctionAgentTools.discover() 方法透過程式探索
如果應用程式需要檢查、篩選或修飾找到的 AppFunction,再向代理程式註冊,請按照下列步驟操作:
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)
裝飾器模式和人工介入迴路
取消訂單、付款或刪除使用者資料等敏感操作,應要求使用者確認,以確保人工介入迴路 (HITL)。新增至 Agent 類別的所有工具都源自 Android AppFunctions,而 AgentTool 類別提供委派建構函式 AgentTool(delegate),因此您可以使用 Decorator 模式包裝探索到的 AppFunction 工具:
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)
}
}
工具設計最佳做法
- 限制動作空間:使用
addAppFunctionIds()方法和ACCESS_LEVEL_SELF常數,將代理的動作空間 (代理可用的工具和權限組合) 限制在足以完成任務的範圍內。如果動作空間過大,裝置端模型就更容易出錯,並會消耗脈絡窗口權杖。 - 在說明中明確指出:Gemini Nano 會根據工具和參數說明,決定何時呼叫工具。清楚說明工具的用途,並提及預期的引數格式 (例如日期或 ID)。
- 傳回結構化錯誤對應:發生預期失敗時,傳回
mapOf("error" to "Order not found")等描述性錯誤對應。這樣一來,Gemini Nano 就能向使用者清楚說明問題。如果工具擲回非預期的例外狀況,執行階段會自動擷取,並將錯誤訊息傳回模型,以便自我修正。 - 保持回傳酬載精簡:裝置端模型的脈絡視窗比伺服器模型小。只傳回與回答使用者查詢相關的欄位,而不是傾印整個資料庫資料列或原始網路回應。
- 在
Dispatchers.IO調度器上執行 I/O:請務必確保工具中的網路要求和 Room 資料庫查詢在Dispatchers.IO調度器上執行,以免封鎖協同程式呼叫端。
後續步驟
- 請參閱「管理工作階段和生命週期」,瞭解如何在應用程式旋轉時維持對話情境,以及如何清除工作階段。
- 在串流事件和 UI 整合中,處理串流文字權杖和工具執行狀態。