Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ class _ChatPageState extends State<ChatPage> {

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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,12 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {

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);
Expand Down
1 change: 1 addition & 0 deletions packages/firebase_ai/firebase_ai/lib/firebase_ai.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export 'src/api.dart'
GenerateContentResponse,
GenerationConfig,
ThinkingConfig,
ThinkingLevel,
HarmBlockThreshold,
HarmCategory,
HarmProbability,
Expand Down
75 changes: 73 additions & 2 deletions packages/firebase_ai/firebase_ai/lib/src/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object?> toJson() => {
if (thinkingBudget case final thinkingBudget?)
'thinkingBudget': thinkingBudget,
if (thinkingLevel case final thinkingLevel?)
'thinkingLevel': thinkingLevel.toJson(),
if (includeThoughts case final includeThoughts?)
'includeThoughts': includeThoughts,
};
Expand Down
66 changes: 63 additions & 3 deletions packages/firebase_ai/firebase_ai/test/api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -527,22 +528,81 @@ 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(), {});
});

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<AssertionError>()));
});
});

Expand Down
Loading