@@ -35,6 +35,7 @@ struct SchemaTests {
3535 SafetySetting ( harmCategory: . dangerousContent, threshold: . blockLowAndAbove) ,
3636 SafetySetting ( harmCategory: . civicIntegrity, threshold: . blockLowAndAbove) ,
3737 ]
38+ let generationConfig = GenerationConfig ( temperature: 0.0 , topP: 0.0 , topK: 1 )
3839
3940 @Test (
4041 arguments: testConfigs (
@@ -72,6 +73,31 @@ struct SchemaTests {
7273 #expect( decodedJSON. count <= 5 , " Expected at most 5 cities, but got \( decodedJSON. count) " )
7374 }
7475
76+ struct CityList : Decodable , SchemaConstraintsProvider {
77+ let cities : [ String ]
78+
79+ static var schemaConstraints : [ AnyHashable : SchemaConstraint ] {
80+ [ CodingKeys . cities: . array( minItems: 3 , maxItems: 5 , description: " A list of city names " ) ]
81+ }
82+ }
83+
84+ @Test ( arguments: InstanceConfig . allConfigs)
85+ func generateObjectItemsSchema( _ config: InstanceConfig ) async throws {
86+ let model = FirebaseAI . componentInstance ( config) . generativeModel (
87+ modelName: ModelNames . gemini2_5_FlashLite,
88+ generationConfig: generationConfig,
89+ safetySettings: safetySettings
90+ )
91+ let prompt = " What are the biggest cities in Canada? "
92+
93+ let response = try await model. generateObject ( CityList . self, prompt: prompt)
94+ let cityList = try #require( try response. getObject ( ) )
95+
96+ let cities = cityList. cities
97+ #expect( cities. count >= 3 , " Expected at least 3 cities, but got \( cities. count) " )
98+ #expect( cities. count <= 5 , " Expected at most 5 cities, but got \( cities. count) " )
99+ }
100+
75101 @Test ( arguments: testConfigs (
76102 instanceConfigs: InstanceConfig . allConfigs,
77103 openAPISchema: . integer(
@@ -102,6 +128,22 @@ struct SchemaTests {
102128 #expect( decodedNumber <= 120.0 , " Expected a number <= 120, but got \( decodedNumber) " )
103129 }
104130
131+ struct ProductInfo : Decodable , SchemaConstraintsProvider {
132+ let productName : String
133+ let rating : Int
134+ let price : Double
135+ let salePrice : Float
136+
137+ static var schemaConstraints : [ AnyHashable : SchemaConstraint ] {
138+ [
139+ CodingKeys . productName: . string( description: " The name of the product " ) ,
140+ CodingKeys . rating: . integer( 1 ... 5 , description: " A rating " ) ,
141+ CodingKeys . price: . number( min: 10.00 , max: 120.00 , description: " A price " ) ,
142+ CodingKeys . salePrice: . number( min: 5.00 , max: 90.00 , description: " A sale price " ) ,
143+ ]
144+ }
145+ }
146+
105147 @Test ( arguments: testConfigs (
106148 instanceConfigs: InstanceConfig . allConfigs,
107149 openAPISchema: . object(
@@ -169,13 +211,6 @@ struct SchemaTests {
169211 ) )
170212 func generateContentSchemaNumberRangeMultiType( _ config: InstanceConfig ,
171213 _ schema: SchemaType ) async throws {
172- struct ProductInfo : Codable {
173- let productName : String
174- let rating : Int
175- let price : Double
176- let salePrice : Float
177- }
178-
179214 let model = FirebaseAI . componentInstance ( config) . generativeModel (
180215 modelName: ModelNames . gemini2_5_FlashLite,
181216 generationConfig: SchemaTests . generationConfig ( schema: schema) ,
@@ -197,6 +232,29 @@ struct SchemaTests {
197232 #expect( rating <= 5 , " Expected a rating <= 5, but got \( rating) " )
198233 }
199234
235+ @Test ( arguments: InstanceConfig . allConfigs)
236+ func generateObjectSchemaNumberRangeMultiType( _ config: InstanceConfig ) async throws {
237+ let model = FirebaseAI . componentInstance ( config) . generativeModel (
238+ modelName: ModelNames . gemini2_5_FlashLite,
239+ generationConfig: generationConfig,
240+ safetySettings: safetySettings
241+ )
242+ let prompt = " Describe a premium wireless headphone, including a user rating and price. "
243+
244+ let response = try await model. generateObject ( ProductInfo . self, prompt: prompt)
245+ let productInfo = try #require( try response. getObject ( ) )
246+
247+ let price = productInfo. price
248+ let salePrice = productInfo. salePrice
249+ let rating = productInfo. rating
250+ #expect( price >= 10.0 , " Expected a price >= 10.00, but got \( price) " )
251+ #expect( price <= 120.0 , " Expected a price <= 120.00, but got \( price) " )
252+ #expect( salePrice >= 5.0 , " Expected a salePrice >= 5.00, but got \( salePrice) " )
253+ #expect( salePrice <= 90.0 , " Expected a salePrice <= 90.00, but got \( salePrice) " )
254+ #expect( rating >= 1 , " Expected a rating >= 1, but got \( rating) " )
255+ #expect( rating <= 5 , " Expected a rating <= 5, but got \( rating) " )
256+ }
257+
200258 fileprivate struct MailingAddress {
201259 enum PostalInfo {
202260 struct Canada : Decodable {
@@ -354,6 +412,44 @@ struct SchemaTests {
354412 }
355413 }
356414
415+ struct Catalog : Decodable {
416+ let name : String
417+ let categories : [ Category ]
418+
419+ struct Category : Decodable {
420+ let title : String
421+ let items : [ Item ]
422+
423+ struct Item : Decodable {
424+ let name : String
425+ let price : Double
426+ }
427+ }
428+ }
429+
430+ @Test ( arguments: InstanceConfig . allConfigs)
431+ func generateObjectSchemaNestedTypes( _ config: InstanceConfig ) async throws {
432+ let model = FirebaseAI . componentInstance ( config) . generativeModel (
433+ modelName: ModelNames . gemini2_5_FlashLite,
434+ generationConfig: generationConfig,
435+ safetySettings: safetySettings
436+ )
437+ let prompt = """
438+ Create a catalog named 'Tech' with a category 'Computers' containing an item 'Laptop' for \
439+ $999.99.
440+ """
441+
442+ let response = try await model. generateObject ( Catalog . self, prompt: prompt)
443+ let catalog = try #require( try response. getObject ( ) )
444+
445+ #expect( catalog. name == " Tech " )
446+ #expect( catalog. categories. count == 1 )
447+ #expect( catalog. categories [ 0 ] . title == " Computers " )
448+ #expect( catalog. categories [ 0 ] . items. count == 1 )
449+ #expect( catalog. categories [ 0 ] . items [ 0 ] . name == " Laptop " )
450+ #expect( catalog. categories [ 0 ] . items [ 0 ] . price == 999.99 )
451+ }
452+
357453 enum SchemaType : CustomTestStringConvertible {
358454 case openAPI( Schema )
359455 case json( JSONObject )
0 commit comments