स्ट्रक्चर्ड आउटपुट जनरेट करना

अगर आपको Gemini Nano से मिले जवाबों को JSON जैसे कुछ फ़ॉर्मैट में पार्स करना है, तो डायरेक्ट मॉडल ऐक्सेस के साथ स्ट्रक्चर्ड आउटपुट एपीआई का इस्तेमाल करें.

स्ट्रक्चर्ड आउटपुट एपीआई की मदद से, Kotlin क्लास और एनोटेशन का इस्तेमाल करके, टारगेट आउटपुट स्ट्रक्चर को तय किया जाता है. इसके बाद, डायरेक्ट मॉडल ऐक्सेस, आपके Kotlin ऑब्जेक्ट के तौर पर जवाब देता है.

स्ट्रक्चर्ड आउटपुट जनरेट करना, इन कामों के लिए काफ़ी मददगार होता है:

  • इकाई निकालना: बिना स्ट्रक्चर वाले टेक्स्ट से स्ट्रक्चर्ड फ़ील्ड (उदाहरण के लिए, इवेंट का नाम, तारीख, जगह) निकालना.
  • कैटगरी में बांटना: इनपुट टेक्स्ट को पहले से तय की गई कैटगरी में बांटना.
  • डेटा सीरियलाइज़ेशन: उपयोगकर्ता के अनस्ट्रक्चर्ड इनपुट को ऐसे फ़ॉर्मैट में बदलना जो डेटाबेस स्टोरेज या एपीआई कॉल के लिए सही हो.

ज़रूरी शर्तें

यह पुष्टि करने के लिए कि डिवाइस पर स्ट्रक्चर्ड आउटपुट एपीआई उपलब्ध है, isStructuredOutputFeatureAvailable() एपीआई का इस्तेमाल करें. अगर डिवाइस पर स्ट्रक्चर्ड आउटपुट एपीआई उपलब्ध है, तो एपीआई true दिखाता है. अगर उपलब्ध नहीं है, तो false दिखाता है.

suspend fun isStructuredOutputFeatureAvailable(): Boolean

स्ट्रक्चर्ड आउटपुट एपीआई के लिए, ये ज़रूरी शर्तें भी पूरी करनी होंगी:

  • Android एपीआई लेवल 26 या इसके बाद का वर्शन (minSdk 26)
  • केएसपी प्लगिन का वर्शन 2.3.6 या इसके बाद का वर्शन

सीमाएं

स्ट्रक्चर्ड आउटपुट एपीआई की ये सीमाएं हैं:

  • यह सिर्फ़ Kotlin में काम करता है.
  • ProGuard, एनोटेट की गई क्लास को पार्स करने में रुकावट डाल सकता है. अगर आपको पार्स करने में गड़बड़ियां मिलती हैं, तो एनोटेट की गई क्लास को नियमों को बनाए रखने में जोड़ें, ताकि उन्हें ProGuard से बाहर रखा जा सके. उदाहरण के लिए:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }

प्रोजेक्ट कॉन्फ़िगर करना

स्ट्रक्चर्ड आउटपुट एपीआई का इस्तेमाल शुरू करने के लिए, यह तरीका अपनाएं:

  1. अपने ऐप्लिकेशन-लेवल की build.gradle.kts (या build.gradle) फ़ाइल में com.google.mlkit:genai-prompt डिपेंडेंसी जोड़ें. ऐसा तब करें, जब आपने पहले से ऐसा न किया हो.

  2. प्रोजेक्ट-लेवल की build.gradle.kts फ़ाइल में केएसपी प्लग इन जोड़ें. केएसपी प्लगिन के ऐसे वर्शन का इस्तेमाल करें जो आपके Kotlin वर्शन के साथ काम करता हो. हमारा सुझाव है कि आप केएसपी का 2.3.6 या इसके बाद का वर्शन इस्तेमाल करें.

    dependencies {
        ...
        classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6"
    }
    
  3. अपने ऐप्लिकेशन-लेवल की build.gradle.kts फ़ाइल में, स्ट्रक्चर्ड कंपाइलर डिपेंडेंसी जोड़ें:

    dependencies {
        ...
        ksp("com.google.mlkit:genai-schema-compiler:1.0.0-alpha1")
    }
    

आउटपुट का स्ट्रक्चर तय करें

Kotlin डेटा क्लास का इस्तेमाल करके, उस डेटा का स्ट्रक्चर तय करें जो आपको मॉडल से चाहिए. आउटपुट के स्ट्रक्चर को तय करने के लिए, दो मुख्य एनोटेशन हैं:

  • क्लास को स्ट्रक्चर्ड आउटपुट के टारगेट के तौर पर तय करने के लिए, @Generable एनोटेशन का इस्तेमाल करें.
  • क्लास प्रॉपर्टी पर @Guide एनोटेशन का इस्तेमाल करें. इससे मॉडल के आउटपुट को गाइड करने के लिए, ब्यौरे और पाबंदियां दी जा सकती हैं.

यहां दिए गए उदाहरण में, पौधे की जानकारी निकालने के लिए एक स्ट्रक्चर तय किया गया है:

import com.google.mlkit.genai.schema.annotations.Generable
import com.google.mlkit.genai.schema.annotations.Guide

@Generable
data class PlantList(
    @Guide(description = "The list of plants found", minItems = 1, maxItems = 5)
    val plants: List<Plant>
)

@Generable("Information about a plant species")
data class Plant(
    @Guide(description = "The common name of the plant")
    val commonName: String,
    
    @Guide(description = "The full latin scientific name of the plant")
    val scientificName: String,
    
    @Guide(
        description = "The maximum height of the plant in centimeters.",
        minimum = 1.0,
        maximum = 10000.0
    )
    val maxHeightCm: Int,
    
    @Guide(description = "Whether the plant is poisonous or not")
    val isPoisonous: Boolean?,
    
    @Guide(
        description = "The primary continent where this plant is native to",
        enumValues = ["Africa", "Antarctica", "Asia", "Australia", "Europe", "North America", "South America"]
    )
    val nativeContinent: String
)

इस्तेमाल किए जा सकने वाले टाइप और पाबंदियां

@Generable एनोटेट की गई क्लास में, ये टाइप इस्तेमाल किए जा सकते हैं. साथ ही, इनमें @Guide से जुड़ी पाबंदियां भी शामिल हैं:

टाइप ब्यौरा इस्तेमाल की जा सकने वाली @Guide पाबंदियां
String टेक्स्ट के लिए. description, enumValues
Double / Float फ़्लोटिंग-पॉइंट नंबर के लिए. description, minimum, maximum
Int / Long पूर्ण संख्याओं के लिए. description, minimum, maximum
Boolean सही/गलत वैल्यू के लिए. description
List<T> इस्तेमाल किए जा सकने वाले टाइप या नेस्ट की गई @Generable क्लास की सूचियों के लिए. description, minItems, maxItems
List<String> String वैल्यू की सूचियों के लिए. description, enumValues, minItems, maxItems
@Generable क्लास नेस्ट किए गए स्ट्रक्चर्ड ऑब्जेक्ट के लिए. description

स्ट्रक्चर्ड कॉन्टेंट जनरेट करना

स्ट्रक्चर्ड आउटपुट का अनुरोध करने के लिए, generateTypedContentRequest हेल्पर फ़ंक्शन का इस्तेमाल करें. इससे अपने स्टैंडर्ड प्रॉम्प्ट को रैप करें और टारगेट आउटपुट क्लास की जानकारी दें.

// 1. Initialize your GenerativeModel as usual
val generativeModel = Generation.getClient()

// 2. Prepare the prompt text
val promptText = "List some common plants found in California."
val baseRequest = GenerateContentRequest.Builder(TextPart(promptText)).build()

// 3. Create the typed request, specifying the target class (e.g., PlantList)
val typedRequest = generateTypedContentRequest(
    generateContentRequest = baseRequest,
    outputClass = PlantList::class
)

// 4. Run the inference
try {
    val typedResponse = generativeModel.generateContent(typedRequest)
    
    // 5. Access the parsed object
    // The response candidates contain the parsed object of type T (PlantList in this case)
    val plantList: PlantList? = typedResponse.candidates.firstOrNull()?.response
    
    if (plantList != null) {
        // Process the structured data
        for (plant in plantList.plants) {
            Log.d("StructuredOutput", "Found plant: ${plant.commonName} (${plant.scientificName})")
        }
    } else {
        Log.e("StructuredOutput", "Failed to parse response into the desired structure.")
        
        // Inspect finish reason for details
        val finishReason = typedResponse.candidates.firstOrNull()?.finishReason
        Log.d("StructuredOutput", "Finish reason: $finishReason")
    }
} catch (e: GenAiException) {
    // Handle API errors
    when (e.errorCode) {
        GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS -> {
            Log.e("StructuredOutput", "The class structure is not supported.")
        }
        GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE -> {
            Log.e("StructuredOutput", "The model generated values that violate the schema constraints.")
        }
        else -> {
            Log.e("StructuredOutput", "API error: ${e.message}")
        }
    }
}

जवाब जनरेट होने की प्रोसेस पूरी होने की वजहों और गड़बड़ियों को मैनेज करना

स्ट्रक्चर्ड आउटपुट एपीआई का इस्तेमाल करते समय, आपको एपीआई से मिलने वाले संभावित अपवादों को हैंडल करना चाहिए. साथ ही, अगर पार्स किया गया रिस्पॉन्स शून्य है, तो रिस्पॉन्स कैंडिडेट में finishReason प्रॉपर्टी की जांच करनी चाहिए.

finishReason की वैल्यू

finishReason प्रॉपर्टी के लिए, इनमें से कोई एक वैल्यू इस्तेमाल की जा सकती है:

  • TypedFinishReason.STOP: मॉडल ने आउटपुट जनरेट कर लिया है और आउटपुट, स्कीमा से मैच करता है.
  • TypedFinishReason.MAX_TOKENS: मॉडल ने काम करना बंद कर दिया, क्योंकि टोकन की सीमा पूरी हो गई है. आउटपुट अधूरा हो सकता है.
  • TypedFinishReason.PARSE_CLASS_ERROR: मॉडल ने जनरेशन पूरा कर लिया है, लेकिन नतीजे के तौर पर मिले JSON को टारगेट Kotlin क्लास में पार्स नहीं किया जा सका.
  • TypedFinishReason.STRUCTURE_NOT_ANNOTATED: टारगेट क्लास या उसकी नेस्ट की गई क्लास में, ज़रूरी @Generable एनोटेशन मौजूद नहीं है.
  • TypedFinishReason.STRUCTURE_VALUES_INVALID: जनरेट की गई वैल्यू ने @Guide एनोटेशन में तय की गई पाबंदियों का उल्लंघन किया है. उदाहरण के लिए, वैल्यू रेंज से बाहर है, लिस्ट का साइज़ तय सीमा से बाहर है.
  • TypedFinishReason.OTHER: अन्य वजहों से, जनरेट करने की प्रोसेस रोक दी गई है.

अपवाद

स्ट्रक्चर्ड आउटपुट एपीआई, गड़बड़ी के इन कोड के साथ GenAiException दिखा सकता है:

  • GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS (-104): एनोटेट की गई क्लास का स्ट्रक्चर अमान्य है या इसमें ऐसे टाइप शामिल हैं जो काम नहीं करते. आम तौर पर, यह गड़बड़ी डेवलपमेंट के समय कॉन्फ़िगरेशन में हुई गड़बड़ी की वजह से होती है. अपनी @Generable डेटा क्लास की परिभाषा की समीक्षा करें, ताकि यह पता चल सके कि सभी प्रॉपर्टी टाइप काम करते हैं और कोई सर्कुलर डिपेंडेंसी नहीं है.
  • GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE (-105): मॉडल से जनरेट की गई वैल्यू अमान्य हैं या वे शर्तों की पुष्टि नहीं करती हैं. यह रनटाइम से जुड़ी गड़बड़ी है. अगर आपको यह गड़बड़ी बार-बार दिखती है, तो ये तरीके आज़माएं:
    • प्रॉम्प्ट के निर्देशों को बेहतर बनाना, ताकि मॉडल को ज़्यादा सटीक तरीके से गाइड किया जा सके.
    • अगर @Guide एनोटेशन में पाबंदियां (जैसे, कम से कम, ज़्यादा से ज़्यादा या सूची के साइज़ की सीमाएं) मॉडल की क्षमताओं के हिसाब से बहुत ज़्यादा हैं, तो उन्हें कम करना.
    • अपने ऐप्लिकेशन में फ़ॉलबैक रणनीति लागू करना. जैसे, अनुरोध को फिर से भेजना या डिफ़ॉल्ट स्थिति दिखाना.

टोकन की गिनती करना

यह देखने के लिए कि आपका स्ट्रक्चर्ड प्रॉम्प्ट, इनपुट टोकन की सीमा के अंदर है या नहीं, countTokens() तरीके का इस्तेमाल करके टोकन की संख्या का हिसाब लगाएं.

स्ट्रक्चर्ड आउटपुट के अनुरोधों के लिए, मॉडल को स्कीमा के स्ट्रक्चर के बारे में बताना ज़रूरी है. इसलिए, सिर्फ़ रॉ प्रॉम्प्ट टेक्स्ट (GenerateContentRequest इंस्टेंस का इस्तेमाल करके) पर टोकन की गिनती करना सटीक नहीं होता. टोकन की सटीक संख्या जानने के लिए, आपको पूरा GenerateTypedContentRequest इंस्टेंस पास करना होगा. इसमें आपकी टारगेट क्लास और स्कीमा कॉन्फ़िगरेशन शामिल हैं. इसे countTokens() तरीके में पास करना होगा:

suspend fun <T : Any> countTokens(request: GenerateTypedContentRequest<T>): CountTokensResponse