Generare output strutturato

Se devi analizzare le risposte dell'API Prompt in determinati formati, ad esempio JSON, per un'ulteriore elaborazione, utilizza l'API Structured Output.

Con l'API Structured Output, definisci la struttura di output di destinazione utilizzando classi e annotazioni Kotlin. L'API Prompt restituisce quindi una risposta sotto forma di oggetto Kotlin.

La generazione di output strutturato è particolarmente utile per attività come le seguenti:

  • Estrazione di entità: estrazione di campi strutturati (ad esempio, nome dell'evento, data, luogo) da testo non strutturato.
  • Classificazione: categorizzazione del testo di input in categorie predefinite.
  • Serializzazione dei dati: conversione dell'input utente non strutturato in un formato adatto per l'archiviazione del database o le chiamate API.

Prerequisiti

Per verificare che l'API Structured Output sia disponibile sul dispositivo, utilizza l'API isStructuredOutputFeatureAvailable(). L'API restituisce true se l'API Structured Output è disponibile sul dispositivo e false in caso contrario.

suspend fun isStructuredOutputFeatureAvailable(): Boolean

L'API Structured Output presenta anche i seguenti requisiti:

  • Livello API Android 26 o superiore (minSdk 26)
  • Versione del plug-in KSP 2.3.6 o superiore

Limitazioni

L'API Structured Output presenta le seguenti limitazioni:

  • Funziona solo in Kotlin.
  • ProGuard potrebbe interferire con l'analisi della classe annotata. Aggiungi la tua classe annotata alle tue regole di conservazione per escluderla da ProGuard se ricevi errori di analisi, ad esempio:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }

Configura il progetto

Per iniziare a utilizzare l'API Structured Output:

  1. Aggiungi l'API Prompt di ML Kit come dipendenza nel file a livello di app build.gradle.kts (o build.gradle), se non l'hai già fatto.

  2. Aggiungi il plug-in KSP al file build.gradle.kts a livello di progetto. Utilizza una versione del plug-in KSP compatibile con la tua versione di Kotlin; ti consigliamo la versione KSP 2.3.6 o successive.

    dependencies {
        ...
        classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6"
    }
    
  3. Aggiungi le dipendenze del compilatore strutturato al file build.gradle.kts a livello di app:

    dependencies {
        ...
        ksp("com.google.mlkit:genai-schema-compiler:1.0.0-alpha1")
    }
    

Definisci la struttura di output

Definisci la struttura dei dati che vuoi che il modello restituisca utilizzando le classi di dati Kotlin. Esistono due annotazioni principali per definire la struttura di output:

  • Utilizza l'annotazione @Generable per definire la classe come target per l'output strutturato.
  • Utilizza le annotazioni @Guide nelle proprietà della classe per fornire descrizioni e vincoli che guidano l'output del modello.

L'esempio seguente definisce una struttura per l'estrazione delle informazioni sulle piante:

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
)

Tipi e vincoli supportati

I seguenti tipi sono supportati all'interno di una classe annotata @Generable, insieme ai rispettivi vincoli @Guide:

Tipo Descrizione Vincoli @Guide supportati
String Per il testo. description, enumValues
Double / Float Per i numeri in virgola mobile. description, minimum, maximum
Int / Long Per i numeri interi. description, minimum, maximum
Boolean Per i valori true/false. description
List<T> Per gli elenchi di tipi supportati o classi @Generable nidificate. description, minItems, maxItems
List<String> Per gli elenchi di valori String. description, enumValues, minItems, maxItems
Classe @Generable Per gli oggetti strutturati nidificati. description

Genera contenuti strutturati

Per richiedere l'output strutturato, utilizza la funzione helper generateTypedContentRequest per racchiudere il prompt standard e specificare la classe di output di destinazione.

// 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}")
        }
    }
}

Gestisci i motivi di completamento e gli errori

Quando utilizzi l'API Structured Output, devi gestire le potenziali eccezioni generate dall'API e controllare la proprietà finishReason nei candidati di risposta se la risposta analizzata è null.

Valori di finishReason

La proprietà finishReason può assumere uno dei seguenti valori:

  • TypedFinishReason.STOP: il modello ha completato la generazione correttamente e l'output corrisponde allo schema.
  • TypedFinishReason.MAX_TOKENS: il modello si è arrestato perché ha raggiunto il limite di token. L'output potrebbe essere incompleto.
  • TypedFinishReason.PARSE_CLASS_ERROR: il modello ha completato la generazione, ma il JSON risultante non è stato possibile analizzarlo nella classe Kotlin di destinazione.
  • TypedFinishReason.STRUCTURE_NOT_ANNOTATED: la classe di destinazione o le relative classi nidificate non hanno l'annotazione @Generable richiesta.
  • TypedFinishReason.STRUCTURE_VALUES_INVALID: i valori generati hanno violato i vincoli definiti nelle annotazioni @Guide (ad esempio, valore fuori intervallo, dimensione dell'elenco fuori dai limiti).
  • TypedFinishReason.OTHER: la generazione è stata interrotta per altri motivi.

Eccezioni

L'API Structured Output potrebbe generare GenAiException con i seguenti codici di errore:

  • GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS (-104): la struttura della classe annotata non è valida o contiene tipi non supportati. In genere si tratta di un errore di configurazione in fase di sviluppo. Esamina la definizione della classe di dati @Generable per verificare che tutti i tipi di proprietà siano supportati e che non esistano dipendenze cicliche.
  • GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE (-105): i valori generati dal modello non sono validi o non superano la verifica dei vincoli. Si tratta di un errore di runtime. Se si verifica spesso questo errore, valuta le seguenti soluzioni:
    • Perfezionare le istruzioni del prompt per guidare il modello in modo più rigoroso.
    • Rilassare i vincoli (come i limiti di dimensione minima, massima o dell'elenco) nelle annotazioni @Guide se sono troppo restrittivi per le funzionalità del modello.
    • Implementare una strategia di fallback nell'app, ad esempio riprovare la richiesta o visualizzare uno stato predefinito.

Contare i token

Per verificare se il prompt strutturato rientra nel limite di token di input, calcola il conteggio dei token utilizzando il countTokens() metodo.

Poiché le richieste di output strutturato devono indicare al modello la struttura dello schema, il conteggio dei token solo sul testo del prompt non elaborato (utilizzando un'istanza GenerateContentRequest) non è accurato. Per ottenere un conteggio accurato dei token, devi passare l'istanza GenerateTypedContentRequest completa, che include la classe di destinazione e le configurazioni dello schema, al metodo countTokens():

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