生成結構化輸出內容

如要將 Prompt API 的回覆內容剖析為特定格式 (例如 JSON),以便進一步處理,請使用 Structured Output API。

您可以使用 Kotlin 類別和註解,透過 Structured Output API 定義目標輸出結構。接著,Prompt API 會以 Kotlin 物件的形式傳回回應。

生成結構化輸出內容特別適合用於下列工作:

  • 實體擷取:從非結構化文字中擷取結構化欄位 (例如活動名稱、日期、地點)。
  • 分類:將輸入文字分類至預先定義的類別。
  • 資料序列化:將非結構化使用者輸入內容轉換為適合資料庫儲存或 API 呼叫的格式。

必要條件

如要確認裝置是否支援 Structured Output API,請使用 isStructuredOutputFeatureAvailable() API。如果裝置支援結構化輸出 API,API 會傳回 true,否則會傳回 false

suspend fun isStructuredOutputFeatureAvailable(): Boolean

結構化輸出 API 也須符合下列規定:

  • Android API 級別 26 以上 (minSdk 26)
  • KSP 外掛程式 2.3.6 以上版本

限制

結構化輸出 API 有下列限制:

  • 僅適用於 Kotlin。
  • ProGuard 可能會干擾註解類別的剖析作業。如果剖析時發生錯誤,請將註解類別新增至保留規則,從 ProGuard 中排除這些類別,例如:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }

設定專案

如要開始使用結構化輸出 API,請按照下列步驟操作:

  1. 在應用程式層級的 build.gradle.kts (或 build.gradle) 檔案中,將 ML Kit Prompt API 新增為依附元件 (如果尚未新增)。

  2. 在專案層級的 build.gradle.kts 檔案中新增 KSP 外掛程式。使用與 Kotlin 版本相容的 KSP 外掛程式版本;建議使用 KSP 2.3.6 以上版本。

    dependencies {
        ...
        classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6"
    }
    
  3. 將結構化編譯器依附元件新增至應用程式層級的 build.gradle.kts 檔案:

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

定義輸出結構

使用 Kotlin 資料類別,定義模型要傳回的資料結構。定義輸出結構的主要註解有兩種:

  • 使用 @Generable 註解將類別定義為結構化輸出的目標。
  • 在類別屬性上使用 @Guide 註解,提供說明和限制,引導模型輸出結果。

以下範例定義用於擷取植物資訊的結構:

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
)

支援的類型和限制

使用 @Generable 註解的類別支援下列型別,以及各自的 @Guide 限制:

類型 說明 支援的 @Guide 限制
String 文字。 descriptionenumValues
Double/Float 適用於浮點數。 descriptionminimummaximum
Int/Long 只能輸入整數。 descriptionminimummaximum
Boolean 適用於 true/false 值。 description
List<T> 如需支援的型別或巢狀 @Generable 類別清單,請參閱下文。 descriptionminItemsmaxItems
List<String> 如需 String 值清單。 descriptionenumValuesminItemsmaxItems
@Generable 類別 適用於巢狀結構化物件。 description

生成結構化內容

如要要求結構化輸出內容,請使用 generateTypedContentRequest 輔助函式包裝標準提示,並指定目標輸出類別。

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

處理完成原因和錯誤

使用結構化輸出 API 時,您應處理 API 可能擲回的例外狀況,並在剖析的回應為空值時,檢查回應候選項目中的 finishReason 屬性。

finishReason 值

finishReason 屬性可採用下列其中一個值:

  • TypedFinishReason.STOP:模型已順利完成生成,且輸出內容符合結構定義。
  • TypedFinishReason.MAX_TOKENS:模型達到詞元用量上限,因此停止運作。輸出內容可能不完整。
  • TypedFinishReason.PARSE_CLASS_ERROR:模型已完成生成,但產生的 JSON 無法剖析為目標 Kotlin 類別。
  • TypedFinishReason.STRUCTURE_NOT_ANNOTATED:目標類別或其巢狀類別缺少必要的 @Generable 註解。
  • TypedFinishReason.STRUCTURE_VALUES_INVALID:產生的值違反 @Guide 註解中定義的限制 (例如值超出範圍、清單大小超出界限)。
  • TypedFinishReason.OTHER:因其他原因而停止生成。

例外狀況

Structured Output API 可能會擲回 GenAiException,並顯示下列錯誤代碼:

  • GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS (-104):註解類別的結構無效或包含不支援的型別。這通常是開發期間的設定錯誤。請檢查 @Generable 資料類別定義,確認所有屬性類型都受到支援,且沒有任何循環依附元件。
  • GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE (-105):模型產生的值無效或未通過限制驗證。這是執行階段錯誤。如果經常遇到這項錯誤,請考慮下列解決方案:
    • 調整提示指令,更嚴格地引導模型。
    • 如果 @Guide 註解中的限制 (例如最小值、最大值或清單大小限制) 對模型功能過於嚴格,請放寬限制。
    • 在應用程式中實作備援策略,例如重試要求或顯示預設狀態。

計算詞元數

如要檢查結構化提示詞是否在輸入權杖限制內,請使用 countTokens() 方法計算詞元數。

由於結構化輸出要求需要指示模型結構定義結構,因此只計算原始提示文字的權杖 (使用 GenerateContentRequest 例項) 並不準確。如要取得準確的權杖數量,您必須將完整的 GenerateTypedContentRequest 執行個體 (包括目標類別和結構定義設定) 傳遞至 countTokens() 方法:

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