추가 처리를 위해 Prompt API의 응답을 JSON과 같은 특정 형식으로 파싱해야 하는 경우 구조화된 출력 API를 사용하세요.
구조화된 출력 API를 사용하면 Kotlin 클래스와 주석을 사용하여 대상 출력 구조를 정의할 수 있습니다. 그러면 Prompt API가 Kotlin 객체 형식으로 응답을 반환합니다.
구조화된 출력 생성은 다음과 같은 작업에 특히 유용합니다.
- 항목 추출: 구조화되지 않은 텍스트에서 구조화된 필드 (예: 이벤트 이름, 날짜, 위치)를 추출합니다.
- 분류: 입력 텍스트를 사전 정의된 카테고리로 분류합니다.
- 데이터 직렬화: 구조화되지 않은 사용자 입력을 데이터베이스 스토리지 또는 API 호출에 적합한 형식으로 변환합니다.
기본 요건
기기에서 구조화된 출력 API를 사용할 수 있는지 확인하려면 isStructuredOutputFeatureAvailable() API를 사용하세요. API는 기기에서 구조화된 출력 API를 사용할 수 있는 경우 true를 반환하고, 그렇지 않은 경우 false를 반환합니다.
suspend fun isStructuredOutputFeatureAvailable(): Boolean
구조화된 출력 API에는 다음과 같은 요구사항도 있습니다.
- Android API 수준 26 이상 (
minSdk26) - KSP 플러그인 버전 2.3.6 이상
제한사항
구조화된 출력 API에는 다음과 같은 제한사항이 있습니다.
- Kotlin에서만 작동합니다.
- ProGuard가 주석 처리된 클래스의 파싱을 방해할 수 있습니다. 예를 들어 파싱 오류가 발생하는 경우 주석 처리된 클래스를 keep rules에 추가하여 ProGuard에서 제외하세요.
# Keep classes used by structured output for deserialization for release builds.
-keep class com.google.mlkit.genai.demo.kotlin.Plant { *; }
프로젝트 구성
구조화된 출력 API를 시작하려면 다음 단계를 따르세요.
ML Kit Prompt API를 종속 항목으로 추가합니다. 아직 추가하지 않은 경우 앱 수준
build.gradle.kts(또는build.gradle) 파일에 추가합니다.프로젝트 수준
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 데이터 클래스를 사용하여 모델이 반환할 데이터의 구조를 정의합니다. 출력 구조를 정의하는 데는 두 가지 주요 주석이 있습니다.
@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
)
지원되는 유형 및 제약조건
다음 유형은 각각의 @Guide 제약조건과 함께 @Generable 주석 처리된 클래스 내에서 지원됩니다.
| 유형 | 설명 | 지원되는 @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,
// 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에서 발생할 수 있는 예외를 처리하고 파싱된 응답이 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: 다른 이유로 생성이 중지되었습니다.
예외
구조화된 출력 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