Skip to content

Commit 5b82827

Browse files
authored
[Vertex AI] Make text computed property handle mixed-parts responses (#12921)
1 parent 3e44a2f commit 5b82827

File tree

4 files changed

+119
-2
lines changed

4 files changed

+119
-2
lines changed

FirebaseVertexAI/Sources/GenerateContentResponse.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,17 @@ public struct GenerateContentResponse {
4545
Logging.default.error("Could not get text from a response that had no candidates.")
4646
return nil
4747
}
48-
guard let text = candidate.content.parts.first?.text else {
48+
let textValues: [String] = candidate.content.parts.compactMap { part in
49+
guard case let .text(text) = part else {
50+
return nil
51+
}
52+
return text
53+
}
54+
guard textValues.count > 0 else {
4955
Logging.default.error("Could not get a text part from the first candidate.")
5056
return nil
5157
}
52-
return text
58+
return textValues.joined(separator: " ")
5359
}
5460

5561
/// Returns function calls found in any `Part`s of the first candidate of the response, if any.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"candidates": [
3+
{
4+
"content": {
5+
"parts": [
6+
{
7+
"text": "The sum of [1, 2,"
8+
},
9+
{
10+
"functionCall": {
11+
"name": "sum",
12+
"args": {
13+
"y": 1,
14+
"x": 2
15+
}
16+
}
17+
},
18+
{
19+
"text": "3] is"
20+
},
21+
{
22+
"functionCall": {
23+
"name": "sum",
24+
"args": {
25+
"y": 3,
26+
"x": 3
27+
}
28+
}
29+
}
30+
],
31+
"role": "model"
32+
},
33+
"finishReason": "STOP",
34+
"index": 0
35+
}
36+
]
37+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"candidates": [
3+
{
4+
"content": {
5+
"parts": [
6+
{
7+
"functionCall": {
8+
"name": "sum",
9+
"args": {
10+
"y": 1,
11+
"x": 2
12+
}
13+
}
14+
},
15+
{
16+
"functionCall": {
17+
"name": "sum",
18+
"args": {
19+
"y": 3,
20+
"x": 4
21+
}
22+
}
23+
},
24+
{
25+
"functionCall": {
26+
"name": "sum",
27+
"args": {
28+
"y": 5,
29+
"x": 6
30+
}
31+
}
32+
}
33+
],
34+
"role": "model"
35+
},
36+
"finishReason": "STOP",
37+
"index": 0
38+
}
39+
]
40+
}

FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,40 @@ final class GenerativeModelTests: XCTestCase {
261261
XCTAssertEqual(response.functionCalls, [functionCall])
262262
}
263263

264+
func testGenerateContent_success_functionCall_parallelCalls() async throws {
265+
MockURLProtocol
266+
.requestHandler = try httpRequestHandler(
267+
forResource: "unary-success-function-call-parallel-calls",
268+
withExtension: "json"
269+
)
270+
271+
let response = try await model.generateContent(testPrompt)
272+
273+
XCTAssertEqual(response.candidates.count, 1)
274+
let candidate = try XCTUnwrap(response.candidates.first)
275+
XCTAssertEqual(candidate.content.parts.count, 3)
276+
let functionCalls = response.functionCalls
277+
XCTAssertEqual(functionCalls.count, 3)
278+
}
279+
280+
func testGenerateContent_success_functionCall_mixedContent() async throws {
281+
MockURLProtocol
282+
.requestHandler = try httpRequestHandler(
283+
forResource: "unary-success-function-call-mixed-content",
284+
withExtension: "json"
285+
)
286+
287+
let response = try await model.generateContent(testPrompt)
288+
289+
XCTAssertEqual(response.candidates.count, 1)
290+
let candidate = try XCTUnwrap(response.candidates.first)
291+
XCTAssertEqual(candidate.content.parts.count, 4)
292+
let functionCalls = response.functionCalls
293+
XCTAssertEqual(functionCalls.count, 2)
294+
let text = try XCTUnwrap(response.text)
295+
XCTAssertEqual(text, "The sum of [1, 2, 3] is")
296+
}
297+
264298
func testGenerateContent_appCheck_validToken() async throws {
265299
let appCheckToken = "test-valid-token"
266300
model = GenerativeModel(

0 commit comments

Comments
 (0)