Skip to content

feat(firebaseai): Add new attributes for schema #17340

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

Merged
merged 1 commit into from
May 8, 2025
Merged
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
126 changes: 122 additions & 4 deletions packages/firebase_vertexai/firebase_vertexai/lib/src/schema.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,65 @@ final class Schema {
this.type, {
this.format,
this.description,
this.title,
this.nullable,
this.enumValues,
this.items,
this.minItems,
this.maxItems,
this.minimum,
this.maximum,
this.properties,
this.optionalProperties,
this.propertyOrdering,
this.anyOf,
});

/// Construct a schema for an object with one or more properties.
Schema.object({
required Map<String, Schema> properties,
List<String>? optionalProperties,
List<String>? propertyOrdering,
String? description,
String? title,
bool? nullable,
}) : this(
SchemaType.object,
properties: properties,
optionalProperties: optionalProperties,
propertyOrdering: propertyOrdering,
description: description,
title: title,
nullable: nullable,
);

/// Construct a schema for an array of values with a specified type.
Schema.array({
required Schema items,
String? description,
String? title,
bool? nullable,
int? minItems,
int? maxItems,
}) : this(
SchemaType.array,
description: description,
title: title,
nullable: nullable,
items: items,
minItems: minItems,
maxItems: maxItems,
);

/// Construct a schema for bool value.
Schema.boolean({
String? description,
String? title,
bool? nullable,
}) : this(
SchemaType.boolean,
description: description,
title: title,
nullable: nullable,
);

Expand All @@ -71,49 +90,95 @@ final class Schema {
/// The [format] may be "int32" or "int64".
Schema.integer({
String? description,
String? title,
bool? nullable,
String? format,
int? minimum,
int? maximum,
}) : this(
SchemaType.integer,
description: description,
title: title,
nullable: nullable,
format: format,
minimum: minimum?.toDouble(),
maximum: maximum?.toDouble(),
);

/// Construct a schema for a non-integer number.
///
/// The [format] may be "float" or "double".
Schema.number({
String? description,
String? title,
bool? nullable,
String? format,
double? minimum,
double? maximum,
}) : this(
SchemaType.number,
description: description,
title: title,
nullable: nullable,
format: format,
minimum: minimum,
maximum: maximum,
);

/// Construct a schema for String value with enumerated possible values.
Schema.enumString({
required List<String> enumValues,
String? description,
String? title,
bool? nullable,
}) : this(
SchemaType.string,
enumValues: enumValues,
description: description,
title: title,
nullable: nullable,
format: 'enum',
);

/// Construct a schema for a String value.
Schema.string({
String? description,
String? title,
bool? nullable,
String? format,
}) : this(SchemaType.string,
description: description, nullable: nullable, format: format);
}) : this(
SchemaType.string,
description: description,
title: title,
nullable: nullable,
format: format,
);

/// Construct a schema representing a value that must conform to
/// *any* (one or more) of the provided sub-schemas.
///
/// This schema instructs the model to produce data that is valid against at
/// least one of the schemas listed in the `schemas` array. This is useful
/// when a field can accept multiple distinct types or structures.
///
/// **Example:** A field that can hold either a simple user ID (integer) or a
/// detailed user object.
/// ```
/// Schema.anyOf(anyOf: [
/// .Schema.integer(description: "User ID"),
/// .Schema.object(properties: [
/// "userId": Schema.integer(),
/// "userName": Schema.string()
/// ], description: "Detailed User Object")
/// ])
/// ```
/// The generated data could be decoded based on which schema it matches.
Schema.anyOf({
required List<Schema> schemas,
}) : this(
SchemaType.anyOf, // The type will be ignored in toJson
anyOf: schemas,
);

/// The type of this value.
SchemaType type;
Expand All @@ -134,6 +199,13 @@ final class Schema {
/// Parameter description may be formatted as Markdown.
String? description;

/// A human-readable name/summary for the schema or a specific property.
///
/// This helps document the schema's purpose but doesn't typically constrain
/// the generated value. It can subtly guide the model by clarifying the
/// intent of a field.
String? title;

/// Whether the value mey be null.
bool? nullable;

Expand All @@ -143,6 +215,18 @@ final class Schema {
/// Schema for the elements if this is a [SchemaType.array].
Schema? items;

/// An integer specifying the minimum number of items [SchemaType.array] must contain.
int? minItems;

/// An integer specifying the maximum number of items [SchemaType.array] must contain.
int? maxItems;

/// The minimum value of a numeric type.
double? minimum;

/// The maximum value of a numeric type.
double? maximum;

/// Properties of this type if this is a [SchemaType.object].
Map<String, Schema>? properties;

Expand All @@ -153,14 +237,40 @@ final class Schema {
/// treated as required properties
List<String>? optionalProperties;

/// Suggesting order of the properties.
///
/// A specific hint provided to the Gemini model, suggesting the order in
/// which the keys should appear in the generated JSON string.
/// Important: Standard JSON objects are inherently unordered collections of
/// key-value pairs. While the model will try to respect PropertyOrdering in
/// its textual JSON output.
List<String>? propertyOrdering;

/// An array of [Schema] objects to validate generated content.
///
/// The generated data must be valid against *any* (one or more)
/// of the schemas listed in this array. This allows specifying multiple
/// possible structures or types for a single field.
///
/// For example, a value could be either a `String` or an `Int`:
/// ```
/// Schema.anyOf(schemas: [Schema.string(), Schema.integer()]);
List<Schema>? anyOf;

/// Convert to json object.
Map<String, Object> toJson() => {
'type': type.toJson(),
if (type != SchemaType.anyOf)
'type': type.toJson(), // Omit the field while type is anyOf
if (format case final format?) 'format': format,
if (description case final description?) 'description': description,
if (title case final title?) 'title': title,
if (nullable case final nullable?) 'nullable': nullable,
if (enumValues case final enumValues?) 'enum': enumValues,
if (items case final items?) 'items': items.toJson(),
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)
Expand All @@ -173,6 +283,10 @@ final class Schema {
.where((key) => !optionalProperties!.contains(key))
.toList()
: properties!.keys.toList(),
if (propertyOrdering case final propertyOrdering?)
'propertyOrdering': propertyOrdering,
if (anyOf case final anyOf?)
'anyOf': anyOf.map((e) => e.toJson()).toList(),
};
}

Expand All @@ -194,7 +308,10 @@ enum SchemaType {
array,

/// object type
object;
object,

/// This schema is anyOf type.
anyOf;

/// Convert to json object.
String toJson() => switch (this) {
Expand All @@ -204,5 +321,6 @@ enum SchemaType {
boolean => 'BOOLEAN',
array => 'ARRAY',
object => 'OBJECT',
anyOf => 'null',
};
}
Loading
Loading