إنشاء ناتج منظَّم

إذا كنت بحاجة إلى تحليل الردود من Gemini Nano إلى تنسيقات معيّنة، مثل JSON، لمزيد من المعالجة، استخدِم Structured Output API مع الوصول المباشر إلى النموذج.

باستخدام واجهة برمجة التطبيقات Structured Output API، يمكنك تحديد بنية المخرجات المستهدَفة باستخدام فئات Kotlin والتعليقات التوضيحية. بعد ذلك، يعرض الوصول المباشر إلى النموذج ردًا في شكل كائن Kotlin.

يُعدّ إنشاء مخرجات منظَّمة مفيدًا بشكل خاص لمهام مثل ما يلي:

  • استخراج الكيانات: استخراج الحقول المنظَّمة (مثل اسم الحدث والتاريخ والموقع الجغرافي) من نص غير منظَّم
  • التصنيف: تصنيف النص المُدخَل ضِمن فئات محدَّدة مسبقًا
  • تسلسل البيانات: تحويل البيانات غير المنظَّمة التي يُدخلها المستخدم إلى تنسيق مناسب للتخزين في قاعدة البيانات أو لطلبات البيانات من واجهة برمجة التطبيقات

المتطلبات الأساسية

للتحقّق من توفّر واجهة برمجة التطبيقات Structured Output API على الجهاز، استخدِم واجهة برمجة التطبيقات isStructuredOutputFeatureAvailable(). تعرض واجهة برمجة التطبيقات true إذا كانت واجهة برمجة التطبيقات Structured Output متاحة على الجهاز، وfalse في الحالات الأخرى.

suspend fun isStructuredOutputFeatureAvailable(): Boolean

تتطلّب واجهة Structured Output API أيضًا ما يلي:

  • مستوى واجهة برمجة التطبيقات 26 أو أعلى من نظام التشغيل Android (minSdk 26)
  • الإصدار 2.3.6 أو إصدار أحدث من المكوّن الإضافي KSP

القيود

تنطبق القيود التالية على واجهة برمجة التطبيقات Structured Output API:

  • تعمل هذه الميزة في Kotlin فقط.
  • قد يتداخل ProGuard مع تحليل الصف الذي يتضمّن تعليقات توضيحية. أضِف فئتك التي تتضمّن تعليقات توضيحية إلى قواعد الاحتفاظ لاستبعادها من ProGuard إذا ظهرت لك أخطاء في التحليل، على سبيل المثال:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }

ضبط المشروع

لبدء استخدام Structured Output API، اتّبِع الخطوات التالية:

  1. أضِف تبعية com.google.mlkit:genai-prompt في ملف build.gradle.kts (أو build.gradle) على مستوى التطبيق، إذا لم تكن قد أضفتها من قبل.

  2. أضِف المكوّن الإضافي KSP إلى ملف build.gradle.kts على مستوى المشروع. استخدِم إصدارًا من المكوّن الإضافي KSP يتوافق مع إصدار Kotlin، وننصحك باستخدام الإصدار 2.3.6 من KSP أو إصدار أحدث.

    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}")
        }
    }
}

التعامل مع أسباب الإنهاء والأخطاء

عند استخدام Structured Output API، يجب معالجة الاستثناءات المحتملة التي تظهر من واجهة برمجة التطبيقات وفحص السمة 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: تم إيقاف عملية الإنشاء لأسباب أخرى.

الاستثناءات

قد تعرض واجهة برمجة التطبيقات Structured Output API الخطأ 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