Сгенерировать структурированный вывод

Если вам необходимо преобразовать ответы от Prompt API в определенные форматы, например JSON, для дальнейшей обработки, используйте Structured Output API.

С помощью Structured Output API вы определяете целевую структуру выходных данных, используя классы Kotlin и аннотации. Затем Prompt API возвращает ответ в виде вашего объекта Kotlin.

Создание структурированных выходных данных особенно полезно для решения следующих задач:

  • Извлечение сущностей : извлечение структурированных полей (например, название события, дата, местоположение) из неструктурированного текста.
  • Классификация : Разделение входного текста на предопределенные категории.
  • Сериализация данных : преобразование неструктурированного пользовательского ввода в формат, подходящий для хранения в базе данных или для вызовов API.

Предварительные требования

Чтобы проверить доступность API структурированного вывода на устройстве, используйте функцию isStructuredOutputFeatureAvailable() . Функция возвращает true если API структурированного вывода доступен на устройстве, и false в противном случае.

suspend fun isStructuredOutputFeatureAvailable(): Boolean

API для структурированного вывода также предъявляет следующие требования:

  • Android API уровня 26 или выше ( minSdk 26)
  • Версия плагина KSP 2.3.6 или выше.

Ограничения

API структурированного вывода имеет следующие ограничения:

  • Работает только на Kotlin.
  • ProGuard может мешать разбору вашего аннотированного класса. Добавьте ваш аннотированный класс в правила сохранения , чтобы исключить его из обработки ProGuard в случае ошибок разбора, например:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }

Настройка проекта

Для начала работы с API структурированного вывода выполните следующие шаги:

  1. Если вы еще этого не сделали, добавьте API ML Kit Prompt в качестве зависимости в файл build.gradle.kts (или build.gradle ) на уровне вашего приложения.

  2. Добавьте плагин KSP в файл build.gradle.kts вашего проекта. Используйте версию плагина KSP, совместимую с вашей версией Kotlin; мы рекомендуем версию KSP 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 Для значений true/false. description
List<T> Для получения списков поддерживаемых типов или вложенных классов с аннотацией @Generable . description , minItems , maxItems
List<String> Для списков String значений. description , enumValues , minItems , maxItems
@Generable class Для вложенных структурированных объектов. 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}")
        }
    }
}

Обрабатывать причины завершения и ошибки.

При использовании API структурированного вывода следует обрабатывать потенциальные исключения, генерируемые API, и проверять свойство finishReason в кандидатах ответа, если проанализированный ответ равен null.

значения 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 : Генерация остановлена ​​по другим причинам.

Исключения

API структурированного вывода может генерировать исключение GenAiException со следующими кодами ошибок:

  • GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS (-104): Структура аннотированного класса недопустима или содержит неподдерживаемые типы. Обычно это ошибка конфигурации на этапе разработки. Проверьте определение класса данных с аннотацией @Generable , чтобы убедиться, что поддерживаются все типы свойств и отсутствуют циклические зависимости.
  • GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE (-105): Значения, сгенерированные моделью, недопустимы или не прошли проверку ограничений. Это ошибка выполнения. Если вы часто сталкиваетесь с этой ошибкой, рассмотрите следующие решения:
    • Уточните ваши инструкции, чтобы более строго направлять модель.
    • Смягчите ограничения (например, минимальное, максимальное значение или ограничения размера списка) в аннотациях @Guide , если они слишком ограничительны для возможностей модели.
    • Внедрение в ваше приложение стратегии резервного копирования, например, повторная попытка запроса или отображение состояния по умолчанию.

Подсчет токенов

Чтобы проверить, соответствует ли ваш структурированный запрос лимиту входных токенов, вычислите количество токенов с помощью метода countTokens() .

Поскольку запросы структурированного вывода должны указывать модели структуру схемы, подсчет токенов только на основе исходного текста запроса (с использованием экземпляра GenerateContentRequest ) неточен. Для получения точного подсчета токенов необходимо передать в метод countTokens() полный экземпляр GenerateTypedContentRequest , включающий конфигурацию целевого класса и схемы:

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