Если вам нужно преобразовать ответы Gemini Nano в определенные форматы, например JSON, для дальнейшей обработки, используйте Structured Output API с прямым доступом к модели.
С помощью Structured Output API вы определяете целевую структуру выходных данных, используя классы и аннотации Kotlin. Затем прямой доступ к модели возвращает ответ в виде объекта Kotlin.
Генерация структурированных выходных данных особенно полезна для таких задач, как: следующее:
- Извлечение объектов. Извлечение структурированных полей (например, название события, дата, местоположение) из неструктурированного текста.
- Классификация. Распределение входного текста по стандартным категориям.
- Сериализация данных. Преобразование неструктурированных пользовательских данных в формат, подходящий для хранения в базе данных или вызовов API.
Требования
Чтобы проверить, доступен ли Structured Output API на устройстве, используйте isStructuredOutputFeatureAvailable() API. API возвращает true, если Structured Output API доступен на устройстве, и false в противном случае.
suspend fun isStructuredOutputFeatureAvailable(): Boolean
Для Structured Output API также действуют следующие требования:
- Android API уровня 26 или выше (
minSdk26) - Плагин KSP версии 2.3.6 или выше.
Ограничения
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, выполните следующие действия:
Добавьте зависимость
com.google.mlkit:genai-promptв файлbuild.gradle.kts(илиbuild.gradle) на уровне приложения, если вы ещё этого не сделали.Добавьте плагин 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" }Добавьте зависимости структурированного компилятора в файл
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 необходимо обрабатывать потенциальные исключения, генерируемые 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: генерация остановлена по другой причине.
Исключения
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