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

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

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

إنّ إنشاء نتائج منظَّمة مفيد بشكل خاص للمهام التالية:

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

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

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

suspend fun isStructuredOutputFeatureAvailable(): Boolean

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

  • مستوى واجهة برمجة التطبيقات Android 26 أو أعلى (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. أضِف واجهة برمجة التطبيقات Prompt API من ML Kit كعنصر تابع في ملف 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,
    // 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}")
        }
    }
}

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

عند استخدام 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