如果您需要将 Gemini Nano 的回答解析为特定格式(例如 JSON)以进行进一步处理,请使用 Structured Output API 直接访问模型。
借助 Structured Output API,您可以使用 Kotlin 类和注解来定义目标输出结构。然后,直接模型访问会以 Kotlin 对象的形式返回响应。
生成结构化输出对于以下任务特别有用:
- 实体提取:从非结构化文本中提取结构化字段(例如事件名称、日期、地点)。
- 分类:将输入文本归入预定义的类别。
- 数据序列化:将非结构化用户输入转换为适合数据库存储或 API 调用的格式。
前提条件
如需验证设备上是否可以使用 Structured Output API,请使用 isStructuredOutputFeatureAvailable() API。如果设备上提供了 Structured Output API,该 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依赖项(如果您尚未添加)。将 KSP 插件添加到项目级
build.gradle.kts文件中。使用与您的 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
)
支持的类型和限制
@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注解中的限制(例如最小值、最大值或列表大小限制)对模型功能限制过严,请放宽这些限制。 - 在应用中实现后备策略,例如重试请求或显示默认状态。
统计 token 数量
如需检查结构化提示是否在输入 token 限制范围内,请使用 countTokens() 方法计算 token 数量。
由于结构化输出请求需要向模型指示架构结构,因此仅对原始提示文本(使用 GenerateContentRequest 实例)统计 token 并不准确。如需获取准确的 token 数量,您必须将完整的 GenerateTypedContentRequest 实例(包括目标类和架构配置)传递给 countTokens() 方法:
suspend fun <T : Any> countTokens(request: GenerateTypedContentRequest<T>): CountTokensResponse