Gerar saída estruturada

Se você precisar analisar as respostas da API Prompt em determinados formatos, como JSON, para processamento adicional, use a API Structured Output.

Com a API Structured Output, você define a estrutura de saída de destino usando classes e anotações do Kotlin. A API Prompt retorna uma resposta na forma do seu objeto Kotlin.

A geração de saída estruturada é particularmente útil para tarefas como as seguintes:

  • Extração de entidades: extrair campos estruturados (por exemplo, nome do evento, data, local) de texto não estruturado.
  • Classificação: categorizar o texto de entrada em categorias predefinidas.
  • Serialização de dados: converter a entrada não estruturada do usuário em um formato adequado para armazenamento de banco de dados ou chamadas de API.

Pré-requisitos

Para verificar se a API Structured Output está disponível no dispositivo, use a API isStructuredOutputFeatureAvailable(). A API retorna true se a API Structured Output estiver disponível no dispositivo e false caso contrário.

suspend fun isStructuredOutputFeatureAvailable(): Boolean

A API Structured Output também tem os seguintes requisitos:

  • Nível 26 ou mais recente da API do Android (minSdk 26)
  • Plug-in KSP versão 2.3.6 ou mais recente

Limitações

A API Structured Output tem as seguintes limitações:

  • Funciona apenas no Kotlin.
  • O ProGuard pode interferir na análise da classe anotada. Adicione a sua classe anotada às suas regras de manutenção para excluí-las do ProGuard se você receber erros de análise, por exemplo:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }

Configurar projeto

Para começar a usar a API Structured Output, siga estas etapas:

  1. Adicione a API Prompt do Kit de ML como uma dependência no arquivo no nível do app build.gradle.kts (ou build.gradle), se ainda não tiver feito isso.

  2. Adicione o plug-in KSP ao arquivo build.gradle.kts no nível do projeto. Use uma versão do plug-in KSP que seja compatível com a versão do Kotlin. Recomendamos a versão 2.3.6 ou mais recente do KSP.

    dependencies {
        ...
        classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6"
    }
    
  3. Adicione as dependências do compilador estruturado ao arquivo build.gradle.kts no nível do app:

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

Definir a estrutura de saída

Defina a estrutura dos dados que você quer que o modelo retorne usando classes de dados do Kotlin. Há duas anotações principais para definir a estrutura de saída:

  • Use a anotação @Generable para definir a classe como um destino para saída estruturada.
  • Use as anotações @Guide nas propriedades da classe para fornecer descrições e restrições que orientam a saída do modelo.

O exemplo a seguir define uma estrutura para extrair informações 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 e restrições compatíveis

Os tipos a seguir são compatíveis em uma classe anotada @Generable, juntamente com as restrições @Guide correspondentes:

Tipo Descrição Restrições @Guide compatíveis
String Para texto. description, enumValues
Double / Float Para números de ponto flutuante. description, minimum, maximum
Int / Long Para números inteiros. description, minimum, maximum
Boolean Para valores verdadeiro/falso. description
List<T> Para listas de tipos compatíveis ou classes @Generable aninhadas. description, minItems, maxItems
List<String> Para listas de valores String. description, enumValues, minItems, maxItems
Classe @Generable Para objetos estruturados aninhados. description

Gerar conteúdo estruturado

Para solicitar uma saída estruturada, use a função auxiliar generateTypedContentRequest para encapsular o prompt padrão e especificar a classe de saída 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}")
        }
    }
}

Processar motivos de conclusão e erros

Ao usar a API Structured Output, processe possíveis exceções geradas pela API e inspecione a propriedade finishReason nos candidatos de resposta se a resposta analisada for nula.

Valores de finishReason

A propriedade finishReason pode assumir um dos seguintes valores:

  • TypedFinishReason.STOP: o modelo terminou a geração com sucesso e a saída corresponde ao esquema.
  • TypedFinishReason.MAX_TOKENS: o modelo parou porque atingiu o limite de tokens. A saída pode estar incompleta.
  • TypedFinishReason.PARSE_CLASS_ERROR: o modelo concluiu a geração, mas o JSON resultante não pôde ser analisado na classe de destino do Kotlin.
  • TypedFinishReason.STRUCTURE_NOT_ANNOTATED: a classe de destino ou as classes aninhadas não têm a anotação @Generable necessária.
  • TypedFinishReason.STRUCTURE_VALUES_INVALID: os valores gerados violaram as restrições definidas nas anotações @Guide (por exemplo, valor fora do intervalo, tamanho da lista fora dos limites).
  • TypedFinishReason.OTHER: a geração foi interrompida por outros motivos.

Exceções

A API Structured Output pode gerar GenAiException com os seguintes códigos de erro:

  • GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS (-104): a estrutura da classe anotada é inválida ou contém tipos não compatíveis. Normalmente, esse é um erro de configuração no momento do desenvolvimento. Revise a definição da classe de dados @Generable para verificar se todos os tipos de propriedade são compatíveis e se não há dependências circulares.
  • GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE (-105): os valores gerados pelo modelo são inválidos ou falham na verificação de restrições. Esse é um erro de execução. Se você encontrar esse erro com frequência, considere as seguintes soluções:
    • Refinar as instruções do prompt para orientar o modelo de forma mais rigorosa.
    • Relaxar as restrições (como limites mínimos, máximos ou de tamanho da lista) nas anotações @Guide se elas forem muito restritivas para os recursos do modelo.
    • Implementar uma estratégia de fallback no app, como tentar a solicitação novamente ou mostrar um estado padrão.

Contar tokens

Para verificar se o prompt estruturado está dentro do limite de tokens de entrada, calcule a contagem de tokens usando o countTokens() método.

Como as solicitações de saída estruturada precisam instruir o modelo sobre a estrutura do esquema, a contagem de tokens apenas no texto do prompt bruto (usando uma instância GenerateContentRequest) não é precisa. Para receber uma contagem de tokens precisa, transmita a instância GenerateTypedContentRequest completa, que inclui a classe de destino e as configurações de esquema, para o método countTokens():

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