Skip to content

Commit 990acb1

Browse files
committed
Add unit test for empty parts handling when streaming
1 parent e979a61 commit 990acb1

File tree

3 files changed

+36
-7
lines changed

3 files changed

+36
-7
lines changed

FirebaseAI/Sources/GenerativeModel.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ public final class GenerativeModel: Sendable {
230230
let responseStream = generativeAIService.loadRequestStream(request: generateContentRequest)
231231
Task {
232232
do {
233+
var didYieldResponse = false
233234
for try await response in responseStream {
234235
// Check the prompt feedback to see if the prompt was blocked.
235236
if response.promptFeedback?.blockReason != nil {
@@ -254,9 +255,20 @@ public final class GenerativeModel: Sendable {
254255
)
255256
} else {
256257
continuation.yield(response)
258+
didYieldResponse = true
257259
}
258260
}
259-
continuation.finish()
261+
262+
// Throw an error if all responses were skipped due to empty content.
263+
if didYieldResponse {
264+
continuation.finish()
265+
} else {
266+
continuation.finish(throwing: GenerativeModel.generateContentError(
267+
from: InvalidCandidateError.emptyContent(
268+
underlyingError: Candidate.EmptyContentError()
269+
)
270+
))
271+
}
260272
} catch {
261273
continuation.finish(throwing: GenerativeModel.generateContentError(from: error))
262274
return

FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,23 @@ final class GenerativeModelGoogleAITests: XCTestCase {
509509
XCTAssertTrue(thoughtSignature.hasPrefix("CiIBVKhc7vB+vaaq6rA"))
510510
}
511511

512+
func testGenerateContentStream_success_ignoresEmptyParts() async throws {
513+
MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler(
514+
forResource: "streaming-success-empty-parts",
515+
withExtension: "txt",
516+
subdirectory: googleAISubdirectory
517+
)
518+
519+
let stream = try model.generateContentStream("Hi")
520+
for try await response in stream {
521+
let candidate = try XCTUnwrap(response.candidates.first)
522+
XCTAssertGreaterThan(candidate.content.parts.count, 0)
523+
let text = response.text
524+
let inlineData = response.inlineDataParts.first
525+
XCTAssertTrue(text != nil || inlineData != nil, "Response did not contain text or data")
526+
}
527+
}
528+
512529
func testGenerateContentStream_failureInvalidAPIKey() async throws {
513530
MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler(
514531
forResource: "unary-failure-api-key",

FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -786,12 +786,12 @@ final class GenerativeModelVertexAITests: XCTestCase {
786786
XCTFail("Should throw GenerateContentError.internalError; no error thrown.")
787787
} catch let GenerateContentError
788788
.internalError(underlying: invalidCandidateError as InvalidCandidateError) {
789-
guard case let .emptyContent(decodingError) = invalidCandidateError else {
789+
guard case let .emptyContent(underlyingError) = invalidCandidateError else {
790790
XCTFail("Not an InvalidCandidateError.emptyContent error: \(invalidCandidateError)")
791791
return
792792
}
793-
_ = try XCTUnwrap(decodingError as? DecodingError,
794-
"Not a DecodingError: \(decodingError)")
793+
_ = try XCTUnwrap(underlyingError as? Candidate.EmptyContentError,
794+
"Not an empty content error: \(underlyingError)")
795795
} catch {
796796
XCTFail("Should throw GenerateContentError.internalError; error thrown: \(error)")
797797
}
@@ -1047,8 +1047,8 @@ final class GenerativeModelVertexAITests: XCTestCase {
10471047
return
10481048
}
10491049
_ = try XCTUnwrap(
1050-
emptyContentUnderlyingError as? DecodingError,
1051-
"Not a decoding error: \(emptyContentUnderlyingError)"
1050+
emptyContentUnderlyingError as? Candidate.EmptyContentError,
1051+
"Not an empty content error: \(emptyContentUnderlyingError)"
10521052
)
10531053
}
10541054

@@ -1595,7 +1595,7 @@ final class GenerativeModelVertexAITests: XCTestCase {
15951595
return
15961596
}
15971597

1598-
XCTAssert(contentError is DecodingError)
1598+
XCTAssert(contentError is Candidate.EmptyContentError)
15991599
return
16001600
}
16011601

0 commit comments

Comments
 (0)