diff --git a/packages/firebase_ai/firebase_ai/example/ios/Podfile b/packages/firebase_ai/firebase_ai/example/ios/Podfile
index a419e4239013..9d7ef08d52b2 100644
--- a/packages/firebase_ai/firebase_ai/example/ios/Podfile
+++ b/packages/firebase_ai/firebase_ai/example/ios/Podfile
@@ -29,7 +29,6 @@ flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
- use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
diff --git a/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/contents.xcworkspacedata
index 21a3cc14c74e..1d526a16ed0f 100644
--- a/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,7 +4,4 @@
-
-
diff --git a/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d981003d68..000000000000
--- a/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
deleted file mode 100644
index f9b0d7c5ea15..000000000000
--- a/packages/firebase_ai/firebase_ai/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- PreviewsEnabled
-
-
-
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 e4cd355259dd..60f1bd5e2160 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
@@ -42,8 +42,12 @@ class _FunctionCallingPageState extends State {
late GenerativeModel _functionCallModel;
late GenerativeModel _autoFunctionCallModel;
late GenerativeModel _parallelAutoFunctionCallModel;
+ late GenerativeModel _complexJSONSchemaModel;
+ late GenerativeModel _refSchemaModel;
late GenerativeModel _codeExecutionModel;
late final AutoFunctionDeclaration _autoFetchWeatherTool;
+ late final AutoFunctionDeclaration _autoPlanVacationTool;
+ late final AutoFunctionDeclaration _autoProcessTransactionTool;
final List _messages = [];
bool _loading = false;
bool _enableThinking = false;
@@ -113,6 +117,73 @@ class _FunctionCallingPageState extends State {
return getRestaurantMenu(restaurantName);
},
);
+ _autoPlanVacationTool = AutoFunctionDeclaration(
+ name: 'planVacation',
+ description:
+ 'Plans a complex vacation itinerary combining flights, hotels, and activities.',
+ useJSONSchema: true,
+ parameters: {
+ 'destination':
+ Schema.string(description: 'The city or country to travel to.'),
+ 'travelers': Schema.integer(
+ description: 'Number of travelers.', minimum: 1, maximum: 10),
+ 'travelClass': Schema.enumString(
+ enumValues: ['ECONOMY', 'BUSINESS', 'FIRST'],
+ description: 'The preferred travel class.',
+ ),
+ 'budget':
+ Schema.number(description: 'Total budget for the trip in USD.'),
+ 'activities': Schema.array(
+ items: Schema.string(),
+ description: 'A list of preferred activities.',
+ minItems: 1,
+ ),
+ 'accommodations': Schema.object(
+ description: 'Hotel preferences.',
+ properties: {
+ 'hotelType': Schema.string(),
+ 'stars': Schema.integer(minimum: 1, maximum: 5),
+ 'amenities': Schema.array(items: Schema.string()),
+ },
+ optionalProperties: ['amenities'],
+ ),
+ },
+ callable: (args) async {
+ return {
+ 'status': 'SUCCESS',
+ 'itineraryId': 'TRIP-98765',
+ 'destination': args['destination'],
+ 'estimatedCost': 3500.0,
+ 'message': 'Vacation planned successfully!',
+ };
+ },
+ );
+ _autoProcessTransactionTool = AutoFunctionDeclaration(
+ name: 'processTransaction',
+ description:
+ 'Processes a financial transaction using a predefined transaction model reference.',
+ useJSONSchema: true,
+ parameters: {
+ 'baseTransaction': Schema.object(
+ description: 'The base transaction block.',
+ properties: {
+ 'amount': Schema.number(),
+ 'transactionId': Schema.integer(),
+ },
+ ),
+ 'transaction': Schema.ref('#/properties/baseTransaction'),
+ },
+ callable: (args) async {
+ final transaction = args['transaction'] as Map?;
+ return {
+ 'status': 'SUCCESS',
+ 'amountProcessed': transaction?['amount'],
+ 'transactionId': transaction?['transactionId'],
+ 'message':
+ 'Transaction processed successfully using the reference schema!',
+ };
+ },
+ );
_initializeModel();
}
@@ -202,6 +273,20 @@ class _FunctionCallingPageState extends State {
Tool.codeExecution(),
],
);
+ _complexJSONSchemaModel = aiClient.generativeModel(
+ model: 'gemini-2.5-flash',
+ generationConfig: generationConfig,
+ tools: [
+ Tool.functionDeclarations([_autoPlanVacationTool]),
+ ],
+ );
+ _refSchemaModel = aiClient.generativeModel(
+ model: 'gemini-2.5-flash',
+ generationConfig: generationConfig,
+ tools: [
+ Tool.functionDeclarations([_autoProcessTransactionTool]),
+ ],
+ );
}
// This is a hypothetical API to return a fake weather data collection for
@@ -375,6 +460,28 @@ class _FunctionCallingPageState extends State {
),
],
),
+ const SizedBox(height: 8),
+ Row(
+ children: [
+ Expanded(
+ child: ElevatedButton(
+ onPressed: !_loading
+ ? _testComplexJSONSchemaAutoFunctionCalling
+ : null,
+ child: const Text('Complex JSON Schema FC'),
+ ),
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: ElevatedButton(
+ onPressed: !_loading
+ ? _testRefSchemaAutoFunctionCalling
+ : null,
+ child: const Text('Ref Schema FC'),
+ ),
+ ),
+ ],
+ ),
],
),
),
@@ -608,6 +715,56 @@ class _FunctionCallingPageState extends State {
});
}
+ Future _testComplexJSONSchemaAutoFunctionCalling() async {
+ await _runTest(() async {
+ final chat = _complexJSONSchemaModel.startChat();
+ const prompt =
+ 'I want to plan a vacation to Paris for 2 people. We want to fly Business class, our budget is 5500 USD. We want to do wine tasting and museum tours. We prefer a 4-star boutique hotel with free breakfast.';
+
+ _messages.add(MessageData(text: prompt, fromUser: true));
+ setState(() {});
+
+ final response = await chat.sendMessage(Content.text(prompt));
+
+ final thought = response.thoughtSummary;
+ if (thought != null) {
+ _messages
+ .add(MessageData(text: thought, fromUser: false, isThought: true));
+ }
+
+ if (response.text case final text?) {
+ _messages.add(MessageData(text: text));
+ } else {
+ _messages.add(MessageData(text: 'No text response from model.'));
+ }
+ });
+ }
+
+ Future _testRefSchemaAutoFunctionCalling() async {
+ await _runTest(() async {
+ final chat = _refSchemaModel.startChat();
+ const prompt =
+ r'Process a transaction of \$50.00 for a pair of shoes. The transaction ID is 98765.';
+
+ _messages.add(MessageData(text: prompt, fromUser: true));
+ setState(() {});
+
+ final response = await chat.sendMessage(Content.text(prompt));
+
+ final thought = response.thoughtSummary;
+ if (thought != null) {
+ _messages
+ .add(MessageData(text: thought, fromUser: false, isThought: true));
+ }
+
+ if (response.text case final text?) {
+ _messages.add(MessageData(text: text));
+ } else {
+ _messages.add(MessageData(text: 'No text response from model.'));
+ }
+ });
+ }
+
void _showError(String message) {
showDialog(
context: context,
diff --git a/packages/firebase_ai/firebase_ai/example/macos/Podfile b/packages/firebase_ai/firebase_ai/example/macos/Podfile
index b52666a10389..ff5ddb3b8bdc 100644
--- a/packages/firebase_ai/firebase_ai/example/macos/Podfile
+++ b/packages/firebase_ai/firebase_ai/example/macos/Podfile
@@ -28,7 +28,6 @@ flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
- use_modular_headers!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
diff --git a/packages/firebase_ai/firebase_ai/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_ai/firebase_ai/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
deleted file mode 100644
index 18d981003d68..000000000000
--- a/packages/firebase_ai/firebase_ai/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
- IDEDidComputeMac32BitWarning
-
-
-
diff --git a/packages/firebase_ai/firebase_ai/lib/src/schema.dart b/packages/firebase_ai/firebase_ai/lib/src/schema.dart
index 2b0d5ce6a6bb..a33a08cc1cd4 100644
--- a/packages/firebase_ai/firebase_ai/lib/src/schema.dart
+++ b/packages/firebase_ai/firebase_ai/lib/src/schema.dart
@@ -35,6 +35,14 @@ final class Schema {
this.optionalProperties,
this.propertyOrdering,
this.anyOf,
+ this.ref,
+ this.minProperties,
+ this.maxProperties,
+ this.minLength,
+ this.maxLength,
+ this.pattern,
+ this.example,
+ this.defaultValue,
});
/// Construct a schema for an object with one or more properties.
@@ -45,6 +53,10 @@ final class Schema {
String? description,
String? title,
bool? nullable,
+ int? minProperties,
+ int? maxProperties,
+ Object? example,
+ Object? defaultValue,
}) : this(
SchemaType.object,
properties: properties,
@@ -53,6 +65,10 @@ final class Schema {
description: description,
title: title,
nullable: nullable,
+ minProperties: minProperties,
+ maxProperties: maxProperties,
+ example: example,
+ defaultValue: defaultValue,
);
/// Construct a schema for an array of values with a specified type.
@@ -63,6 +79,8 @@ final class Schema {
bool? nullable,
int? minItems,
int? maxItems,
+ Object? example,
+ Object? defaultValue,
}) : this(
SchemaType.array,
description: description,
@@ -71,6 +89,8 @@ final class Schema {
items: items,
minItems: minItems,
maxItems: maxItems,
+ example: example,
+ defaultValue: defaultValue,
);
/// Construct a schema for bool value.
@@ -78,11 +98,15 @@ final class Schema {
String? description,
String? title,
bool? nullable,
+ Object? example,
+ Object? defaultValue,
}) : this(
SchemaType.boolean,
description: description,
title: title,
nullable: nullable,
+ example: example,
+ defaultValue: defaultValue,
);
/// Construct a schema for an integer number.
@@ -95,6 +119,8 @@ final class Schema {
String? format,
int? minimum,
int? maximum,
+ Object? example,
+ Object? defaultValue,
}) : this(
SchemaType.integer,
description: description,
@@ -103,6 +129,8 @@ final class Schema {
format: format,
minimum: minimum?.toDouble(),
maximum: maximum?.toDouble(),
+ example: example,
+ defaultValue: defaultValue,
);
/// Construct a schema for a non-integer number.
@@ -115,6 +143,8 @@ final class Schema {
String? format,
double? minimum,
double? maximum,
+ Object? example,
+ Object? defaultValue,
}) : this(
SchemaType.number,
description: description,
@@ -123,6 +153,8 @@ final class Schema {
format: format,
minimum: minimum,
maximum: maximum,
+ example: example,
+ defaultValue: defaultValue,
);
/// Construct a schema for String value with enumerated possible values.
@@ -131,6 +163,8 @@ final class Schema {
String? description,
String? title,
bool? nullable,
+ Object? example,
+ Object? defaultValue,
}) : this(
SchemaType.string,
enumValues: enumValues,
@@ -138,6 +172,8 @@ final class Schema {
title: title,
nullable: nullable,
format: 'enum',
+ example: example,
+ defaultValue: defaultValue,
);
/// Construct a schema for a String value.
@@ -146,12 +182,22 @@ final class Schema {
String? title,
bool? nullable,
String? format,
+ int? minLength,
+ int? maxLength,
+ String? pattern,
+ Object? example,
+ Object? defaultValue,
}) : this(
SchemaType.string,
description: description,
title: title,
nullable: nullable,
format: format,
+ minLength: minLength,
+ maxLength: maxLength,
+ pattern: pattern,
+ example: example,
+ defaultValue: defaultValue,
);
/// Construct a schema representing a value that must conform to
@@ -180,6 +226,12 @@ final class Schema {
anyOf: schemas,
);
+ /// Construct a schema referencing another schema.
+ Schema.ref(String ref) : this(
+ SchemaType.ref,
+ ref: ref,
+ );
+
/// The type of this value.
SchemaType type;
@@ -257,10 +309,38 @@ final class Schema {
/// Schema.anyOf(schemas: [Schema.string(), Schema.integer()]);
List? anyOf;
+ /// Reference to another schema.
+ String? ref;
+
+ /// Minimum number of properties for Type.OBJECT.
+ int? minProperties;
+
+ /// Maximum number of properties for Type.OBJECT.
+ int? maxProperties;
+
+ /// Minimum length for Type.STRING.
+ int? minLength;
+
+ /// Maximum length for Type.STRING.
+ int? maxLength;
+
+ /// Pattern for Type.STRING to restrict a string to a regular expression.
+ String? pattern;
+
+ /// Example of the object.
+ ///
+ /// When using [toJSONSchemaJson], this will be serialized as a
+ /// single-element array under the `examples` key.
+ Object? example;
+
+ /// Default value of the field.
+ Object? defaultValue;
+
/// Convert to json object.
Map toJson() => {
- if (type != SchemaType.anyOf)
- 'type': type.toJson(), // Omit the field while type is anyOf
+ if (type != SchemaType.anyOf && type != SchemaType.ref)
+ 'type': type.toJson(), // Omit the field while type is anyOf or ref
+ if (ref case final ref?) r'$ref': ref,
if (format case final format?) 'format': format,
if (description case final description?) 'description': description,
if (title case final title?) 'title': title,
@@ -287,6 +367,59 @@ final class Schema {
'propertyOrdering': propertyOrdering,
if (anyOf case final anyOf?)
'anyOf': anyOf.map((e) => e.toJson()).toList(),
+ if (minProperties case final minProperties?)
+ 'minProperties': minProperties,
+ if (maxProperties case final maxProperties?)
+ 'maxProperties': maxProperties,
+ if (minLength case final minLength?)
+ 'minLength': minLength,
+ if (maxLength case final maxLength?)
+ 'maxLength': maxLength,
+ if (pattern case final pattern?)
+ 'pattern': pattern,
+ if (example case final example?) 'example': example,
+ if (defaultValue case final defaultValue?) 'default': defaultValue,
+ };
+
+ /// Convert to standard JSON Schema object.
+ ///
+ /// Reference: https://ai.google.dev/api/caching#FunctionDeclaration
+ Map toJSONSchemaJson() => {
+ if (type != SchemaType.anyOf && type != SchemaType.ref)
+ 'type': nullable == true ? [type.name, 'null'] : type.name,
+ if (ref case final ref?) r'$ref': ref,
+ if (format case final format?) 'format': format,
+ if (description case final description?) 'description': description,
+ if (title case final title?) 'title': title,
+ if (enumValues case final enumValues?) 'enum': enumValues,
+ if (items case final items?) 'items': items.toJSONSchemaJson(),
+ if (minItems case final minItems?) 'minItems': minItems,
+ if (maxItems case final maxItems?) 'maxItems': maxItems,
+ if (minimum case final minimum?) 'minimum': minimum,
+ if (maximum case final maximum?) 'maximum': maximum,
+ if (properties case final properties?)
+ 'properties': {
+ for (final MapEntry(:key, :value) in properties.entries)
+ key: value.toJSONSchemaJson()
+ },
+ // Calculate required properties based on optionalProperties
+ if (properties != null)
+ 'required': optionalProperties != null
+ ? properties!.keys
+ .where((key) => !optionalProperties!.contains(key))
+ .toList()
+ : properties!.keys.toList(),
+ if (anyOf case final anyOf?)
+ 'anyOf': anyOf.map((e) => e.toJSONSchemaJson()).toList(),
+ if (minProperties case final minProperties?)
+ 'minProperties': minProperties,
+ if (maxProperties case final maxProperties?)
+ 'maxProperties': maxProperties,
+ if (minLength case final minLength?) 'minLength': minLength,
+ if (maxLength case final maxLength?) 'maxLength': maxLength,
+ if (pattern case final pattern?) 'pattern': pattern,
+ if (example case final example?) 'examples': [example],
+ if (defaultValue case final defaultValue?) 'default': defaultValue,
};
}
@@ -310,6 +443,9 @@ enum SchemaType {
/// object type
object,
+ /// This schema is a reference type.
+ ref,
+
/// This schema is anyOf type.
anyOf;
@@ -321,6 +457,7 @@ enum SchemaType {
boolean => 'BOOLEAN',
array => 'ARRAY',
object => 'OBJECT',
+ ref => 'null',
anyOf => 'null',
};
}
diff --git a/packages/firebase_ai/firebase_ai/lib/src/tool.dart b/packages/firebase_ai/firebase_ai/lib/src/tool.dart
index 6b92fbd5d96c..228ad5a001d6 100644
--- a/packages/firebase_ai/firebase_ai/lib/src/tool.dart
+++ b/packages/firebase_ai/firebase_ai/lib/src/tool.dart
@@ -173,7 +173,8 @@ class FunctionDeclaration {
// ignore: public_member_api_docs
FunctionDeclaration(this.name, this.description,
{required Map parameters,
- List optionalParameters = const []})
+ List optionalParameters = const [],
+ this.useJSONSchema = false})
: _schemaObject = Schema.object(
properties: parameters, optionalProperties: optionalParameters);
@@ -186,13 +187,19 @@ class FunctionDeclaration {
/// A brief description of the function.
final String description;
+ /// Whether to output parameters using [Schema.toJSONSchemaJson].
+ final bool useJSONSchema;
+
final Schema _schemaObject;
/// Convert to json object.
Map toJson() => {
'name': name,
'description': description,
- 'parameters': _schemaObject.toJson()
+ if (useJSONSchema)
+ 'parametersJsonSchema': _schemaObject.toJSONSchemaJson()
+ else
+ 'parameters': _schemaObject.toJson(),
};
}
@@ -210,9 +217,12 @@ final class AutoFunctionDeclaration extends FunctionDeclaration {
required String description,
required Map parameters,
List optionalParameters = const [],
+ bool useJSONSchema = false,
required this.callable,
}) : super(name, description,
- parameters: parameters, optionalParameters: optionalParameters);
+ parameters: parameters,
+ optionalParameters: optionalParameters,
+ useJSONSchema: useJSONSchema);
/// The callable function that this declaration represents.
final FutureOr