Jeśli chcesz przeanalizować odpowiedzi z interfejsu Prompt API w określonych formatach, np. JSON, aby je dalej przetwarzać, użyj interfejsu Structured Output API.
Za pomocą interfejsu Structured Output API możesz zdefiniować docelową strukturę danych wyjściowych za pomocą klas i adnotacji Kotlin. Interfejs Prompt API zwraca wtedy odpowiedź w postaci obiektu Kotlin.
Generowanie uporządkowanych danych wyjściowych jest szczególnie przydatne w przypadku takich zadań:
- Wyodrębnianie encji: wyodrębnianie uporządkowanych pól (np. nazwy wydarzenia , daty, lokalizacji) z nieustrukturyzowanego tekstu.
- Klasyfikacja: kategoryzowanie tekstu wejściowego według wstępnie zdefiniowanych kategorii.
- Serializacja danych: przekształcanie nieustrukturyzowanych danych wejściowych użytkownika w format odpowiedni do przechowywania w bazie danych lub wywołań interfejsu API.
Wymagania wstępne
Aby sprawdzić, czy interfejs Structured Output API jest dostępny na urządzeniu, użyj interfejsu isStructuredOutputFeatureAvailable() API. Jeśli interfejs Structured Output API jest dostępny na urządzeniu, interfejs API zwraca wartość true, a w przeciwnym razie – false.
suspend fun isStructuredOutputFeatureAvailable(): Boolean
Interfejs Structured Output API ma też te wymagania:
- Poziom 26 lub wyższy interfejsu Android API (
minSdk26) - Wtyczka KSP w wersji 2.3.6 lub nowszej
Ograniczenia
Interfejs Structured Output API ma te ograniczenia:
- Działa tylko w języku Kotlin.
- ProGuard może zakłócać analizowanie klasy z adnotacjami. Jeśli podczas analizowania wystąpią błędy, dodaj klasę z adnotacjami do reguł keep, aby wykluczyć ją z ProGuard, np.:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }
Konfigurowanie projektu
Aby zacząć korzystać z interfejsu Structured Output API:
Dodaj interfejs ML Kit Prompt API jako zależność w pliku na poziomie aplikacji
build.gradle.kts(lubbuild.gradle), jeśli jeszcze tego nie zrobisz.Dodaj wtyczkę KSP do pliku
build.gradle.ktsna poziomie projektu. Użyj wtyczki KSP w wersji zgodnej z Twoją wersją języka Kotlin. Zalecamy używanie wtyczki KSP w wersji 2.3.6 lub nowszej.dependencies { ... classpath "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.6" }Dodaj zależności kompilatora strukturalnego do pliku
build.gradle.ktsna poziomie aplikacji:dependencies { ... ksp("com.google.mlkit:genai-schema-compiler:1.0.0-alpha1") }
Definiowanie struktury danych wyjściowych
Zdefiniuj strukturę danych, które mają być zwracane przez model, za pomocą klas danych Kotlin. Do definiowania struktury danych wyjściowych służą 2 główne adnotacje:
- Użyj adnotacji
@Generable, aby zdefiniować klasę jako cel uporządkowanych danych wyjściowych. - Użyj adnotacji
@Guidewe właściwościach klasy, aby podać opisy i ograniczenia, które będą kierować danymi wyjściowymi modelu.
Ten przykład definiuje strukturę do wyodrębniania informacji o roślinach:
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
)
Obsługiwane typy i ograniczenia
W klasie z adnotacją @Generable obsługiwane są te typy wraz z odpowiednimi ograniczeniami @Guide:
| Typ | Opis | Obsługiwane ograniczenia @Guide |
|---|---|---|
String |
Tekst. | description, enumValues |
Double / Float |
Liczby zmiennoprzecinkowe. | description, minimum, maximum |
Int / Long |
Liczby całkowite. | description, minimum, maximum |
Boolean |
Wartości prawda/fałsz. | description |
List<T> |
Listy obsługiwanych typów lub zagnieżdżonych klas @Generable.
|
description, minItems, maxItems |
List<String> |
Listy wartości String. |
description, enumValues, minItems,
maxItems |
Klasa @Generable |
Zagnieżdżone obiekty strukturalne. | description |
Generowanie uporządkowanych treści
Aby poprosić o uporządkowane dane wyjściowe, użyj funkcji pomocniczej generateTypedContentRequest, aby opakować standardowy prompt i określić docelową klasę danych wyjściowych.
// 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}")
}
}
}
Obsługa przyczyn zakończenia i błędów
Gdy używasz interfejsu Structured Output API, musisz obsługiwać potencjalne wyjątki zgłaszane przez interfejs API i sprawdzać właściwość finishReason w kandydatach odpowiedzi, jeśli przeanalizowana odpowiedź ma wartość null.
Wartości finishReason
Właściwość finishReason może przyjmować jedną z tych wartości:
TypedFinishReason.STOP: model zakończył generowanie i dane wyjściowe są zgodne ze schematem.TypedFinishReason.MAX_TOKENS: model został zatrzymany, ponieważ osiągnął limit tokenów. Dane wyjściowe mogą być niepełne.TypedFinishReason.PARSE_CLASS_ERROR: model zakończył generowanie, ale nie udało się przeanalizować wynikowego kodu JSON w docelowej klasie Kotlin.TypedFinishReason.STRUCTURE_NOT_ANNOTATED: w klasie docelowej lub jej zagnieżdżonych klasach brakuje wymaganej adnotacji@Generable.TypedFinishReason.STRUCTURE_VALUES_INVALID: wygenerowane wartości naruszyły ograniczenia zdefiniowane w adnotacjach@Guide(np. wartość poza zakresem, rozmiar listy poza zakresem).TypedFinishReason.OTHER: generowanie zostało zatrzymane z innych powodów.
Wyjątki
Interfejs Structured Output API może zgłaszać wyjątek GenAiException z tymi kodami błędów:
GenAiException.STRUCTURED_OUTPUT_INVALID_CLASS(-104): struktura klasy z adnotacjami jest nieprawidłowa lub zawiera nieobsługiwane typy. Zwykle jest to błąd konfiguracji w czasie programowania. Sprawdź definicję klasy danych@Generable, aby upewnić się, że wszystkie typy właściwości są obsługiwane i że nie ma zależności cyklicznych.GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE(-105): wartości wygenerowane przez model są nieprawidłowe lub nie spełniają ograniczeń. Jest to błąd czasu działania. Jeśli ten błąd występuje często, rozważ te rozwiązania:- Uściślenie instrukcji prompta, aby ściślej kierować modelem.
- Złagodzenie ograniczeń (np. limitów minimalnych, maksymalnych lub rozmiaru listy) w adnotacjach
@Guide, jeśli są one zbyt restrykcyjne dla możliwości modelu. - Wdrożenie w aplikacji strategii rezerwowej, np. ponowienie próby wysłania żądania lub wyświetlenie stanu domyślnego.
Zliczanie tokenów
Aby sprawdzić, czy uporządkowany prompt mieści się w limicie tokenów wejściowych, oblicz
liczbę tokenów za pomocą countTokens() metody.
Ponieważ żądania uporządkowanych danych wyjściowych muszą zawierać instrukcje dla modelu dotyczące struktury schematu, zliczanie tokenów tylko w tekście prompta (za pomocą instancji GenerateContentRequest) nie jest dokładne. Aby uzyskać dokładną liczbę tokenów, musisz przekazać do metody countTokens() całą instancję GenerateTypedContentRequest, która zawiera docelową klasę i konfiguracje schematu:
suspend fun <T : Any> countTokens(request: GenerateTypedContentRequest<T>): CountTokensResponse