diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/chat_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/chat_page.dart index 388cc76572d1..c4d4b5798d83 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/chat_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/chat_page.dart @@ -49,8 +49,12 @@ class _ChatPageState extends State { void _initializeChat() { final generationConfig = GenerationConfig( - thinkingConfig: - _enableThinking ? ThinkingConfig(includeThoughts: true) : null, + thinkingConfig: _enableThinking + ? ThinkingConfig.withThinkingLevel( + ThinkingLevel.high, + includeThoughts: true, + ) + : null, ); if (widget.useVertexBackend) { _model = FirebaseAI.vertexAI(auth: FirebaseAuth.instance).generativeModel( diff --git a/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart b/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart index 8a529069b5ba..4c500aefde1c 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/pages/function_calling_page.dart @@ -53,8 +53,12 @@ class _FunctionCallingPageState extends State { void _initializeModel() { final generationConfig = GenerationConfig( - thinkingConfig: - _enableThinking ? ThinkingConfig(includeThoughts: true) : null, + thinkingConfig: _enableThinking + ? ThinkingConfig.withThinkingLevel( + ThinkingLevel.high, + includeThoughts: true, + ) + : null, ); if (widget.useVertexBackend) { var vertexAI = FirebaseAI.vertexAI(auth: FirebaseAuth.instance); diff --git a/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart b/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart index 00c8a03added..6c05e772f062 100644 --- a/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart +++ b/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart @@ -23,6 +23,7 @@ export 'src/api.dart' GenerateContentResponse, GenerationConfig, ThinkingConfig, + ThinkingLevel, HarmBlockThreshold, HarmCategory, HarmProbability, diff --git a/packages/firebase_ai/firebase_ai/lib/src/api.dart b/packages/firebase_ai/firebase_ai/lib/src/api.dart index 1f9b654affa5..0c1849c3c18d 100644 --- a/packages/firebase_ai/firebase_ai/lib/src/api.dart +++ b/packages/firebase_ai/firebase_ai/lib/src/api.dart @@ -962,21 +962,92 @@ enum ResponseModalities { String toJson() => _jsonString; } +/// A preset that balances the trade-off between reasoning quality and response +/// speed for a model's "thinking" process. +/// +/// Note, not all models support every level. +enum ThinkingLevel { + /// Minimal thinking level. + minimal('MINIMAL'), + + /// Low thinking level. + low('LOW'), + + /// Medium thinking level. + medium('MEDIUM'), + + /// High thinking level. + high('HIGH'); + + const ThinkingLevel(this._jsonString); + final String _jsonString; + + // ignore: public_member_api_docs + String toJson() => _jsonString; +} + /// Config for thinking features. class ThinkingConfig { - // ignore: public_member_api_docs - ThinkingConfig({this.thinkingBudget, this.includeThoughts}); + /// Deprecated public constructor of [ThinkingConfig]. + /// + /// Keep for backwards compatibility. + /// [thinkingBudget] and [thinkingLevel] cannot be set at the same time. + @Deprecated( + 'Use ThinkingConfig.withThinkingBudget() or ThinkingConfig.withThinkingLevel() instead.') + ThinkingConfig( + {this.thinkingBudget, this.thinkingLevel, this.includeThoughts}) + : assert( + !(thinkingBudget != null && thinkingLevel != null), + 'thinkingBudget and thinkingLevel cannot be set at the same time.', + ); + + // Private constructor + ThinkingConfig._( + {this.thinkingBudget, this.thinkingLevel, this.includeThoughts}); + + /// Initializes [ThinkingConfig] with [thinkingBudget]. + /// + /// Used for Gemini models 2.5 and earlier. + factory ThinkingConfig.withThinkingBudget(int? thinkingBudget, + {bool? includeThoughts}) => + ThinkingConfig._( + thinkingBudget: thinkingBudget, includeThoughts: includeThoughts); + + /// Initializes [ThinkingConfig] with [thinkingLevel]. + /// + /// Used for Gemini models 3.0 and newer. + /// See https://ai.google.dev/gemini-api/docs/thinking#thinking-levels + factory ThinkingConfig.withThinkingLevel(ThinkingLevel? thinkingLevel, + {bool? includeThoughts}) => + ThinkingConfig._( + thinkingLevel: thinkingLevel, includeThoughts: includeThoughts); /// The number of thoughts tokens that the model should generate. + /// + /// The range of supported thinking budget values depends on the model. + /// https://firebase.google.com/docs/ai-logic/thinking?api=dev#supported-thinking-budget-values + /// To use the default thinking budget or thinking level for a model, set this + /// value to null or omit it. + /// To disable thinking, when supported by the model, set this value to `0`. + /// To use dynamic thinking, allowing the model to decide on the thinking + /// budget based on the task, set this value to `-1`. final int? thinkingBudget; /// Whether to include thoughts in the response. final bool? includeThoughts; + /// A preset that controls the model's "thinking" process. + /// + /// Use [ThinkingLevel.low] for faster responses on less complex tasks, and + /// [ThinkingLevel.high] for better reasoning on more complex tasks. + final ThinkingLevel? thinkingLevel; + // ignore: public_member_api_docs Map toJson() => { if (thinkingBudget case final thinkingBudget?) 'thinkingBudget': thinkingBudget, + if (thinkingLevel case final thinkingLevel?) + 'thinkingLevel': thinkingLevel.toJson(), if (includeThoughts case final includeThoughts?) 'includeThoughts': includeThoughts, }; diff --git a/packages/firebase_ai/firebase_ai/test/api_test.dart b/packages/firebase_ai/firebase_ai/test/api_test.dart index 9ed053a91031..a6124006f515 100644 --- a/packages/firebase_ai/firebase_ai/test/api_test.dart +++ b/packages/firebase_ai/firebase_ai/test/api_test.dart @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// ignore_for_file: deprecated_member_use_from_same_package import 'dart:convert'; import 'package:firebase_ai/src/api.dart'; @@ -527,11 +528,27 @@ void main() { group('ThinkingConfig', () { test('toJson with thinkingBudget set', () { final config = ThinkingConfig(thinkingBudget: 123); + expect(config.toJson(), {'thinkingBudget': 123}); }); - test('toJson with thinkingBudget null', () { + test('toJson with thinkingLevel set', () { + final config = ThinkingConfig.withThinkingLevel(ThinkingLevel.high, + includeThoughts: true); + + expect( + config.toJson(), {'thinkingLevel': 'HIGH', 'includeThoughts': true}); + }); + + test('toJson with includeThoughts set', () { + final config = ThinkingConfig(includeThoughts: true); + + expect(config.toJson(), {'includeThoughts': true}); + }); + + test('toJson with thinkingBudget and thinkingLevel null', () { final config = ThinkingConfig(); + // Expecting the key to be absent or the value to be explicitly null, // depending on implementation. Current implementation omits the key. expect(config.toJson(), {}); @@ -539,10 +556,53 @@ void main() { test('constructor initializes thinkingBudget', () { final config = ThinkingConfig(thinkingBudget: 456); + expect(config.thinkingBudget, 456); + expect(config.thinkingLevel, isNull); + expect(config.includeThoughts, isNull); + }); + + test('constructor initializes thinkingLevel', () { + final config = ThinkingConfig(thinkingLevel: ThinkingLevel.low); + + expect(config.thinkingBudget, isNull); + expect(config.thinkingLevel, ThinkingLevel.low); + expect(config.includeThoughts, isNull); + }); - final configNull = ThinkingConfig(); - expect(configNull.thinkingBudget, isNull); + test('constructor initializes includeThoughts', () { + final config = ThinkingConfig(includeThoughts: true); + + expect(config.thinkingBudget, isNull); + expect(config.thinkingLevel, isNull); + expect(config.includeThoughts, isTrue); + }); + + test('withThinkingBudget factory initializes correctly', () { + final config = + ThinkingConfig.withThinkingBudget(789, includeThoughts: false); + + expect(config.thinkingBudget, 789); + expect(config.thinkingLevel, isNull); + expect(config.includeThoughts, isFalse); + }); + + test('withThinkingLevel factory initializes correctly', () { + final config = ThinkingConfig.withThinkingLevel(ThinkingLevel.medium, + includeThoughts: true); + + expect(config.thinkingBudget, isNull); + expect(config.thinkingLevel, ThinkingLevel.medium); + expect(config.includeThoughts, isTrue); + }); + + test( + 'deprecated constructor throws AssertionError if both thinkingBudget and thinkingLevel are provided', + () { + expect( + () => ThinkingConfig( + thinkingBudget: 100, thinkingLevel: ThinkingLevel.high), + throwsA(isA())); }); });