Si necesitas analizar las respuestas de Gemini Nano en ciertos formatos, como JSON, para un procesamiento posterior, usa la API de Structured Output con acceso directo al modelo.
Con la API de Structured Output, defines la estructura de salida de destino con clases y anotaciones de Kotlin. Luego, el acceso directo al modelo devuelve una respuesta en el formato de tu objeto de Kotlin.
Generar resultados estructurados es particularmente útil para tareas como las siguientes:
- Extracción de entidades: Extrae campos estructurados (por ejemplo, nombre del evento, fecha y ubicación) de texto no estructurado.
- Clasificación: Clasifica el texto de entrada en categorías predefinidas.
- Serialización de datos: Convierte la entrada del usuario no estructurada en un formato adecuado para el almacenamiento en bases de datos o las llamadas a la API.
Requisitos previos
Para verificar que la API de Structured Output esté disponible en el dispositivo, usa la API de
isStructuredOutputFeatureAvailable(). La API devuelve true si la API de Structured Output está disponible en el dispositivo y false en caso contrario.
suspend fun isStructuredOutputFeatureAvailable(): Boolean
La API de Structured Output también tiene los siguientes requisitos:
- Nivel de API de Android 26 o superior (
minSdk26) - Complemento de KSP versión 2.3.6 o posterior
Limitaciones
La API de Structured Output tiene las siguientes limitaciones:
- Solo funciona en Kotlin.
- ProGuard podría interferir en el análisis de tu clase anotada. Agrega tu clase anotada a tus reglas de conservación para excluirlas de ProGuard si obtienes errores de análisis, por ejemplo:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }
Configurar proyecto
Para comenzar a usar la API de Structured Output, sigue estos pasos:
Agrega la dependencia de
com.google.mlkit:genai-prompten tu archivobuild.gradle.kts(obuild.gradle) a nivel de la app, si aún no lo hiciste.Agrega el complemento de KSP al archivo
build.gradle.ktsa nivel del proyecto. Usa una versión del complemento de KSP que sea compatible con tu versión de Kotlin. Te recomendamos la versión 2.3.6 o una posterior de KSP.dependencies { ... classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6" }Agrega las dependencias del compilador estructurado al archivo
build.gradle.ktsa nivel de la app:dependencies { ... ksp("com.google.mlkit:genai-schema-compiler:1.0.0-alpha1") }
Define la estructura del resultado
Define la estructura de los datos que quieres que devuelva el modelo con las clases de datos de Kotlin. Existen dos anotaciones principales para definir la estructura de salida:
- Usa la anotación
@Generablepara definir la clase como un destino para el resultado estructurado. - Usa las anotaciones
@Guideen las propiedades de la clase para proporcionar descripciones y restricciones que guíen el resultado del modelo.
En el siguiente ejemplo, se define una estructura para extraer información de plantas:
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
)
Tipos y restricciones admitidos
Los siguientes tipos se admiten dentro de una clase anotada con @Generable, junto con sus respectivas restricciones de @Guide:
| Tipo | Descripción | Restricciones de @Guide admitidas |
|---|---|---|
String |
Para texto | description, enumValues |
Double/Float |
Para números de punto flotante. | description, minimum, maximum |
Int/Long |
Para números enteros. | description, minimum, maximum |
Boolean |
Para valores de verdadero o falso. | description |
List<T> |
Para listas de tipos admitidos o clases @Generable anidadas.
|
description, minItems, maxItems |
List<String> |
Para listas de valores String. |
description, enumValues, minItems,
maxItems |
Clase @Generable |
Para objetos estructurados anidados. | description |
Genera contenido estructurado
Para solicitar un resultado estructurado, usa la función auxiliar generateTypedContentRequest para unir tu instrucción estándar y especificar la clase de resultado de destino.
// 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}")
}
}
}
Controla los motivos de finalización y los errores
Cuando uses la API de Structured Output, debes controlar las posibles excepciones
que arroje la API y, si la respuesta analizada es nula, inspeccionar la propiedad finishReason en los candidatos
de respuesta.
Valores de finishReason
La propiedad finishReason puede tener uno de los siguientes valores:
TypedFinishReason.STOP: El modelo terminó de generar correctamente y el resultado coincide con el esquema.TypedFinishReason.MAX_TOKENS: El modelo se detuvo porque alcanzó el límite de tokens. Es posible que el resultado esté incompleto.TypedFinishReason.PARSE_CLASS_ERROR: El modelo completó la generación, pero el JSON resultante no se pudo analizar en la clase de Kotlin de destino.TypedFinishReason.STRUCTURE_NOT_ANNOTATED: A la clase de destino o a sus clases anidadas les falta la anotación@Generablerequerida.TypedFinishReason.STRUCTURE_VALUES_INVALID: Los valores generados infringieron las restricciones definidas en las anotaciones@Guide(por ejemplo, valor fuera de rango, tamaño de la lista fuera de los límites).TypedFinishReason.OTHER: La generación se detuvo por otros motivos.
Excepciones
La API de Structured Output puede arrojar GenAiException con los siguientes
códigos de error:
GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS(-104): La estructura de la clase anotada no es válida o contiene tipos no admitidos. Por lo general, se trata de un error de configuración en el tiempo de desarrollo. Revisa la definición de la clase de datos@Generablepara verificar que se admitan todos los tipos de propiedades y que no haya dependencias circulares.GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE(-105): Los valores que genera el modelo no son válidos o no cumplen con la verificación de restricciones. Este es un error de tiempo de ejecución. Si encuentras este error con frecuencia, considera las siguientes soluciones:- Refinar las instrucciones de la instrucción para guiar al modelo de forma más estricta
- Flexibiliza las restricciones (como los límites de tamaño mínimo, máximo o de lista) en
tus anotaciones de
@Guidesi son demasiado restrictivas para las capacidades del modelo. - Implementar una estrategia de resguardo en tu app, como volver a intentar la solicitud o mostrar un estado predeterminado
Cuenta tokens
Para verificar si tu instrucción estructurada está dentro del límite de tokens de entrada, calcula
el recuento de tokens con el método countTokens().
Debido a que las solicitudes de salida estructurada deben indicarle al modelo la estructura del esquema, el recuento de tokens solo en el texto de la instrucción sin procesar (con una instancia de GenerateContentRequest) no es preciso. Para obtener un recuento de tokens preciso, debes pasar la instancia de GenerateTypedContentRequest completa, que
incluye la clase de destino y los parámetros de configuración del esquema, al método
countTokens():
suspend fun <T : Any> countTokens(request: GenerateTypedContentRequest<T>): CountTokensResponse