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/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 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) + } +}