टूल तय करें

टूल की मदद से, उपयोगकर्ता के डिवाइस पर मौजूद एजेंट को ऐप्लिकेशन का डेटा ऐक्सेस करने, बैकएंड सेवाओं के साथ कम्यूनिकेट करने, और उपयोगकर्ता की ओर से कार्रवाइयां करने की सुविधा मिलती है.

टूल कैसे काम करते हैं

जब कोई उपयोगकर्ता कोई प्रॉम्प्ट सबमिट करता है, तो AgentRunner क्लास चार चरणों में एजेंटिक लूप को एक्ज़ीक्यूट करती है:

  1. ऑब्ज़र्व करना: Gemini Nano, उपयोगकर्ता के प्रॉम्प्ट और मौजूदा बातचीत के कॉन्टेक्स्ट का विश्लेषण करता है. इसके लिए, वह रजिस्टर किए गए टूल के एलान (एजेंट का ऐक्शन स्पेस) का इस्तेमाल करता है.
  2. वजह: मॉडल यह तय करता है कि ऐप्लिकेशन में मौजूद टूल की ज़रूरत है या नहीं. अगर किसी टूल की ज़रूरत होती है, तो Gemini Nano एक स्ट्रक्चर्ड टूल इन्वोकेशन जनरेट करता है. इसमें फ़ंक्शन का नाम और टाइप किए गए आर्ग्युमेंट शामिल होते हैं.
  3. कार्रवाई: AgentRunner इंस्टेंस, इस कॉल को अपने-आप इंटरसेप्ट करता है और आपके ऐप्लिकेशन में Kotlin फ़ंक्शन को लागू करता है.
  4. सुझाव/राय का आकलन करना: टूल के आउटपुट को Gemini Nano में वापस फ़ीड किया जाता है. मॉडल, सुझाव, शिकायत या राय का आकलन करता है और उपयोगकर्ता को जवाब देता है या किसी दूसरे टूल को कॉल करता है. ऐसा तब तक किया जाता है, जब तक टास्क पूरा करने की शर्त पूरी नहीं हो जाती. जैसे, लक्ष्य पूरा करना या maxTurns की सीमा तक पहुंचना.

Android AppFunctions के साथ डिक्लेरेटिव टूल

टूल तय करने का सुझाया गया तरीका, Android AppFunctions (androidx.appfunctions) का इस्तेमाल करके, डिक्लेरेटिव तरीके से टूल तय करना है. AppFunctions, ऐप्लिकेशन में मौजूद सुविधाओं को डिक्लेयर करने के लिए, एक स्टैंडर्ड प्लैटफ़ॉर्म मॉडल उपलब्ध कराता है. इसमें स्कीमा अपने-आप जनरेट होता है.

1. ग्रेडल डिपेंडेंसी जोड़ना

अपने मॉड्यूल की build.gradle.kts फ़ाइल में, genai-agent के साथ AppFunctions आर्टफ़ैक्ट जोड़ें:

dependencies {
    implementation("com.google.mlkit:genai-agent:1.0.0-alpha1")
    implementation("androidx.appfunctions:appfunctions:1.0.0-alpha13")
    ksp("androidx.appfunctions:appfunctions-compiler:1.0.0-alpha13")
}

2. टूल के तरीकों को @AppFunctionDeclaration एनोटेशन के साथ एनोटेट करें

सार्वजनिक टूल के तरीकों को @AppFunctionDeclaration(isDescribedByKDoc = true) एनोटेशन के साथ एनोटेट करें. पैरामीटर की जानकारी, स्टैंडर्ड KDoc @param टैग में दी गई है.

AppFunctions के लिए ज़रूरी है कि रिटर्न वैल्यू, प्रिमिटिव टाइप (जैसे, String, Int या Boolean) या स्ट्रक्चर्ड डेटा क्लास हों. इन क्लास को @AppFunctionSerializable एनोटेशन के साथ एनोटेट किया गया हो.

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

/**
 * Structured details for an order.
 */
@AppFunctionSerializable
data class OrderDetails(
    val orderId: String,
    val status: String,
    val estimatedDelivery: String?,
    val itemCount: Int,
)

/**
 * Result returned when cancelling an order.
 */
@AppFunctionSerializable
data class CancelOrderResult(
    val orderId: String,
    val cancelled: Boolean,
    val message: String,
)

@AppFunctionServiceEntryPoint(
    serviceName = "OrderAppFunctionService",
    appFunctionXmlFileName = "order_app_functions",
)
abstract class OrderTools : AppFunctionService() {

    private val orderRepository = OrderRepository()

    /**
     * Fetches details and delivery status for a customer order by ID.
     *
     * @param orderId The alphanumeric order identifier (for example, ORD-8492)
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun fetch_order_details(
        orderId: String
    ): OrderDetails = withContext(Dispatchers.IO) {
        val order = orderRepository.findOrder(orderId)
        if (order != null) {
            OrderDetails(
                orderId = order.id,
                status = order.status,
                estimatedDelivery = order.deliveryEstimate,
                itemCount = order.items.size,
            )
        } else {
            OrderDetails(
                orderId = orderId,
                status = "NOT_FOUND",
                estimatedDelivery = null,
                itemCount = 0,
            )
        }
    }

    /**
     * Requests a cancellation or return for an active order.
     *
     * @param orderId The order identifier to cancel
     * @param reason The reason provided by the user for cancellation
     */
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    suspend fun cancel_order(
        orderId: String,
        reason: String? = null
    ): CancelOrderResult = withContext(Dispatchers.IO) {
        val success = orderRepository.cancelOrder(orderId, reason)
        CancelOrderResult(
            orderId = orderId,
            cancelled = success,
            message = if (success) {
                "Order cancellation processed."
            } else {
                "Order cannot be cancelled at this stage."
            }
        )
    }
}

जनरेट की गई सेवा को अपने ऐप्लिकेशन की 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>

3. ACCESS_LEVEL_SELF कॉन्सटेंट की मदद से, कॉलर के ऐक्सेस पर पाबंदी लगाना

यह ज़रूरी नहीं है कि AppFunctions को सिस्टम या बाहरी असिस्टेंट के लिए सार्वजनिक किया जाए. Android में, ऐक्सेस कंट्रोल को कॉलर के ऐक्सेस लेवल के ज़रिए मैनेज किया जाता है. इसके लिए, कॉलर की पहचान की मैन्युअल तरीके से पुष्टि करने की ज़रूरत नहीं होती.

डिफ़ॉल्ट रूप से (ACCESS_LEVEL_ANDROID_TRUSTED कॉन्सटेंट का इस्तेमाल करके), AppFunction को प्लैटफ़ॉर्म से सर्टिफ़िकेट पाए हुए उपयोगकर्ता के डिवाइस पर मौजूद ऐसे असिस्टेंट ढूंढ सकते हैं और चला सकते हैं जिनके पास 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() {

    private val orderRepository = OrderRepository()

    /**
     * Cancels an order. Restricted exclusively to this app's internal agent.
     */
    @OptIn(ExperimentalAppFunctionsApi::class)
    @AppFunctionDeclaration(isDescribedByKDoc = true)
    @AppFunctionAccessLevel(
        level = AppFunctionMetadata.ACCESS_LEVEL_SELF,
        isCompatEnforcementEnabled = true,
    )
    suspend fun cancel_order(orderId: String): CancelOrderResult {
        // Only your app's internal agent can invoke this function
        val success = orderRepository.cancelOrder(orderId)
        return CancelOrderResult(
            orderId = orderId,
            cancelled = success,
            message = if (success) "Cancelled." else "Failed."
        )
    }
}

इस प्लैटफ़ॉर्म पर, कॉलर के लिए तीन ऐक्सेस लेवल उपलब्ध हैं:

  • ACCESS_LEVEL_SELF (100): यह सिर्फ़ होस्ट ऐप्लिकेशन के लिए है. बाहरी असिस्टेंट, सिस्टम के प्लैटफ़ॉर्म, और अन्य ऐप्लिकेशन, आईपीसी पर फ़ंक्शन को न तो खोज सकते हैं और न ही उसे शुरू कर सकते हैं. इस लेवल का इस्तेमाल, संवेदनशील कार्रवाइयों या ऐसे टूल के लिए करें जो सिर्फ़ आपके ऐप्लिकेशन के इंटरनल एजेंट के लिए उपलब्ध होने चाहिए.
  • ACCESS_LEVEL_SYSTEM (200): यह सिर्फ़ उन सिस्टम कॉलर के लिए उपलब्ध है जिनके पास android.Manifest.permission#EXECUTE_APP_FUNCTIONS_SYSTEM अनुमति है. जैसे, सिस्टम लॉन्चर या हाल ही में इस्तेमाल किए गए ऐप्लिकेशन और होस्ट ऐप्लिकेशन.
  • ACCESS_LEVEL_ANDROID_TRUSTED (300, डिफ़ॉल्ट): यह प्लैटफ़ॉर्म से सर्टिफ़ाइड उन उपयोगकर्ता के डिवाइस पर एजेंट के लिए उपलब्ध है जिनके पास EXECUTE_APP_FUNCTIONS अनुमति, सिस्टम के प्लैटफ़ॉर्म, और होस्ट ऐप्लिकेशन है.

4. एजेंट के साथ टूल रजिस्टर करना

एजेंट के साथ AppFunctions को अपने-आप रजिस्टर किया जा सकता है. इसके लिए, enableAppFunctions() तरीके का इस्तेमाल करें. इसके अलावा, प्रोग्राम के हिसाब से रजिस्टर करने के लिए, AppFunctionAgentTools.discover() तरीके का इस्तेमाल करें:

enableAppFunctions() तरीके से अपने-आप खोजे जाने की सुविधा

ऐसे आसान ऐप्लिकेशन के लिए जिनमें AppFunctions, मॉडल के कॉल करने पर अपने-आप लागू होते हैं:

import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner

val agent = Agent.builder()
    .setName("OrderAssistant")
    .setInstructions("Assist users with order tracking.")
    .enableAppFunctions(true) // Automatically discovers all AppFunctions in your app package
    .setMaxTurns(10)
    .build()

val runner = AgentRunner(agent)

इसके अलावा, addAppFunctionIds("fetch_order_details", "cancel_order") तरीके को कॉल करके, खोज के दायरे को सिर्फ़ AppFunction के खास आईडी तक सीमित किया जा सकता है. इससे, ऐप्लिकेशन में मौजूद सभी फ़ंक्शन को एक्सपोज़ करने की ज़रूरत नहीं पड़ती.

AppFunctionAgentTools.discover() वाले तरीके का इस्तेमाल करके, प्रोग्राम के हिसाब से अपने-आप होने वाली प्रोसेस

जब आपके ऐप्लिकेशन को खोजे गए AppFunctions की जांच करनी हो, उन्हें फ़िल्टर करना हो या एजेंट के साथ रजिस्टर करने से पहले उन्हें सजाना हो, तो:

import com.google.mlkit.genai.agent.Agent
import com.google.mlkit.genai.agent.AgentRunner
import com.google.mlkit.genai.agent.AppFunctionAgentTools

val discoveredTools = AppFunctionAgentTools.discover(
    enabledAppFunctionIds = listOf("fetch_order_details", "cancel_order") // Optional ID filter
)

val agent = Agent.builder()
    .setName("OrderAssistant")
    .setInstructions("Assist users with order tracking.")
    .addTools(discoveredTools)
    .build()

val runner = AgentRunner(agent)

डेकोरेटर पैटर्न और निगरानी में काम करने वाला एआई मॉडल

संवेदनशील कार्रवाइयों के लिए, निगरानी में काम करने वाले एआई मॉडल (एचआईटीएल) का इस्तेमाल किया जाना चाहिए. जैसे, ऑर्डर रद्द करना, पेमेंट करना या उपयोगकर्ता का डेटा मिटाना. इसके लिए, उपयोगकर्ता की पुष्टि ज़रूरी है. Agent क्लास में जोड़े गए सभी टूल, Android AppFunctions से जनरेट होते हैं. साथ ही, AgentTool क्लास, डेलिगेट करने वाला कंस्ट्रक्टर AgentTool(delegate) उपलब्ध कराती है, ताकि खोजे गए AppFunction टूल को डेकोरेटर पैटर्न का इस्तेमाल करके रैप किया जा सके:

import com.google.mlkit.genai.agent.AgentTool

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?> {
        val confirmed = onConfirm(name, args)
        if (!confirmed) {
            return mapOf(
                "status" to "CANCELLED_BY_USER",
                "message" to "Action was declined by the user."
            )
        }
        return checkNotNull(delegate).execute(args)
    }
}

टूल डिज़ाइन करने के सबसे सही तरीके

  • ऐक्शन स्पेस का दायरा तय करना: एजेंट के ऐक्शन स्पेस (एजेंट को दिए गए टूल और अनुमतियों का सेट) को सिर्फ़ उसके टास्क के लिए ज़रूरी टूल और अनुमतियों तक सीमित रखें. इसके लिए, addAppFunctionIds() तरीके और ACCESS_LEVEL_SELF कॉन्सटेंट का इस्तेमाल करें. अगर ऐक्शन स्पेस बहुत बड़ा है, तो उपयोगकर्ता के डिवाइस पर मौजूद मॉडल में गड़बड़ी होने की संभावना ज़्यादा होती है. साथ ही, यह कॉन्टेक्स्ट विंडो के टोकन का इस्तेमाल करता है.
  • साफ़ तौर पर जानकारी दें: Gemini Nano, टूल और पैरामीटर के ब्यौरे के आधार पर यह तय करता है कि किसी टूल को कब कॉल करना है. टूल के मकसद के बारे में साफ़ तौर पर बताएं. साथ ही, आर्ग्युमेंट के लिए ज़रूरी फ़ॉर्मैट (जैसे, तारीख या आईडी) के बारे में बताएं.
  • स्ट्रक्चर्ड गड़बड़ी के मैप दिखाएं: गड़बड़ी होने पर, mapOf("error" to "Order not found") जैसे गड़बड़ी के मैप दिखाएं. इससे Gemini Nano, उपयोगकर्ता को समस्या के बारे में बेहतर तरीके से बता पाता है. अगर कोई टूल अचानक कोई गड़बड़ी दिखाता है, तो रनटाइम उसे अपने-आप पकड़ लेता है. साथ ही, गड़बड़ी के मैसेज को मॉडल को वापस भेज देता है, ताकि वह खुद को ठीक कर सके.
  • रिटर्न पेलोड को फ़ोकस में रखें: उपयोगकर्ता के डिवाइस पर मौजूद मॉडल में, सर्वर मॉडल की तुलना में छोटी कॉन्टेक्स्ट विंडो होती हैं. पूरे डेटाबेस की लाइनें या रॉ नेटवर्क रिस्पॉन्स डंप करने के बजाय, सिर्फ़ वे फ़ील्ड दिखाएं जो उपयोगकर्ता की क्वेरी का जवाब देने के लिए ज़रूरी हैं.
  • Dispatchers.IO डिस्पैचर पर I/O चलाएं: हमेशा पक्का करें कि टूल में नेटवर्क के अनुरोध और रूम डेटाबेस क्वेरी, Dispatchers.IO डिस्पैचर पर लागू हों. इससे कोरूटीन कॉलर को ब्लॉक होने से रोका जा सकता है.

अगले चरण