Loading...
Loading...
Builds autonomous on-device agentic workflows in Android apps using the ML Kit GenAI Agent API powered by Gemini Nano and AICore. Use this skill when implementing conversational agents, multi-turn chat with AgentSession in a ViewModel, autonomous tool calling using Android AppFunctions (@AppFunctionDeclaration and AppFunctionAgentTools.discover), ephemeral single-turn execution with runner.runSingleTurnAsync, or handling GenAiException error resilience. It solves the problem of orchestrating local reasoning loops, typed tool dispatch, and session persistence on-device with zero cloud latency and privacy preservation. Don't use for cloud-based Gemini models or Vertex AI, single-turn text generation without agent loops (suggest Prompt API), or non-Android platforms.
npx skill4agent add android/skills mlkit-agent-api// 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.*
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)Agent.buildermaxTurns = 10AgentRunnertemperature = 0.0fAgentRunner(agent)MlKitContextAgentRunner(context, agent)AgentSessionParcelablesessionViewModelpackage 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()
}
}Contentsession.runAsyncrunner.runSingleTurnAsyncContentContent.builder().text(userPrompt).build()textTokenstextTokensFlow<AgentEvent>import com.google.mlkit.genai.agent.textTokenssession.closeViewModel.onClearedAgentEventFlow<AgentEvent>textTokensimport com.google.mlkit.genai.agent.AgentEventAgentEvent.TextMessage(text, isPartial)AgentEvent.FunctionCall(name, args)AgentEvent.FunctionResponse(name, response)else -> {}when (event)AgentEventAgent@AppFunctionDeclarationenableAppFunctions(true)addAppFunctionIds(...)AppFunctionAgentTools.discover(...)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"
)
)
}
}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)@AppFunctionSerializableMap<String, Any?>ACCESS_LEVEL_SELF@OptIn(ExperimentalAppFunctionsApi::class)@AppFunctionAccessLevel(level = AppFunctionMetadata.ACCESS_LEVEL_SELF, isCompatEnforcementEnabled = true)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
@Tool@Paramcom.google.mlkit.genai.schema.annotations.*AgentTool.fromAnnotatedObjectAgent.Builder.addTools(annotatedObject: Any)FunctionToolAgent@androidx.appfunctions.AppFunctionDeclarationAgent.Builder.enableAppFunctions(true)AppFunctionAgentTools.discoverrunner.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()
}runner.runSingleTurnAsync(content)session.closeViewModel.onClearedsession.deleterunner.deleteSession(sessionId)GenAiExceptionimport com.google.mlkit.genai.common.GenAiExceptionGenAiException.ErrorCode.MAX_TURNS_EXCEEDED-112maxTurnsGenAiException.ErrorCode.NOT_AVAILABLE8GenAiException.ErrorCode.BUSY9