透過 ML Kit 的裝置端Agentic Workflows (com.google.mlkit:genai-agent,可透過 AICore 開發人員預覽版計畫取得),您可以建構由 Gemini Nano 和 Android AICore 系統服務支援的自主多輪對話式代理。
AgentRunner 類別建構於 GenerativeModel 類別之上,提供直接模型存取權,可自動執行完整的代理迴圈:規劃多步驟動作、叫用類型工具 (例如查詢應用程式存放區、呼叫後端 API 或讀取本機資料庫)、評估工具結果,以及在裝置端管理對話情境,完全沒有網路延遲,且能充分保護使用者隱私。GenerativeModel 類別支援用戶端管理的多輪對話記錄,使用 List<Content> 物件 (具有 USER 和 MODEL 角色) 和 sessionId 值,而 AgentRunner 類別則提供代管的多輪對話工作階段 (使用 AgentSession 介面),可自動記錄各輪對話的記憶體內對話記錄,因此您不必手動管理轉錄稿。靜態工具宣告和系統指令也會自動快取在裝置端,不需額外設定,就能縮短第一個詞元的產生延遲時間。
核心元件
Agent類別:使用Agent.builder()方法宣告指令、可用工具和執行護欄。自動預設為maxTurns = 10。AgentRunner類別:以GenerativeModel類別為基礎建構,可協調自主推論迴圈,並使用裝置端模型管理工作階段。Dispatchers.IO預設為主執行緒安全,temperature預設為null(遵循模型的預設值;傳遞temperature = 0.0f則會使用貪婪解碼)。AgentSession介面:使用runner.getOrCreateSession(sessionId)方法取得的範圍多輪對話工作階段控制代碼,會使用runAsync(content)和close()方法,自動記錄各輪對話的記憶體內對話記錄。AgentEvent介面:反應式 KotlinFlow串流,會發出文字符記和工具更新。AgentTool類別:模型可呼叫的類型函式,使用 Android AppFunctions (@AppFunctionDeclaration) 定義。GenAiException類別:類型化例外狀況階層 (com.google.mlkit.genai.common.GenAiException),用於表示失敗狀態,例如MAX_TURNS_EXCEEDED、NOT_AVAILABLE或BUSY。
Android 技能
前往 GitHub 查看使用 ML Kit Agent API
android skills add mlkit-agent-api必要條件和依附元件
整合 Agent API 前,請確認開發環境和 Gradle 設定符合下列規定。
平台和硬體需求
- Android 版本:Android 14 (API 級別 34) 以上版本,且已註冊 AICore 開發人員預覽版計畫。
- 支援的裝置:如需支援的裝置清單和 Gemini Nano 模型版本,請參閱「自訂生成和代理裝置支援」。
- Minimum SDK:
minSdk 26以上版本。 - Kotlin:Kotlin 2.3 以上版本。
Gradle 設定
將 genai-agent 依附元件新增至模組的 build.gradle.kts 檔案:
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")
}
雖然 genai-agent 本身不需要註解處理器或編譯器外掛程式,但使用 androidx.appfunctions 定義工具時,需要 appfunctions-compiler KSP 外掛程式。
快速入門導覽課程:初始化代理
以下程式碼範例說明如何例項化裝置端代理和執行工具:
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)
定義工具
模型可以呼叫類型化工具,查詢應用程式資料、與後端服務通訊,或觸發應用程式內動作。工具是使用 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",
)
}
}
在應用程式的 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>
使用 ACCESS_LEVEL_SELF 常數限制呼叫端存取權
AppFunctions 不必對系統或外部呼叫端公開。在 Android 中,存取控管是透過呼叫端存取層級處理,而非檢查呼叫端身分。
根據預設 (使用 ACCESS_LEVEL_ANDROID_TRUSTED 常數),AppFunctions 可
由具備
EXECUTE_APP_FUNCTIONS 系統權限、經平台認證的裝置端助理呼叫。如果工具僅供應用程式的內部代理使用 (例如管理使用者購物車或私人資料庫記錄),請使用 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."
}
}
如果使用 ACCESS_LEVEL_SELF 常數限制,平台會禁止外部介面和系統助理透過 IPC 探索或執行函式。
設定代理
使用系統指令設定 Agent 執行個體,並啟用 AppFunctions,然後初始化 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)
您可以選擇呼叫 addAppFunctionIds("fetch_order_details", "cancel_order") 方法,將探索範圍嚴格限制在特定 AppFunction ID,而非公開所有應用程式內函式。
代理預設設定
- 回合限制:
Agent.Builder類別會自動預設maxTurns = 10做為終止條件,防止無限的工具呼叫迴圈。如果是標準對話或工具呼叫工作流程,您不需要手動呼叫setMaxTurns(10)方法。 - 確定性貪婪解碼:
AgentRunner類別會將預設temperature參數設為null,並遵循基礎模型的預設值。如要在裝置上確定工具選取和 JSON 結構定義遵循,請將temperature = 0.0f(temperature) 傳遞至AgentRunner建構函式。請避免設定雲端式溫度 (> 0.4f),否則會增加幻覺風險,並降低行動裝置模型的結構定義品質。
多輪執行和串流事件
在 Android ViewModel 類別中使用 runner.getOrCreateSession(sessionId) 方法,取得 AgentSession 執行個體。工作階段會傳回 Flow<AgentEvent> 串流,發出即時文字權杖和工具執行更新:
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()
}
}
直接串流文字詞元
如果對話介面不需要在 UI 中單獨顯示工具執行詳細資料,請在 Flow<AgentEvent> 介面上使用 textTokens() 擴充功能函式:
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)
}
單輪執行
如果是一次性工作,且對話記錄不應保留在 RAM 中 (例如相片標記或臨時文字摘要),請直接呼叫 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)
}
直接叫用 runner.runSingleTurnAsync(content) 方法時,AgentRunner 執行個體會在內部指派臨時工作階段 ID,並在完成後自動從記憶體中刪除對話狀態,避免記憶體流失。
工作階段生命週期和記憶體管理
多輪對話記錄會儲存在記憶體中,並依 sessionId 值建立索引:
- 設定變更:將
AgentSession執行個體保留在 AndroidViewModel類別中,即可在螢幕旋轉時自動保留對話記錄,不必在每次旋轉時都傳遞字串。 - 工作階段淘汰:對話串關閉時,呼叫
session.delete()方法同步淘汰工作階段,或在ViewModel.onCleared()方法中呼叫非暫停session.close()方法,釋出 RAM。 - 大量移除:呼叫
runner.deleteSession(sessionId)方法釋出特定工作階段,或呼叫runner.clearAllSessions()方法移除所有快取的對話。 - 執行器生命週期:
AgentRunner類別會實作AutoCloseable介面。在多個畫面中共用執行器時,請在每個畫面的ViewModel.onCleared()方法中關閉個別AgentSession控制代碼,並在完全關閉執行器時呼叫runner.close()方法。 - 並行工作階段:單一
AgentRunner執行個體可同時託管多個不同的工作階段。每個sessionId值都會保留獨立的對話記錄。
使用 GenAiException 類別處理錯誤
代理執行期間發生執行階段失敗,例如代理達到工具回合上限,但未生成回覆,或裝置端 AI 暫時無法使用,系統會發出或擲回 GenAiException 例外狀況 (com.google.mlkit.genai.common.GenAiException)。請使用 Kotlin 協同程式 Flow catch 運算子,以反應式方式處理失敗:
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
}
主要錯誤代碼
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED(-112):代理超過最大回合限制 (maxTurns限制),但未生成最終回覆。GenAiException.ErrorCode.NOT_AVAILABLE(8):Gemini Nano 模型或 AICore 系統服務無法使用或尚未就緒。GenAiException.ErrorCode.BUSY(9):系統忙碌中,或呼叫端應用程式超出配額。