ML Kit के उपयोगकर्ता के डिवाइस पर एजेंटिक वर्कफ़्लो (com.google.mlkit:genai-agent, AICore डेवलपर के लिए झलक program के ज़रिए उपलब्ध) की मदद से, Gemini Nano और Android AICore सिस्टम सर्विस की मदद से काम करने वाले, अपने-आप काम करने वाले, सिलसिलेवार बातचीत करने वाले एजेंट बनाए जा सकते हैं.
GenerativeModel क्लास के ऊपर बनाई गई AgentRunner क्लास,
एजेंटिक लूप को पूरी तरह से ऑटोमेट करती है. क्लास,
मॉडल का डायरेक्ट ऐक्सेस देती है. एजेंटिक लूप में ये काम शामिल हैं: कई चरणों वाली कार्रवाइयों की योजना बनाना, टाइप किए गए टूल को शुरू करना (जैसे,
ऐप्लिकेशन रिपॉज़िटरी से क्वेरी करना, बैकएंड एपीआई को कॉल करना या लोकल डेटाबेस को पढ़ना),
टूल के नतीजों का आकलन करना, और डिवाइस पर बातचीत के कॉन्टेक्स्ट को मैनेज करना.
इसके लिए, नेटवर्क लेटेंसी की ज़रूरत नहीं होती और उपयोगकर्ता की निजता का पूरा ध्यान रखा जाता है. 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 (एपीआई लेवल 34) या इसके बाद का वर्शन, जो AICore Developer Preview program में रजिस्टर किया गया हो.
- इस्तेमाल किए जा सकने वाले डिवाइस: इस्तेमाल किए जा सकने वाले डिवाइसों और उनके Gemini Nano मॉडल के वर्शन की सूची देखने के लिए, कस्टम जनरेशन और एजेंट की सुविधा वाले डिवाइस पर जाएं.
- SDK टूल का कम से कम वर्शन:
minSdk 26या इसके बाद का वर्शन. - Kotlin: Kotlin 2.3 या इसके बाद का वर्शन.
Gradle कॉन्फ़िगरेशन
अपने मॉड्यूल की build.gradle.kts फ़ाइल में, genai-agent डिपेंडेंसी जोड़ें:
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 केएसपी प्लगिन की ज़रूरत होती है.
क्विकस्टार्ट: एजेंट को शुरू करना
नीचे दिए गए कोड सैंपल में, उपयोगकर्ता के डिवाइस पर मौजूद एजेंट और रनर को इंस्टैंशिएट करने का तरीका बताया गया है:
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 कॉन्सटेंट की मदद से पाबंदी लगाने पर, प्लैटफ़ॉर्म बाहरी प्लैटफ़ॉर्म और सिस्टम असिस्टेंट को आईपीसी पर फ़ंक्शन खोजने या उसे लागू करने से रोकता है.
एजेंट को कॉन्फ़िगर करना
सिस्टम के निर्देशों के साथ 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 के खास आईडी तक सीमित किया जा सकता है. इससे, ऐप्लिकेशन में मौजूद सभी फ़ंक्शन को एक्सपोज़ करने की ज़रूरत नहीं पड़ती.
एजेंट की डिफ़ॉल्ट सेटिंग
- टर्न की सीमाएं:
Agent.Builderक्लास, डिफ़ॉल्ट रूप सेmaxTurns = 10को खत्म होने की शर्त के तौर पर सेट करती है. इससे टूल-कॉलिंग के अनंत लूप को रोका जा सकता है. आपको स्टैंडर्ड बातचीत या टूल-कॉलिंग वर्कफ़्लो के लिए,setMaxTurns(10)तरीके को मैन्युअल तरीके से कॉल करने की ज़रूरत नहीं है. - डिटरमिनिस्टिक ग्रीडी डिकोडिंग:
AgentRunnerक्लास, डिफ़ॉल्टtemperatureपैरामीटर कोnullपर सेट करती है. इससे, मॉडल के डिफ़ॉल्ट पैरामीटर का इस्तेमाल किया जाता है. डिवाइस पर डिटरमिनिस्टिक टूल चुनने और JSON स्कीमा का पालन करने के लिए,AgentRunnerकंस्ट्रक्टर कोtemperature = 0.0f(temperature) पास करें. क्लाउड-स्टाइल तापमान (> 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()
}
}
टेक्स्ट टोकन को सीधे स्ट्रीम करना
ऐसे बातचीत वाले इंटरफ़ेस के लिए जिनमें टूल के इस्तेमाल की जानकारी को यूज़र इंटरफ़ेस (यूआई) में अलग से दिखाने की ज़रूरत नहीं होती, 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)
}
एक बार में एक्ज़ीक्यूट होने वाले टास्क
ऐसे टास्क जो सिर्फ़ एक बार किए जाने हैं और जिनके लिए रैम में डायलॉग का इतिहास सेव नहीं रखना है (उदाहरण के लिए, फ़ोटो टैग करना या कुछ समय के लिए टेक्स्ट की खास जानकारी देना), सीधे 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 इंस्टेंस, इंटरनल तौर पर कुछ समय के लिए सेशन आईडी असाइन करता है. साथ ही, बातचीत पूरी होने पर, मेमोरी से बातचीत की स्थिति को अपने-आप मिटा देता है. इससे मेमोरी लीक होने से बचती है.
सेशन की लाइफ़साइकल और मेमोरी मैनेज करना
कई बार होने वाले डायलॉग का इतिहास, मेमोरी में सेव किया जाता है और उसे sessionId वैल्यू के हिसाब से इंडेक्स किया जाता है:
- कॉन्फ़िगरेशन में बदलाव: Android
ViewModelक्लास मेंAgentSessionइंस्टेंस को बनाए रखने से, स्क्रीन रोटेशन के दौरान बातचीत का इतिहास अपने-आप सेव हो जाता है. इसके लिए, हर बार स्ट्रिंग को प्लंब करने की ज़रूरत नहीं पड़ती. - सेशन हटाना: किसी बातचीत के थ्रेड को खारिज करने पर, सेशन को सिंक्रोनस तरीके से हटाने के लिए,
session.delete()तरीके को कॉल करें. इसके अलावा, रैम खाली करने के लिए,ViewModel.onCleared()तरीके में, निलंबित न होने वालेsession.close()तरीके को कॉल करें. - एक साथ कई सेशन हटाना: किसी खास सेशन को हटाने के लिए,
runner.deleteSession(sessionId)तरीके को कॉल करें. इसके अलावा, कैश मेमोरी में सेव की गई सभी बातचीत हटाने के लिए,runner.clearAllSessions()तरीके को कॉल करें. - रनर लाइफ़साइकल:
AgentRunnerक्लास,AutoCloseableइंटरफ़ेस को लागू करती है. स्क्रीन पर रनर शेयर करते समय, हर स्क्रीन केViewModel.onCleared()तरीके में अलग-अलगAgentSessionहैंडल बंद करें. साथ ही, रनर को पूरी तरह से बंद करते समयrunner.close()तरीके को कॉल करें. - एक साथ कई सेशन:
AgentRunnerका एक इंस्टेंस, एक साथ कई अलग-अलग सेशन होस्ट कर सकता है. हरsessionIdवैल्यू के लिए, बातचीत का इतिहास अलग से सेव किया जाता है.
GenAiException क्लास की मदद से गड़बड़ी ठीक करना
एजेंट के रनटाइम के दौरान होने वाली गड़बड़ियां, जैसे कि एजेंट बिना कोई जवाब जनरेट किए टूल के इस्तेमाल की तय सीमा तक पहुंच जाता है या उपयोगकर्ता के डिवाइस पर मौजूद एआई कुछ समय के लिए उपलब्ध नहीं होता है, तो उन्हें GenAiException अपवाद (com.google.mlkit.genai.common.GenAiException) के तौर पर दिखाया जाता है. Kotlin Coroutines 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): सिस्टम व्यस्त है या कॉलिंग ऐप्लिकेशन ने अपना कोटा पार कर लिया है.