Tạo đầu ra có cấu trúc

Nếu bạn cần phân tích cú pháp các phản hồi từ Prompt API thành một số định dạng nhất định (chẳng hạn như JSON) để xử lý thêm, hãy sử dụng Structured Output API.

Với Structured Output API, bạn xác định cấu trúc đầu ra mục tiêu bằng cách sử dụng các chú thích và lớp Kotlin. Sau đó, Prompt API sẽ trả về một phản hồi dưới dạng đối tượng Kotlin của bạn.

Việc tạo đầu ra có cấu trúc đặc biệt hữu ích cho các tác vụ như sau:

  • Trích xuất thực thể: Trích xuất các trường có cấu trúc (ví dụ: tên sự kiện, ngày, vị trí) từ văn bản không có cấu trúc.
  • Phân loại: Phân loại văn bản đầu vào thành các danh mục được xác định trước.
  • Chuyển đổi tuần tự dữ liệu: Chuyển đổi dữ liệu đầu vào không có cấu trúc của người dùng sang định dạng phù hợp để lưu trữ cơ sở dữ liệu hoặc lệnh gọi API.

Điều kiện tiên quyết

Để xác minh rằng Structured Output API có trên thiết bị, hãy sử dụng API isStructuredOutputFeatureAvailable(). API này trả về true nếu Structured Output API có trên thiết bị và false nếu không.

suspend fun isStructuredOutputFeatureAvailable(): Boolean

Structured Output API cũng có các yêu cầu sau:

  • Android API cấp 26 trở lên (minSdk 26)
  • Trình bổ trợ KSP phiên bản 2.3.6 trở lên

Các điểm hạn chế

Structured Output API có những hạn chế sau:

  • Chỉ hoạt động trong Kotlin.
  • ProGuard có thể gây trở ngại cho quá trình phân tích cú pháp lớp được chú thích của bạn. Thêm lớp được chú thích vào các quy tắc lưu giữ để loại trừ các lớp đó khỏi ProGuard nếu bạn gặp lỗi phân tích cú pháp, ví dụ:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }

Định cấu hình dự án

Để bắt đầu sử dụng Structured Output API, hãy làm theo các bước sau:

  1. Thêm Prompt API của Bộ công cụ học máy làm phần phụ thuộc trong tệp build.gradle.kts (hoặc build.gradle) cấp ứng dụng, nếu bạn chưa thêm.

  2. Thêm trình bổ trợ KSP vào tệp build.gradle.kts cấp dự án. Sử dụng một phiên bản trình bổ trợ KSP tương thích với phiên bản Kotlin của bạn; chúng tôi đề xuất phiên bản KSP 2.3.6 trở lên.

    dependencies {
        ...
        classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6"
    }
    
  3. Thêm các phần phụ thuộc trình biên dịch có cấu trúc vào tệp build.gradle.kts ở cấp ứng dụng:

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

Xác định cấu trúc đầu ra

Xác định cấu trúc của dữ liệu mà bạn muốn mô hình trả về bằng cách sử dụng các lớp dữ liệu Kotlin. Có 2 chú thích chính để xác định cấu trúc đầu ra:

  • Sử dụng chú thích @Generable để xác định lớp làm mục tiêu cho đầu ra có cấu trúc.
  • Sử dụng chú giải @Guide trên các thuộc tính của lớp để cung cấp nội dung mô tả và các ràng buộc hướng dẫn đầu ra của mô hình.

Ví dụ sau đây xác định một cấu trúc để trích xuất thông tin về thực vật:

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
)

Các loại và giới hạn được hỗ trợ

Các loại sau đây được hỗ trợ trong một lớp được chú thích @Generable, cùng với các ràng buộc @Guide tương ứng:

Loại Mô tả Các ràng buộc @Guide được hỗ trợ
String Đối với văn bản. description, enumValues
Double/Float Đối với số dấu phẩy động. description, minimum, maximum
Int/Long Đối với số nguyên. description, minimum, maximum
Boolean Đối với các giá trị đúng/sai. description
List<T> Đối với danh sách các loại được hỗ trợ hoặc các lớp @Generable lồng nhau. description, minItems, maxItems
List<String> Đối với danh sách các giá trị String. description, enumValues, minItems, maxItems
Lớp @Generable Đối với các đối tượng có cấu trúc lồng ghép. description

Tạo nội dung có cấu trúc

Để yêu cầu đầu ra có cấu trúc, hãy sử dụng hàm trợ giúp generateTypedContentRequest để bao bọc lời nhắc tiêu chuẩn và chỉ định lớp đầu ra mục tiêu.

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

Xử lý lý do kết thúc và lỗi

Khi sử dụng Structured Output API, bạn nên xử lý các trường hợp ngoại lệ tiềm ẩn do API này tạo ra và kiểm tra thuộc tính finishReason trong các đề xuất phản hồi nếu phản hồi được phân tích cú pháp là giá trị rỗng.

Giá trị finishReason

Thuộc tính finishReason có thể nhận một trong các giá trị sau:

  • TypedFinishReason.STOP: Mô hình đã tạo thành công và đầu ra khớp với giản đồ.
  • TypedFinishReason.MAX_TOKENS: Mô hình đã dừng vì đạt đến giới hạn mã thông báo. Kết quả có thể chưa đầy đủ.
  • TypedFinishReason.PARSE_CLASS_ERROR: Mô hình đã hoàn tất quá trình tạo, nhưng không thể phân tích cú pháp JSON kết quả thành lớp Kotlin mục tiêu.
  • TypedFinishReason.STRUCTURE_NOT_ANNOTATED: Lớp mục tiêu hoặc các lớp lồng nhau của lớp này thiếu chú thích @Generable bắt buộc.
  • TypedFinishReason.STRUCTURE_VALUES_INVALID: Các giá trị được tạo đã vi phạm những ràng buộc được xác định trong chú thích @Guide (ví dụ: giá trị nằm ngoài phạm vi, kích thước danh sách nằm ngoài giới hạn).
  • TypedFinishReason.OTHER: Đã dừng tạo do các lý do khác.

Ngoại lệ

Structured Output API có thể gửi GenAiException kèm theo các mã lỗi sau:

  • GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS (-104): Cấu trúc của lớp được chú thích không hợp lệ hoặc chứa các loại không được hỗ trợ. Đây thường là một lỗi cấu hình trong thời gian phát triển. Xem lại định nghĩa lớp dữ liệu @Generable để kiểm tra xem tất cả các loại thuộc tính đều được hỗ trợ và không có sự phụ thuộc vòng tròn nào.
  • GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE (-105): Các giá trị do mô hình tạo ra không hợp lệ hoặc không xác minh được các ràng buộc. Đây là lỗi thời gian chạy. Nếu bạn thường xuyên gặp phải lỗi này, hãy cân nhắc các giải pháp sau:
    • Tinh chỉnh hướng dẫn trong câu lệnh để hướng dẫn mô hình một cách chặt chẽ hơn.
    • Nới lỏng các ràng buộc (chẳng hạn như giới hạn kích thước tối thiểu, tối đa hoặc danh sách) trong chú thích @Guide nếu các ràng buộc đó quá hạn chế đối với các chức năng của mô hình.
    • Triển khai một chiến lược dự phòng trong ứng dụng của bạn, chẳng hạn như thử lại yêu cầu hoặc hiển thị trạng thái mặc định.

Đếm mã thông báo

Để kiểm tra xem câu lệnh có cấu trúc của bạn có nằm trong giới hạn token đầu vào hay không, hãy tính số token bằng phương thức countTokens().

Vì các yêu cầu về đầu ra có cấu trúc cần hướng dẫn mô hình về cấu trúc lược đồ, nên việc chỉ tính số lượng mã thông báo trên văn bản lời nhắc thô (bằng cách sử dụng một phiên bản GenerateContentRequest) là không chính xác. Để nhận được số lượng mã thông báo chính xác, bạn phải truyền toàn bộ thực thể GenerateTypedContentRequest (bao gồm cả cấu hình lớp và giản đồ mục tiêu) đến phương thức countTokens():

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