mlkit-agent-api
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTargeting AICore Developer Preview. This skill provides authoritative
execution patterns and failure-prevention rules for on-device agentic workflows
in Android apps.
适用于 AICore Developer Preview。此 skill 为 Android 应用中的端侧 agent 工作流提供权威的执行模式和故障预防规则。
1. Prerequisites and setup
1. 前提条件与设置
Gradle dependencies
Gradle 依赖项
// app/build.gradle.kts
dependencies {
// Core ML Kit GenAI Agent API
implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
// Android AppFunctions dependency for on-device tool declarations
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
}[!IMPORTANT] Always use official SDK interfaces from,com.google.mlkit.genai.agent.*,androidx.appfunctions.*, andcom.google.mlkit.genai.prompt.*. Never declare local placeholder interfaces or fallback mocks.com.google.mlkit.genai.common.*
// app/build.gradle.kts
dependencies {
// Core ML Kit GenAI Agent API
implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
// Android AppFunctions dependency for on-device tool declarations
implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
}[!IMPORTANT] 始终使用来自以下包的官方 SDK 接口:、com.google.mlkit.genai.agent.*、androidx.appfunctions.*和com.google.mlkit.genai.prompt.*。 切勿声明本地占位接口或回退 mock。com.google.mlkit.genai.common.*
2. Minimal agent and runner initialization
2. 最小化 Agent 和 Runner 初始化
Configure an with and wrap execution in :
AgentAgent.builderAgentRunnerimport com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
val agent = Agent.builder()
.setName("OnDeviceAssistant")
.setInstructions("You are a helpful on-device assistant.")
.build()
val runner = AgentRunner(agent)- defaults to
Agent.builderautomatically to prevent infinite tool loops.maxTurns = 10 - defaults to greedy decoding (
AgentRunner) for deterministic tool selection and schema compliance.temperature = 0.0f - automatically resolves application context using
AgentRunner(agent)for AppFunction execution in Android apps. An explicitMlKitContextoverload is available for headless tests or custom context scopes.AgentRunner(context, agent)
使用 配置 ,并通过 封装执行:
Agent.builderAgentAgentRunnerimport com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
val agent = Agent.builder()
.setName("OnDeviceAssistant")
.setInstructions("You are a helpful on-device assistant.")
.build()
val runner = AgentRunner(agent)- 自动将
Agent.builder设为maxTurns,以防止无限的工具调用循环。10 - 默认采用贪心解码(
AgentRunner),以实现确定性的工具选择并确保符合 schema。temperature = 0.0f - 使用
AgentRunner(agent)自动解析应用上下文,以在 Android 应用中执行 AppFunction。对于无头测试或自定义上下文作用域, 可使用显式的MlKitContext重载。AgentRunner(context, agent)
3. Multi-turn chat and lifecycle (ViewModel)
3. 多轮聊天与生命周期(ViewModel)
AgentSessionParcelablesessionViewModelWhen executing single-turn ephemeral queries, managing concurrent
multi-sessions, or requiring explicit RAM cleanup, you must follow the
guidelines in Session lifecycle.
package com.example.app
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AgentSession
import com.google.mlkit.genai.agent.textTokens
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.launch
class ChatViewModel : ViewModel() {
private val agent = Agent.builder()
.setInstructions("You are a helpful on-device assistant.")
.build()
private val runner = AgentRunner(agent)
private val session: AgentSession =
runner.getOrCreateSession("chat_session")
private val _uiState = MutableStateFlow<ChatUiState>(ChatUiState.Idle)
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
fun sendMessage(prompt: String) {
viewModelScope.launch {
_uiState.value = ChatUiState.Loading("")
try {
// Content.builder() required; runAsync takes Content
val content = Content.builder().text(prompt).build()
var fullResponse = ""
session.runAsync(content)
// Extension function requiring textTokens import
.textTokens()
.collect { token ->
fullResponse += token
_uiState.value = ChatUiState.Loading(fullResponse)
}
_uiState.value = ChatUiState.Success(fullResponse)
} catch (e: GenAiException) {
// Catch GenAiException to prevent app crashes on AI errors
val err = e.localizedMessage ?: "AI error (${e.errorCode})"
_uiState.value = ChatUiState.Error(err)
}
}
}
override fun onCleared() {
super.onCleared()
// Evict conversation session from RAM when ViewModel is destroyed
session.close()
}
}AgentSessionParcelableViewModelsession在执行临时单轮查询、管理并发多会话或需要显式清理 RAM 时,必须遵循
会话生命周期 中的指南。
package com.example.app
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AgentSession
import com.google.mlkit.genai.agent.textTokens
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.launch
class ChatViewModel : ViewModel() {
private val agent = Agent.builder()
.setInstructions("You are a helpful on-device assistant.")
.build()
private val runner = AgentRunner(agent)
private val session: AgentSession =
runner.getOrCreateSession("chat_session")
private val _uiState = MutableStateFlow<ChatUiState>(ChatUiState.Idle)
val uiState: StateFlow<ChatUiState> = _uiState.asStateFlow()
fun sendMessage(prompt: String) {
viewModelScope.launch {
_uiState.value = ChatUiState.Loading("")
try {
// Content.builder() required; runAsync takes Content
val content = Content.builder().text(prompt).build()
var fullResponse = ""
session.runAsync(content)
// Extension function requiring textTokens import
.textTokens()
.collect { token ->
fullResponse += token
_uiState.value = ChatUiState.Loading(fullResponse)
}
_uiState.value = ChatUiState.Success(fullResponse)
} catch (e: GenAiException) {
// Catch GenAiException to prevent app crashes on AI errors
val err = e.localizedMessage ?: "AI error (${e.errorCode})"
_uiState.value = ChatUiState.Error(err)
}
}
}
override fun onCleared() {
super.onCleared()
// Evict conversation session from RAM when ViewModel is destroyed
session.close()
}
}Essential lifecycle and coroutine rules
关键生命周期和协程规则
-
object :
Contentandsession.runAsyncrequire arunner.runSingleTurnAsyncobject. Always build it usingContent.Content.builder().text(userPrompt).build() -
extension :
textTokensis a Kotlin extension function ontextTokens. Always includeFlow<AgentEvent>.import com.google.mlkit.genai.agent.textTokens -
Session cleanup : Always invokein
session.closeto evict conversational RAM and cancel pending tasks.ViewModel.onCleared -
stream : When collecting
AgentEventdirectly instead ofFlow<AgentEvent>, always includetextTokens. Match againstimport com.google.mlkit.genai.agent.AgentEvent,AgentEvent.TextMessage(text, isPartial), andAgentEvent.FunctionCall(name, args), and always include anAgentEvent.FunctionResponse(name, response)branch inelse -> {}expressions (when (event)is non-exhaustive for forward compatibility):AgentEventimport com.google.mlkit.genai.agent.AgentEventsession.runAsync(content).collect { event -> when (event) { is AgentEvent.FunctionCall -> { _uiState.update { it.copy(activeToolStatus = "Tool started: ${event.name}") } } is AgentEvent.FunctionResponse -> { _uiState.update { it.copy(activeToolStatus = "Tool completed: ${event.name}") } } is AgentEvent.TextMessage -> { _uiState.update { it.copy(responseText = it.responseText + event.text) } } else -> {} // Required: AgentEvent is non-exhaustive for forward compat } }
-
对象:
Content和session.runAsync需要runner.runSingleTurnAsync对象。始终使用Content构建。Content.builder().text(userPrompt).build() -
扩展函数:
textTokens是textTokens上的 Kotlin 扩展函数。始终包含导入Flow<AgentEvent>。com.google.mlkit.genai.agent.textTokens -
会话清理:始终在中调用
ViewModel.onCleared,以释放对话占用的 RAM 并取消待处理任务。session.close -
流:直接收集
AgentEvent而不是使用Flow<AgentEvent>时,始终包含导入textTokens。匹配com.google.mlkit.genai.agent.AgentEvent、AgentEvent.TextMessage(text, isPartial)和AgentEvent.FunctionCall(name, args),并且始终在AgentEvent.FunctionResponse(name, response)表达式中包含when (event)分支(为保证向前兼容,else -> {}是非穷尽类型):AgentEventimport com.google.mlkit.genai.agent.AgentEventsession.runAsync(content).collect { event -> when (event) { is AgentEvent.FunctionCall -> { _uiState.update { it.copy(activeToolStatus = "Tool started: ${event.name}") } } is AgentEvent.FunctionResponse -> { _uiState.update { it.copy(activeToolStatus = "Tool completed: ${event.name}") } } is AgentEvent.TextMessage -> { _uiState.update { it.copy(responseText = it.responseText + event.text) } } else -> {} // Required: AgentEvent is non-exhaustive for forward compat } }
4. Tool calling recipe
4. 工具调用方案
All tools used with originate from Android AppFunctions
(), registered using the declarative path
( or ) or advanced discovery
().
Agent@AppFunctionDeclarationenableAppFunctions(true)addAppFunctionIds(...)AppFunctionAgentTools.discover(...)When you need to restrict caller access levels for private in-app tools or wrap
discovered AppFunctions with human-in-the-loop (HITL) confirmation decorators,
you must consult Tools and functions.
所有与 一起使用的工具都源自 Android AppFunctions
(),并通过声明式路径
( 或 )或高级发现机制
()注册。
Agent@AppFunctionDeclarationenableAppFunctions(true)addAppFunctionIds(...)AppFunctionAgentTools.discover(...)当需要为应用内私有工具限制调用方访问级别,或使用 human-in-the-loop(HITL)确认装饰器包装已发现的 AppFunctions 时,必须查阅
工具与函数。
Option A: Declarative AppFunctions path
选项 A:声明式 AppFunctions 路径
Define tools declaratively in your app using AndroidX AppFunctions:
package com.example.app.tools
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
@AppFunctionSerializable
data class CalendarEvents(val date: String, val events: List<String>)
class CalendarTools {
/**
* Fetches scheduled calendar appointments for a specific date in
* YYYY-MM-DD format.
*
* @param date Date string in YYYY-MM-DD format
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
fun fetch_calendar(
date: String
): CalendarEvents {
return CalendarEvents(
date = date,
events = listOf(
"Team Standup at 10:00 AM",
"Product Review at 2:00 PM"
)
)
}
}Enable AppFunctions on and initialize :
Agent.BuilderAgentRunnerimport com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
val agent = Agent.builder()
.setName("CalendarAssistant")
.setInstructions("You assist with calendar queries.")
// Automatically discovers AppFunctions in your app package
.enableAppFunctions(true)
// Optional: scope to specific function IDs or method names
.addAppFunctionIds("fetch_calendar")
.build()
val runner = AgentRunner(agent)- AppFunction Contract : Tool methods take domain parameters directly (no framework context parameter required). Returns must be primitives or data classes (
@AppFunctionSerializableisn't supported for AppFunctions).Map<String, Any?> - Restricting Callers with : AppFunctions don't have to be public to external callers. To restrict execution strictly to your app's internal agent (blocking external system assistants and IPC callers), annotate with
ACCESS_LEVEL_SELFand@OptIn(ExperimentalAppFunctionsApi::class).@AppFunctionAccessLevel(level = AppFunctionMetadata.ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
使用 AndroidX AppFunctions 在应用中声明式定义工具:
package com.example.app.tools
import androidx.appfunctions.AppFunctionDeclaration
import androidx.appfunctions.AppFunctionSerializable
@AppFunctionSerializable
data class CalendarEvents(val date: String, val events: List<String>)
class CalendarTools {
/**
* Fetches scheduled calendar appointments for a specific date in
* YYYY-MM-DD format.
*
* @param date Date string in YYYY-MM-DD format
*/
@AppFunctionDeclaration(isDescribedByKDoc = true)
fun fetch_calendar(
date: String
): CalendarEvents {
return CalendarEvents(
date = date,
events = listOf(
"Team Standup at 10:00 AM",
"Product Review at 2:00 PM"
)
)
}
}在 上启用 AppFunctions 并初始化 :
Agent.BuilderAgentRunnerimport com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
val agent = Agent.builder()
.setName("CalendarAssistant")
.setInstructions("You assist with calendar queries.")
// Automatically discovers AppFunctions in your app package
.enableAppFunctions(true)
// Optional: scope to specific function IDs or method names
.addAppFunctionIds("fetch_calendar")
.build()
val runner = AgentRunner(agent)- AppFunction 约定:工具方法直接接受领域参数(无需框架上下文参数)。返回值必须是基本类型或 data 类(AppFunctions 不支持
@AppFunctionSerializable)。Map<String, Any?> - 使用 限制调用方:AppFunctions 无须向外部调用方公开。若要将执行严格限制在应用内部 Agent(阻止外部系统助手和 IPC 调用方),请使用
ACCESS_LEVEL_SELF和@OptIn(ExperimentalAppFunctionsApi::class)进行注解。@AppFunctionAccessLevel(level = AppFunctionMetadata.ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)
Option B: Advanced discovery and decorator pattern
选项 B:高级发现与装饰器模式
For inspecting, filtering, or decorating discovered AppFunctions (such as
Human-in-the-Loop confirmation wrappers using ) before
registering with the agent:
AgentTool(delegate)import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AgentTool
import com.google.mlkit.genai.agent.AppFunctionAgentTools
// Discover tools (optionally scoped to function IDs)
val discoveredTools = AppFunctionAgentTools.discover(
enabledAppFunctionIds = listOf("fetch_calendar")
)
// Optional: Wrap sensitive tools using AgentTool(delegate) decorator
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?> {
if (!onConfirm(name, args)) {
return mapOf("status" to "CANCELLED_BY_USER")
}
return checkNotNull(delegate).execute(args)
}
}
val agent = Agent.builder()
.setName("CalendarAssistant")
.setInstructions("You assist with calendar queries.")
.addTools(discoveredTools)
.build()
val runner = AgentRunner(agent)[!WARNING] Mutual exclusivity : Configure AppFunctions using either the declarative path (onenableAppFunctions(true)) or the advanced path (Agent.Builderpassed toAppFunctionAgentTools.discover), but not both. Using both simultaneously triggers a warning and registers duplicate tool schemas.addTools
在注册到 Agent 之前,检查、过滤或装饰已发现的 AppFunctions(例如使用 的 Human-in-the-Loop 确认包装器):
AgentTool(delegate)import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AgentTool
import com.google.mlkit.genai.agent.AppFunctionAgentTools
// Discover tools (optionally scoped to function IDs)
val discoveredTools = AppFunctionAgentTools.discover(
enabledAppFunctionIds = listOf("fetch_calendar")
)
// Optional: Wrap sensitive tools using AgentTool(delegate) decorator
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?> {
if (!onConfirm(name, args)) {
return mapOf("status" to "CANCELLED_BY_USER")
}
return checkNotNull(delegate).execute(args)
}
}
val agent = Agent.builder()
.setName("CalendarAssistant")
.setInstructions("You assist with calendar queries.")
.addTools(discoveredTools)
.build()
val runner = AgentRunner(agent)[!WARNING] 互斥性:使用声明式路径(上的Agent.Builder)或高级路径(通过enableAppFunctions(true)传入addTools)配置 AppFunctions,但不要同时使用两者。同时使用会触发警告并注册重复的工具 schema。AppFunctionAgentTools.discover
Deprecated annotations (@Tool and @Param)
已弃用的注解(@Tool
和 @Param
)
@Tool@ParamThe legacy and annotations in
, along with
, , and standalone , have been deprecated or removed. All tools
added to must originate from
using
or .
@Tool@Paramcom.google.mlkit.genai.schema.annotations.*AgentTool.fromAnnotatedObjectAgent.Builder.addTools(annotatedObject: Any)FunctionToolAgent@androidx.appfunctions.AppFunctionDeclarationAgent.Builder.enableAppFunctions(true)AppFunctionAgentTools.discovercom.google.mlkit.genai.schema.annotations.*@Tool@ParamAgentTool.fromAnnotatedObjectAgent.Builder.addTools(annotatedObject: Any)FunctionToolAgent@androidx.appfunctions.AppFunctionDeclarationAgent.Builder.enableAppFunctions(true)AppFunctionAgentTools.discover5. Ephemeral single-turn execution
5. 临时单轮执行
For one-off tasks where conversation history isn't needed (such as summarizing
text, formatting data, or answering standalone questions), invoke
directly:
runner.runSingleTurnAsync(content)import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.textTokens
import com.google.mlkit.genai.prompt.Content
import kotlinx.coroutines.flow.Flow
// Executes a single-turn query ephemerally without session persistence:
fun askQuickQuestion(runner: AgentRunner, question: String): Flow<String> {
val content = Content.builder().text(question).build()
return runner.runSingleTurnAsync(content).textTokens()
}- automatically creates and evicts an ephemeral session upon Flow completion.
runner.runSingleTurnAsync(content) - Cleanup reference :
- : Releases session resources and cancels coroutines (call in
session.close).ViewModel.onCleared - or
session.delete: Permanently deletes the session from the runner.runner.deleteSession(sessionId)
对于不需要对话历史记录的一次性任务(例如摘要文本、格式化数据或回答独立问题),直接调用
:
runner.runSingleTurnAsync(content)import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.textTokens
import com.google.mlkit.genai.prompt.Content
import kotlinx.coroutines.flow.Flow
// Executes a single-turn query ephemerally without session persistence:
fun askQuickQuestion(runner: AgentRunner, question: String): Flow<String> {
val content = Content.builder().text(question).build()
return runner.runSingleTurnAsync(content).textTokens()
}- 会在 Flow 完成时自动创建并回收临时 session。
runner.runSingleTurnAsync(content) - 清理参考:
- :释放会话资源并取消协程(在
session.close中调用)。ViewModel.onCleared - 或
session.delete:从 runner 中永久删除会话。runner.deleteSession(sessionId)
6. Error handling and GenAiException reference
6. 错误处理与 GenAiException 参考
Runtime failures during on-device execution (such as model unavailability or
exceeding maximum turn limits) are emitted or thrown as :
GenAiExceptionimport com.google.mlkit.genai.common.GenAiException端侧执行期间发生的运行时故障(例如模型不可用或超过最大轮次限制)会通过 发出或抛出:
GenAiExceptionimport com.google.mlkit.genai.common.GenAiExceptionKey error codes
关键错误代码
- (
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED): The agent exceeded-112(default 10) without completing the reasoning loop.maxTurns - (
GenAiException.ErrorCode.NOT_AVAILABLE): The Gemini Nano model or AICore service isn't available on this device.8 - (
GenAiException.ErrorCode.BUSY): System or AICore is busy, or battery or thermal quota has been reached.9
- (
GenAiException.ErrorCode.MAX_TURNS_EXCEEDED):Agent 超过-112(默认值为 10),未能完成推理循环。maxTurns - (
GenAiException.ErrorCode.NOT_AVAILABLE):此设备上不存在 Gemini Nano 模型或 AICore 服务。8 - (
GenAiException.ErrorCode.BUSY):系统或 AICore 正忙,或已达到电池或热配额限制。9