From 55df2e54f88e3cf394d4ae80114b615e49fc6106 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Wed, 28 May 2025 18:13:28 +0100 Subject: [PATCH 1/9] feat(ai): add support for setting a thinking budget --- firebase-ai/CHANGELOG.md | 1 + firebase-ai/api.txt | 19 +++++++ .../firebase/ai/type/GenerationConfig.kt | 14 ++++- .../google/firebase/ai/type/ThinkingConfig.kt | 56 +++++++++++++++++++ .../firebase/ai/type/ThinkingConfigTest.kt | 50 +++++++++++++++++ 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt create mode 100644 firebase-ai/src/test/java/com/google/firebase/ai/type/ThinkingConfigTest.kt diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index 241a23f0f63..9f722e7df9e 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +* [feat] Added support for setting thinking budget for the Gemini models that support it. (#6990) * [fixed] Fixed `FirebaseAI.getInstance` StackOverflowException (#6971) * [fixed] Fixed an issue that was causing the SDK to send empty `FunctionDeclaration` descriptions to the API. diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index c9d55f52295..7905250eb15 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -362,6 +362,7 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.GenerationConfig.Builder setResponseSchema(com.google.firebase.ai.type.Schema? responseSchema); method public com.google.firebase.ai.type.GenerationConfig.Builder setStopSequences(java.util.List? stopSequences); method public com.google.firebase.ai.type.GenerationConfig.Builder setTemperature(Float? temperature); + method public com.google.firebase.ai.type.GenerationConfig.Builder setThinkingConfig(com.google.firebase.ai.type.ThinkingConfig? thinkingConfig); method public com.google.firebase.ai.type.GenerationConfig.Builder setTopK(Integer? topK); method public com.google.firebase.ai.type.GenerationConfig.Builder setTopP(Float? topP); field public Integer? candidateCount; @@ -373,6 +374,7 @@ package com.google.firebase.ai.type { field public com.google.firebase.ai.type.Schema? responseSchema; field public java.util.List? stopSequences; field public Float? temperature; + field public com.google.firebase.ai.type.ThinkingConfig? thinkingConfig; field public Integer? topK; field public Float? topP; } @@ -881,6 +883,23 @@ package com.google.firebase.ai.type { property public final String text; } + public final class ThinkingConfig { + ctor public ThinkingConfig(Integer? thinkingBudget); + method public Integer? getThinkingBudget(); + property public final Integer? thinkingBudget; + } + + public static final class ThinkingConfig.Builder { + ctor public ThinkingConfig.Builder(); + method public com.google.firebase.ai.type.ThinkingConfig build(); + method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(Integer? thinkingBudget); + field public Integer? thinkingBudget; + } + + public final class ThinkingConfigKt { + method public static com.google.firebase.ai.type.ThinkingConfig thinkingConfig(kotlin.jvm.functions.Function1 init); + } + public final class Tool { method public static com.google.firebase.ai.type.Tool functionDeclarations(java.util.List functionDeclarations); field public static final com.google.firebase.ai.type.Tool.Companion Companion; 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 1c2d2680bb1..7bab7fdf806 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 @@ -91,6 +91,7 @@ private constructor( internal val responseMimeType: String?, internal val responseSchema: Schema?, internal val responseModalities: List?, + internal val thinkingConfig: ThinkingConfig?, ) { /** @@ -135,6 +136,7 @@ private constructor( @JvmField public var responseMimeType: String? = null @JvmField public var responseSchema: Schema? = null @JvmField public var responseModalities: List? = null + @JvmField public var thinkingConfig: ThinkingConfig? = null public fun setTemperature(temperature: Float?): Builder = apply { this.temperature = temperature @@ -165,6 +167,9 @@ private constructor( public fun setResponseModalities(responseModalities: List?): Builder = apply { this.responseModalities = responseModalities } + public fun setThinkingConfig(thinkingConfig: ThinkingConfig?): Builder = apply { + this.thinkingConfig = thinkingConfig + } /** Create a new [GenerationConfig] with the attached arguments. */ public fun build(): GenerationConfig = @@ -179,7 +184,8 @@ private constructor( frequencyPenalty = frequencyPenalty, responseMimeType = responseMimeType, responseSchema = responseSchema, - responseModalities = responseModalities + responseModalities = responseModalities, + thinkingConfig = thinkingConfig ) } @@ -195,7 +201,8 @@ private constructor( presencePenalty = presencePenalty, responseMimeType = responseMimeType, responseSchema = responseSchema?.toInternal(), - responseModalities = responseModalities?.map { it.toInternal() } + responseModalities = responseModalities?.map { it.toInternal() }, + thinkingConfig = thinkingConfig?.toInternal() ) @Serializable @@ -210,7 +217,8 @@ private constructor( @SerialName("presence_penalty") val presencePenalty: Float? = null, @SerialName("frequency_penalty") val frequencyPenalty: Float? = null, @SerialName("response_schema") val responseSchema: Schema.Internal? = null, - @SerialName("response_modalities") val responseModalities: List? = null + @SerialName("response_modalities") val responseModalities: List? = null, + @SerialName("thinking_config") val thinkingConfig: ThinkingConfig.Internal? = null ) public companion object { diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt new file mode 100644 index 00000000000..2c01dcc62c4 --- /dev/null +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2025 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 + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** Configuration parameters for thinking features. */ +public class ThinkingConfig(public val thinkingBudget: Int?) { + + public class Builder() { + @JvmField public var thinkingBudget: Int? = null + + /** The number of thoughts tokens that the model should generate. */ + public fun setThinkingBudget(thinkingBudget: Int?): Builder = apply { + this.thinkingBudget = thinkingBudget + } + + public fun build(): ThinkingConfig = ThinkingConfig(thinkingBudget = thinkingBudget) + } + + internal fun toInternal() = Internal(thinkingBudget) + + @Serializable + internal data class Internal(@SerialName("thinking_budget") val thinkingBudget: Int?) +} + +/** + * Helper method to construct a [ThinkingConfig] in a DSL-like manner. + * + * Example Usage: + * ``` + * thinkingConfig { + * thinkingBudget = 0 // disable thinking + * } + * ``` + */ +public fun thinkingConfig(init: ThinkingConfig.Builder.() -> Unit): ThinkingConfig { + val builder = ThinkingConfig.Builder() + builder.init() + return builder.build() +} diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/type/ThinkingConfigTest.kt b/firebase-ai/src/test/java/com/google/firebase/ai/type/ThinkingConfigTest.kt new file mode 100644 index 00000000000..009c039e906 --- /dev/null +++ b/firebase-ai/src/test/java/com/google/firebase/ai/type/ThinkingConfigTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2025 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 + +import io.kotest.assertions.json.shouldEqualJson +import io.kotest.matchers.equals.shouldBeEqual +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.junit.Test + +internal class ThinkingConfigTest { + + @Test + fun `Basic ThinkingConfig`() { + val thinkingConfig = ThinkingConfig.Builder().setThinkingBudget(1024).build() + + val expectedJson = + """ + { + "thinking_budget": 1024 + } + """ + .trimIndent() + + Json.encodeToString(thinkingConfig.toInternal()).shouldEqualJson(expectedJson) + } + + @Test + fun `thinkingConfig DSL correctly delegates to ThinkingConfig#Builder`() { + val thinkingConfig = ThinkingConfig.Builder().setThinkingBudget(1024).build() + + val thinkingConfigDsl = thinkingConfig { thinkingBudget = 1024 } + + thinkingConfig.thinkingBudget?.shouldBeEqual(thinkingConfigDsl.thinkingBudget as Int) + } +} From 80deba114d0a2e9473c8c55c953b327612c912ae Mon Sep 17 00:00:00 2001 From: rosariopf Date: Wed, 28 May 2025 18:34:15 +0100 Subject: [PATCH 2/9] chore(ai): minor version bump --- firebase-ai/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/gradle.properties b/firebase-ai/gradle.properties index b9f800fb7d6..1c7c87996dd 100644 --- a/firebase-ai/gradle.properties +++ b/firebase-ai/gradle.properties @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=16.0.1 +version=16.1.0 latestReleasedVersion=16.0.0 From 67ccb8eb66d15b1f23fd16e20e66841164daa84d Mon Sep 17 00:00:00 2001 From: rosariopf Date: Fri, 27 Jun 2025 14:59:38 +0100 Subject: [PATCH 3/9] feat: add `UsageMetadata#thoughtsTokenCount` --- .../kotlin/com/google/firebase/ai/type/UsageMetadata.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/UsageMetadata.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/UsageMetadata.kt index 1b858a1e6cd..1c7d39103fb 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/UsageMetadata.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/UsageMetadata.kt @@ -28,6 +28,7 @@ import kotlinx.serialization.Serializable * prompt. * @param candidatesTokensDetails The breakdown, by modality, of how many tokens are consumed by the * candidates. + * @param thoughtsTokenCount The number of tokens used by the model's internal "thinking" process. */ public class UsageMetadata( public val promptTokenCount: Int, @@ -35,6 +36,7 @@ public class UsageMetadata( public val totalTokenCount: Int, public val promptTokensDetails: List, public val candidatesTokensDetails: List, + public val thoughtsTokenCount: Int, ) { @Serializable @@ -44,6 +46,7 @@ public class UsageMetadata( val totalTokenCount: Int? = null, val promptTokensDetails: List? = null, val candidatesTokensDetails: List? = null, + val thoughtsTokenCount: Int? = null, ) { internal fun toPublic(): UsageMetadata = @@ -52,7 +55,8 @@ public class UsageMetadata( candidatesTokenCount ?: 0, totalTokenCount ?: 0, promptTokensDetails = promptTokensDetails?.map { it.toPublic() } ?: emptyList(), - candidatesTokensDetails = candidatesTokensDetails?.map { it.toPublic() } ?: emptyList() + candidatesTokensDetails = candidatesTokensDetails?.map { it.toPublic() } ?: emptyList(), + thoughtsTokenCount ?: 0 ) } } From 4fa568a5c57e62e832d8c6cc8a4c1273abb2d57a Mon Sep 17 00:00:00 2001 From: rosariopf Date: Fri, 27 Jun 2025 15:04:19 +0100 Subject: [PATCH 4/9] chore: bump responses version --- firebase-ai/update_responses.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/update_responses.sh b/firebase-ai/update_responses.sh index 7d6ea18e0ee..baf2676dfbb 100755 --- a/firebase-ai/update_responses.sh +++ b/firebase-ai/update_responses.sh @@ -17,7 +17,7 @@ # This script replaces mock response files for Vertex AI unit tests with a fresh # clone of the shared repository of Vertex AI test data. -RESPONSES_VERSION='v13.*' # The major version of mock responses to use +RESPONSES_VERSION='v14.*' # The major version of mock responses to use REPO_NAME="vertexai-sdk-test-data" REPO_LINK="https://github.com/FirebaseExtended/$REPO_NAME.git" From 414d9ce7573d74937ef1490a2252773cbb070cf3 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Fri, 27 Jun 2025 15:23:37 +0100 Subject: [PATCH 5/9] chore: update api.txt --- firebase-ai/api.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index f574fd97496..cb03c3c8a36 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -972,16 +972,18 @@ package com.google.firebase.ai.type { } public final class UsageMetadata { - ctor public UsageMetadata(int promptTokenCount, Integer? candidatesTokenCount, int totalTokenCount, java.util.List promptTokensDetails, java.util.List candidatesTokensDetails); + ctor public UsageMetadata(int promptTokenCount, Integer? candidatesTokenCount, int totalTokenCount, java.util.List promptTokensDetails, java.util.List candidatesTokensDetails, int thoughtsTokenCount); method public Integer? getCandidatesTokenCount(); method public java.util.List getCandidatesTokensDetails(); method public int getPromptTokenCount(); method public java.util.List getPromptTokensDetails(); + method public int getThoughtsTokenCount(); method public int getTotalTokenCount(); property public final Integer? candidatesTokenCount; property public final java.util.List candidatesTokensDetails; property public final int promptTokenCount; property public final java.util.List promptTokensDetails; + property public final int thoughtsTokenCount; property public final int totalTokenCount; } From 346908c25124a092efcf449afa42af112b029a75 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Fri, 27 Jun 2025 15:26:57 +0100 Subject: [PATCH 6/9] chore(ai): 16.2.1 --> 16.3.0 --- firebase-ai/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/gradle.properties b/firebase-ai/gradle.properties index 546c015493e..c0a96853e52 100644 --- a/firebase-ai/gradle.properties +++ b/firebase-ai/gradle.properties @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=16.2.1 +version=16.3.0 latestReleasedVersion=16.2.0 From 468b4876091f8e4104e5e3468ff76119c3e29119 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Fri, 27 Jun 2025 17:12:19 +0100 Subject: [PATCH 7/9] internal constructor; update CHANGELOG --- firebase-ai/CHANGELOG.md | 3 ++- firebase-ai/api.txt | 5 +---- .../google/firebase/ai/type/ThinkingConfig.kt | 16 ++++++++++++---- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index dde5511f8fe..cc5452bbe79 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased -* [feat] Added support for setting thinking budget for the Gemini models that support it. (#6990) +* [feature] Added support for configuring the "thinking" budget when using Gemini + 2.5 series models. (#6990) # 16.2.0 * [changed] Deprecate the `totalBillableCharacters` field (only usable with pre-2.0 models). (#7042) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index cb03c3c8a36..681a56b02f7 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -936,15 +936,12 @@ package com.google.firebase.ai.type { } public final class ThinkingConfig { - ctor public ThinkingConfig(Integer? thinkingBudget); - method public Integer? getThinkingBudget(); - property public final Integer? thinkingBudget; } public static final class ThinkingConfig.Builder { ctor public ThinkingConfig.Builder(); method public com.google.firebase.ai.type.ThinkingConfig build(); - method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(Integer? thinkingBudget); + method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(int thinkingBudget); field public Integer? thinkingBudget; } diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt index 2c01dcc62c4..bbdfed32640 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt @@ -20,13 +20,21 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** Configuration parameters for thinking features. */ -public class ThinkingConfig(public val thinkingBudget: Int?) { +public class ThinkingConfig +private constructor( + internal val thinkingBudget: Int? = null, +) { public class Builder() { - @JvmField public var thinkingBudget: Int? = null + @JvmField + @set:JvmSynthetic // hide void setter from Java + public var thinkingBudget: Int? = null - /** The number of thoughts tokens that the model should generate. */ - public fun setThinkingBudget(thinkingBudget: Int?): Builder = apply { + /** + * Indicates the thinking budget in tokens. 0 is DISABLED. -1 is AUTOMATIC. The default values + * and allowed ranges are model dependent. + */ + public fun setThinkingBudget(thinkingBudget: Int): Builder = apply { this.thinkingBudget = thinkingBudget } From 74706c1b700f43e8766e23c61c8c506235b10847 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Fri, 27 Jun 2025 17:27:22 +0100 Subject: [PATCH 8/9] update api.txt --- firebase-ai/api.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index 681a56b02f7..de15b02eb76 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -942,7 +942,6 @@ package com.google.firebase.ai.type { ctor public ThinkingConfig.Builder(); method public com.google.firebase.ai.type.ThinkingConfig build(); method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(int thinkingBudget); - field public Integer? thinkingBudget; } public final class ThinkingConfigKt { From f780f6b986d27e882ea605c95345931c596cc7b1 Mon Sep 17 00:00:00 2001 From: rosariopf Date: Fri, 27 Jun 2025 17:32:10 +0100 Subject: [PATCH 9/9] major version bump --- firebase-ai/gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/gradle.properties b/firebase-ai/gradle.properties index c0a96853e52..d8c5952f79e 100644 --- a/firebase-ai/gradle.properties +++ b/firebase-ai/gradle.properties @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -version=16.3.0 +version=17.0.0 latestReleasedVersion=16.2.0