Gemini Nano からのレスポンスを JSON などの特定の形式に解析して変換し、さらに処理する必要がある場合は、直接モデルアクセスで Structured Output API を使用します。
Structured Output API では、Kotlin のクラスとアノテーションを使用して、ターゲットの出力構造を定義します。直接モデルアクセスは、Kotlin オブジェクトの形式でレスポンスを返します。
構造化された出力の生成は、次のようなタスクに特に役立ちます。
- エンティティ抽出: 非構造化テキストから構造化フィールド(イベント名、日付、場所など)を抽出します。
- 分類: 入力テキストを事前定義されたカテゴリに分類します。
- データ シリアル化: 構造化されていないユーザー入力を、データベース ストレージや API 呼び出しに適した形式に変換します。
前提条件
Structured Output API がデバイスで利用可能かどうかを確認するには、isStructuredOutputFeatureAvailable() API を使用します。この API は、Structured Output API がデバイスで利用可能な場合は true を返し、それ以外の場合は false を返します。
suspend fun isStructuredOutputFeatureAvailable(): Boolean
Structured Output API には、次の要件もあります。
- Android API レベル 26 以降(
minSdk26) - KSP プラグイン バージョン 2.3.6 以降
制限事項
Structured Output API には次の制限があります。
- Kotlin でのみ機能します。
- ProGuard がアノテーション付きクラスの解析を妨げる可能性があります。解析エラーが発生した場合は、アノテーション付きクラスを保持ルールに追加して、ProGuard から除外します。次に例を示します。
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }
プロジェクトの構成
Structured Output API を使い始める手順は次のとおりです。
アプリレベルの
build.gradle.kts(またはbuild.gradle)ファイルにcom.google.mlkit:genai-prompt依存関係を追加します(まだ追加していない場合)。プロジェクト レベルの
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" }アプリレベルの
build.gradle.ktsファイルに、構造化コンパイラの依存関係を追加します。dependencies { ... ksp("com.google.mlkit:genai-schema-compiler:1.0.0-alpha1") }
出力構造を定義する
Kotlin データクラスを使用して、モデルが返すデータの構造を定義します。出力構造を定義するための主なアノテーションは 2 つあります。
@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 |
テキストの場合。 | description、enumValues |
Double / Float |
浮動小数点数の場合。 | description、minimum、maximum |
Int / Long |
整数。 | description、minimum、maximum |
Boolean |
true / false 値の場合。 | description |
List<T> |
サポートされている型またはネストされた @Generable クラスのリスト。
|
description、minItems、maxItems |
List<String> |
String 値のリスト。 |
description、enumValues、minItems、maxItems |
@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
)
// 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}")
}
}
}
終了理由とエラーを処理する
Structured Output API を使用する場合は、API によってスローされる可能性のある例外を処理し、解析されたレスポンスが null の場合は、レスポンス候補の 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