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

अगर आपको Prompt API से मिले रिस्पॉन्स को JSON जैसे फ़ॉर्मैट में पार्स करके आगे की प्रोसेस करनी है, तो स्ट्रक्चर्ड आउटपुट एपीआई का इस्तेमाल करें.

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

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

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

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

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

suspend fun isStructuredOutputFeatureAvailable(): Boolean

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

  • Android API लेवल 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. अगर आपने अब तक ML Kit Prompt API को डिपेंडेंसी के तौर पर अपने ऐप्लिकेशन-लेवल की build.gradle.kts (या build.gradle) फ़ाइल में नहीं जोड़ा है, तो उसे जोड़ें.

  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,
    // Instructs ML Kit to include the generated schema structure in the prompt
    // sent to AICore. This should always be set to `true` unless the model
    // already knows what output format to use.
    includeSchemaInPrompt = true
)

// 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