From 9035fde56272acdd0b57bcd91b11f7f31297ae7d Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 25 Apr 2025 17:31:53 -0400 Subject: [PATCH 1/4] [Firebase AI] Rename internal `APIConfig` enums --- FirebaseAI/Sources/FirebaseAI.swift | 10 +++--- FirebaseAI/Sources/GenerativeModel.swift | 8 ++--- .../Sources/Types/Internal/APIConfig.swift | 25 ++++++++++----- .../Requests/CountTokensRequest.swift | 2 +- FirebaseAI/Sources/Types/Public/Backend.swift | 4 +-- .../CountTokensIntegrationTests.swift | 6 ++-- .../Tests/Utilities/InstanceConfig.swift | 32 +++++++++---------- .../Tests/Unit/Types/BackendTests.swift | 6 ++-- .../Unit/Types/Internal/APIConfigTests.swift | 12 +++---- .../Requests/CountTokensRequestTests.swift | 2 +- .../Tests/Unit/VertexComponentTests.swift | 22 ++++++------- 11 files changed, 69 insertions(+), 60 deletions(-) diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index e3fa0fbc44a..6658690b31b 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -42,7 +42,7 @@ public final class FirebaseAI: Sendable { ) // Verify that the `FirebaseAI` instance is always configured with the production endpoint since // this is the public API surface for creating an instance. - assert(instance.apiConfig.service.endpoint == .firebaseVertexAIProd) + assert(instance.apiConfig.service.endpoint == .firebaseProxyProd) assert(instance.apiConfig.version == .v1beta) return instance } @@ -159,7 +159,7 @@ public final class FirebaseAI: Sendable { let location: String? static let defaultVertexAIAPIConfig = APIConfig( - service: .vertexAI(endpoint: .firebaseVertexAIProd), + service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta ) @@ -218,7 +218,7 @@ public final class FirebaseAI: Sendable { switch apiConfig.service { case .vertexAI: return vertexAIModelResourceName(modelName: modelName) - case .developer: + case .googleAI: return developerModelResourceName(modelName: modelName) } } @@ -242,10 +242,10 @@ public final class FirebaseAI: Sendable { private func developerModelResourceName(modelName: String) -> String { switch apiConfig.service.endpoint { - case .firebaseVertexAIStaging, .firebaseVertexAIProd: + case .firebaseProxyStaging, .firebaseProxyProd: let projectID = firebaseInfo.projectID return "projects/\(projectID)/models/\(modelName)" - case .generativeLanguage: + case .geminiDeveloperDirect: return "models/\(modelName)" } } diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index a9ebef87b8b..73a31188bf8 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -277,7 +277,7 @@ public final class GenerativeModel: Sendable { let requestContent = switch apiConfig.service { case .vertexAI: content - case .developer: + case .googleAI: // The `role` defaults to "user" but is ignored in `countTokens`. However, it is erroneously // erroneously counted towards the prompt and total token count when using the Developer API // backend; set to `nil` to avoid token count discrepancies between `countTokens` and @@ -290,10 +290,10 @@ public final class GenerativeModel: Sendable { // "models/model-name". This field is unaltered by the Firebase backend before forwarding the // request to the Generative Language backend, which expects the form "models/model-name". let generateContentRequestModelResourceName = switch apiConfig.service { - case .vertexAI, .developer(endpoint: .generativeLanguage): + case .vertexAI, .googleAI(endpoint: .geminiDeveloperDirect): modelResourceName - case .developer(endpoint: .firebaseVertexAIProd), - .developer(endpoint: .firebaseVertexAIStaging): + case .googleAI(endpoint: .firebaseProxyProd), + .googleAI(endpoint: .firebaseProxyStaging): "models/\(modelName)" } diff --git a/FirebaseAI/Sources/Types/Internal/APIConfig.swift b/FirebaseAI/Sources/Types/Internal/APIConfig.swift index 6562570ffe6..7491448c20f 100644 --- a/FirebaseAI/Sources/Types/Internal/APIConfig.swift +++ b/FirebaseAI/Sources/Types/Internal/APIConfig.swift @@ -50,7 +50,7 @@ extension APIConfig { /// The Gemini Developer API provided by Google AI. /// /// See the [Google AI docs](https://ai.google.dev/gemini-api/docs) for more details. - case developer(endpoint: Endpoint) + case googleAI(endpoint: Endpoint) /// The specific network address to use for API requests. /// @@ -59,7 +59,7 @@ extension APIConfig { switch self { case let .vertexAI(endpoint: endpoint): return endpoint - case let .developer(endpoint: endpoint): + case let .googleAI(endpoint: endpoint): return endpoint } } @@ -69,14 +69,23 @@ extension APIConfig { extension APIConfig.Service { /// Network addresses for generative AI API services. enum Endpoint: String { - /// The Vertex AI in Firebase production endpoint. - case firebaseVertexAIProd = "https://firebasevertexai.googleapis.com" + /// The Firebase proxy production endpoint. + /// + /// This endpoint supports both Google AI and Vertex AI. + case firebaseProxyProd = "https://firebasevertexai.googleapis.com" - /// The Vertex AI in Firebase staging endpoint; for SDK development and testing only. - case firebaseVertexAIStaging = "https://staging-firebasevertexai.sandbox.googleapis.com" + /// The Firebase proxy staging endpoint; for SDK development and testing only. + /// + /// This endpoint supports both the Gemini Developer API (commonly referred to as Google AI) + /// and the Gemini API in Vertex AI (commonly referred to simply as Vertex AI). + case firebaseProxyStaging = "https://staging-firebasevertexai.sandbox.googleapis.com" - /// The Gemini Developer API production endpoint; for SDK development and testing only. - case generativeLanguage = "https://generativelanguage.googleapis.com" + /// The Gemini Developer API (Google AI) direct production endpoint; for SDK development and + /// testing only. + /// + /// This bypasses the Firebase proxy and directly connects to the Gemini Developer API + /// (Google AI) backend. This endpoint only supports the Gemini Developer API, not Vertex AI. + case geminiDeveloperDirect = "https://generativelanguage.googleapis.com" } } diff --git a/FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift b/FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift index 2f48ca9a32b..dbd0049c457 100644 --- a/FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift +++ b/FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift @@ -71,7 +71,7 @@ extension CountTokensRequest: Encodable { switch apiConfig.service { case .vertexAI: try encodeForVertexAI(to: encoder) - case .developer: + case .googleAI: try encodeForDeveloper(to: encoder) } } diff --git a/FirebaseAI/Sources/Types/Public/Backend.swift b/FirebaseAI/Sources/Types/Public/Backend.swift index fecd9d9c8d8..132f3a2cd72 100644 --- a/FirebaseAI/Sources/Types/Public/Backend.swift +++ b/FirebaseAI/Sources/Types/Public/Backend.swift @@ -25,7 +25,7 @@ public struct Backend { /// for a list of supported locations. public static func vertexAI(location: String = "us-central1") -> Backend { return Backend( - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1beta), + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta), location: location ) } @@ -33,7 +33,7 @@ public struct Backend { /// Initializes a `Backend` configured for the Google Developer API. public static func googleAI() -> Backend { return Backend( - apiConfig: APIConfig(service: .developer(endpoint: .firebaseVertexAIProd), version: .v1beta), + apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta), location: nil ) } diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift index 0f43a9bbb66..311574348ee 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift @@ -60,7 +60,7 @@ struct CountTokensIntegrationTests { switch config.apiConfig.service { case .vertexAI: #expect(response.totalBillableCharacters == 16) - case .developer: + case .googleAI: #expect(response.totalBillableCharacters == nil) } #expect(response.promptTokensDetails.count == 1) @@ -87,7 +87,7 @@ struct CountTokensIntegrationTests { switch config.apiConfig.service { case .vertexAI: #expect(response.totalBillableCharacters == 61) - case .developer: + case .googleAI: #expect(response.totalBillableCharacters == nil) } #expect(response.promptTokensDetails.count == 1) @@ -144,7 +144,7 @@ struct CountTokensIntegrationTests { case .vertexAI: #expect(response.totalTokens == 65) #expect(response.totalBillableCharacters == 170) - case .developer: + case .googleAI: // The Developer API erroneously ignores the `responseSchema` when counting tokens, resulting // in a lower total count than Vertex AI. #expect(response.totalTokens == 34) diff --git a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index a0f2279dbcb..a79f5fe7b30 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -21,30 +21,30 @@ import VertexAITestApp struct InstanceConfig { static let vertexV1 = InstanceConfig( - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) ) static let vertexV1Staging = InstanceConfig( - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIStaging), version: .v1) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyStaging), version: .v1) ) static let vertexV1Beta = InstanceConfig( - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1beta) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) ) static let vertexV1BetaStaging = InstanceConfig( - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIStaging), version: .v1beta) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyStaging), version: .v1beta) ) static let developerV1Beta = InstanceConfig( - apiConfig: APIConfig(service: .developer(endpoint: .firebaseVertexAIProd), version: .v1beta) + apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta) ) static let developerV1BetaStaging = InstanceConfig( - apiConfig: APIConfig(service: .developer(endpoint: .firebaseVertexAIStaging), version: .v1beta) + apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta) ) static let developerV1Spark = InstanceConfig( appName: FirebaseAppNames.spark, - apiConfig: APIConfig(service: .developer(endpoint: .generativeLanguage), version: .v1) + apiConfig: APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1) ) static let developerV1BetaSpark = InstanceConfig( appName: FirebaseAppNames.spark, - apiConfig: APIConfig(service: .developer(endpoint: .generativeLanguage), version: .v1beta) + apiConfig: APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1beta) ) static let allConfigs = [ vertexV1, @@ -71,11 +71,11 @@ struct InstanceConfig { static let vertexV1AppCheckNotConfigured = InstanceConfig( appName: FirebaseAppNames.appCheckNotConfigured, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) ) static let vertexV1BetaAppCheckNotConfigured = InstanceConfig( appName: FirebaseAppNames.appCheckNotConfigured, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1beta) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) ) let appName: String? @@ -96,8 +96,8 @@ struct InstanceConfig { switch apiConfig.service { case .vertexAI: return "Vertex AI" - case .developer: - return "Developer" + case .googleAI: + return "Google AI" } } @@ -109,11 +109,11 @@ struct InstanceConfig { extension InstanceConfig: CustomTestStringConvertible { var testDescription: String { let endpointSuffix = switch apiConfig.service.endpoint { - case .firebaseVertexAIProd: + case .firebaseProxyProd: "" - case .firebaseVertexAIStaging: + case .firebaseProxyStaging: " - Staging" - case .generativeLanguage: + case .geminiDeveloperDirect: " - Generative Language" } let locationSuffix = location.map { " - \($0)" } ?? "" @@ -132,7 +132,7 @@ extension FirebaseAI { location: location, apiConfig: instanceConfig.apiConfig ) - case .developer: + case .googleAI: assert( instanceConfig.location == nil, "The Developer API is global and does not support `location`." diff --git a/FirebaseAI/Tests/Unit/Types/BackendTests.swift b/FirebaseAI/Tests/Unit/Types/BackendTests.swift index 86c273047a0..e4e87784e68 100644 --- a/FirebaseAI/Tests/Unit/Types/BackendTests.swift +++ b/FirebaseAI/Tests/Unit/Types/BackendTests.swift @@ -19,7 +19,7 @@ import XCTest final class BackendTests: XCTestCase { func testVertexAI_defaultLocation() { let expectedAPIConfig = APIConfig( - service: .vertexAI(endpoint: .firebaseVertexAIProd), + service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta ) @@ -31,7 +31,7 @@ final class BackendTests: XCTestCase { func testVertexAI_customLocation() { let expectedAPIConfig = APIConfig( - service: .vertexAI(endpoint: .firebaseVertexAIProd), + service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta ) let customLocation = "europe-west1" @@ -44,7 +44,7 @@ final class BackendTests: XCTestCase { func testGoogleAI() { let expectedAPIConfig = APIConfig( - service: .developer(endpoint: .firebaseVertexAIProd), + service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta ) diff --git a/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift index 8735688e76e..928eee7f607 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift @@ -19,21 +19,21 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class APIConfigTests: XCTestCase { func testInitialize_vertexAI_prod_v1() { - let apiConfig = APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1) + let apiConfig = APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) XCTAssertEqual(apiConfig.service.endpoint.rawValue, "https://firebasevertexai.googleapis.com") XCTAssertEqual(apiConfig.version.rawValue, "v1") } func testInitialize_vertexAI_prod_v1beta() { - let apiConfig = APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1beta) + let apiConfig = APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) XCTAssertEqual(apiConfig.service.endpoint.rawValue, "https://firebasevertexai.googleapis.com") XCTAssertEqual(apiConfig.version.rawValue, "v1beta") } func testInitialize_vertexAI_staging_v1() { - let apiConfig = APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIStaging), version: .v1) + let apiConfig = APIConfig(service: .vertexAI(endpoint: .firebaseProxyStaging), version: .v1) XCTAssertEqual( apiConfig.service.endpoint.rawValue, "https://staging-firebasevertexai.sandbox.googleapis.com" @@ -43,7 +43,7 @@ final class APIConfigTests: XCTestCase { func testInitialize_vertexAI_staging_v1beta() { let apiConfig = APIConfig( - service: .vertexAI(endpoint: .firebaseVertexAIStaging), + service: .vertexAI(endpoint: .firebaseProxyStaging), version: .v1beta ) @@ -55,7 +55,7 @@ final class APIConfigTests: XCTestCase { func testInitialize_developer_staging_v1beta() { let apiConfig = APIConfig( - service: .developer(endpoint: .firebaseVertexAIStaging), version: .v1beta + service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) XCTAssertEqual( @@ -65,7 +65,7 @@ final class APIConfigTests: XCTestCase { } func testInitialize_developer_generativeLanguage_v1beta() { - let apiConfig = APIConfig(service: .developer(endpoint: .generativeLanguage), version: .v1beta) + let apiConfig = APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1beta) XCTAssertEqual(apiConfig.service.endpoint.rawValue, "https://generativelanguage.googleapis.com") XCTAssertEqual(apiConfig.version.rawValue, "v1beta") diff --git a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift index 5ca31474f1b..6e2f1f790e8 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift @@ -25,7 +25,7 @@ final class CountTokensRequestTests: XCTestCase { let textPart = TextPart("test-prompt") let vertexAPIConfig = FirebaseAI.defaultVertexAIAPIConfig let developerAPIConfig = APIConfig( - service: .developer(endpoint: .firebaseVertexAIProd), + service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta ) let requestOptions = RequestOptions() diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index d8e92a47ff6..96de9131010 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -58,8 +58,8 @@ class VertexComponentTests: XCTestCase { XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) XCTAssertEqual(vertex.firebaseInfo.apiKey, VertexComponentTests.apiKey) XCTAssertEqual(vertex.location, "us-central1") - XCTAssertEqual(vertex.apiConfig.service, .vertexAI(endpoint: .firebaseVertexAIProd)) - XCTAssertEqual(vertex.apiConfig.service.endpoint, .firebaseVertexAIProd) + XCTAssertEqual(vertex.apiConfig.service, .vertexAI(endpoint: .firebaseProxyProd)) + XCTAssertEqual(vertex.apiConfig.service.endpoint, .firebaseProxyProd) XCTAssertEqual(vertex.apiConfig.version, .v1beta) } @@ -72,8 +72,8 @@ class VertexComponentTests: XCTestCase { XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) XCTAssertEqual(vertex.firebaseInfo.apiKey, VertexComponentTests.apiKey) XCTAssertEqual(vertex.location, location) - XCTAssertEqual(vertex.apiConfig.service, .vertexAI(endpoint: .firebaseVertexAIProd)) - XCTAssertEqual(vertex.apiConfig.service.endpoint, .firebaseVertexAIProd) + XCTAssertEqual(vertex.apiConfig.service, .vertexAI(endpoint: .firebaseProxyProd)) + XCTAssertEqual(vertex.apiConfig.service.endpoint, .firebaseProxyProd) XCTAssertEqual(vertex.apiConfig.version, .v1beta) } @@ -88,8 +88,8 @@ class VertexComponentTests: XCTestCase { XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) XCTAssertEqual(vertex.firebaseInfo.apiKey, VertexComponentTests.apiKey) XCTAssertEqual(vertex.location, location) - XCTAssertEqual(vertex.apiConfig.service, .vertexAI(endpoint: .firebaseVertexAIProd)) - XCTAssertEqual(vertex.apiConfig.service.endpoint, .firebaseVertexAIProd) + XCTAssertEqual(vertex.apiConfig.service, .vertexAI(endpoint: .firebaseProxyProd)) + XCTAssertEqual(vertex.apiConfig.service.endpoint, .firebaseProxyProd) XCTAssertEqual(vertex.apiConfig.version, .v1beta) } @@ -155,12 +155,12 @@ class VertexComponentTests: XCTestCase { let vertex1 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1beta) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) ) let vertex2 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) ) // Ensure they are different instances. @@ -207,7 +207,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let apiConfig = APIConfig(service: .developer(endpoint: .generativeLanguage), version: .v1beta) + let apiConfig = APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1beta) let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) let model = "test-model-name" @@ -219,7 +219,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_firebaseVertexAI() throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig( - service: .developer(endpoint: .firebaseVertexAIStaging), + service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) @@ -250,7 +250,7 @@ class VertexComponentTests: XCTestCase { func testGenerativeModel_developerAPI() async throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig( - service: .developer(endpoint: .firebaseVertexAIStaging), + service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) From 46b08ed4cd92f1e52edc6bcb5923f0f84ef43d29 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 1 May 2025 16:35:25 -0400 Subject: [PATCH 2/4] Conform `APIConfig` to `Encodable` --- FirebaseAI/Sources/Types/Internal/APIConfig.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FirebaseAI/Sources/Types/Internal/APIConfig.swift b/FirebaseAI/Sources/Types/Internal/APIConfig.swift index 7491448c20f..7dcd8b4dd40 100644 --- a/FirebaseAI/Sources/Types/Internal/APIConfig.swift +++ b/FirebaseAI/Sources/Types/Internal/APIConfig.swift @@ -13,7 +13,7 @@ // limitations under the License. /// Configuration for the generative AI backend API used by this SDK. -struct APIConfig: Sendable, Hashable { +struct APIConfig: Sendable, Hashable, Encodable { /// The service to use for generative AI. /// /// This controls which backend API is used by the SDK. @@ -39,7 +39,7 @@ extension APIConfig { /// See [Vertex AI and Google AI /// differences](https://cloud.google.com/vertex-ai/generative-ai/docs/overview#how-gemini-vertex-different-gemini-aistudio) /// for a comparison of the two [API services](https://google.aip.dev/9#api-service). - enum Service: Hashable { + enum Service: Hashable, Encodable { /// The Gemini Enterprise API provided by Vertex AI. /// /// See the [Cloud @@ -68,7 +68,7 @@ extension APIConfig { extension APIConfig.Service { /// Network addresses for generative AI API services. - enum Endpoint: String { + enum Endpoint: String, Encodable { /// The Firebase proxy production endpoint. /// /// This endpoint supports both Google AI and Vertex AI. @@ -91,7 +91,7 @@ extension APIConfig.Service { extension APIConfig { /// Versions of the configured API service (`APIConfig.Service`). - enum Version: String { + enum Version: String, Encodable { /// The stable channel for version 1 of the API. case v1 From 73f22d3876f8e0a1e198897a665fb93116fb40e5 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 1 May 2025 16:52:15 -0400 Subject: [PATCH 3/4] Rename `.geminiDeveloperDirect` to `.googleAIBypassProxy` --- FirebaseAI/Sources/FirebaseAI.swift | 2 +- FirebaseAI/Sources/GenerativeModel.swift | 2 +- FirebaseAI/Sources/Types/Internal/APIConfig.swift | 2 +- FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift | 2 +- FirebaseAI/Tests/Unit/VertexComponentTests.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 9417ac45b15..b7e3ad2e893 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -236,7 +236,7 @@ public final class FirebaseAI: Sendable { case .firebaseProxyStaging, .firebaseProxyProd: let projectID = firebaseInfo.projectID return "projects/\(projectID)/models/\(modelName)" - case .geminiDeveloperDirect: + case .googleAIBypassProxy: return "models/\(modelName)" } } diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index 611d5d1bb13..defe01c4665 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -290,7 +290,7 @@ public final class GenerativeModel: Sendable { // "models/model-name". This field is unaltered by the Firebase backend before forwarding the // request to the Generative Language backend, which expects the form "models/model-name". let generateContentRequestModelResourceName = switch apiConfig.service { - case .vertexAI, .googleAI(endpoint: .geminiDeveloperDirect): + case .vertexAI, .googleAI(endpoint: .googleAIBypassProxy): modelResourceName case .googleAI(endpoint: .firebaseProxyProd), .googleAI(endpoint: .firebaseProxyStaging): diff --git a/FirebaseAI/Sources/Types/Internal/APIConfig.swift b/FirebaseAI/Sources/Types/Internal/APIConfig.swift index 7dcd8b4dd40..0311134be4e 100644 --- a/FirebaseAI/Sources/Types/Internal/APIConfig.swift +++ b/FirebaseAI/Sources/Types/Internal/APIConfig.swift @@ -85,7 +85,7 @@ extension APIConfig.Service { /// /// This bypasses the Firebase proxy and directly connects to the Gemini Developer API /// (Google AI) backend. This endpoint only supports the Gemini Developer API, not Vertex AI. - case geminiDeveloperDirect = "https://generativelanguage.googleapis.com" + case googleAIBypassProxy = "https://generativelanguage.googleapis.com" } } diff --git a/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift index 928eee7f607..fe4c290831a 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift @@ -65,7 +65,7 @@ final class APIConfigTests: XCTestCase { } func testInitialize_developer_generativeLanguage_v1beta() { - let apiConfig = APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1beta) + let apiConfig = APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) XCTAssertEqual(apiConfig.service.endpoint.rawValue, "https://generativelanguage.googleapis.com") XCTAssertEqual(apiConfig.version.rawValue, "v1beta") diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 96de9131010..7202e01f4d6 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -207,7 +207,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let apiConfig = APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1beta) + let apiConfig = APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) let model = "test-model-name" From 8d7b0eec3992ba9a3ee0f91effd1addcecf1803b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 1 May 2025 17:37:10 -0400 Subject: [PATCH 4/4] Update `InstanceConfig` naming --- .../CountTokensIntegrationTests.swift | 6 +- .../GenerateContentIntegrationTests.swift | 15 ++-- .../Tests/Integration/SchemaTests.swift | 8 +- .../Tests/Utilities/InstanceConfig.swift | 73 ++++++++++--------- 4 files changed, 51 insertions(+), 51 deletions(-) diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift index 311574348ee..9be7f1dcb02 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift @@ -71,7 +71,7 @@ struct CountTokensIntegrationTests { @Test( /* System instructions are not supported on the v1 Developer API. */ - arguments: InstanceConfig.allConfigsExceptDeveloperV1 + arguments: InstanceConfig.allConfigsExceptGoogleAI_v1 ) func countTokens_text_systemInstruction(_ config: InstanceConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( @@ -98,7 +98,7 @@ struct CountTokensIntegrationTests { @Test(arguments: [ /* System instructions are not supported on the v1 Developer API. */ - InstanceConfig.developerV1Spark, + InstanceConfig.googleAI_v1_freeTier_bypassProxy, ]) func countTokens_text_systemInstruction_unsupported(_ config: InstanceConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( @@ -120,7 +120,7 @@ struct CountTokensIntegrationTests { @Test( /* System instructions are not supported on the v1 Developer API. */ - arguments: InstanceConfig.allConfigsExceptDeveloperV1 + arguments: InstanceConfig.allConfigsExceptGoogleAI_v1 ) func countTokens_jsonSchema(_ config: InstanceConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index d8bc9395d02..c07803a8381 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -78,7 +78,7 @@ struct GenerateContentIntegrationTests { @Test( "Generate an enum and provide a system instruction", /* System instructions are not supported on the v1 Developer API. */ - arguments: InstanceConfig.allConfigsExceptDeveloperV1 + arguments: InstanceConfig.allConfigsExceptGoogleAI_v1 ) func generateContentEnum(_ config: InstanceConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( @@ -118,9 +118,9 @@ struct GenerateContentIntegrationTests { // TODO(andrewheard): Vertex AI configs temporarily disabled to due empty SafetyRatings bug. // InstanceConfig.vertexV1, // InstanceConfig.vertexV1Beta, - InstanceConfig.developerV1Beta, - InstanceConfig.developerV1BetaStaging, - InstanceConfig.developerV1BetaSpark, + InstanceConfig.googleAI_v1beta, + InstanceConfig.googleAI_v1beta_staging, + InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ]) func generateImage(_ config: InstanceConfig) async throws { let generationConfig = GenerationConfig( @@ -216,12 +216,7 @@ struct GenerateContentIntegrationTests { // MARK: - App Check Tests - @Test(arguments: [ - InstanceConfig.vertexV1AppCheckNotConfigured, - InstanceConfig.vertexV1BetaAppCheckNotConfigured, - // App Check is not supported on the Generative Language Developer API endpoint since it - // bypasses the Firebase AI SDK proxy. - ]) + @Test(arguments: InstanceConfig.appCheckNotConfiguredConfigs) func generateContent_appCheckNotConfigured_shouldFail(_ config: InstanceConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift index a4448540f85..bafd74e6008 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift @@ -48,7 +48,7 @@ struct SchemaTests { storage = Storage.storage() } - @Test(arguments: InstanceConfig.allConfigsExceptDeveloperV1) + @Test(arguments: InstanceConfig.allConfigsExceptGoogleAI_v1) func generateContentSchemaItems(_ config: InstanceConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, @@ -73,7 +73,7 @@ struct SchemaTests { #expect(decodedJSON.count <= 5, "Expected at most 5 cities, but got \(decodedJSON.count)") } - @Test(arguments: InstanceConfig.allConfigsExceptDeveloperV1) + @Test(arguments: InstanceConfig.allConfigsExceptGoogleAI_v1) func generateContentSchemaNumberRange(_ config: InstanceConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, @@ -96,7 +96,7 @@ struct SchemaTests { #expect(decodedNumber <= 120.0, "Expected a number <= 120, but got \(decodedNumber)") } - @Test(arguments: InstanceConfig.allConfigsExceptDeveloperV1) + @Test(arguments: InstanceConfig.allConfigsExceptGoogleAI_v1) func generateContentSchemaNumberRangeMultiType(_ config: InstanceConfig) async throws { struct ProductInfo: Codable { let productName: String @@ -149,7 +149,7 @@ struct SchemaTests { #expect(rating <= 5, "Expected a rating <= 5, but got \(rating)") } - @Test(arguments: InstanceConfig.allConfigsExceptDeveloperV1) + @Test(arguments: InstanceConfig.allConfigsExceptGoogleAI_v1) func generateContentAnyOfSchema(_ config: InstanceConfig) async throws { struct MailingAddress: Decodable { let streetAddress: String diff --git a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index 805ebe68618..ebafa1be61c 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -19,62 +19,66 @@ import VertexAITestApp @testable import struct FirebaseAI.APIConfig -struct InstanceConfig { - static let vertexV1 = InstanceConfig( +struct InstanceConfig: Equatable, Encodable { + static let vertexAI_v1 = InstanceConfig( apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) ) - static let vertexV1Staging = InstanceConfig( + static let vertexAI_v1_staging = InstanceConfig( apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyStaging), version: .v1) ) - static let vertexV1Beta = InstanceConfig( + static let vertexAI_v1beta = InstanceConfig( apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) ) - static let vertexV1BetaStaging = InstanceConfig( + static let vertexAI_v1beta_staging = InstanceConfig( apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyStaging), version: .v1beta) ) - static let developerV1Beta = InstanceConfig( + static let googleAI_v1beta = InstanceConfig( apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta) ) - static let developerV1BetaStaging = InstanceConfig( + static let googleAI_v1beta_staging = InstanceConfig( apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta) ) - static let developerV1Spark = InstanceConfig( + static let googleAI_v1_freeTier_bypassProxy = InstanceConfig( appName: FirebaseAppNames.spark, - apiConfig: APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1) + apiConfig: APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1) ) - static let developerV1BetaSpark = InstanceConfig( + static let googleAI_v1beta_freeTier_bypassProxy = InstanceConfig( appName: FirebaseAppNames.spark, - apiConfig: APIConfig(service: .googleAI(endpoint: .geminiDeveloperDirect), version: .v1beta) + apiConfig: APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) ) - static let allConfigs = [ - vertexV1, - vertexV1Staging, - vertexV1Beta, - vertexV1BetaStaging, - developerV1Beta, - developerV1BetaStaging, - developerV1Spark, - developerV1BetaSpark, - ] - static let allConfigsExceptDeveloperV1 = [ - vertexV1, - vertexV1Staging, - vertexV1Beta, - vertexV1BetaStaging, - developerV1Beta, - developerV1BetaStaging, - developerV1BetaSpark, + static let allConfigs = [ + vertexAI_v1, + vertexAI_v1_staging, + vertexAI_v1beta, + vertexAI_v1beta_staging, + googleAI_v1beta, + googleAI_v1beta_staging, + googleAI_v1_freeTier_bypassProxy, + googleAI_v1beta_freeTier_bypassProxy, ] + static let allConfigsExceptGoogleAI_v1 = allConfigs.filter { + $0 != googleAI_v1_freeTier_bypassProxy + } - static let vertexV1AppCheckNotConfigured = InstanceConfig( + static let vertexAI_v1_appCheckNotConfigured = InstanceConfig( appName: FirebaseAppNames.appCheckNotConfigured, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) ) - static let vertexV1BetaAppCheckNotConfigured = InstanceConfig( + static let vertexAI_v1beta_appCheckNotConfigured = InstanceConfig( appName: FirebaseAppNames.appCheckNotConfigured, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) ) + static let googleAI_v1beta_appCheckNotConfigured = InstanceConfig( + appName: FirebaseAppNames.appCheckNotConfigured, + apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta) + ) + + static let appCheckNotConfiguredConfigs = [ + vertexAI_v1_appCheckNotConfigured, + vertexAI_v1beta_appCheckNotConfigured, + googleAI_v1beta_appCheckNotConfigured, + ] let appName: String? let location: String? @@ -106,17 +110,18 @@ struct InstanceConfig { extension InstanceConfig: CustomTestStringConvertible { var testDescription: String { + let freeTierDesignator = (appName == FirebaseAppNames.spark) ? " - Free Tier" : "" let endpointSuffix = switch apiConfig.service.endpoint { case .firebaseProxyProd: "" case .firebaseProxyStaging: " - Staging" - case .geminiDeveloperDirect: - " - Generative Language" + case .googleAIBypassProxy: + " - Bypass Proxy" } let locationSuffix = location.map { " - \($0)" } ?? "" - return "\(serviceName) (\(versionName))\(endpointSuffix)\(locationSuffix)" + return "\(serviceName) (\(versionName))\(freeTierDesignator)\(endpointSuffix)\(locationSuffix)" } }