Skip to content

Commit 25f1a17

Browse files
authored
[Vertex AI] Add APIConfig to support switching to the Developer API (#14521)
1 parent 2583fef commit 25f1a17

17 files changed

+412
-166
lines changed

FirebaseVertexAI/Sources/APIVersion.swift

Lines changed: 0 additions & 30 deletions
This file was deleted.

FirebaseVertexAI/Sources/Constants.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ import Foundation
1616

1717
/// Constants associated with the Vertex AI for Firebase SDK.
1818
enum Constants {
19-
/// The Vertex AI backend endpoint URL.
20-
static let baseURL = "https://firebasevertexai.googleapis.com"
21-
2219
/// The base reverse-DNS name for `NSError` or `CustomNSError` error domains.
2320
///
2421
/// - Important: A suffix must be appended to produce an error domain (e.g.,

FirebaseVertexAI/Sources/CountTokensRequest.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ struct CountTokensRequest {
2323
let tools: [Tool]?
2424
let generationConfig: GenerationConfig?
2525

26+
let apiConfig: APIConfig
2627
let options: RequestOptions
2728
}
2829

@@ -31,7 +32,8 @@ extension CountTokensRequest: GenerativeAIRequest {
3132
typealias Response = CountTokensResponse
3233

3334
var url: URL {
34-
URL(string: "\(Constants.baseURL)/\(options.apiVersion)/\(model):countTokens")!
35+
URL(string:
36+
"\(apiConfig.service.endpoint.rawValue)/\(apiConfig.version.rawValue)/\(model):countTokens")!
3537
}
3638
}
3739

FirebaseVertexAI/Sources/GenerateContentRequest.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ struct GenerateContentRequest: Sendable {
2424
let tools: [Tool]?
2525
let toolConfig: ToolConfig?
2626
let systemInstruction: ModelContent?
27-
let isStreaming: Bool
27+
let apiConfig: APIConfig
28+
let apiMethod: APIMethod
2829
let options: RequestOptions
2930
}
3031

@@ -40,16 +41,28 @@ extension GenerateContentRequest: Encodable {
4041
}
4142
}
4243

44+
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
45+
extension GenerateContentRequest {
46+
enum APIMethod: String {
47+
case generateContent
48+
case streamGenerateContent
49+
case countTokens
50+
}
51+
}
52+
4353
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
4454
extension GenerateContentRequest: GenerativeAIRequest {
4555
typealias Response = GenerateContentResponse
4656

4757
var url: URL {
48-
let modelURL = "\(Constants.baseURL)/\(options.apiVersion)/\(model)"
49-
if isStreaming {
50-
return URL(string: "\(modelURL):streamGenerateContent?alt=sse")!
51-
} else {
52-
return URL(string: "\(modelURL):generateContent")!
58+
let modelURL = "\(apiConfig.service.endpoint.rawValue)/\(apiConfig.version.rawValue)/\(model)"
59+
switch apiMethod {
60+
case .generateContent:
61+
return URL(string: "\(modelURL):\(apiMethod.rawValue)")!
62+
case .streamGenerateContent:
63+
return URL(string: "\(modelURL):\(apiMethod.rawValue)?alt=sse")!
64+
case .countTokens:
65+
fatalError("\(Self.self) should be a property of \(CountTokensRequest.self).")
5366
}
5467
}
5568
}

FirebaseVertexAI/Sources/GenerativeAIRequest.swift

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,17 @@ protocol GenerativeAIRequest: Sendable, Encodable {
2525

2626
/// Configuration parameters for sending requests to the backend.
2727
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
28-
// TODO(#14405): Make the `apiVersion` constructor public in Firebase 12 with a default of `.v1`.
2928
public struct RequestOptions: Sendable {
3029
/// The request’s timeout interval in seconds; if not specified uses the default value for a
3130
/// `URLRequest`.
3231
let timeout: TimeInterval
3332

34-
/// The API version to use in requests to the backend.
35-
let apiVersion: String
36-
37-
/// Initializes a request options object.
38-
///
39-
/// - Parameters:
40-
/// - timeout: The request’s timeout interval in seconds; defaults to 180 seconds.
41-
/// - apiVersion: The API version to use in requests to the backend.
42-
init(timeout: TimeInterval = 180.0, apiVersion: APIVersion) {
43-
self.timeout = timeout
44-
self.apiVersion = apiVersion.versionIdentifier
45-
}
46-
4733
/// Initializes a request options object.
4834
///
4935
/// - Parameters:
5036
/// - timeout: The request’s timeout interval in seconds; defaults to 180 seconds.
5137
public init(timeout: TimeInterval = 180.0) {
52-
self.init(timeout: timeout, apiVersion: .v1beta)
38+
self.timeout = timeout
5339
}
5440
}
5541

FirebaseVertexAI/Sources/GenerativeModel.swift

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public final class GenerativeModel: Sendable {
2323
/// The resource name of the model in the backend; has the format "models/model-name".
2424
let modelResourceName: String
2525

26+
/// Configuration for the backend API used by this model.
27+
let apiConfig: APIConfig
28+
2629
/// The backing service responsible for sending and receiving model requests to the backend.
2730
let generativeAIService: GenerativeAIService
2831

@@ -48,8 +51,8 @@ public final class GenerativeModel: Sendable {
4851
///
4952
/// - Parameters:
5053
/// - name: The name of the model to use, for example `"gemini-1.0-pro"`.
51-
/// - projectID: The project ID from the Firebase console.
52-
/// - apiKey: The API key for your project.
54+
/// - firebaseInfo: Firebase data used by the SDK, including project ID and API key.
55+
/// - apiConfig: Configuration for the backend API used by this model.
5356
/// - generationConfig: The content generation parameters your model should use.
5457
/// - safetySettings: A value describing what types of harmful content your model should allow.
5558
/// - tools: A list of ``Tool`` objects that the model may use to generate the next response.
@@ -60,6 +63,7 @@ public final class GenerativeModel: Sendable {
6063
/// - urlSession: The `URLSession` to use for requests; defaults to `URLSession.shared`.
6164
init(name: String,
6265
firebaseInfo: FirebaseInfo,
66+
apiConfig: APIConfig,
6367
generationConfig: GenerationConfig? = nil,
6468
safetySettings: [SafetySetting]? = nil,
6569
tools: [Tool]?,
@@ -68,6 +72,7 @@ public final class GenerativeModel: Sendable {
6872
requestOptions: RequestOptions,
6973
urlSession: URLSession = .shared) {
7074
modelResourceName = name
75+
self.apiConfig = apiConfig
7176
generativeAIService = GenerativeAIService(
7277
firebaseInfo: firebaseInfo,
7378
urlSession: urlSession
@@ -118,15 +123,18 @@ public final class GenerativeModel: Sendable {
118123
-> GenerateContentResponse {
119124
try content.throwIfError()
120125
let response: GenerateContentResponse
121-
let generateContentRequest = GenerateContentRequest(model: modelResourceName,
122-
contents: content,
123-
generationConfig: generationConfig,
124-
safetySettings: safetySettings,
125-
tools: tools,
126-
toolConfig: toolConfig,
127-
systemInstruction: systemInstruction,
128-
isStreaming: false,
129-
options: requestOptions)
126+
let generateContentRequest = GenerateContentRequest(
127+
model: modelResourceName,
128+
contents: content,
129+
generationConfig: generationConfig,
130+
safetySettings: safetySettings,
131+
tools: tools,
132+
toolConfig: toolConfig,
133+
systemInstruction: systemInstruction,
134+
apiConfig: apiConfig,
135+
apiMethod: .generateContent,
136+
options: requestOptions
137+
)
130138
do {
131139
response = try await generativeAIService.loadRequest(request: generateContentRequest)
132140
} catch {
@@ -175,15 +183,18 @@ public final class GenerativeModel: Sendable {
175183
public func generateContentStream(_ content: [ModelContent]) throws
176184
-> AsyncThrowingStream<GenerateContentResponse, Error> {
177185
try content.throwIfError()
178-
let generateContentRequest = GenerateContentRequest(model: modelResourceName,
179-
contents: content,
180-
generationConfig: generationConfig,
181-
safetySettings: safetySettings,
182-
tools: tools,
183-
toolConfig: toolConfig,
184-
systemInstruction: systemInstruction,
185-
isStreaming: true,
186-
options: requestOptions)
186+
let generateContentRequest = GenerateContentRequest(
187+
model: modelResourceName,
188+
contents: content,
189+
generationConfig: generationConfig,
190+
safetySettings: safetySettings,
191+
tools: tools,
192+
toolConfig: toolConfig,
193+
systemInstruction: systemInstruction,
194+
apiConfig: apiConfig,
195+
apiMethod: .streamGenerateContent,
196+
options: requestOptions
197+
)
187198

188199
return AsyncThrowingStream { continuation in
189200
let responseStream = generativeAIService.loadRequestStream(request: generateContentRequest)
@@ -249,6 +260,7 @@ public final class GenerativeModel: Sendable {
249260
systemInstruction: systemInstruction,
250261
tools: tools,
251262
generationConfig: generationConfig,
263+
apiConfig: apiConfig,
252264
options: requestOptions
253265
)
254266
return try await generativeAIService.loadRequest(request: countTokensRequest)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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+
/// Configuration for the generative AI backend API used by this SDK.
16+
struct APIConfig: Sendable, Hashable {
17+
/// The service to use for generative AI.
18+
///
19+
/// This controls which backend API is used by the SDK.
20+
let service: Service
21+
22+
/// The version of the selected API to use, e.g., "v1".
23+
let version: Version
24+
25+
/// Initializes an API configuration.
26+
///
27+
/// - Parameters:
28+
/// - service: The API service to use for generative AI.
29+
/// - version: The version of the API to use.
30+
init(service: Service, version: Version) {
31+
self.service = service
32+
self.version = version
33+
}
34+
}
35+
36+
extension APIConfig {
37+
/// API services providing generative AI functionality.
38+
///
39+
/// See [Vertex AI and Google AI
40+
/// differences](https://cloud.google.com/vertex-ai/generative-ai/docs/overview#how-gemini-vertex-different-gemini-aistudio)
41+
/// for a comparison of the two [API services](https://google.aip.dev/9#api-service).
42+
enum Service: Hashable {
43+
/// The Gemini Enterprise API provided by Vertex AI.
44+
///
45+
/// See the [Cloud
46+
/// docs](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference) for
47+
/// more details.
48+
case vertexAI
49+
50+
/// The Gemini Developer API provided by Google AI.
51+
///
52+
/// See the [Google AI docs](https://ai.google.dev/gemini-api/docs) for more details.
53+
case developer(endpoint: Endpoint)
54+
55+
/// The specific network address to use for API requests.
56+
///
57+
/// This must correspond with the API set in `service`.
58+
var endpoint: Endpoint {
59+
switch self {
60+
case .vertexAI:
61+
return .firebaseVertexAIProd
62+
case let .developer(endpoint: endpoint):
63+
return endpoint
64+
}
65+
}
66+
}
67+
}
68+
69+
extension APIConfig.Service {
70+
/// Network addresses for generative AI API services.
71+
enum Endpoint: String {
72+
/// The Vertex AI in Firebase production endpoint.
73+
case firebaseVertexAIProd = "https://firebasevertexai.googleapis.com"
74+
75+
/// The Vertex AI in Firebase staging endpoint; for SDK development and testing only.
76+
case firebaseVertexAIStaging = "https://staging-firebasevertexai.sandbox.googleapis.com"
77+
78+
/// The Gemini Developer API production endpoint; for SDK development and testing only.
79+
case generativeLanguage = "https://generativelanguage.googleapis.com"
80+
}
81+
}
82+
83+
extension APIConfig {
84+
/// Versions of the configured API service (`APIConfig.Service`).
85+
enum Version: String {
86+
/// The stable channel for version 1 of the API.
87+
case v1
88+
89+
/// The beta channel for version 1 of the API.
90+
case v1beta
91+
}
92+
}

FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ import Foundation
1717
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
1818
struct ImagenGenerationRequest<ImageType: ImagenImageRepresentable>: Sendable {
1919
let model: String
20+
let apiConfig: APIConfig
2021
let options: RequestOptions
2122
let instances: [ImageGenerationInstance]
2223
let parameters: ImageGenerationParameters
2324

24-
init(model: String, options: RequestOptions, instances: [ImageGenerationInstance],
25+
init(model: String,
26+
apiConfig: APIConfig,
27+
options: RequestOptions,
28+
instances: [ImageGenerationInstance],
2529
parameters: ImageGenerationParameters) {
2630
self.model = model
31+
self.apiConfig = apiConfig
2732
self.options = options
2833
self.instances = instances
2934
self.parameters = parameters
@@ -35,7 +40,8 @@ extension ImagenGenerationRequest: GenerativeAIRequest where ImageType: Decodabl
3540
typealias Response = ImagenGenerationResponse<ImageType>
3641

3742
var url: URL {
38-
return URL(string: "\(Constants.baseURL)/\(options.apiVersion)/\(model):predict")!
43+
return URL(string:
44+
"\(apiConfig.service.endpoint.rawValue)/\(apiConfig.version.rawValue)/\(model):predict")!
3945
}
4046
}
4147

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public final class ImagenModel {
3030
/// The resource name of the model in the backend; has the format "models/model-name".
3131
let modelResourceName: String
3232

33+
/// Configuration for the backend API used by this model.
34+
let apiConfig: APIConfig
35+
3336
/// The backing service responsible for sending and receiving model requests to the backend.
3437
let generativeAIService: GenerativeAIService
3538

@@ -42,11 +45,13 @@ public final class ImagenModel {
4245

4346
init(name: String,
4447
firebaseInfo: FirebaseInfo,
48+
apiConfig: APIConfig,
4549
generationConfig: ImagenGenerationConfig?,
4650
safetySettings: ImagenSafetySettings?,
4751
requestOptions: RequestOptions,
4852
urlSession: URLSession = .shared) {
4953
modelResourceName = name
54+
self.apiConfig = apiConfig
5055
generativeAIService = GenerativeAIService(
5156
firebaseInfo: firebaseInfo,
5257
urlSession: urlSession
@@ -123,6 +128,7 @@ public final class ImagenModel {
123128
-> ImagenGenerationResponse<T> where T: Decodable, T: ImagenImageRepresentable {
124129
let request = ImagenGenerationRequest<T>(
125130
model: modelResourceName,
131+
apiConfig: apiConfig,
126132
options: requestOptions,
127133
instances: [ImageGenerationInstance(prompt: prompt)],
128134
parameters: parameters

0 commit comments

Comments
 (0)