@@ -489,6 +489,73 @@ struct GenerateContentIntegrationTests {
489
489
#expect( response == expectedResponse)
490
490
}
491
491
492
+ @Test ( arguments: [
493
+ ( InstanceConfig . vertexAI_v1beta, ModelNames . gemini2FlashPreviewImageGeneration) ,
494
+ ( InstanceConfig . vertexAI_v1beta_global, ModelNames . gemini2FlashPreviewImageGeneration) ,
495
+ ( InstanceConfig . vertexAI_v1beta_global, ModelNames . gemini2_5_FlashImagePreview) ,
496
+ ( InstanceConfig . googleAI_v1beta, ModelNames . gemini2FlashPreviewImageGeneration) ,
497
+ ( InstanceConfig . googleAI_v1beta, ModelNames . gemini2_5_FlashImagePreview) ,
498
+ // Note: The following configs are commented out for easy one-off manual testing.
499
+ // (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemini2FlashPreviewImageGeneration)
500
+ // (InstanceConfig.googleAI_v1beta_freeTier, ModelNames.gemini2FlashPreviewImageGeneration),
501
+ // (
502
+ // InstanceConfig.googleAI_v1beta_freeTier_bypassProxy,
503
+ // ModelNames.gemini2FlashPreviewImageGeneration
504
+ // ),
505
+ ] )
506
+ func generateImageStreaming( _ config: InstanceConfig , modelName: String ) async throws {
507
+ let generationConfig = GenerationConfig (
508
+ temperature: 0.0 ,
509
+ topP: 0.0 ,
510
+ topK: 1 ,
511
+ responseModalities: [ . text, . image]
512
+ )
513
+ let safetySettings = safetySettings. filter {
514
+ // HARM_CATEGORY_CIVIC_INTEGRITY is deprecated in Vertex AI but only rejected when using the
515
+ // 'gemini-2.0-flash-preview-image-generation' model.
516
+ $0. harmCategory != . civicIntegrity
517
+ }
518
+ let model = FirebaseAI . componentInstance ( config) . generativeModel (
519
+ modelName: modelName,
520
+ generationConfig: generationConfig,
521
+ safetySettings: safetySettings
522
+ )
523
+ let prompt = " Generate an image of a cute cartoon kitten playing with a ball of yarn "
524
+
525
+ let stream = try model. generateContentStream ( prompt)
526
+
527
+ var inlineDataParts = [ InlineDataPart] ( )
528
+ for try await response in stream {
529
+ let candidate = try #require( response. candidates. first)
530
+ let inlineDataPart = candidate. content. parts. first { $0 is InlineDataPart } as? InlineDataPart
531
+ if let inlineDataPart {
532
+ inlineDataParts. append ( inlineDataPart)
533
+ let inlineDataPartsViaAccessor = response. inlineDataParts
534
+ #expect( inlineDataPartsViaAccessor. count == 1 )
535
+ #expect( inlineDataPartsViaAccessor == response. inlineDataParts)
536
+ }
537
+ let textPart = candidate. content. parts. first { $0 is TextPart } as? TextPart
538
+ #expect(
539
+ inlineDataPart != nil || textPart != nil || candidate. finishReason == . stop,
540
+ " No text or image found in the candidate "
541
+ )
542
+ }
543
+
544
+ #expect( inlineDataParts. count == 1 )
545
+ let inlineDataPart = try #require( inlineDataParts. first)
546
+ #expect( inlineDataPart. mimeType == " image/png " )
547
+ #expect( inlineDataPart. data. count > 0 )
548
+ #if canImport(UIKit)
549
+ let uiImage = try #require( UIImage ( data: inlineDataPart. data) )
550
+ // Gemini 2.0 Flash Experimental returns images sized to fit within a 1024x1024 pixel box but
551
+ // dimensions may vary depending on the aspect ratio.
552
+ #expect( uiImage. size. width <= 1024 )
553
+ #expect( uiImage. size. width >= 500 )
554
+ #expect( uiImage. size. height <= 1024 )
555
+ #expect( uiImage. size. height >= 500 )
556
+ #endif // canImport(UIKit)
557
+ }
558
+
492
559
// MARK: - App Check Tests
493
560
494
561
@Test ( arguments: InstanceConfig . appCheckNotConfiguredConfigs)
0 commit comments