Skip to content

Commit b54dc0e

Browse files
committed
[Firebase AI] Add handling for Google AI-formatted CitationMetadata
1 parent 5659604 commit b54dc0e

File tree

2 files changed

+100
-1
lines changed

2 files changed

+100
-1
lines changed

FirebaseAI/Sources/GenerateContentResponse.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,23 @@ extension Candidate: Decodable {
385385
}
386386

387387
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
388-
extension CitationMetadata: Decodable {}
388+
extension CitationMetadata: Decodable {
389+
enum CodingKeys: CodingKey {
390+
case citations // Vertex AI
391+
case citationSources // Google AI
392+
}
393+
394+
public init(from decoder: any Decoder) throws {
395+
let container = try decoder.container(keyedBy: CodingKeys.self)
396+
397+
// Decode for Google API if `citationSources` key is present.
398+
if container.contains(.citationSources) {
399+
citations = try container.decode([Citation].self, forKey: .citationSources)
400+
} else { // Fallback to default Vertex AI decoding.
401+
citations = try container.decode([Citation].self, forKey: .citations)
402+
}
403+
}
404+
}
389405

390406
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
391407
extension Citation: Decodable {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseAI
16+
import XCTest
17+
18+
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
19+
final class CitationMetadataTests: XCTestCase {
20+
let decoder = JSONDecoder()
21+
22+
// MARK: - Google AI Format Decoding
23+
24+
func testDecodeCitationMetadata_googleAIFormat() throws {
25+
let json = """
26+
{
27+
"citationSources": [
28+
{
29+
"startIndex": 100,
30+
"endIndex": 200,
31+
"uri": "https://example.com/citation-1"
32+
}
33+
]
34+
}
35+
"""
36+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
37+
38+
let citationMetadata = try decoder.decode(
39+
CitationMetadata.self,
40+
from: jsonData
41+
)
42+
43+
XCTAssertEqual(citationMetadata.citations.count, 1)
44+
let citation = try XCTUnwrap(citationMetadata.citations.first)
45+
XCTAssertEqual(citation.startIndex, 100)
46+
XCTAssertEqual(citation.endIndex, 200)
47+
XCTAssertEqual(citation.uri, "https://example.com/citation-1")
48+
XCTAssertNil(citation.license)
49+
XCTAssertNil(citation.publicationDate)
50+
XCTAssertNil(citation.title)
51+
}
52+
53+
// MARK: - Vertex AI Format Decoding
54+
55+
func testDecodeCitationMetadata_vertexAIFormat_basic() throws {
56+
let json = """
57+
{
58+
"citations": [
59+
{
60+
"startIndex": 100,
61+
"endIndex": 200,
62+
"uri": "https://example.com/citation-1"
63+
}
64+
]
65+
}
66+
"""
67+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
68+
69+
let citationMetadata = try decoder.decode(
70+
CitationMetadata.self,
71+
from: jsonData
72+
)
73+
74+
XCTAssertEqual(citationMetadata.citations.count, 1)
75+
let citation = try XCTUnwrap(citationMetadata.citations.first)
76+
XCTAssertEqual(citation.startIndex, 100)
77+
XCTAssertEqual(citation.endIndex, 200)
78+
XCTAssertEqual(citation.uri, "https://example.com/citation-1")
79+
XCTAssertNil(citation.license)
80+
XCTAssertNil(citation.publicationDate)
81+
XCTAssertNil(citation.title)
82+
}
83+
}

0 commit comments

Comments
 (0)