@@ -47,12 +47,24 @@ struct GenerateContentIntegrationTests {
4747 storage = Storage . storage ( )
4848 }
4949
50- @Test ( arguments: InstanceConfig . allConfigs)
51- func generateContent( _ config: InstanceConfig ) async throws {
50+ @Test ( arguments: [
51+ ( InstanceConfig . vertexAI_v1, ModelNames . gemini2FlashLite) ,
52+ ( InstanceConfig . vertexAI_v1_staging, ModelNames . gemini2FlashLite) ,
53+ ( InstanceConfig . vertexAI_v1beta, ModelNames . gemini2FlashLite) ,
54+ ( InstanceConfig . vertexAI_v1beta_staging, ModelNames . gemini2FlashLite) ,
55+ ( InstanceConfig . googleAI_v1beta, ModelNames . gemini2FlashLite) ,
56+ ( InstanceConfig . googleAI_v1beta, ModelNames . gemma3_27B) ,
57+ ( InstanceConfig . googleAI_v1beta_staging, ModelNames . gemini2FlashLite) ,
58+ ( InstanceConfig . googleAI_v1beta_staging, ModelNames . gemma3_27B) ,
59+ ( InstanceConfig . googleAI_v1_freeTier_bypassProxy, ModelNames . gemini2FlashLite) ,
60+ ( InstanceConfig . googleAI_v1beta_freeTier_bypassProxy, ModelNames . gemini2FlashLite) ,
61+ ( InstanceConfig . googleAI_v1beta_freeTier_bypassProxy, ModelNames . gemma3_27B) ,
62+ ] )
63+ func generateContent( _ config: InstanceConfig , modelName: String ) async throws {
5264 let model = FirebaseAI . componentInstance ( config) . generativeModel (
53- modelName: ModelNames . gemini2FlashLite ,
65+ modelName: modelName ,
5466 generationConfig: generationConfig,
55- safetySettings: safetySettings
67+ safetySettings: safetySettings,
5668 )
5769 let prompt = " Where is Google headquarters located? Answer with the city name only. "
5870
@@ -62,17 +74,22 @@ struct GenerateContentIntegrationTests {
6274 #expect( text == " Mountain View " )
6375
6476 let usageMetadata = try #require( response. usageMetadata)
65- #expect( usageMetadata. promptTokenCount == 13 )
77+ #expect( usageMetadata. promptTokenCount. isEqual ( to : 13 , accuracy : tokenCountAccuracy ) )
6678 #expect( usageMetadata. candidatesTokenCount. isEqual ( to: 3 , accuracy: tokenCountAccuracy) )
6779 #expect( usageMetadata. totalTokenCount. isEqual ( to: 16 , accuracy: tokenCountAccuracy) )
6880 #expect( usageMetadata. promptTokensDetails. count == 1 )
6981 let promptTokensDetails = try #require( usageMetadata. promptTokensDetails. first)
7082 #expect( promptTokensDetails. modality == . text)
7183 #expect( promptTokensDetails. tokenCount == usageMetadata. promptTokenCount)
72- #expect( usageMetadata. candidatesTokensDetails. count == 1 )
73- let candidatesTokensDetails = try #require( usageMetadata. candidatesTokensDetails. first)
74- #expect( candidatesTokensDetails. modality == . text)
75- #expect( candidatesTokensDetails. tokenCount == usageMetadata. candidatesTokenCount)
84+ // The field `candidatesTokensDetails` is not included when using Gemma models.
85+ if modelName == ModelNames . gemma3_27B {
86+ #expect( usageMetadata. candidatesTokensDetails. isEmpty)
87+ } else {
88+ #expect( usageMetadata. candidatesTokensDetails. count == 1 )
89+ let candidatesTokensDetails = try #require( usageMetadata. candidatesTokensDetails. first)
90+ #expect( candidatesTokensDetails. modality == . text)
91+ #expect( candidatesTokensDetails. tokenCount == usageMetadata. candidatesTokenCount)
92+ }
7693 }
7794
7895 @Test (
@@ -168,24 +185,35 @@ struct GenerateContentIntegrationTests {
168185
169186 // MARK: Streaming Tests
170187
171- @Test ( arguments: InstanceConfig . allConfigs)
172- func generateContentStream( _ config: InstanceConfig ) async throws {
173- let expectedText = """
174- 1. Mercury
175- 2. Venus
176- 3. Earth
177- 4. Mars
178- 5. Jupiter
179- 6. Saturn
180- 7. Uranus
181- 8. Neptune
182- """
188+ @Test ( arguments: [
189+ ( InstanceConfig . vertexAI_v1, ModelNames . gemini2FlashLite) ,
190+ ( InstanceConfig . vertexAI_v1_staging, ModelNames . gemini2FlashLite) ,
191+ ( InstanceConfig . vertexAI_v1beta, ModelNames . gemini2FlashLite) ,
192+ ( InstanceConfig . vertexAI_v1beta_staging, ModelNames . gemini2FlashLite) ,
193+ ( InstanceConfig . googleAI_v1beta, ModelNames . gemini2FlashLite) ,
194+ ( InstanceConfig . googleAI_v1beta, ModelNames . gemma3_27B) ,
195+ ( InstanceConfig . googleAI_v1beta_staging, ModelNames . gemini2FlashLite) ,
196+ ( InstanceConfig . googleAI_v1beta_staging, ModelNames . gemma3_27B) ,
197+ ( InstanceConfig . googleAI_v1_freeTier_bypassProxy, ModelNames . gemini2FlashLite) ,
198+ ( InstanceConfig . googleAI_v1beta_freeTier_bypassProxy, ModelNames . gemini2FlashLite) ,
199+ ( InstanceConfig . googleAI_v1beta_freeTier_bypassProxy, ModelNames . gemma3_27B) ,
200+ ] )
201+ func generateContentStream( _ config: InstanceConfig , modelName: String ) async throws {
202+ let expectedResponse = [
203+ " Mercury " , " Venus " , " Earth " , " Mars " , " Jupiter " , " Saturn " , " Uranus " , " Neptune " ,
204+ ]
183205 let prompt = """
184- What are the names of the planets in the solar system, ordered from closest to furthest from
185- the sun? Answer with a Markdown numbered list of the names and no other text.
206+ Generate a JSON array of strings. The array must contain the names of the planets in Earth's \
207+ solar system, ordered from closest to furthest from the Sun.
208+
209+ Constraints:
210+ - Output MUST be only the JSON array.
211+ - Do NOT include any introductory or explanatory text.
212+ - Do NOT wrap the JSON in Markdown code blocks (e.g., ```json ... ``` or ``` ... ```).
213+ - The response must start with '[' and end with ']'.
186214 """
187215 let model = FirebaseAI . componentInstance ( config) . generativeModel (
188- modelName: ModelNames . gemini2FlashLite ,
216+ modelName: modelName ,
189217 generationConfig: generationConfig,
190218 safetySettings: safetySettings
191219 )
@@ -194,7 +222,13 @@ struct GenerateContentIntegrationTests {
194222 let stream = try chat. sendMessageStream ( prompt)
195223 var textValues = [ String] ( )
196224 for try await value in stream {
197- try textValues. append ( #require( value. text) )
225+ if let text = value. text {
226+ textValues. append ( text)
227+ } else if let finishReason = value. candidates. first? . finishReason {
228+ #expect( finishReason == . stop)
229+ } else {
230+ Issue . record ( " Expected a candidate with a `TextPart` or a `finishReason`; got \( value) . " )
231+ }
198232 }
199233
200234 let userHistory = try #require( chat. history. first)
@@ -206,11 +240,9 @@ struct GenerateContentIntegrationTests {
206240 #expect( modelHistory. role == " model " )
207241 #expect( modelHistory. parts. count == 1 )
208242 let modelTextPart = try #require( modelHistory. parts. first as? TextPart )
209- let modelText = modelTextPart. text. trimmingCharacters ( in: . whitespacesAndNewlines)
210- #expect( modelText == expectedText)
211- #expect( textValues. count > 1 )
212- let text = textValues. joined ( ) . trimmingCharacters ( in: . whitespacesAndNewlines)
213- #expect( text == expectedText)
243+ let modelJSONData = try #require( modelTextPart. text. data ( using: . utf8) )
244+ let response = try JSONDecoder ( ) . decode ( [ String ] . self, from: modelJSONData)
245+ #expect( response == expectedResponse)
214246 }
215247
216248 // MARK: - App Check Tests
0 commit comments