Si necesitas analizar las respuestas de la API de Prompt en ciertos formatos, como JSON, para su procesamiento posterior, usa la API de Structured Output.
Con la API de Structured Output, defines la estructura de salida de destino con clases y anotaciones de Kotlin. Luego, la API de Prompt muestra una respuesta en forma de tu objeto de Kotlin.
La generación de resultados estructurados es particularmente útil para tareas como las siguientes:
- Extracción de entidades: Extrae campos estructurados (por ejemplo, nombre del evento, fecha, ubicación) de texto no estructurado.
- Clasificación: Categoriza 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 de 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 muestra 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) - Versión 2.3.6 o superior del complemento KSP
Limitaciones
La API de Structured Output tiene las siguientes limitaciones:
- Funciona solo en Kotlin.
- ProGuard podría interferir con el análisis de tu clase anotada. Agrega tu clase anotada a tus reglas de conservación para excluirla 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 API de Prompt del ML Kit como una dependencia en tu archivo
build.gradle.kts(obuild.gradle) a nivel de la app, si aún no lo hiciste.Agrega el complemento KSP al archivo
build.gradle.ktsa nivel del proyecto. Usa una versión del complemento KSP que sea compatible con tu versión de Kotlin. Recomendamos la versión 2.3.6 o superior 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 de salida
Define la estructura de los datos que deseas que el modelo muestre 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 son compatibles dentro de una clase anotada con @Generable, junto con sus respectivas restricciones @Guide:
| Tipo | Descripción | Restricciones @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 verdaderos o falsos | 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 encapsular tu mensaje estándar y especificar la clase de salida 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,
// 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}")
}
}
}
Maneja motivos de finalización y errores
Cuando uses la API de Structured Output, debes controlar las posibles excepciones que arroja la API y examinar la propiedad finishReason en los candidatos de respuesta si la respuesta analizada es nula.
Valores de finishReason
La propiedad finishReason puede tomar uno de los siguientes valores:
TypedFinishReason.STOP: El modelo terminó de generarse 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: La clase de destino o sus clases anidadas no tienen la anotación@Generableobligatoria.TypedFinishReason.STRUCTURE_VALUES_INVALID: Los valores generados infringieron las restricciones definidas en las anotaciones@Guide(por ejemplo, valor fuera de rango, tamaño de lista fuera de 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 tiempo de desarrollo. Revisa la definición de tu 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 generados por el modelo no son válidos o no superan la verificación de restricciones. Este es un error en tiempo de ejecución. Si encuentras este error con frecuencia, considera las siguientes soluciones:- Refina las instrucciones del mensaje para guiar el modelo de forma más estricta.
- Relaja las restricciones (como los límites de tamaño mínimo, máximo o de lista) en tus anotaciones
@Guidesi son demasiado restrictivas para las capacidades del modelo. - Implementa una estrategia de resguardo en tu app, como reintentar la solicitud o mostrar un estado predeterminado.
Cuenta tokens
Para verificar si tu mensaje estructurado está dentro del límite de tokens de entrada, calcula
el recuento de tokens con el countTokens() método.
Debido a que las solicitudes de resultados estructurados deben indicarle al modelo la estructura del esquema, el recuento de tokens solo en el texto del mensaje sin procesar (con una instancia de GenerateContentRequest) no es preciso. Para obtener un recuento de tokens preciso, debes pasar la instancia completa de GenerateTypedContentRequest, que incluye las configuraciones de esquema y clase de destino, al método countTokens():
suspend fun <T : Any> countTokens(request: GenerateTypedContentRequest<T>): CountTokensResponse