Skip to content

Commit 939ffca

Browse files
thatfiredevrlazo
andauthored
feat(ai): add support for setting a thinking budget (#6999)
This should resolve #6990 --------- Co-authored-by: Rodrigo Lazo Paz <[email protected]>
1 parent 731f727 commit 939ffca

File tree

7 files changed

+151
-8
lines changed

7 files changed

+151
-8
lines changed

firebase-ai/CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Unreleased
2+
* [feature] Added support for configuring the "thinking" budget when using Gemini
3+
2.5 series models. (#6990)
24
* [feature] **Breaking Change**: Add support for Grounding with Google Search (#7042).
35
* **Action Required:** Update all references of `groundingAttributions`, `webSearchQueries`, `retrievalQueries` in `GroundingMetadata` to be non-optional.
46

5-
67
# 16.2.0
78
* [changed] Deprecate the `totalBillableCharacters` field (only usable with pre-2.0 models). (#7042)
89
* [feature] Added support for extra schema properties like `title`, `minItems`, `maxItems`, `minimum`
@@ -33,4 +34,3 @@
3334

3435
Note: This feature is in Public Preview, which means that it is not subject to any SLA or
3536
deprecation policy and could change in backwards-incompatible ways.
36-

firebase-ai/api.txt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ package com.google.firebase.ai.type {
364364
method public com.google.firebase.ai.type.GenerationConfig.Builder setResponseSchema(com.google.firebase.ai.type.Schema? responseSchema);
365365
method public com.google.firebase.ai.type.GenerationConfig.Builder setStopSequences(java.util.List<java.lang.String>? stopSequences);
366366
method public com.google.firebase.ai.type.GenerationConfig.Builder setTemperature(Float? temperature);
367+
method public com.google.firebase.ai.type.GenerationConfig.Builder setThinkingConfig(com.google.firebase.ai.type.ThinkingConfig? thinkingConfig);
367368
method public com.google.firebase.ai.type.GenerationConfig.Builder setTopK(Integer? topK);
368369
method public com.google.firebase.ai.type.GenerationConfig.Builder setTopP(Float? topP);
369370
field public Integer? candidateCount;
@@ -375,6 +376,7 @@ package com.google.firebase.ai.type {
375376
field public com.google.firebase.ai.type.Schema? responseSchema;
376377
field public java.util.List<java.lang.String>? stopSequences;
377378
field public Float? temperature;
379+
field public com.google.firebase.ai.type.ThinkingConfig? thinkingConfig;
378380
field public Integer? topK;
379381
field public Float? topP;
380382
}
@@ -997,6 +999,19 @@ package com.google.firebase.ai.type {
997999
property public final String text;
9981000
}
9991001

1002+
public final class ThinkingConfig {
1003+
}
1004+
1005+
public static final class ThinkingConfig.Builder {
1006+
ctor public ThinkingConfig.Builder();
1007+
method public com.google.firebase.ai.type.ThinkingConfig build();
1008+
method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(int thinkingBudget);
1009+
}
1010+
1011+
public final class ThinkingConfigKt {
1012+
method public static com.google.firebase.ai.type.ThinkingConfig thinkingConfig(kotlin.jvm.functions.Function1<? super com.google.firebase.ai.type.ThinkingConfig.Builder,kotlin.Unit> init);
1013+
}
1014+
10001015
public final class Tool {
10011016
method public static com.google.firebase.ai.type.Tool functionDeclarations(java.util.List<com.google.firebase.ai.type.FunctionDeclaration> functionDeclarations);
10021017
method public static com.google.firebase.ai.type.Tool googleSearch(com.google.firebase.ai.type.GoogleSearch googleSearch = com.google.firebase.ai.type.GoogleSearch());
@@ -1019,16 +1034,18 @@ package com.google.firebase.ai.type {
10191034
}
10201035

10211036
public final class UsageMetadata {
1022-
ctor public UsageMetadata(int promptTokenCount, Integer? candidatesTokenCount, int totalTokenCount, java.util.List<com.google.firebase.ai.type.ModalityTokenCount> promptTokensDetails, java.util.List<com.google.firebase.ai.type.ModalityTokenCount> candidatesTokensDetails);
1037+
ctor public UsageMetadata(int promptTokenCount, Integer? candidatesTokenCount, int totalTokenCount, java.util.List<com.google.firebase.ai.type.ModalityTokenCount> promptTokensDetails, java.util.List<com.google.firebase.ai.type.ModalityTokenCount> candidatesTokensDetails, int thoughtsTokenCount);
10231038
method public Integer? getCandidatesTokenCount();
10241039
method public java.util.List<com.google.firebase.ai.type.ModalityTokenCount> getCandidatesTokensDetails();
10251040
method public int getPromptTokenCount();
10261041
method public java.util.List<com.google.firebase.ai.type.ModalityTokenCount> getPromptTokensDetails();
1042+
method public int getThoughtsTokenCount();
10271043
method public int getTotalTokenCount();
10281044
property public final Integer? candidatesTokenCount;
10291045
property public final java.util.List<com.google.firebase.ai.type.ModalityTokenCount> candidatesTokensDetails;
10301046
property public final int promptTokenCount;
10311047
property public final java.util.List<com.google.firebase.ai.type.ModalityTokenCount> promptTokensDetails;
1048+
property public final int thoughtsTokenCount;
10321049
property public final int totalTokenCount;
10331050
}
10341051

firebase-ai/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
version=16.3.0
15+
version=17.0.0
1616
latestReleasedVersion=16.2.0

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/GenerationConfig.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ private constructor(
9191
internal val responseMimeType: String?,
9292
internal val responseSchema: Schema?,
9393
internal val responseModalities: List<ResponseModality>?,
94+
internal val thinkingConfig: ThinkingConfig?,
9495
) {
9596

9697
/**
@@ -135,6 +136,7 @@ private constructor(
135136
@JvmField public var responseMimeType: String? = null
136137
@JvmField public var responseSchema: Schema? = null
137138
@JvmField public var responseModalities: List<ResponseModality>? = null
139+
@JvmField public var thinkingConfig: ThinkingConfig? = null
138140

139141
public fun setTemperature(temperature: Float?): Builder = apply {
140142
this.temperature = temperature
@@ -165,6 +167,9 @@ private constructor(
165167
public fun setResponseModalities(responseModalities: List<ResponseModality>?): Builder = apply {
166168
this.responseModalities = responseModalities
167169
}
170+
public fun setThinkingConfig(thinkingConfig: ThinkingConfig?): Builder = apply {
171+
this.thinkingConfig = thinkingConfig
172+
}
168173

169174
/** Create a new [GenerationConfig] with the attached arguments. */
170175
public fun build(): GenerationConfig =
@@ -179,7 +184,8 @@ private constructor(
179184
frequencyPenalty = frequencyPenalty,
180185
responseMimeType = responseMimeType,
181186
responseSchema = responseSchema,
182-
responseModalities = responseModalities
187+
responseModalities = responseModalities,
188+
thinkingConfig = thinkingConfig
183189
)
184190
}
185191

@@ -195,7 +201,8 @@ private constructor(
195201
presencePenalty = presencePenalty,
196202
responseMimeType = responseMimeType,
197203
responseSchema = responseSchema?.toInternal(),
198-
responseModalities = responseModalities?.map { it.toInternal() }
204+
responseModalities = responseModalities?.map { it.toInternal() },
205+
thinkingConfig = thinkingConfig?.toInternal()
199206
)
200207

201208
@Serializable
@@ -210,7 +217,8 @@ private constructor(
210217
@SerialName("presence_penalty") val presencePenalty: Float? = null,
211218
@SerialName("frequency_penalty") val frequencyPenalty: Float? = null,
212219
@SerialName("response_schema") val responseSchema: Schema.Internal? = null,
213-
@SerialName("response_modalities") val responseModalities: List<String>? = null
220+
@SerialName("response_modalities") val responseModalities: List<String>? = null,
221+
@SerialName("thinking_config") val thinkingConfig: ThinkingConfig.Internal? = null
214222
)
215223

216224
public companion object {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.ai.type
18+
19+
import kotlinx.serialization.SerialName
20+
import kotlinx.serialization.Serializable
21+
22+
/** Configuration parameters for thinking features. */
23+
public class ThinkingConfig
24+
private constructor(
25+
internal val thinkingBudget: Int? = null,
26+
) {
27+
28+
public class Builder() {
29+
@JvmField
30+
@set:JvmSynthetic // hide void setter from Java
31+
public var thinkingBudget: Int? = null
32+
33+
/**
34+
* Indicates the thinking budget in tokens. 0 is DISABLED. -1 is AUTOMATIC. The default values
35+
* and allowed ranges are model dependent.
36+
*/
37+
public fun setThinkingBudget(thinkingBudget: Int): Builder = apply {
38+
this.thinkingBudget = thinkingBudget
39+
}
40+
41+
public fun build(): ThinkingConfig = ThinkingConfig(thinkingBudget = thinkingBudget)
42+
}
43+
44+
internal fun toInternal() = Internal(thinkingBudget)
45+
46+
@Serializable
47+
internal data class Internal(@SerialName("thinking_budget") val thinkingBudget: Int?)
48+
}
49+
50+
/**
51+
* Helper method to construct a [ThinkingConfig] in a DSL-like manner.
52+
*
53+
* Example Usage:
54+
* ```
55+
* thinkingConfig {
56+
* thinkingBudget = 0 // disable thinking
57+
* }
58+
* ```
59+
*/
60+
public fun thinkingConfig(init: ThinkingConfig.Builder.() -> Unit): ThinkingConfig {
61+
val builder = ThinkingConfig.Builder()
62+
builder.init()
63+
return builder.build()
64+
}

firebase-ai/src/main/kotlin/com/google/firebase/ai/type/UsageMetadata.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ import kotlinx.serialization.Serializable
2828
* prompt.
2929
* @param candidatesTokensDetails The breakdown, by modality, of how many tokens are consumed by the
3030
* candidates.
31+
* @param thoughtsTokenCount The number of tokens used by the model's internal "thinking" process.
3132
*/
3233
public class UsageMetadata(
3334
public val promptTokenCount: Int,
3435
public val candidatesTokenCount: Int?,
3536
public val totalTokenCount: Int,
3637
public val promptTokensDetails: List<ModalityTokenCount>,
3738
public val candidatesTokensDetails: List<ModalityTokenCount>,
39+
public val thoughtsTokenCount: Int,
3840
) {
3941

4042
@Serializable
@@ -44,6 +46,7 @@ public class UsageMetadata(
4446
val totalTokenCount: Int? = null,
4547
val promptTokensDetails: List<ModalityTokenCount.Internal>? = null,
4648
val candidatesTokensDetails: List<ModalityTokenCount.Internal>? = null,
49+
val thoughtsTokenCount: Int? = null,
4750
) {
4851

4952
internal fun toPublic(): UsageMetadata =
@@ -52,7 +55,8 @@ public class UsageMetadata(
5255
candidatesTokenCount ?: 0,
5356
totalTokenCount ?: 0,
5457
promptTokensDetails = promptTokensDetails?.map { it.toPublic() } ?: emptyList(),
55-
candidatesTokensDetails = candidatesTokensDetails?.map { it.toPublic() } ?: emptyList()
58+
candidatesTokensDetails = candidatesTokensDetails?.map { it.toPublic() } ?: emptyList(),
59+
thoughtsTokenCount ?: 0
5660
)
5761
}
5862
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.ai.type
18+
19+
import io.kotest.assertions.json.shouldEqualJson
20+
import io.kotest.matchers.equals.shouldBeEqual
21+
import kotlinx.serialization.encodeToString
22+
import kotlinx.serialization.json.Json
23+
import org.junit.Test
24+
25+
internal class ThinkingConfigTest {
26+
27+
@Test
28+
fun `Basic ThinkingConfig`() {
29+
val thinkingConfig = ThinkingConfig.Builder().setThinkingBudget(1024).build()
30+
31+
val expectedJson =
32+
"""
33+
{
34+
"thinking_budget": 1024
35+
}
36+
"""
37+
.trimIndent()
38+
39+
Json.encodeToString(thinkingConfig.toInternal()).shouldEqualJson(expectedJson)
40+
}
41+
42+
@Test
43+
fun `thinkingConfig DSL correctly delegates to ThinkingConfig#Builder`() {
44+
val thinkingConfig = ThinkingConfig.Builder().setThinkingBudget(1024).build()
45+
46+
val thinkingConfigDsl = thinkingConfig { thinkingBudget = 1024 }
47+
48+
thinkingConfig.thinkingBudget?.shouldBeEqual(thinkingConfigDsl.thinkingBudget as Int)
49+
}
50+
}

0 commit comments

Comments
 (0)