Jeśli chcesz przeanalizować odpowiedzi z Gemini Nano i przekonwertować je na określone formaty, np. JSON, w celu dalszego przetwarzania, użyj interfejsu Structured Output API z bezpośrednim dostępem do modelu.
Za pomocą interfejsu Structured Output API definiujesz docelową strukturę danych wyjściowych, używając klas i adnotacji Kotlin. Bezpośredni dostęp do modelu zwraca odpowiedź w postaci obiektu Kotlin.
Generowanie ustrukturyzowanych danych wyjściowych jest szczególnie przydatne w przypadku takich zadań jak:
- Ekstrakcja encji: wyodrębnianie pól uporządkowanych (np. nazwy wydarzenia, daty, lokalizacji) z nieuporządkowanego tekstu.
- Klasyfikacja: przypisywanie tekstu wejściowego do wstępnie zdefiniowanych kategorii.
- Serializacja danych: konwertowanie nieuporządkowanych danych wejściowych użytkownika na 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. Interfejs API zwraca wartość true, jeśli interfejs Structured Output API jest dostępny na urządzeniu, a w przeciwnym razie – false.
suspend fun isStructuredOutputFeatureAvailable(): Boolean
Interfejs Structured Output API ma też te wymagania:
- Android API na poziomie 26 lub wyższym (
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. Dodaj klasę z adnotacjami do reguł zachowywania, aby wykluczyć ją z ProGuard, jeśli wystąpią błędy analizy, na przykład:
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }
Konfigurowanie projektu
Aby rozpocząć korzystanie z interfejsu Structured Output API, wykonaj te czynności:
Dodaj zależność
com.google.mlkit:genai-promptw plikubuild.gradle.kts(lubbuild.gradle) na poziomie aplikacji, jeśli jeszcze nie została dodana.Dodaj wtyczkę KSP do pliku
build.gradle.ktsna poziomie projektu. Użyj wersji wtyczki KSP, która jest zgodna z Twoją wersją Kotlina. Zalecamy 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 uporządkowanego kompilatora do pliku
build.gradle.ktsna poziomie aplikacji:dependencies { ... ksp("com.google.mlkit:genai-schema-compiler:1.0.0-alpha1") }
Zdefiniuj strukturę danych wyjściowych
Zdefiniuj strukturę danych, które model ma zwracać, za pomocą klas danych w języku Kotlin. Do definiowania struktury danych wyjściowych służą 2 główne adnotacje:
- Użyj adnotacji
@Generable, aby zdefiniować klasę jako cel dla ustrukturyzowanych 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.
Poniższy 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 |
W przypadku tekstu. | description, enumValues |
Double/Float |
W przypadku liczb zmiennoprzecinkowych. | 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 String wartości. |
description, enumValues, minItems, maxItems |
@Generable zajęcia |
W przypadku zagnieżdżonych obiektów uporządkowanych. | description |
Generowanie ustrukturyzowanych 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
)
// 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
Podczas korzystania z interfejsu Structured Output API należy obsługiwać potencjalne wyjątki zgłaszane przez interfejs API i sprawdzać właściwość finishReason w odpowiedziach kandydatów, jeśli przeanalizowana odpowiedź ma wartość null.
Wartości finishReason
Właściwość finishReason może mieć jedną z tych wartości:
TypedFinishReason.STOP: model zakończył generowanie, a dane wyjściowe są zgodne ze schematem.TypedFinishReason.MAX_TOKENS: model zatrzymał się, ponieważ osiągnął limit tokenów. Dane wyjściowe mogą być niekompletne.TypedFinishReason.PARSE_CLASS_ERROR: model zakończył generowanie, ale nie udało się przeanalizować wynikowego kodu JSON do docelowej klasy Kotlin.TypedFinishReason.STRUCTURE_NOT_ANNOTATED: w klasie docelowej lub jej klasach zagnieżdżonych brakuje wymaganej adnotacji@Generable.TypedFinishReason.STRUCTURE_VALUES_INVALID: wygenerowane wartości naruszyły ograniczenia zdefiniowane w adnotacjach@Guide(np. wartość spoza zakresu, rozmiar listy poza zakresem).TypedFinishReason.OTHER: generowanie zostało zatrzymane z innych powodów.
Wyjątki
Interfejs Structured Output API może zgłaszać błąd 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 nie ma żadnych zależności zapętlonych.GenAiException.STRUCTURED_OUTPUT_INVALID_VALUE(-105): wartości wygenerowane przez model są nieprawidłowe lub nie przeszły weryfikacji ograniczeń. Jest to błąd środowiska wykonawczego. Jeśli ten błąd występuje często, rozważ te rozwiązania:- ulepszyć instrukcje w prompcie, aby model ściślej się ich trzymał;
- Złagodzenie ograniczeń (takich jak limity minimalne, maksymalne lub rozmiaru listy) w adnotacjach
@Guide, jeśli są zbyt restrykcyjne dla możliwości modelu. - wdrożenie w aplikacji strategii zastępczej, np. ponowienie żądania lub wyświetlenie stanu domyślnego;
Zliczanie tokenów
Aby sprawdzić, czy ustrukturyzowany prompt mieści się w limicie tokenów wejściowych, oblicz liczbę tokenów za pomocą metody countTokens().
Ponieważ żądania ustrukturyzowanych danych wyjściowych muszą instruować model o strukturze schematu, zliczanie tokenów tylko na podstawie nieprzetworzonego tekstu prompta (za pomocą instancji GenerateContentRequest) nie jest dokładne. Aby uzyskać dokładną liczbę tokenów, musisz przekazać do metody countTokens() pełną instancję GenerateTypedContentRequest, która zawiera konfiguracje klasy docelowej i schematu:
suspend fun <T : Any> countTokens(request: GenerateTypedContentRequest<T>): CountTokensResponse