Skip to content

Commit 3c7b987

Browse files
authored
Add google-app-id to Vertex AI requests (#14479)
1 parent df200d7 commit 3c7b987

File tree

8 files changed

+148
-120
lines changed

8 files changed

+148
-120
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 Foundation
16+
17+
import FirebaseAppCheckInterop
18+
import FirebaseAuthInterop
19+
import FirebaseCore
20+
21+
/// Firebase data used by VertexAI
22+
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
23+
struct FirebaseInfo {
24+
let appCheck: AppCheckInterop?
25+
let auth: AuthInterop?
26+
let projectID: String
27+
let apiKey: String
28+
let googleAppID: String
29+
let app: FirebaseApp
30+
31+
init(appCheck: AppCheckInterop? = nil,
32+
auth: AuthInterop? = nil,
33+
projectID: String,
34+
apiKey: String,
35+
googleAppID: String,
36+
firebaseApp: FirebaseApp) {
37+
self.appCheck = appCheck
38+
self.auth = auth
39+
self.projectID = projectID
40+
self.apiKey = apiKey
41+
self.googleAppID = googleAppID
42+
app = firebaseApp
43+
}
44+
}

FirebaseVertexAI/Sources/GenerativeAIService.swift

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,12 @@ struct GenerativeAIService {
2626
/// The Firebase SDK version in the format `fire/<version>`.
2727
static let firebaseVersionTag = "fire/\(FirebaseVersion())"
2828

29-
private let projectID: String
30-
31-
/// Gives permission to talk to the backend.
32-
private let apiKey: String
33-
34-
private let appCheck: AppCheckInterop?
35-
36-
private let auth: AuthInterop?
29+
private let firebaseInfo: FirebaseInfo
3730

3831
private let urlSession: URLSession
3932

40-
init(projectID: String, apiKey: String, appCheck: AppCheckInterop?, auth: AuthInterop?,
41-
urlSession: URLSession) {
42-
self.projectID = projectID
43-
self.apiKey = apiKey
44-
self.appCheck = appCheck
45-
self.auth = auth
33+
init(firebaseInfo: FirebaseInfo, urlSession: URLSession) {
34+
self.firebaseInfo = firebaseInfo
4635
self.urlSession = urlSession
4736
}
4837

@@ -180,14 +169,14 @@ struct GenerativeAIService {
180169
private func urlRequest<T: GenerativeAIRequest>(request: T) async throws -> URLRequest {
181170
var urlRequest = URLRequest(url: request.url)
182171
urlRequest.httpMethod = "POST"
183-
urlRequest.setValue(apiKey, forHTTPHeaderField: "x-goog-api-key")
172+
urlRequest.setValue(firebaseInfo.apiKey, forHTTPHeaderField: "x-goog-api-key")
184173
urlRequest.setValue(
185174
"\(GenerativeAIService.languageTag) \(GenerativeAIService.firebaseVersionTag)",
186175
forHTTPHeaderField: "x-goog-api-client"
187176
)
188177
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
189178

190-
if let appCheck {
179+
if let appCheck = firebaseInfo.appCheck {
191180
let tokenResult = await appCheck.getToken(forcingRefresh: false)
192181
urlRequest.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck")
193182
if let error = tokenResult.error {
@@ -198,10 +187,16 @@ struct GenerativeAIService {
198187
}
199188
}
200189

201-
if let auth, let authToken = try await auth.getToken(forcingRefresh: false) {
190+
if let auth = firebaseInfo.auth, let authToken = try await auth.getToken(
191+
forcingRefresh: false
192+
) {
202193
urlRequest.setValue("Firebase \(authToken)", forHTTPHeaderField: "Authorization")
203194
}
204195

196+
if firebaseInfo.app.isDataCollectionDefaultEnabled {
197+
urlRequest.setValue(firebaseInfo.googleAppID, forHTTPHeaderField: "X-Firebase-AppId")
198+
}
199+
205200
let encoder = JSONEncoder()
206201
urlRequest.httpBody = try encoder.encode(request)
207202
urlRequest.timeoutInterval = request.options.timeout
@@ -260,6 +255,7 @@ struct GenerativeAIService {
260255
// Log specific RPC errors that cannot be mitigated or handled by user code.
261256
// These errors do not produce specific GenerateContentError or CountTokensError cases.
262257
private func logRPCError(_ error: BackendError) {
258+
let projectID = firebaseInfo.projectID
263259
if error.isVertexAIInFirebaseServiceDisabledError() {
264260
VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """
265261
The Vertex AI in Firebase SDK requires the Vertex AI in Firebase API \

FirebaseVertexAI/Sources/GenerativeModel.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,17 @@ public final class GenerativeModel {
5959
/// - requestOptions: Configuration parameters for sending requests to the backend.
6060
/// - urlSession: The `URLSession` to use for requests; defaults to `URLSession.shared`.
6161
init(name: String,
62-
projectID: String,
63-
apiKey: String,
62+
firebaseInfo: FirebaseInfo,
6463
generationConfig: GenerationConfig? = nil,
6564
safetySettings: [SafetySetting]? = nil,
6665
tools: [Tool]?,
6766
toolConfig: ToolConfig? = nil,
6867
systemInstruction: ModelContent? = nil,
6968
requestOptions: RequestOptions,
70-
appCheck: AppCheckInterop?,
71-
auth: AuthInterop?,
7269
urlSession: URLSession = .shared) {
7370
modelResourceName = name
7471
generativeAIService = GenerativeAIService(
75-
projectID: projectID,
76-
apiKey: apiKey,
77-
appCheck: appCheck,
78-
auth: auth,
72+
firebaseInfo: firebaseInfo,
7973
urlSession: urlSession
8074
)
8175
self.generationConfig = generationConfig

FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,14 @@ public final class ImagenModel {
4141
let requestOptions: RequestOptions
4242

4343
init(name: String,
44-
projectID: String,
45-
apiKey: String,
44+
firebaseInfo: FirebaseInfo,
4645
generationConfig: ImagenGenerationConfig?,
4746
safetySettings: ImagenSafetySettings?,
4847
requestOptions: RequestOptions,
49-
appCheck: AppCheckInterop?,
50-
auth: AuthInterop?,
5148
urlSession: URLSession = .shared) {
5249
modelResourceName = name
5350
generativeAIService = GenerativeAIService(
54-
projectID: projectID,
55-
apiKey: apiKey,
56-
appCheck: appCheck,
57-
auth: auth,
51+
firebaseInfo: firebaseInfo,
5852
urlSession: urlSession
5953
)
6054
self.generationConfig = generationConfig

FirebaseVertexAI/Sources/VertexAI.swift

Lines changed: 18 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,13 @@ public class VertexAI {
9191
-> GenerativeModel {
9292
return GenerativeModel(
9393
name: modelResourceName(modelName: modelName),
94-
projectID: projectID,
95-
apiKey: apiKey,
94+
firebaseInfo: firebaseInfo,
9695
generationConfig: generationConfig,
9796
safetySettings: safetySettings,
9897
tools: tools,
9998
toolConfig: toolConfig,
10099
systemInstruction: systemInstruction,
101-
requestOptions: requestOptions,
102-
appCheck: appCheck,
103-
auth: auth
100+
requestOptions: requestOptions
104101
)
105102
}
106103

@@ -126,13 +123,10 @@ public class VertexAI {
126123
requestOptions: RequestOptions = RequestOptions()) -> ImagenModel {
127124
return ImagenModel(
128125
name: modelResourceName(modelName: modelName),
129-
projectID: projectID,
130-
apiKey: apiKey,
126+
firebaseInfo: firebaseInfo,
131127
generationConfig: generationConfig,
132128
safetySettings: safetySettings,
133-
requestOptions: requestOptions,
134-
appCheck: appCheck,
135-
auth: auth
129+
requestOptions: requestOptions
136130
)
137131
}
138132

@@ -142,12 +136,8 @@ public class VertexAI {
142136

143137
// MARK: - Private
144138

145-
/// The `FirebaseApp` associated with this `VertexAI` instance.
146-
private let app: FirebaseApp
147-
148-
private let appCheck: AppCheckInterop?
149-
150-
private let auth: AuthInterop?
139+
/// Firebase data relevant to Vertex AI.
140+
let firebaseInfo: FirebaseInfo
151141

152142
#if compiler(>=6)
153143
/// A map of active `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in
@@ -165,25 +155,26 @@ public class VertexAI {
165155
private static var instancesLock: os_unfair_lock = .init()
166156
#endif
167157

168-
let projectID: String
169-
let apiKey: String
170158
let location: String
171159

172160
init(app: FirebaseApp, location: String) {
173-
self.app = app
174-
appCheck = ComponentType<AppCheckInterop>.instance(for: AppCheckInterop.self, in: app.container)
175-
auth = ComponentType<AuthInterop>.instance(for: AuthInterop.self, in: app.container)
176-
177161
guard let projectID = app.options.projectID else {
178162
fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.")
179163
}
180-
self.projectID = projectID
181-
182164
guard let apiKey = app.options.apiKey else {
183165
fatalError("The Firebase app named \"\(app.name)\" has no API key in its configuration.")
184166
}
185-
self.apiKey = apiKey
186-
167+
firebaseInfo = FirebaseInfo(
168+
appCheck: ComponentType<AppCheckInterop>.instance(
169+
for: AppCheckInterop.self,
170+
in: app.container
171+
),
172+
auth: ComponentType<AuthInterop>.instance(for: AuthInterop.self, in: app.container),
173+
projectID: projectID,
174+
apiKey: apiKey,
175+
googleAppID: app.options.googleAppID,
176+
firebaseApp: app
177+
)
187178
self.location = location
188179
}
189180

@@ -205,6 +196,7 @@ public class VertexAI {
205196
""")
206197
}
207198

199+
let projectID = firebaseInfo.projectID
208200
return "projects/\(projectID)/locations/\(location)/publishers/google/models/\(modelName)"
209201
}
210202
}

FirebaseVertexAI/Tests/Unit/ChatTests.swift

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import Foundation
1616
import XCTest
1717

18+
import FirebaseCore
1819
@testable import FirebaseVertexAI
1920

2021
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
@@ -53,14 +54,19 @@ final class ChatTests: XCTestCase {
5354
return (response, fileURL.lines)
5455
}
5556

57+
let app = FirebaseApp(instanceWithName: "testApp",
58+
options: FirebaseOptions(googleAppID: "ignore",
59+
gcmSenderID: "ignore"))
5660
let model = GenerativeModel(
5761
name: "my-model",
58-
projectID: "my-project-id",
59-
apiKey: "API_KEY",
62+
firebaseInfo: FirebaseInfo(
63+
projectID: "my-project-id",
64+
apiKey: "API_KEY",
65+
googleAppID: "My app ID",
66+
firebaseApp: app
67+
),
6068
tools: nil,
6169
requestOptions: RequestOptions(),
62-
appCheck: nil,
63-
auth: nil,
6470
urlSession: urlSession
6571
)
6672
let chat = Chat(model: model, history: [])

0 commit comments

Comments
 (0)