Skip to content
Draft
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
@@ -0,0 +1,24 @@
import 'package:firebase_ai/firebase_ai.dart';

part 'generable_example.g.dart';

@Generable(description: 'A mock user for testing')
class MockUser {
@Guide(description: 'The user name', pattern: r'^[a-zA-Z]+$')
final String name;

@Guide(description: 'The user age', minimum: 0, maximum: 120)
final int age;

MockUser({required this.name, required this.age});

Map<String, dynamic> toJson() => {
'name': name,
'age': age,
};
}

@GenerateTool(name: 'get_mock_user')
Future<MockUser> getMockUser(String name) async {
return MockUser(name: name, age: 30);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/firebase_ai/firebase_ai/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,13 @@ dependencies:
waveform_flutter: ^1.2.0

dev_dependencies:
build_runner: ^2.4.8
firebase_ai_generator: ^0.1.0
flutter_lints: ^4.0.0
flutter_test:
sdk: flutter


flutter:

# The following line ensures that the Material Icons font is
Expand Down
2 changes: 2 additions & 0 deletions packages/firebase_ai/firebase_ai/lib/firebase_ai.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.

export 'src/annotations.dart' show Generable, Guide, GenerateTool;
export 'src/api.dart'
show
BlockReason,
Expand All @@ -33,6 +34,7 @@ export 'src/api.dart'
SafetyRating,
SafetySetting,
UsageMetadata;
export 'src/auto_schema.dart' show AutoSchema;
export 'src/base_model.dart'
show
GenerativeModel,
Expand Down
49 changes: 49 additions & 0 deletions packages/firebase_ai/firebase_ai/lib/src/annotations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2026 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.

/// Annotation for classes that can be used to generate a JSON Schema.
class Generable {
/// A brief description of the generated object.
final String? description;

/// Creates a [Generable] annotation.
const Generable({this.description});
}

/// Annotation for fields to provide constraints and documentation for the schema.
class Guide {
/// Description of the field.
final String? description;

/// Minimum value for numeric fields.
final num? minimum;

/// Maximum value for numeric fields.
final num? maximum;

/// Regular expression pattern for string fields.
final String? pattern;

/// Creates a [Guide] annotation.
const Guide({this.description, this.minimum, this.maximum, this.pattern});
}

/// Annotation for functions that can be used as tools by the model.
class GenerateTool {
/// The name of the tool. If omitted, the function name is used.
final String? name;

/// Creates a [GenerateTool] annotation.
const GenerateTool({this.name});
}
29 changes: 29 additions & 0 deletions packages/firebase_ai/firebase_ai/lib/src/auto_schema.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2026 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.

/// A class that holds the JSON Schema representation and a factory to
/// create a typed object from a JSON map.
///
/// This serves as a bridge between the generated schema and the SDK's
/// type-safe methods for object generation and function calling.
class AutoSchema<T> {
/// The raw JSON Schema map for the model.
final Map<String, dynamic> schemaMap;

/// A function that creates an instance of [T] from a JSON map.
final T Function(Map<String, dynamic>) fromJson;

/// Creates an [AutoSchema].
const AutoSchema({required this.schemaMap, required this.fromJson});
}
1 change: 1 addition & 0 deletions packages/firebase_ai/firebase_ai/lib/src/base_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import 'live_session.dart';
import 'platform_header_helper.dart';
import 'server_template/template_tool.dart';
import 'tool.dart';
import 'auto_schema.dart';

part 'generative_model.dart';
part 'imagen/imagen_model.dart';
Expand Down
48 changes: 48 additions & 0 deletions packages/firebase_ai/firebase_ai/lib/src/generative_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,54 @@ final class GenerativeModel extends BaseApiClientModel {
return response.map(_serializationStrategy.parseGenerateContentResponse);
}

/// Generates an object of type [T] responding to [prompt].
///
/// Uses the provided [schema] to constrain the output to match the JSON
/// schema and uses [schema.fromJson] to parse the result.
Future<T> generateObject<T>(
AutoSchema<T> schema,
Iterable<Content> prompt, {
List<SafetySetting>? safetySettings,
GenerationConfig? generationConfig,
List<Tool>? tools,
ToolConfig? toolConfig,
}) async {
final config = GenerationConfig(
candidateCount: generationConfig?.candidateCount,
stopSequences: generationConfig?.stopSequences,
maxOutputTokens: generationConfig?.maxOutputTokens,
temperature: generationConfig?.temperature,
topP: generationConfig?.topP,
topK: generationConfig?.topK,
presencePenalty: generationConfig?.presencePenalty,
frequencyPenalty: generationConfig?.frequencyPenalty,
responseModalities: generationConfig?.responseModalities,
thinkingConfig: generationConfig?.thinkingConfig,
responseMimeType: 'application/json',
responseJsonSchema: schema.schemaMap,
);

final response = await generateContent(
prompt,
safetySettings: safetySettings,
generationConfig: config,
tools: tools,
toolConfig: toolConfig,
);

final text = response.text;
if (text == null) {
throw FirebaseAIException('No text returned from model');
}

final json = jsonDecode(text);
if (json is! Map<String, dynamic>) {
throw FirebaseAIException('Expected JSON map from model, got: $text');
}

return schema.fromJson(json);
}

/// Counts the total number of tokens in [contents].
///
/// Sends a "countTokens" API request for the configured model,
Expand Down
49 changes: 49 additions & 0 deletions packages/firebase_ai/firebase_ai/test/model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,55 @@ void main() {
});
});

group('generate object', () {
test('can make successful request and parse object', () async {
final (client, model) = createModel();
const prompt = 'Some prompt';
const result = '{"name": "John", "age": 30}';

final schema = AutoSchema<Map<String, dynamic>>(
schemaMap: const {
'type': 'OBJECT',
'properties': {
'name': {'type': 'STRING'},
'age': {'type': 'INTEGER'},
},
},
fromJson: (json) => json,
);

final response = await client.checkRequest(
() => model.generateObject(schema, [Content.text(prompt)]),
verifyRequest: (uri, request) {
expect(request['generationConfig'], {
'responseMimeType': 'application/json',
'responseJsonSchema': {
'type': 'OBJECT',
'properties': {
'name': {'type': 'STRING'},
'age': {'type': 'INTEGER'},
},
},
});
},
response: {
'candidates': [
{
'content': {
'role': 'model',
'parts': [
{'text': result},
],
},
},
],
},
);
expect(response['name'], 'John');
expect(response['age'], 30);
});
});

group('generate content stream', () {
test('can make successful request', () async {
final (client, model) = createModel();
Expand Down
7 changes: 7 additions & 0 deletions packages/firebase_ai/firebase_ai_generator/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
builders:
firebase_ai_generator:
import: "package:firebase_ai_generator/firebase_ai_generator.dart"
builder_factories: ["firebaseAiBuilder"]
build_extensions: {".dart": [".g.dart"]}
auto_apply: dependents
build_to: source
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2026 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.

import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'src/schema_generator.dart';
import 'src/tool_generator.dart';

/// Builder factory for Firebase AI.
Builder firebaseAiBuilder(BuilderOptions options) {
return PartBuilder([SchemaGenerator(), ToolGenerator()], '.g.dart');
}
Loading
Loading