Generare output strutturato

Se devi analizzare le risposte di Gemini Nano in determinati formati, ad esempio JSON, per un'ulteriore elaborazione, utilizza l'API Structured Output con accesso diretto al modello.

Con l'API Structured Output, definisci la struttura di output di destinazione utilizzando classi e annotazioni Kotlin. L'accesso diretto al modello 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, data e luogo dell'evento) 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 all'archiviazione nel database o alle 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, altrimenti restituisce false.

suspend fun isStructuredOutputFeatureAvailable(): Boolean

L'API Structured Output prevede anche i seguenti requisiti:

  • Livello API Android 26 o versioni successive (minSdk 26)
  • Plug-in KSP versione 2.3.6 o successive

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 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, segui questi passaggi:

  1. Aggiungi la dipendenza com.google.mlkit:genai-prompt nel file build.gradle.kts (o build.gradle) a livello di app, 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 2.3.6 o successive di KSP.

    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 dell'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 sulle proprietà della classe per fornire descrizioni e vincoli che guidano l'output del modello.

L'esempio seguente definisce una struttura per l'estrazione di 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

All'interno di una classe annotata @Generable sono supportati i seguenti tipi, 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 e maximum
Int/Long Per i numeri interi. description, minimum e maximum
Boolean Per i valori vero/falso. description
List<T> Per elenchi di tipi supportati o classi @Generable nidificate. description, minItems e maxItems
List<String> Per gli elenchi di valori String. description, enumValues, minItems, maxItems
Classe @Generable Per oggetti strutturati nidificati. description

Genera contenuti strutturati

Per richiedere un output strutturato, utilizza la funzione helper generateTypedContentRequest per aggregare 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
)

// 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 interruzione e gli errori

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

Valori di finishReason

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

  • TypedFinishReason.STOP: il modello ha terminato la generazione correttamente e l'output corrisponde allo schema.
  • TypedFinishReason.MAX_TOKENS: il modello si è interrotto 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 analizzato nella classe Kotlin di destinazione.
  • TypedFinishReason.STRUCTURE_NOT_ANNOTATED: nella classe target o nelle sue classi nidificate manca l'annotazione @Generable obbligatoria.
  • 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: generazione 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 siano presenti dipendenze circolari.
  • GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE (-105): i valori generati dal modello non sono validi o non superano la verifica dei vincoli. Questo è un errore di runtime. Se riscontri spesso questo errore, prendi in considerazione le seguenti soluzioni:
    • Perfezionare le istruzioni del prompt per guidare il modello in modo più rigoroso.
    • Allentare i vincoli (come i limiti minimi, massimi o di dimensione dell'elenco) nelle annotazioni @Guide se sono troppo restrittivi per le funzionalità del modello.
    • Implementare una strategia di riserva 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 metodo countTokens().

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

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