From a2bd5e74f27ff7828f9907d0f791e1dddb642cfa Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:17:14 +0000 Subject: [PATCH 1/3] feat(ai): add support for `imageConfig` and `NO_IMAGE` FinishReason --- .../google/firebase/ai/type/AspectRatio.kt | 33 +++++++++ .../com/google/firebase/ai/type/Candidate.kt | 7 +- .../firebase/ai/type/GenerationConfig.kt | 14 +++- .../google/firebase/ai/type/ImageConfig.kt | 74 +++++++++++++++++++ 4 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 firebase-ai/src/main/kotlin/com/google/firebase/ai/type/AspectRatio.kt create mode 100644 firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/AspectRatio.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/AspectRatio.kt new file mode 100644 index 00000000000..5b252bbc6d2 --- /dev/null +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/AspectRatio.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.ai.type + +/** Represents the aspect ratio that the generated image should conform to. */ +public class AspectRatio private constructor(internal val internalVal: String) { + public companion object { + @JvmField public val SQUARE_1x1: AspectRatio = AspectRatio("1:1") + @JvmField public val PORTRAIT_2x3: AspectRatio = AspectRatio("2:3") + @JvmField public val LANDSCAPE_3x2: AspectRatio = AspectRatio("3:2") + @JvmField public val PORTRAIT_3x4: AspectRatio = AspectRatio("3:4") + @JvmField public val LANDSCAPE_4x3: AspectRatio = AspectRatio("4:3") + @JvmField public val PORTRAIT_4x5: AspectRatio = AspectRatio("4:5") + @JvmField public val LANDSCAPE_5x4: AspectRatio = AspectRatio("5:4") + @JvmField public val PORTRAIT_9x16: AspectRatio = AspectRatio("9:16") + @JvmField public val LANDSCAPE_16x9: AspectRatio = AspectRatio("16:9") + @JvmField public val LANDSCAPE_21x9: AspectRatio = AspectRatio("21:9") + } +} diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index b7671aa0ccb..7cbbcdf9cbb 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -237,7 +237,8 @@ public class FinishReason private constructor(public val name: String, public va BLOCKLIST, PROHIBITED_CONTENT, SPII, - MALFORMED_FUNCTION_CALL; + MALFORMED_FUNCTION_CALL, + @SerialName("NO_IMAGE") NO_IMAGE; internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) @@ -252,6 +253,7 @@ public class FinishReason private constructor(public val name: String, public va PROHIBITED_CONTENT -> FinishReason.PROHIBITED_CONTENT SPII -> FinishReason.SPII MALFORMED_FUNCTION_CALL -> FinishReason.MALFORMED_FUNCTION_CALL + NO_IMAGE -> FinishReason.NO_IMAGE else -> FinishReason.UNKNOWN } } @@ -291,6 +293,9 @@ public class FinishReason private constructor(public val name: String, public va /** The function call generated by the model is invalid. */ @JvmField public val MALFORMED_FUNCTION_CALL: FinishReason = FinishReason("MALFORMED_FUNCTION_CALL", 9) + + /** No image was generated. */ + @JvmField public val NO_IMAGE: FinishReason = FinishReason("NO_IMAGE", 10) } } diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt index 7bab7fdf806..77d2752a8b5 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt @@ -92,6 +92,7 @@ private constructor( internal val responseSchema: Schema?, internal val responseModalities: List?, internal val thinkingConfig: ThinkingConfig?, + internal val imageConfig: ImageConfig?, ) { /** @@ -137,6 +138,7 @@ private constructor( @JvmField public var responseSchema: Schema? = null @JvmField public var responseModalities: List? = null @JvmField public var thinkingConfig: ThinkingConfig? = null + @JvmField public var imageConfig: ImageConfig? = null public fun setTemperature(temperature: Float?): Builder = apply { this.temperature = temperature @@ -170,6 +172,9 @@ private constructor( public fun setThinkingConfig(thinkingConfig: ThinkingConfig?): Builder = apply { this.thinkingConfig = thinkingConfig } + public fun setImageConfig(imageConfig: ImageConfig?): Builder = apply { + this.imageConfig = imageConfig + } /** Create a new [GenerationConfig] with the attached arguments. */ public fun build(): GenerationConfig = @@ -185,7 +190,8 @@ private constructor( responseMimeType = responseMimeType, responseSchema = responseSchema, responseModalities = responseModalities, - thinkingConfig = thinkingConfig + thinkingConfig = thinkingConfig, + imageConfig = imageConfig ) } @@ -202,7 +208,8 @@ private constructor( responseMimeType = responseMimeType, responseSchema = responseSchema?.toInternal(), responseModalities = responseModalities?.map { it.toInternal() }, - thinkingConfig = thinkingConfig?.toInternal() + thinkingConfig = thinkingConfig?.toInternal(), + imageConfig = imageConfig?.toInternal() ) @Serializable @@ -218,7 +225,8 @@ private constructor( @SerialName("frequency_penalty") val frequencyPenalty: Float? = null, @SerialName("response_schema") val responseSchema: Schema.Internal? = null, @SerialName("response_modalities") val responseModalities: List? = null, - @SerialName("thinking_config") val thinkingConfig: ThinkingConfig.Internal? = null + @SerialName("thinking_config") val thinkingConfig: ThinkingConfig.Internal? = null, + @SerialName("image_config") val imageConfig: ImageConfig.Internal? = null ) public companion object { diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt new file mode 100644 index 00000000000..b92034eab1b --- /dev/null +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.ai.type + +/** + * Configuration parameters to use for image generation. + * + * @property aspectRatio The aspect ratio of the generated image. + */ +public class ImageConfig internal constructor(internal val aspectRatio: AspectRatio?) { + + /** + * Builder for creating an [ImageConfig]. + * + * Mainly intended for Java interop. Kotlin consumers should use [imageConfig] for a more + * idiomatic experience. + * + * @property aspectRatio See [ImageConfig.aspectRatio]. + * @see [imageConfig] + */ + public class Builder { + @JvmField public var aspectRatio: AspectRatio? = null + + public fun setAspectRatio(aspectRatio: AspectRatio?): Builder = apply { this.aspectRatio = aspectRatio } + + /** Create a new [ImageConfig] with the attached arguments. */ + public fun build(): ImageConfig = ImageConfig(aspectRatio = aspectRatio) + } + + internal fun toInternal() = Internal(aspectRatio = aspectRatio?.internalVal) + + internal data class Internal(val aspectRatio: String?) + + public companion object { + + /** + * Alternative casing for [ImageConfig.Builder]: + * ``` + * val config = ImageConfig.builder() + * ``` + */ + public fun builder(): Builder = Builder() + } +} + +/** + * Helper method to construct an [ImageConfig] in a DSL-like manner. + * + * Example Usage: + * ``` + * imageConfig { + * aspectRatio = AspectRatio.LANDSCAPE_16x9 + * } + * ``` + */ +public fun imageConfig(init: ImageConfig.Builder.() -> Unit): ImageConfig { + val builder = ImageConfig.builder() + builder.init() + return builder.build() +} From 7ce97273601ec1bfbb851bafdfdd32e7f2920017 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 2 Oct 2025 13:51:13 +0100 Subject: [PATCH 2/3] minor tweaks --- firebase-ai/api.txt | 37 +++++++++++++++++++ .../com/google/firebase/ai/type/Candidate.kt | 27 ++++++++++++-- .../google/firebase/ai/type/ImageConfig.kt | 25 +++++-------- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index a390a14147e..e361a25423f 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -165,6 +165,23 @@ package com.google.firebase.ai.type { public final class APINotConfiguredException extends com.google.firebase.ai.type.FirebaseAIException { } + public final class AspectRatio { + field public static final com.google.firebase.ai.type.AspectRatio.Companion Companion; + field public static final com.google.firebase.ai.type.AspectRatio LANDSCAPE_16x9; + field public static final com.google.firebase.ai.type.AspectRatio LANDSCAPE_21x9; + field public static final com.google.firebase.ai.type.AspectRatio LANDSCAPE_3x2; + field public static final com.google.firebase.ai.type.AspectRatio LANDSCAPE_4x3; + field public static final com.google.firebase.ai.type.AspectRatio LANDSCAPE_5x4; + field public static final com.google.firebase.ai.type.AspectRatio PORTRAIT_2x3; + field public static final com.google.firebase.ai.type.AspectRatio PORTRAIT_3x4; + field public static final com.google.firebase.ai.type.AspectRatio PORTRAIT_4x5; + field public static final com.google.firebase.ai.type.AspectRatio PORTRAIT_9x16; + field public static final com.google.firebase.ai.type.AspectRatio SQUARE_1x1; + } + + public static final class AspectRatio.Companion { + } + public final class AudioRecordInitializationFailedException extends com.google.firebase.ai.type.FirebaseAIException { ctor public AudioRecordInitializationFailedException(String message); } @@ -323,8 +340,13 @@ package com.google.firebase.ai.type { property public final int ordinal; field public static final com.google.firebase.ai.type.FinishReason BLOCKLIST; field public static final com.google.firebase.ai.type.FinishReason.Companion Companion; + field public static final com.google.firebase.ai.type.FinishReason IMAGE_OTHER; + field public static final com.google.firebase.ai.type.FinishReason IMAGE_PROHIBITED_CONTENT; + field public static final com.google.firebase.ai.type.FinishReason IMAGE_RECITATION; + field public static final com.google.firebase.ai.type.FinishReason IMAGE_SAFETY; field public static final com.google.firebase.ai.type.FinishReason MALFORMED_FUNCTION_CALL; field public static final com.google.firebase.ai.type.FinishReason MAX_TOKENS; + field public static final com.google.firebase.ai.type.FinishReason NO_IMAGE; field public static final com.google.firebase.ai.type.FinishReason OTHER; field public static final com.google.firebase.ai.type.FinishReason PROHIBITED_CONTENT; field public static final com.google.firebase.ai.type.FinishReason RECITATION; @@ -412,6 +434,7 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.GenerationConfig build(); method public com.google.firebase.ai.type.GenerationConfig.Builder setCandidateCount(Integer? candidateCount); method public com.google.firebase.ai.type.GenerationConfig.Builder setFrequencyPenalty(Float? frequencyPenalty); + method public com.google.firebase.ai.type.GenerationConfig.Builder setImageConfig(com.google.firebase.ai.type.ImageConfig? imageConfig); method public com.google.firebase.ai.type.GenerationConfig.Builder setMaxOutputTokens(Integer? maxOutputTokens); method public com.google.firebase.ai.type.GenerationConfig.Builder setPresencePenalty(Float? presencePenalty); method public com.google.firebase.ai.type.GenerationConfig.Builder setResponseMimeType(String? responseMimeType); @@ -424,6 +447,7 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.GenerationConfig.Builder setTopP(Float? topP); field public Integer? candidateCount; field public Float? frequencyPenalty; + field public com.google.firebase.ai.type.ImageConfig? imageConfig; field public Integer? maxOutputTokens; field public Float? presencePenalty; field public String? responseMimeType; @@ -571,6 +595,19 @@ package com.google.firebase.ai.type { public static final class HarmSeverity.Companion { } + public final class ImageConfig { + } + + public static final class ImageConfig.Builder { + ctor public ImageConfig.Builder(); + method public com.google.firebase.ai.type.ImageConfig build(); + method public com.google.firebase.ai.type.ImageConfig.Builder setAspectRatio(com.google.firebase.ai.type.AspectRatio? aspectRatio); + } + + public final class ImageConfigKt { + method public static com.google.firebase.ai.type.ImageConfig imageConfig(kotlin.jvm.functions.Function1 init); + } + public final class ImagePart implements com.google.firebase.ai.type.Part { ctor public ImagePart(android.graphics.Bitmap image); method public android.graphics.Bitmap getImage(); diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt index 7cbbcdf9cbb..c43c6adc979 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/Candidate.kt @@ -238,7 +238,11 @@ public class FinishReason private constructor(public val name: String, public va PROHIBITED_CONTENT, SPII, MALFORMED_FUNCTION_CALL, - @SerialName("NO_IMAGE") NO_IMAGE; + IMAGE_SAFETY, + IMAGE_PROHIBITED_CONTENT, + IMAGE_RECITATION, + IMAGE_OTHER, + NO_IMAGE; internal object Serializer : KSerializer by FirstOrdinalSerializer(Internal::class) @@ -253,6 +257,10 @@ public class FinishReason private constructor(public val name: String, public va PROHIBITED_CONTENT -> FinishReason.PROHIBITED_CONTENT SPII -> FinishReason.SPII MALFORMED_FUNCTION_CALL -> FinishReason.MALFORMED_FUNCTION_CALL + IMAGE_SAFETY -> FinishReason.IMAGE_SAFETY + IMAGE_PROHIBITED_CONTENT -> FinishReason.IMAGE_PROHIBITED_CONTENT + IMAGE_RECITATION -> FinishReason.IMAGE_RECITATION + IMAGE_OTHER -> FinishReason.IMAGE_OTHER NO_IMAGE -> FinishReason.NO_IMAGE else -> FinishReason.UNKNOWN } @@ -294,8 +302,21 @@ public class FinishReason private constructor(public val name: String, public va @JvmField public val MALFORMED_FUNCTION_CALL: FinishReason = FinishReason("MALFORMED_FUNCTION_CALL", 9) - /** No image was generated. */ - @JvmField public val NO_IMAGE: FinishReason = FinishReason("NO_IMAGE", 10) + /** Token generation stopped because generated images has safety violations. */ + @JvmField public val IMAGE_SAFETY: FinishReason = FinishReason("IMAGE_SAFETY", 10) + + /** Image generation stopped because generated images has other prohibited content. */ + @JvmField + public val IMAGE_PROHIBITED_CONTENT: FinishReason = FinishReason("IMAGE_PROHIBITED_CONTENT", 11) + + /** Image generation stopped due to recitation. */ + @JvmField public val IMAGE_RECITATION: FinishReason = FinishReason("IMAGE_RECITATION", 12) + + /** Image generation stopped because of other miscellaneous issue. */ + @JvmField public val IMAGE_OTHER: FinishReason = FinishReason("IMAGE_OTHER", 13) + + /** The model was expected to generate an image, but none was generated. */ + @JvmField public val NO_IMAGE: FinishReason = FinishReason("NO_IMAGE", 14) } } diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt index b92034eab1b..c6f487688a3 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ImageConfig.kt @@ -16,6 +16,8 @@ package com.google.firebase.ai.type +import kotlinx.serialization.Serializable + /** * Configuration parameters to use for image generation. * @@ -33,9 +35,13 @@ public class ImageConfig internal constructor(internal val aspectRatio: AspectRa * @see [imageConfig] */ public class Builder { - @JvmField public var aspectRatio: AspectRatio? = null + @JvmField + @set:JvmSynthetic // hide void setter from Java + public var aspectRatio: AspectRatio? = null - public fun setAspectRatio(aspectRatio: AspectRatio?): Builder = apply { this.aspectRatio = aspectRatio } + public fun setAspectRatio(aspectRatio: AspectRatio?): Builder = apply { + this.aspectRatio = aspectRatio + } /** Create a new [ImageConfig] with the attached arguments. */ public fun build(): ImageConfig = ImageConfig(aspectRatio = aspectRatio) @@ -43,18 +49,7 @@ public class ImageConfig internal constructor(internal val aspectRatio: AspectRa internal fun toInternal() = Internal(aspectRatio = aspectRatio?.internalVal) - internal data class Internal(val aspectRatio: String?) - - public companion object { - - /** - * Alternative casing for [ImageConfig.Builder]: - * ``` - * val config = ImageConfig.builder() - * ``` - */ - public fun builder(): Builder = Builder() - } + @Serializable internal data class Internal(val aspectRatio: String?) } /** @@ -68,7 +63,7 @@ public class ImageConfig internal constructor(internal val aspectRatio: AspectRa * ``` */ public fun imageConfig(init: ImageConfig.Builder.() -> Unit): ImageConfig { - val builder = ImageConfig.builder() + val builder = ImageConfig.Builder() builder.init() return builder.build() } From a9a39dd901cb84669c62e442d2cf7dbaee916b56 Mon Sep 17 00:00:00 2001 From: Rosario Fernandes Date: Thu, 2 Oct 2025 14:06:01 +0100 Subject: [PATCH 3/3] fix serialization tests --- .../test/java/com/google/firebase/ai/SerializationTests.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt b/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt index 177fde19a8b..df7d7e7de6f 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/SerializationTests.kt @@ -148,7 +148,12 @@ internal class SerializationTests { "BLOCKLIST", "PROHIBITED_CONTENT", "SPII", - "MALFORMED_FUNCTION_CALL" + "MALFORMED_FUNCTION_CALL", + "IMAGE_SAFETY", + "IMAGE_PROHIBITED_CONTENT", + "IMAGE_RECITATION", + "IMAGE_OTHER", + "NO_IMAGE" ] }, "safetyRatings": {