Skip to content

feat(firebaseai): add responseJsonSchema to GenerationConfig #17564

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
26 changes: 25 additions & 1 deletion packages/firebase_ai/firebase_ai/lib/src/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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<String, Object?>? responseJsonSchema;

/// Config for thinking features.
///
/// An error will be returned if this field is set for models that don't
Expand All @@ -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(),
};
Expand Down
35 changes: 35 additions & 0 deletions packages/firebase_ai/firebase_ai/test/api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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<Map<String, Object?>>());
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<String, Object?>();
expect(
() => GenerationConfig(
responseSchema: schema, responseJsonSchema: jsonSchema),
throwsA(isA<AssertionError>()));
});

test('GenerationConfig toJson with empty stopSequences (omitted)', () {
final config = GenerationConfig(stopSequences: []);
expect(config.toJson(), {}); // Empty list for stopSequences is omitted
Expand Down