From ac7294be2144eaf10dadc500ebd4a5371b315442 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Thu, 11 Dec 2025 00:08:49 -0500 Subject: [PATCH 01/11] [AI] Add support for Thinking Levels Gemini 2.5 series models and newer utilize a thinking process before generating a response. Thinking levels are an alternative to thinking budgets as they are preset values that Gemini 3 models can use to limit how many tokens are dedicated to the thinking process. The [thinking documentation](https://firebase.google.com/docs/ai-logic/thinking) gives more details. --- .../google/firebase/ai/type/ThinkingConfig.kt | 39 +++++++++++++--- .../google/firebase/ai/type/ThinkingLevel.kt | 44 +++++++++++++++++++ 2 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt 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 b220de57e86..6d2680a0411 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 @@ -19,11 +19,17 @@ package com.google.firebase.ai.type import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -/** Configuration parameters for thinking features. */ +/** + * Gemini 2.5 series models and newer utilize a thinking process before generating a response. This + * allows them to reason through complex problems and plan a more coherent and accurate answer. See + * the [thinking documentation](https://firebase.google.com/docs/ai-logic/thinking) for more + * details. + */ public class ThinkingConfig private constructor( internal val thinkingBudget: Int? = null, - internal val includeThoughts: Boolean? = null + internal val includeThoughts: Boolean? = null, + internal val thinkingLevel: ThinkingLevel? = null ) { public class Builder() { @@ -35,11 +41,19 @@ private constructor( @set:JvmSynthetic // hide void setter from Java public var includeThoughts: Boolean? = null + @JvmField + @set:JvmSynthetic // hide void setter from Java + public var thinkingLevel: ThinkingLevel? = null + /** - * Indicates the thinking budget in tokens. `0` is disabled. `-1` is dynamic. The default values - * and allowed ranges are model dependent. + * Indicates the thinking budget in tokens. + * + * Use `0` for disabled, and `-1` for dynamic. The range of + * [supported thinking budget values](https://firebase.google.com/docs/ai-logic/thinking#supported-thinking-budget-values) + * depends on the model. */ public fun setThinkingBudget(thinkingBudget: Int): Builder = apply { + assert(thinkingLevel == null) { "Cannot set both `thinkingBudget` and `thinkingLevel`" } this.thinkingBudget = thinkingBudget } @@ -55,16 +69,27 @@ private constructor( this.includeThoughts = includeThoughts } + /** Indicates the thinking budget based in Levels. */ + public fun setThinkingLevel(thinkingLevel: ThinkingLevel): Builder = apply { + assert(thinkingBudget == null) { "Cannot set both `thinkingBudget` and `thinkingLevel`" } + this.thinkingLevel = thinkingLevel + } + public fun build(): ThinkingConfig = - ThinkingConfig(thinkingBudget = thinkingBudget, includeThoughts = includeThoughts) + ThinkingConfig( + thinkingBudget = thinkingBudget, + includeThoughts = includeThoughts, + thinkingLevel = thinkingLevel + ) } - internal fun toInternal() = Internal(thinkingBudget, includeThoughts) + internal fun toInternal() = Internal(thinkingBudget, includeThoughts, thinkingLevel?.toInternal()) @Serializable internal data class Internal( @SerialName("thinking_budget") val thinkingBudget: Int? = null, - val includeThoughts: Boolean? = null + val includeThoughts: Boolean? = null, + @SerialName("thinking_level") val thinkingLevel: ThinkingLevel.Internal? = null, ) } diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt new file mode 100644 index 00000000000..b745df90eab --- /dev/null +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt @@ -0,0 +1,44 @@ +/* + * 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 + +/** Specifies the quality of the thinking response. */ +public class ThinkingLevel private constructor(public val ordinal: Int) { + internal fun toInternal() = + when (this) { + LOW -> Internal.LOW + HIGH -> Internal.HIGH + else -> throw makeMissingCaseException("ThinkingLevel", ordinal) + } + + @Serializable + internal enum class Internal { + @SerialName("THINKING_LEVEL_UNSPECIFIED") UNSPECIFIED, + LOW, + HIGH, + } + public companion object { + /** A lower quality thinking response, which provides low latency. */ + @JvmField public val LOW: ThinkingLevel = ThinkingLevel(0) + + /** A higher quality thinking response, which may increase latency. */ + @JvmField public val HIGH: ThinkingLevel = ThinkingLevel(1) + } +} From 86cdb5c818e6f87fce79640799b7d27a9ddf2046 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Thu, 11 Dec 2025 00:15:46 -0500 Subject: [PATCH 02/11] Add changelog entry --- firebase-ai/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index 70e0cf2cf53..4e84bd09e1f 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [feature] Added support for configuring thinking levels with Gemini 3 series + mdels and onwards. - [changed] Added `LiveAudioConversationConfig` to control different aspects of the conversation while using the `startAudioConversation` function. - [fixed] Fixed an issue causing streaming chat interactions to drop thought signatures. (#7562) From 0fa258bee35c91faa868e30fbaa6bb01250d526f Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Thu, 11 Dec 2025 00:18:54 -0500 Subject: [PATCH 03/11] Add PR number to changelog entry --- firebase-ai/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firebase-ai/CHANGELOG.md b/firebase-ai/CHANGELOG.md index 4e84bd09e1f..389f4fadbe1 100644 --- a/firebase-ai/CHANGELOG.md +++ b/firebase-ai/CHANGELOG.md @@ -1,7 +1,7 @@ # Unreleased - [feature] Added support for configuring thinking levels with Gemini 3 series - mdels and onwards. + mdels and onwards. (#7599) - [changed] Added `LiveAudioConversationConfig` to control different aspects of the conversation while using the `startAudioConversation` function. - [fixed] Fixed an issue causing streaming chat interactions to drop thought signatures. (#7562) From 6f3645c3ef2db1d5d0582a32584a2cd7a38b83fc Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Thu, 11 Dec 2025 00:34:00 -0500 Subject: [PATCH 04/11] Throw exception instead of using assert --- .../kotlin/com/google/firebase/ai/type/ThinkingConfig.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 6d2680a0411..2c21948cbb9 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 @@ -53,7 +53,8 @@ private constructor( * depends on the model. */ public fun setThinkingBudget(thinkingBudget: Int): Builder = apply { - assert(thinkingLevel == null) { "Cannot set both `thinkingBudget` and `thinkingLevel`" } + if (thinkingLevel != null) + throw IllegalArgumentException("Cannot set both `thinkingBudget` and `thinkingLevel`") this.thinkingBudget = thinkingBudget } @@ -71,7 +72,8 @@ private constructor( /** Indicates the thinking budget based in Levels. */ public fun setThinkingLevel(thinkingLevel: ThinkingLevel): Builder = apply { - assert(thinkingBudget == null) { "Cannot set both `thinkingBudget` and `thinkingLevel`" } + if (thinkingBudget != null) + throw IllegalArgumentException("Cannot set both `thinkingBudget` and `thinkingLevel`") this.thinkingLevel = thinkingLevel } From 05216ef56d13f5c7160fa378cb389b08e09deffd Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Thu, 11 Dec 2025 09:45:58 -0500 Subject: [PATCH 05/11] Update api.txt --- firebase-ai/api.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index 357fb1c2a8b..0ce07c6b221 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -1289,12 +1289,24 @@ package com.google.firebase.ai.type { method public com.google.firebase.ai.type.ThinkingConfig build(); method public com.google.firebase.ai.type.ThinkingConfig.Builder setIncludeThoughts(boolean includeThoughts); method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingBudget(int thinkingBudget); + method public com.google.firebase.ai.type.ThinkingConfig.Builder setThinkingLevel(com.google.firebase.ai.type.ThinkingLevel thinkingLevel); } public final class ThinkingConfigKt { method public static com.google.firebase.ai.type.ThinkingConfig thinkingConfig(kotlin.jvm.functions.Function1 init); } + public final class ThinkingLevel { + method public int getOrdinal(); + property public final int ordinal; + field public static final com.google.firebase.ai.type.ThinkingLevel.Companion Companion; + field public static final com.google.firebase.ai.type.ThinkingLevel HIGH; + field public static final com.google.firebase.ai.type.ThinkingLevel LOW; + } + + public static final class ThinkingLevel.Companion { + } + public final class Tool { method public static com.google.firebase.ai.type.Tool codeExecution(); method public static com.google.firebase.ai.type.Tool functionDeclarations(java.util.List functionDeclarations); From 8cc20dc9dfe4cd05924f9f996a47e8c9a47fa93f Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Mon, 15 Dec 2025 10:39:27 -0500 Subject: [PATCH 06/11] Update error message and add new levels. --- .../google/firebase/ai/type/ThinkingConfig.kt | 20 +++++++++++-------- .../google/firebase/ai/type/ThinkingLevel.kt | 13 ++++++++++-- 2 files changed, 23 insertions(+), 10 deletions(-) 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 2c21948cbb9..932dd8d79eb 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 @@ -54,10 +54,21 @@ private constructor( */ public fun setThinkingBudget(thinkingBudget: Int): Builder = apply { if (thinkingLevel != null) - throw IllegalArgumentException("Cannot set both `thinkingBudget` and `thinkingLevel`") + throw IllegalArgumentException( + "`thinkingLevel` already set. Cannot set both `thinkingBudget` and `thinkingLevel`" + ) this.thinkingBudget = thinkingBudget } + /** Indicates the thinking budget based in Levels. */ + public fun setThinkingLevel(thinkingLevel: ThinkingLevel): Builder = apply { + if (thinkingBudget != null) + throw IllegalArgumentException( + "`thinkingBudget` already set. Cannot set both `thinkingBudget` and `thinkingLevel`" + ) + this.thinkingLevel = thinkingLevel + } + /** * Indicates whether to request the model to include the thoughts parts in the response. * @@ -70,13 +81,6 @@ private constructor( this.includeThoughts = includeThoughts } - /** Indicates the thinking budget based in Levels. */ - public fun setThinkingLevel(thinkingLevel: ThinkingLevel): Builder = apply { - if (thinkingBudget != null) - throw IllegalArgumentException("Cannot set both `thinkingBudget` and `thinkingLevel`") - this.thinkingLevel = thinkingLevel - } - public fun build(): ThinkingConfig = ThinkingConfig( thinkingBudget = thinkingBudget, diff --git a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt index b745df90eab..b124ca57296 100644 --- a/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt +++ b/firebase-ai/src/main/kotlin/com/google/firebase/ai/type/ThinkingLevel.kt @@ -23,7 +23,9 @@ import kotlinx.serialization.Serializable public class ThinkingLevel private constructor(public val ordinal: Int) { internal fun toInternal() = when (this) { + MINIMAL -> Internal.MINIMAL LOW -> Internal.LOW + MEDIUM -> Internal.MEDIUM HIGH -> Internal.HIGH else -> throw makeMissingCaseException("ThinkingLevel", ordinal) } @@ -31,14 +33,21 @@ public class ThinkingLevel private constructor(public val ordinal: Int) { @Serializable internal enum class Internal { @SerialName("THINKING_LEVEL_UNSPECIFIED") UNSPECIFIED, + MINIMAL, LOW, + MEDIUM, HIGH, } public companion object { + /** A minimal quality thinking response, which provides the lowest latency. */ + @JvmField public val MINIMAL: ThinkingLevel = ThinkingLevel(0) /** A lower quality thinking response, which provides low latency. */ - @JvmField public val LOW: ThinkingLevel = ThinkingLevel(0) + @JvmField public val LOW: ThinkingLevel = ThinkingLevel(1) + + /** A medium quality thinking response. */ + @JvmField public val MEDIUM: ThinkingLevel = ThinkingLevel(2) /** A higher quality thinking response, which may increase latency. */ - @JvmField public val HIGH: ThinkingLevel = ThinkingLevel(1) + @JvmField public val HIGH: ThinkingLevel = ThinkingLevel(3) } } From 84830d433df8eeeb2ef27c2d6c6f299bf29f051d Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Mon, 15 Dec 2025 10:40:06 -0500 Subject: [PATCH 07/11] Update api.txt --- firebase-ai/api.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/firebase-ai/api.txt b/firebase-ai/api.txt index 0ce07c6b221..c77d286500d 100644 --- a/firebase-ai/api.txt +++ b/firebase-ai/api.txt @@ -1302,6 +1302,8 @@ package com.google.firebase.ai.type { field public static final com.google.firebase.ai.type.ThinkingLevel.Companion Companion; field public static final com.google.firebase.ai.type.ThinkingLevel HIGH; field public static final com.google.firebase.ai.type.ThinkingLevel LOW; + field public static final com.google.firebase.ai.type.ThinkingLevel MEDIUM; + field public static final com.google.firebase.ai.type.ThinkingLevel MINIMAL; } public static final class ThinkingLevel.Companion { From 7eff3c049aaedbe8790a53c839d582176f5fedd8 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Date: Wed, 17 Dec 2025 18:02:29 -0500 Subject: [PATCH 08/11] Bump version correctly --- 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 215d4a50f32..2181d491391 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=17.7.1 +version=17.8.0 latestReleasedVersion=17.7.0 From 1f60f000468e3e166275a0e3487ab9d071d0e2e0 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Paz Date: Mon, 22 Dec 2025 16:40:17 -0500 Subject: [PATCH 09/11] Move check into `build()` method We couldn't keep the implementation where it was originally placed due to java compatibility issues. Custom setters, necessary to enable checks in place when using the build pattern, are not compatible with `@JvmField`s Also, added tests. --- .../google/firebase/ai/type/ThinkingConfig.kt | 17 +++-- .../firebase/ai/GenerativeModelTesting.kt | 62 +++++++++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) 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 932dd8d79eb..2b9a57d2f97 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 @@ -53,19 +53,11 @@ private constructor( * depends on the model. */ public fun setThinkingBudget(thinkingBudget: Int): Builder = apply { - if (thinkingLevel != null) - throw IllegalArgumentException( - "`thinkingLevel` already set. Cannot set both `thinkingBudget` and `thinkingLevel`" - ) this.thinkingBudget = thinkingBudget } /** Indicates the thinking budget based in Levels. */ public fun setThinkingLevel(thinkingLevel: ThinkingLevel): Builder = apply { - if (thinkingBudget != null) - throw IllegalArgumentException( - "`thinkingBudget` already set. Cannot set both `thinkingBudget` and `thinkingLevel`" - ) this.thinkingLevel = thinkingLevel } @@ -81,12 +73,17 @@ private constructor( this.includeThoughts = includeThoughts } - public fun build(): ThinkingConfig = - ThinkingConfig( + public fun build(): ThinkingConfig { + if (thinkingBudget != null && thinkingLevel != null) + throw IllegalArgumentException( + "`thinkingBudget` already set. Cannot set both `thinkingBudget` and `thinkingLevel`" + ) + return ThinkingConfig( thinkingBudget = thinkingBudget, includeThoughts = includeThoughts, thinkingLevel = thinkingLevel ) + } } internal fun toInternal() = Internal(thinkingBudget, includeThoughts, thinkingLevel?.toInternal()) diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt b/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt index 582b722376a..2868baf2435 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt @@ -33,7 +33,10 @@ import com.google.firebase.ai.type.RequestOptions import com.google.firebase.ai.type.SafetySetting import com.google.firebase.ai.type.ServerException import com.google.firebase.ai.type.TextPart +import com.google.firebase.ai.type.ThinkingLevel import com.google.firebase.ai.type.content +import com.google.firebase.ai.type.generationConfig +import com.google.firebase.ai.type.thinkingConfig import io.kotest.assertions.json.shouldContainJsonKey import io.kotest.assertions.json.shouldContainJsonKeyValue import io.kotest.assertions.throwables.shouldThrow @@ -249,4 +252,63 @@ internal class GenerativeModelTesting { ) ) } + + @Test + fun `thinkingLevel and thinkingBudget are mutually exclusive`() = doBlocking { + val exception = + shouldThrow { + thinkingConfig { + thinkingLevel = ThinkingLevel.MEDIUM + thinkingBudget = 1 + } + } + exception.message shouldContain "Cannot set both" + } + + @Test + fun `correctly setting thinkingLevel in request`() = doBlocking { + val mockEngine = MockEngine { + respond( + generateContentResponseAsJsonString("text response"), + HttpStatusCode.OK, + headersOf(HttpHeaders.ContentType, "application/json") + ) + } + + val apiController = + APIController( + "super_cool_test_key", + "gemini-2.5-flash", + RequestOptions(), + mockEngine, + TEST_CLIENT_ID, + mockFirebaseApp, + TEST_VERSION, + TEST_APP_ID, + null, + ) + + val generativeModel = + GenerativeModel( + "gemini-2.5-flash", + generationConfig = + generationConfig { + thinkingConfig = thinkingConfig { thinkingLevel = ThinkingLevel.MEDIUM } + }, + controller = apiController + ) + + withTimeout(5.seconds) { generativeModel.generateContent("my test prompt") } + + mockEngine.requestHistory.shouldNotBeEmpty() + + val request = mockEngine.requestHistory.first().body + request.shouldBeInstanceOf() + + request.text.let { + print(request.text) + it shouldContainJsonKey "generation_config" + it.shouldContainJsonKeyValue("$.generation_config.thinking_config.thinking_level", "MEDIUM") + } + } } From 510f4203ad9e0bea46e623125718600bd776c652 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Date: Mon, 22 Dec 2025 17:03:48 -0500 Subject: [PATCH 10/11] Fix formatting in ThinkingConfig.kt --- .../main/kotlin/com/google/firebase/ai/type/ThinkingConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2b9a57d2f97..0355ec80952 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 @@ -29,7 +29,7 @@ public class ThinkingConfig private constructor( internal val thinkingBudget: Int? = null, internal val includeThoughts: Boolean? = null, - internal val thinkingLevel: ThinkingLevel? = null + internal val thinkingLevel: ThinkingLevel? = null, ) { public class Builder() { From 2d9dbb0063f3f75f003da47a2e3be903b042b718 Mon Sep 17 00:00:00 2001 From: Rodrigo Lazo Date: Mon, 22 Dec 2025 17:04:22 -0500 Subject: [PATCH 11/11] Remove unnecessary print statement Remove print statement from request text validation. --- .../test/java/com/google/firebase/ai/GenerativeModelTesting.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt b/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt index 2868baf2435..b84f89fd223 100644 --- a/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt +++ b/firebase-ai/src/test/java/com/google/firebase/ai/GenerativeModelTesting.kt @@ -306,7 +306,6 @@ internal class GenerativeModelTesting { request.shouldBeInstanceOf() request.text.let { - print(request.text) it shouldContainJsonKey "generation_config" it.shouldContainJsonKeyValue("$.generation_config.thinking_config.thinking_level", "MEDIUM") }