Skip to content

Commit e4a0652

Browse files
feat(firebase_ai): add thinking level to ThinkingConfig (#17937)
* add the thinkingLevel api * Add test cases * Apply suggestions from code review Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * more review feedback * more comments --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent c78f56e commit e4a0652

File tree

5 files changed

+149
-9
lines changed

5 files changed

+149
-9
lines changed

packages/firebase_ai/firebase_ai/example/lib/pages/chat_page.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,12 @@ class _ChatPageState extends State<ChatPage> {
4949

5050
void _initializeChat() {
5151
final generationConfig = GenerationConfig(
52-
thinkingConfig:
53-
_enableThinking ? ThinkingConfig(includeThoughts: true) : null,
52+
thinkingConfig: _enableThinking
53+
? ThinkingConfig.withThinkingLevel(
54+
ThinkingLevel.high,
55+
includeThoughts: true,
56+
)
57+
: null,
5458
);
5559
if (widget.useVertexBackend) {
5660
_model = FirebaseAI.vertexAI(auth: FirebaseAuth.instance).generativeModel(

packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,12 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {
5353

5454
void _initializeModel() {
5555
final generationConfig = GenerationConfig(
56-
thinkingConfig:
57-
_enableThinking ? ThinkingConfig(includeThoughts: true) : null,
56+
thinkingConfig: _enableThinking
57+
? ThinkingConfig.withThinkingLevel(
58+
ThinkingLevel.high,
59+
includeThoughts: true,
60+
)
61+
: null,
5862
);
5963
if (widget.useVertexBackend) {
6064
var vertexAI = FirebaseAI.vertexAI(auth: FirebaseAuth.instance);

packages/firebase_ai/firebase_ai/lib/firebase_ai.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export 'src/api.dart'
2323
GenerateContentResponse,
2424
GenerationConfig,
2525
ThinkingConfig,
26+
ThinkingLevel,
2627
HarmBlockThreshold,
2728
HarmCategory,
2829
HarmProbability,

packages/firebase_ai/firebase_ai/lib/src/api.dart

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -962,21 +962,92 @@ enum ResponseModalities {
962962
String toJson() => _jsonString;
963963
}
964964

965+
/// A preset that balances the trade-off between reasoning quality and response
966+
/// speed for a model's "thinking" process.
967+
///
968+
/// Note, not all models support every level.
969+
enum ThinkingLevel {
970+
/// Minimal thinking level.
971+
minimal('MINIMAL'),
972+
973+
/// Low thinking level.
974+
low('LOW'),
975+
976+
/// Medium thinking level.
977+
medium('MEDIUM'),
978+
979+
/// High thinking level.
980+
high('HIGH');
981+
982+
const ThinkingLevel(this._jsonString);
983+
final String _jsonString;
984+
985+
// ignore: public_member_api_docs
986+
String toJson() => _jsonString;
987+
}
988+
965989
/// Config for thinking features.
966990
class ThinkingConfig {
967-
// ignore: public_member_api_docs
968-
ThinkingConfig({this.thinkingBudget, this.includeThoughts});
991+
/// Deprecated public constructor of [ThinkingConfig].
992+
///
993+
/// Keep for backwards compatibility.
994+
/// [thinkingBudget] and [thinkingLevel] cannot be set at the same time.
995+
@Deprecated(
996+
'Use ThinkingConfig.withThinkingBudget() or ThinkingConfig.withThinkingLevel() instead.')
997+
ThinkingConfig(
998+
{this.thinkingBudget, this.thinkingLevel, this.includeThoughts})
999+
: assert(
1000+
!(thinkingBudget != null && thinkingLevel != null),
1001+
'thinkingBudget and thinkingLevel cannot be set at the same time.',
1002+
);
1003+
1004+
// Private constructor
1005+
ThinkingConfig._(
1006+
{this.thinkingBudget, this.thinkingLevel, this.includeThoughts});
1007+
1008+
/// Initializes [ThinkingConfig] with [thinkingBudget].
1009+
///
1010+
/// Used for Gemini models 2.5 and earlier.
1011+
factory ThinkingConfig.withThinkingBudget(int? thinkingBudget,
1012+
{bool? includeThoughts}) =>
1013+
ThinkingConfig._(
1014+
thinkingBudget: thinkingBudget, includeThoughts: includeThoughts);
1015+
1016+
/// Initializes [ThinkingConfig] with [thinkingLevel].
1017+
///
1018+
/// Used for Gemini models 3.0 and newer.
1019+
/// See https://ai.google.dev/gemini-api/docs/thinking#thinking-levels
1020+
factory ThinkingConfig.withThinkingLevel(ThinkingLevel? thinkingLevel,
1021+
{bool? includeThoughts}) =>
1022+
ThinkingConfig._(
1023+
thinkingLevel: thinkingLevel, includeThoughts: includeThoughts);
9691024

9701025
/// The number of thoughts tokens that the model should generate.
1026+
///
1027+
/// The range of supported thinking budget values depends on the model.
1028+
/// https://firebase.google.com/docs/ai-logic/thinking?api=dev#supported-thinking-budget-values
1029+
/// To use the default thinking budget or thinking level for a model, set this
1030+
/// value to null or omit it.
1031+
/// To disable thinking, when supported by the model, set this value to `0`.
1032+
/// To use dynamic thinking, allowing the model to decide on the thinking
1033+
/// budget based on the task, set this value to `-1`.
9711034
final int? thinkingBudget;
9721035

9731036
/// Whether to include thoughts in the response.
9741037
final bool? includeThoughts;
9751038

1039+
/// A preset that controls the model's "thinking" process.
1040+
///
1041+
/// Use [ThinkingLevel.low] for faster responses on less complex tasks, and
1042+
/// [ThinkingLevel.high] for better reasoning on more complex tasks.
1043+
final ThinkingLevel? thinkingLevel;
1044+
9761045
// ignore: public_member_api_docs
9771046
Map<String, Object?> toJson() => {
9781047
if (thinkingBudget case final thinkingBudget?)
9791048
'thinkingBudget': thinkingBudget,
1049+
if (thinkingLevel case final thinkingLevel?)
1050+
'thinkingLevel': thinkingLevel.toJson(),
9801051
if (includeThoughts case final includeThoughts?)
9811052
'includeThoughts': includeThoughts,
9821053
};

packages/firebase_ai/firebase_ai/test/api_test.dart

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15+
// ignore_for_file: deprecated_member_use_from_same_package
1516
import 'dart:convert';
1617

1718
import 'package:firebase_ai/src/api.dart';
@@ -527,22 +528,81 @@ void main() {
527528
group('ThinkingConfig', () {
528529
test('toJson with thinkingBudget set', () {
529530
final config = ThinkingConfig(thinkingBudget: 123);
531+
530532
expect(config.toJson(), {'thinkingBudget': 123});
531533
});
532534

533-
test('toJson with thinkingBudget null', () {
535+
test('toJson with thinkingLevel set', () {
536+
final config = ThinkingConfig.withThinkingLevel(ThinkingLevel.high,
537+
includeThoughts: true);
538+
539+
expect(
540+
config.toJson(), {'thinkingLevel': 'HIGH', 'includeThoughts': true});
541+
});
542+
543+
test('toJson with includeThoughts set', () {
544+
final config = ThinkingConfig(includeThoughts: true);
545+
546+
expect(config.toJson(), {'includeThoughts': true});
547+
});
548+
549+
test('toJson with thinkingBudget and thinkingLevel null', () {
534550
final config = ThinkingConfig();
551+
535552
// Expecting the key to be absent or the value to be explicitly null,
536553
// depending on implementation. Current implementation omits the key.
537554
expect(config.toJson(), {});
538555
});
539556

540557
test('constructor initializes thinkingBudget', () {
541558
final config = ThinkingConfig(thinkingBudget: 456);
559+
542560
expect(config.thinkingBudget, 456);
561+
expect(config.thinkingLevel, isNull);
562+
expect(config.includeThoughts, isNull);
563+
});
564+
565+
test('constructor initializes thinkingLevel', () {
566+
final config = ThinkingConfig(thinkingLevel: ThinkingLevel.low);
567+
568+
expect(config.thinkingBudget, isNull);
569+
expect(config.thinkingLevel, ThinkingLevel.low);
570+
expect(config.includeThoughts, isNull);
571+
});
543572

544-
final configNull = ThinkingConfig();
545-
expect(configNull.thinkingBudget, isNull);
573+
test('constructor initializes includeThoughts', () {
574+
final config = ThinkingConfig(includeThoughts: true);
575+
576+
expect(config.thinkingBudget, isNull);
577+
expect(config.thinkingLevel, isNull);
578+
expect(config.includeThoughts, isTrue);
579+
});
580+
581+
test('withThinkingBudget factory initializes correctly', () {
582+
final config =
583+
ThinkingConfig.withThinkingBudget(789, includeThoughts: false);
584+
585+
expect(config.thinkingBudget, 789);
586+
expect(config.thinkingLevel, isNull);
587+
expect(config.includeThoughts, isFalse);
588+
});
589+
590+
test('withThinkingLevel factory initializes correctly', () {
591+
final config = ThinkingConfig.withThinkingLevel(ThinkingLevel.medium,
592+
includeThoughts: true);
593+
594+
expect(config.thinkingBudget, isNull);
595+
expect(config.thinkingLevel, ThinkingLevel.medium);
596+
expect(config.includeThoughts, isTrue);
597+
});
598+
599+
test(
600+
'deprecated constructor throws AssertionError if both thinkingBudget and thinkingLevel are provided',
601+
() {
602+
expect(
603+
() => ThinkingConfig(
604+
thinkingBudget: 100, thinkingLevel: ThinkingLevel.high),
605+
throwsA(isA<AssertionError>()));
546606
});
547607
});
548608

0 commit comments

Comments
 (0)