From fcb2ec0a1edd434f44e9da05ae84d0b55315799e Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 7 Apr 2026 08:46:10 -0700 Subject: [PATCH 1/3] Init setup with errors --- .../example/lib/schema/generable_example.dart | 24 +++ .../lib/schema/generable_example.g.dart | 32 ++++ .../firebase_ai/example/pubspec.yaml | 3 + .../firebase_ai/lib/firebase_ai.dart | 2 + .../firebase_ai/lib/src/annotations.dart | 49 ++++++ .../firebase_ai/lib/src/auto_schema.dart | 29 ++++ .../firebase_ai/lib/src/base_model.dart | 1 + .../firebase_ai/lib/src/generative_model.dart | 48 ++++++ .../firebase_ai/test/model_test.dart | 49 ++++++ .../firebase_ai_generator/build.yaml | 7 + .../lib/firebase_ai_generator.dart | 23 +++ .../lib/src/schema_generator.dart | 140 ++++++++++++++++++ .../lib/src/tool_generator.dart | 103 +++++++++++++ .../firebase_ai_generator/pubspec.yaml | 14 ++ 14 files changed, 524 insertions(+) create mode 100644 packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.dart create mode 100644 packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart create mode 100644 packages/firebase_ai/firebase_ai/lib/src/annotations.dart create mode 100644 packages/firebase_ai/firebase_ai/lib/src/auto_schema.dart create mode 100644 packages/firebase_ai/firebase_ai_generator/build.yaml create mode 100644 packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart create mode 100644 packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart create mode 100644 packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart create mode 100644 packages/firebase_ai/firebase_ai_generator/pubspec.yaml diff --git a/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.dart b/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.dart new file mode 100644 index 000000000000..d40e224eac96 --- /dev/null +++ b/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.dart @@ -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 toJson() => { + 'name': name, + 'age': age, + }; +} + +@GenerateTool(name: 'get_mock_user') +Future getMockUser(String name) async { + return MockUser(name: name, age: 30); +} diff --git a/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart b/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart new file mode 100644 index 000000000000..25270fcadada --- /dev/null +++ b/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart @@ -0,0 +1,32 @@ + + +// ************************************************************************** +// SchemaGenerator +// ************************************************************************** + +/// Auto-generated schema for MockUser. +const MockUserSchema = AutoSchema( + schemaMap: const {'type': 'OBJECT', 'properties': {'name': {'description': 'The user name', 'type': 'STRING', 'pattern': '^[a-zA-Z]+$'}, 'age': {'description': 'The user age', 'type': 'INTEGER', 'minimum': 0, 'maximum': 120}}}, + fromJson: (json) => MockUser( + name: json['name'] as String, + age: json['age'] as int, +), +); + +// ************************************************************************** +// ToolGenerator +// ************************************************************************** + +/// Auto-generated tool wrapper for getMockUser. +final getMockUserTool = AutoFunctionDeclaration( + name: 'get_mock_user', + description: 'Auto-generated tool for getMockUser', + parameters: const {'type': 'OBJECT', 'properties': {'name': {'type': 'STRING'}}}, + callable: (args) async { + // Extract arguments + final _name = args['name'] as String; + final result = await getMockUser(_name, ); + return result.toJson(); // Assumes result has toJson +} +, +); diff --git a/packages/firebase_ai/firebase_ai/example/pubspec.yaml b/packages/firebase_ai/firebase_ai/example/pubspec.yaml index 1dd1ee5ce2db..627a79db01ea 100644 --- a/packages/firebase_ai/firebase_ai/example/pubspec.yaml +++ b/packages/firebase_ai/firebase_ai/example/pubspec.yaml @@ -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 diff --git a/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart b/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart index 730c8516c045..b1abdc4d6211 100644 --- a/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart +++ b/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +export 'src/annotations.dart' show Generable, Guide, GenerateTool; +export 'src/auto_schema.dart' show AutoSchema; export 'src/api.dart' show BlockReason, diff --git a/packages/firebase_ai/firebase_ai/lib/src/annotations.dart b/packages/firebase_ai/firebase_ai/lib/src/annotations.dart new file mode 100644 index 000000000000..58f4512be44a --- /dev/null +++ b/packages/firebase_ai/firebase_ai/lib/src/annotations.dart @@ -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}); +} diff --git a/packages/firebase_ai/firebase_ai/lib/src/auto_schema.dart b/packages/firebase_ai/firebase_ai/lib/src/auto_schema.dart new file mode 100644 index 000000000000..3d38d0bf95a9 --- /dev/null +++ b/packages/firebase_ai/firebase_ai/lib/src/auto_schema.dart @@ -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 { + /// The raw JSON Schema map for the model. + final Map schemaMap; + + /// A function that creates an instance of [T] from a JSON map. + final T Function(Map) fromJson; + + /// Creates an [AutoSchema]. + const AutoSchema({required this.schemaMap, required this.fromJson}); +} diff --git a/packages/firebase_ai/firebase_ai/lib/src/base_model.dart b/packages/firebase_ai/firebase_ai/lib/src/base_model.dart index edd5a9a7c7b2..607e26e69e77 100644 --- a/packages/firebase_ai/firebase_ai/lib/src/base_model.dart +++ b/packages/firebase_ai/firebase_ai/lib/src/base_model.dart @@ -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'; diff --git a/packages/firebase_ai/firebase_ai/lib/src/generative_model.dart b/packages/firebase_ai/firebase_ai/lib/src/generative_model.dart index 2bf58351eafe..195821d4c1bc 100644 --- a/packages/firebase_ai/firebase_ai/lib/src/generative_model.dart +++ b/packages/firebase_ai/firebase_ai/lib/src/generative_model.dart @@ -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 generateObject( + AutoSchema schema, + Iterable prompt, { + List? safetySettings, + GenerationConfig? generationConfig, + List? 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) { + 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, diff --git a/packages/firebase_ai/firebase_ai/test/model_test.dart b/packages/firebase_ai/firebase_ai/test/model_test.dart index c14cf5ca3b95..506eab70d1c3 100644 --- a/packages/firebase_ai/firebase_ai/test/model_test.dart +++ b/packages/firebase_ai/firebase_ai/test/model_test.dart @@ -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>( + 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(); diff --git a/packages/firebase_ai/firebase_ai_generator/build.yaml b/packages/firebase_ai/firebase_ai_generator/build.yaml new file mode 100644 index 000000000000..b54badc97530 --- /dev/null +++ b/packages/firebase_ai/firebase_ai_generator/build.yaml @@ -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 diff --git a/packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart b/packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart new file mode 100644 index 000000000000..cac5749ada28 --- /dev/null +++ b/packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart @@ -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 SharedPartBuilder([SchemaGenerator(), ToolGenerator()], 'firebase_ai'); +} diff --git a/packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart b/packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart new file mode 100644 index 000000000000..8d280e5ca129 --- /dev/null +++ b/packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart @@ -0,0 +1,140 @@ +// 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:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:build/build.dart'; +import 'dart:convert'; +import 'package:source_gen/source_gen.dart'; +import 'package:firebase_ai/src/annotations.dart'; // Import annotations + +/// Generator for [Generable] annotation. +class SchemaGenerator extends GeneratorForAnnotation { + @override + String generateForAnnotatedElement( + Element element, ConstantReader annotation, BuildStep buildStep) { + if (element is! ClassElement) { + throw InvalidGenerationSourceError( + '@Generable can only be applied to classes.', + element: element); + } + + final className = element.name; + final fields = element.fields; + + final schemaMap = { + 'type': 'OBJECT', + 'properties': {}, + }; + + final fromJsonBuffer = StringBuffer(); + fromJsonBuffer.writeln('(json) => $className('); + + for (final field in fields) { + if (field.isSynthetic) continue; // Skip getters/setters + + final fieldName = field.name; + final fieldType = field.type; + + // Check for @Guide annotation + final guideAnnotation = + TypeChecker.fromRuntime(Guide).firstAnnotationOf(field); + String? description; + num? minimum; + num? maximum; + String? pattern; + + if (guideAnnotation != null) { + final reader = ConstantReader(guideAnnotation); + description = reader.read('description').literalValue as String?; + minimum = reader.read('minimum').literalValue as num?; + maximum = reader.read('maximum').literalValue as num?; + pattern = reader.read('pattern').literalValue as String?; + } + + final fieldSchema = {}; + if (description != null) fieldSchema['description'] = description; + + if (fieldType.isDartCoreString) { + fieldSchema['type'] = 'STRING'; + if (pattern != null) fieldSchema['pattern'] = pattern; + } else if (fieldType.isDartCoreInt) { + fieldSchema['type'] = 'INTEGER'; + if (minimum != null) fieldSchema['minimum'] = minimum; + if (maximum != null) fieldSchema['maximum'] = maximum; + } else if (fieldType.isDartCoreDouble) { + fieldSchema['type'] = 'NUMBER'; + if (minimum != null) fieldSchema['minimum'] = minimum; + if (maximum != null) fieldSchema['maximum'] = maximum; + } else if (fieldType.isDartCoreBool) { + fieldSchema['type'] = 'BOOLEAN'; + } else if (fieldType.isDartCoreList) { + fieldSchema['type'] = 'ARRAY'; + // Handle list item type if possible + final typeArgs = (fieldType as ParameterizedType).typeArguments; + if (typeArgs.isNotEmpty) { + final itemType = typeArgs.first; + fieldSchema['items'] = _mapPrimitiveType(itemType); + } + } else { + // Handle nested objects or enums + fieldSchema['type'] = 'OBJECT'; // Fallback + } + + (schemaMap['properties'] as Map)[fieldName] = fieldSchema; + + fromJsonBuffer.writeln(' $fieldName: json[\'$fieldName\'] as ${_mapDartType(fieldType)},'); + } + + fromJsonBuffer.write(')'); + + return ''' +/// Auto-generated schema for $className. +const ${className}Schema = AutoSchema<$className>( + schemaMap: const ${_mapToDartCode(schemaMap)}, + fromJson: $fromJsonBuffer, +); +'''; + } + + Map _mapPrimitiveType(DartType type) { + if (type.isDartCoreString) return {'type': 'STRING'}; + if (type.isDartCoreInt) return {'type': 'INTEGER'}; + if (type.isDartCoreDouble) return {'type': 'NUMBER'}; + if (type.isDartCoreBool) return {'type': 'BOOLEAN'}; + return {'type': 'OBJECT'}; + } + + String _mapDartType(DartType type) { + if (type.isDartCoreString) return 'String'; + if (type.isDartCoreInt) return 'int'; + if (type.isDartCoreDouble) return 'double'; + if (type.isDartCoreBool) return 'bool'; + if (type.isDartCoreList) { + final typeArgs = (type as ParameterizedType).typeArguments; + if (typeArgs.isNotEmpty) { + return 'List<${_mapDartType(typeArgs.first)}>'; + } + return 'List'; + } + return 'Map'; + } + + String _mapToDartCode(Map map) { + return jsonEncode(map) + .replaceAll('"', "'") + .replaceAll(':', ': ') + .replaceAll(',', ', '); + } +} diff --git a/packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart b/packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart new file mode 100644 index 000000000000..0786a26a5b11 --- /dev/null +++ b/packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart @@ -0,0 +1,103 @@ +// 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:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:build/build.dart'; +import 'dart:convert'; +import 'package:source_gen/source_gen.dart'; +import 'package:firebase_ai/src/annotations.dart'; // Import annotations + +/// Generator for [GenerateTool] annotation. +class ToolGenerator extends GeneratorForAnnotation { + @override + String generateForAnnotatedElement( + Element element, ConstantReader annotation, BuildStep buildStep) { + if (element is! FunctionElement) { + throw InvalidGenerationSourceError( + '@GenerateTool can only be applied to functions.', + element: element); + } + + final functionName = element.name; + final parameters = element.parameters; + final toolName = annotation.read('name').literalValue as String? ?? functionName; + final description = element.documentationComment ?? 'Auto-generated tool for $functionName'; + + final parametersSchema = { + 'type': 'OBJECT', + 'properties': {}, + }; + + final callableBuffer = StringBuffer(); + callableBuffer.writeln('(args) async {'); + callableBuffer.writeln(' // Extract arguments'); + + for (final param in parameters) { + final paramName = param.name; + final paramType = param.type; + + final fieldSchema = {}; + if (paramType.isDartCoreString) { + fieldSchema['type'] = 'STRING'; + } else if (paramType.isDartCoreInt) { + fieldSchema['type'] = 'INTEGER'; + } else if (paramType.isDartCoreDouble) { + fieldSchema['type'] = 'NUMBER'; + } else if (paramType.isDartCoreBool) { + fieldSchema['type'] = 'BOOLEAN'; + } else { + fieldSchema['type'] = 'OBJECT'; + } + + (parametersSchema['properties'] as Map)[paramName] = fieldSchema; + + callableBuffer.writeln(' final _$paramName = args[\'$paramName\'] as ${_mapDartType(paramType)};'); + } + + callableBuffer.write(' final result = await $functionName('); + for (final param in parameters) { + final paramName = param.name; + callableBuffer.write('_$paramName, '); + } + callableBuffer.writeln(');'); + callableBuffer.writeln(' return result.toJson(); // Assumes result has toJson'); + callableBuffer.writeln('}'); + + return ''' +/// Auto-generated tool wrapper for $functionName. +final ${functionName}Tool = AutoFunctionDeclaration( + name: '$toolName', + description: '$description', + parameters: const ${_mapToDartCode(parametersSchema)}, + callable: $callableBuffer, +); +'''; + } + + String _mapDartType(DartType type) { + if (type.isDartCoreString) return 'String'; + if (type.isDartCoreInt) return 'int'; + if (type.isDartCoreDouble) return 'double'; + if (type.isDartCoreBool) return 'bool'; + return 'Map'; + } + + String _mapToDartCode(Map map) { + return jsonEncode(map) + .replaceAll('"', "'") + .replaceAll(':', ': ') + .replaceAll(',', ', '); + } +} diff --git a/packages/firebase_ai/firebase_ai_generator/pubspec.yaml b/packages/firebase_ai/firebase_ai_generator/pubspec.yaml new file mode 100644 index 000000000000..bdb5802729cc --- /dev/null +++ b/packages/firebase_ai/firebase_ai_generator/pubspec.yaml @@ -0,0 +1,14 @@ +name: firebase_ai_generator +description: Code generator for Firebase AI. +version: 0.1.0 +environment: + sdk: '>=3.2.0 <4.0.0' + +dependencies: + analyzer: ^6.4.1 + build: ^2.4.1 + source_gen: ^1.5.0 + firebase_ai: ^3.10.0 + +dev_dependencies: + build_runner: ^2.4.8 From c87548b7fbd820014acae1169138687339265ae4 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 7 Apr 2026 08:46:19 -0700 Subject: [PATCH 2/3] add to init setup --- packages/firebase_ai/firebase_ai/lib/firebase_ai.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart b/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart index b1abdc4d6211..068ce3f4f602 100644 --- a/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart +++ b/packages/firebase_ai/firebase_ai/lib/firebase_ai.dart @@ -13,7 +13,6 @@ // limitations under the License. export 'src/annotations.dart' show Generable, Guide, GenerateTool; -export 'src/auto_schema.dart' show AutoSchema; export 'src/api.dart' show BlockReason, @@ -35,6 +34,7 @@ export 'src/api.dart' SafetyRating, SafetySetting, UsageMetadata; +export 'src/auto_schema.dart' show AutoSchema; export 'src/base_model.dart' show GenerativeModel, From 20cef501e3ea4c82c57a2c256f49f938f7b795f6 Mon Sep 17 00:00:00 2001 From: Cynthia J Date: Tue, 7 Apr 2026 10:57:24 -0700 Subject: [PATCH 3/3] fix the generated file name --- .../lib/schema/generable_example.g.dart | 44 ++++++++++++++----- .../lib/firebase_ai_generator.dart | 2 +- .../lib/src/schema_generator.dart | 3 +- .../lib/src/tool_generator.dart | 35 +++++++-------- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart b/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart index 25270fcadada..87a08f43e35f 100644 --- a/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart +++ b/packages/firebase_ai/firebase_ai/example/lib/schema/generable_example.g.dart @@ -1,16 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +part of 'generable_example.dart'; // ************************************************************************** // SchemaGenerator // ************************************************************************** /// Auto-generated schema for MockUser. -const MockUserSchema = AutoSchema( - schemaMap: const {'type': 'OBJECT', 'properties': {'name': {'description': 'The user name', 'type': 'STRING', 'pattern': '^[a-zA-Z]+$'}, 'age': {'description': 'The user age', 'type': 'INTEGER', 'minimum': 0, 'maximum': 120}}}, +final MockUserSchema = AutoSchema( + schemaMap: const { + 'type': 'OBJECT', + 'properties': { + 'name': { + 'description': 'The user name', + 'type': 'STRING', + 'pattern': '^[a-zA-Z]+\$' + }, + 'age': { + 'description': 'The user age', + 'type': 'INTEGER', + 'minimum': 0, + 'maximum': 120 + } + } + }, fromJson: (json) => MockUser( - name: json['name'] as String, - age: json['age'] as int, -), + name: json['name'] as String, + age: json['age'] as int, + ), ); // ************************************************************************** @@ -21,12 +38,15 @@ const MockUserSchema = AutoSchema( final getMockUserTool = AutoFunctionDeclaration( name: 'get_mock_user', description: 'Auto-generated tool for getMockUser', - parameters: const {'type': 'OBJECT', 'properties': {'name': {'type': 'STRING'}}}, + parameters: { + 'name': Schema.string(), + }, callable: (args) async { - // Extract arguments - final _name = args['name'] as String; - final result = await getMockUser(_name, ); - return result.toJson(); // Assumes result has toJson -} -, + // Extract arguments + final _name = args['name'] as String; + final result = await getMockUser( + _name, + ); + return result.toJson(); // Assumes result has toJson + }, ); diff --git a/packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart b/packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart index cac5749ada28..5ed4476fb43f 100644 --- a/packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart +++ b/packages/firebase_ai/firebase_ai_generator/lib/firebase_ai_generator.dart @@ -19,5 +19,5 @@ import 'src/tool_generator.dart'; /// Builder factory for Firebase AI. Builder firebaseAiBuilder(BuilderOptions options) { - return SharedPartBuilder([SchemaGenerator(), ToolGenerator()], 'firebase_ai'); + return PartBuilder([SchemaGenerator(), ToolGenerator()], '.g.dart'); } diff --git a/packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart b/packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart index 8d280e5ca129..846242f52b40 100644 --- a/packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart +++ b/packages/firebase_ai/firebase_ai_generator/lib/src/schema_generator.dart @@ -101,7 +101,7 @@ class SchemaGenerator extends GeneratorForAnnotation { return ''' /// Auto-generated schema for $className. -const ${className}Schema = AutoSchema<$className>( +final ${className}Schema = AutoSchema<$className>( schemaMap: const ${_mapToDartCode(schemaMap)}, fromJson: $fromJsonBuffer, ); @@ -134,6 +134,7 @@ const ${className}Schema = AutoSchema<$className>( String _mapToDartCode(Map map) { return jsonEncode(map) .replaceAll('"', "'") + .replaceAll(r'$', r'\$') .replaceAll(':', ': ') .replaceAll(',', ', '); } diff --git a/packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart b/packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart index 0786a26a5b11..01b4030b0b26 100644 --- a/packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart +++ b/packages/firebase_ai/firebase_ai_generator/lib/src/tool_generator.dart @@ -35,11 +35,9 @@ class ToolGenerator extends GeneratorForAnnotation { final toolName = annotation.read('name').literalValue as String? ?? functionName; final description = element.documentationComment ?? 'Auto-generated tool for $functionName'; - final parametersSchema = { - 'type': 'OBJECT', - 'properties': {}, - }; - + final propertiesBuffer = StringBuffer(); + propertiesBuffer.writeln('{'); + final callableBuffer = StringBuffer(); callableBuffer.writeln('(args) async {'); callableBuffer.writeln(' // Extract arguments'); @@ -48,30 +46,30 @@ class ToolGenerator extends GeneratorForAnnotation { final paramName = param.name; final paramType = param.type; - final fieldSchema = {}; + propertiesBuffer.write(" '$paramName': "); if (paramType.isDartCoreString) { - fieldSchema['type'] = 'STRING'; + propertiesBuffer.writeln('Schema.string(),'); } else if (paramType.isDartCoreInt) { - fieldSchema['type'] = 'INTEGER'; + propertiesBuffer.writeln('Schema.integer(),'); } else if (paramType.isDartCoreDouble) { - fieldSchema['type'] = 'NUMBER'; + propertiesBuffer.writeln('Schema.number(),'); } else if (paramType.isDartCoreBool) { - fieldSchema['type'] = 'BOOLEAN'; + propertiesBuffer.writeln('Schema.boolean(),'); } else { - fieldSchema['type'] = 'OBJECT'; + propertiesBuffer.writeln('Schema.object(properties: {}), // TODO: Handle complex types'); } - (parametersSchema['properties'] as Map)[paramName] = fieldSchema; - - callableBuffer.writeln(' final _$paramName = args[\'$paramName\'] as ${_mapDartType(paramType)};'); + callableBuffer.writeln(" final _$paramName = args['$paramName'] as ${_mapDartType(paramType)};"); } - callableBuffer.write(' final result = await $functionName('); + propertiesBuffer.write(' }'); + + callableBuffer.writeln(' final result = await $functionName('); for (final param in parameters) { final paramName = param.name; - callableBuffer.write('_$paramName, '); + callableBuffer.writeln(' _$paramName,'); } - callableBuffer.writeln(');'); + callableBuffer.writeln(' );'); callableBuffer.writeln(' return result.toJson(); // Assumes result has toJson'); callableBuffer.writeln('}'); @@ -80,7 +78,7 @@ class ToolGenerator extends GeneratorForAnnotation { final ${functionName}Tool = AutoFunctionDeclaration( name: '$toolName', description: '$description', - parameters: const ${_mapToDartCode(parametersSchema)}, + parameters: $propertiesBuffer, callable: $callableBuffer, ); '''; @@ -97,6 +95,7 @@ final ${functionName}Tool = AutoFunctionDeclaration( String _mapToDartCode(Map map) { return jsonEncode(map) .replaceAll('"', "'") + .replaceAll(r'$', r'\$') .replaceAll(':', ': ') .replaceAll(',', ', '); }