diff --git a/packages/firebase_ai/firebase_ai/lib/src/api.dart b/packages/firebase_ai/firebase_ai/lib/src/api.dart index 7a482c087f1d..f669446c7629 100644 --- a/packages/firebase_ai/firebase_ai/lib/src/api.dart +++ b/packages/firebase_ai/firebase_ai/lib/src/api.dart @@ -998,8 +998,10 @@ final class GenerationConfig extends BaseGenerationConfig { super.responseModalities, this.responseMimeType, this.responseSchema, + this.responseJsonSchema, this.thinkingConfig, - }); + }) : assert(responseSchema == null || responseJsonSchema == null, + 'responseSchema and responseJsonSchema cannot both be set.'); /// The set of character sequences (up to 5) that will stop output generation. /// @@ -1018,8 +1020,28 @@ final class GenerationConfig extends BaseGenerationConfig { /// /// - Note: This only applies when the [responseMimeType] supports /// a schema; currently this is limited to `application/json`. + /// + /// Only one of [responseSchema] or [responseJsonSchema] may be specified at + /// the same time. final Schema? responseSchema; + /// The response schema as a JSON-compatible map. + /// + /// - Note: This only applies when the [responseMimeType] supports a schema; + /// currently this is limited to `application/json`. + /// + /// This schema can include more advanced features of JSON than the [Schema] + /// class taken by [responseSchema] supports. See the [Gemini + /// documentation](https://ai.google.dev/api/generate-content#FIELDS.response_json_schema) + /// about the limitations of this feature. + /// + /// Notably, this feature is only supported on Gemini 2.5 and later. Use + /// [responseSchema] for earlier models. + /// + /// Only one of [responseSchema] or [responseJsonSchema] may be specified at + /// the same time. + final Map? responseJsonSchema; + /// Config for thinking features. /// /// An error will be returned if this field is set for models that don't @@ -1036,6 +1058,8 @@ final class GenerationConfig extends BaseGenerationConfig { 'responseMimeType': responseMimeType, if (responseSchema case final responseSchema?) 'responseSchema': responseSchema.toJson(), + if (responseJsonSchema case final responseJsonSchema?) + 'responseJsonSchema': responseJsonSchema, if (thinkingConfig case final thinkingConfig?) 'thinkingConfig': thinkingConfig.toJson(), }; diff --git a/packages/firebase_ai/firebase_ai/test/api_test.dart b/packages/firebase_ai/firebase_ai/test/api_test.dart index a21b17a0ee56..72a4e8536f23 100644 --- a/packages/firebase_ai/firebase_ai/test/api_test.dart +++ b/packages/firebase_ai/firebase_ai/test/api_test.dart @@ -11,6 +11,9 @@ // 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 'dart:convert'; + import 'package:firebase_ai/firebase_ai.dart'; import 'package:firebase_ai/src/api.dart'; @@ -442,6 +445,38 @@ void main() { }); }); + test('GenerationConfig toJson with responseJsonSchema', () { + final jsonSchema = { + 'type': 'object', + 'properties': { + 'recipeName': {'type': 'string'} + }, + 'required': ['recipeName'] + }; + final config = GenerationConfig( + responseMimeType: 'application/json', + responseJsonSchema: jsonSchema, + ); + final json = config.toJson(); + expect(json['responseMimeType'], 'application/json'); + final dynamic responseSchema = json['responseJsonSchema']; + expect(responseSchema, isA>()); + expect(responseSchema, equals(jsonSchema)); + }); + + test( + 'throws assertion if both responseSchema and responseJsonSchema are provided', + () { + final schema = Schema.object(properties: {}); + final jsonSchema = + (json.decode('{"type": "string", "title": "MyString"}') as Map) + .cast(); + expect( + () => GenerationConfig( + responseSchema: schema, responseJsonSchema: jsonSchema), + throwsA(isA())); + }); + test('GenerationConfig toJson with empty stopSequences (omitted)', () { final config = GenerationConfig(stopSequences: []); expect(config.toJson(), {}); // Empty list for stopSequences is omitted