借助机器学习套件的设备端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接口:发出文本 token 和工具更新的响应式 KotlinFlow流。AgentTool类:可由模型调用的类型化函数,使用 Android AppFunctions (@AppFunctionDeclaration) 定义。GenAiException类:类型化异常层次结构 (com.google.mlkit.genai.common.GenAiException),用于指示故障状态,例如MAX_TURNS_EXCEEDED、NOT_AVAILABLE或BUSY。
Android skills
在 GitHub 上查看使用机器学习套件 Agent API
android skills add mlkit-agent-api前提条件和依赖项
在集成 Agent API 之前,请确保您的开发环境和 Gradle 配置满足以下要求。
平台和硬件要求
- Android 版本:已加入 AICore 开发者预览版计划的 Android 14(API 级别 34)或更高版本。
- 支持的设备:如需查看支持的设备及其 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 常量进行限制,平台会阻止外部 Surface 和系统助理通过 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> 流,该流会发出实时文本 token 和工具执行更新:
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()
}
}
直接流式传输文本 token
对于无需在界面中单独显示工具执行详情的对话式界面,请使用 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 值编入索引:
- 配置更改:在 Android
ViewModel类中保留AgentSession实例,可在屏幕旋转时自动保留对话记录,而无需在每次旋转时都传递字符串。 - 会话逐出:在对话会话关闭时,调用
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):系统繁忙或调用方应用已超出其配额。