From f69fda03b0d0bf0882f13b8b965bb82bec19fd0b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 25 Sep 2024 15:59:48 -0400 Subject: [PATCH 1/3] [Vertex AI] Add Firebase Vertex AI API enablement logging --- FirebaseVertexAI/Sources/Constants.swift | 3 +++ FirebaseVertexAI/Sources/Errors.swift | 22 +++++++++++++--- .../Sources/GenerativeAIRequest.swift | 2 ++ .../Sources/GenerativeAIService.swift | 12 ++++++++- FirebaseVertexAI/Sources/VertexLog.swift | 2 +- .../Tests/Unit/GenerativeModelTests.swift | 25 +++++++++++++++++++ 6 files changed, 60 insertions(+), 6 deletions(-) diff --git a/FirebaseVertexAI/Sources/Constants.swift b/FirebaseVertexAI/Sources/Constants.swift index 8f1768d7082..da835d99d79 100644 --- a/FirebaseVertexAI/Sources/Constants.swift +++ b/FirebaseVertexAI/Sources/Constants.swift @@ -21,5 +21,8 @@ import Foundation /// Constants associated with the Vertex AI for Firebase SDK. enum Constants { /// The Vertex AI backend endpoint URL. + /// + /// TODO(andrewheard): Update to "https://firebasevertexai.googleapis.com" after the Firebase + /// Vertex AI API launch. static let baseURL = "https://firebaseml.googleapis.com" } diff --git a/FirebaseVertexAI/Sources/Errors.swift b/FirebaseVertexAI/Sources/Errors.swift index 17a4dd9e201..ec0fd7d678a 100644 --- a/FirebaseVertexAI/Sources/Errors.swift +++ b/FirebaseVertexAI/Sources/Errors.swift @@ -31,9 +31,14 @@ struct RPCError: Error { self.details = details } + // TODO(andrewheard): Remove this method after the Firebase Vertex AI API launch. func isFirebaseMLServiceDisabledError() -> Bool { return details.contains { $0.isFirebaseMLServiceDisabledErrorDetails() } } + + func isFirebaseVertexAIServiceDisabledError() -> Bool { + return details.contains { $0.isFirebaseVertexAIServiceDisabledErrorDetails() } + } } extension RPCError: Decodable { @@ -86,17 +91,26 @@ struct ErrorDetails { return type == ErrorDetails.errorInfoType } + func isServiceDisabledError() -> Bool { + return isErrorInfo() && reason == "SERVICE_DISABLED" && domain == "googleapis.com" + } + + // TODO(andrewheard): Remove this method after the Firebase Vertex AI API launch. func isFirebaseMLServiceDisabledErrorDetails() -> Bool { - guard isErrorInfo() else { + guard isServiceDisabledError() else { return false } - guard reason == "SERVICE_DISABLED" else { + guard let metadata, metadata["service"] == "firebaseml.googleapis.com" else { return false } - guard domain == "googleapis.com" else { + return true + } + + func isFirebaseVertexAIServiceDisabledErrorDetails() -> Bool { + guard isServiceDisabledError() else { return false } - guard let metadata, metadata["service"] == "firebaseml.googleapis.com" else { + guard let metadata, metadata["service"] == "firebasevertexai.googleapis.com" else { return false } return true diff --git a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift index 08b178d5808..0261d4db9bd 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift @@ -31,6 +31,8 @@ public struct RequestOptions { let timeout: TimeInterval? /// The API version to use in requests to the backend. + /// + /// TODO(andrewheard): Update to "v1beta" after the Firebase Vertex AI API launch. let apiVersion = "v2beta" /// Initializes a request options object. diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index e8f7525d3fd..9637bca343d 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -266,8 +266,9 @@ struct GenerativeAIService { // Log specific RPC errors that cannot be mitigated or handled by user code. // These errors do not produce specific GenerateContentError or CountTokensError cases. private func logRPCError(_ error: RPCError) { + // TODO(andrewheard): Remove this check after the Firebase Vertex AI API launch. if error.isFirebaseMLServiceDisabledError() { - VertexLog.error(code: .firebaseMLAPIDisabled, """ + VertexLog.error(code: .firebaseVertexAIAPIDisabled, """ The Vertex AI for Firebase SDK requires the Firebase ML API `firebaseml.googleapis.com` to \ be enabled for your project. Get started in the Firebase Console \ (https://console.firebase.google.com/project/\(projectID)/genai/vertex) or verify that the \ @@ -276,6 +277,15 @@ struct GenerativeAIService { \(projectID)). """) } + + if error.isFirebaseVertexAIServiceDisabledError() { + VertexLog.error(code: .firebaseVertexAIAPIDisabled, """ + The Vertex AI for Firebase SDK requires the Firebase Vertex AI API \ + `firebasevertexai.googleapis.com` to be enabled for your project. Get started by visiting \ + the Firebase Console at: \ + https://console.firebase.google.com/project/\(projectID)/genai/vertex + """) + } } private func parseResponse(_ type: T.Type, from data: Data) throws -> T { diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseVertexAI/Sources/VertexLog.swift index c69d019975f..bea4fca18d5 100644 --- a/FirebaseVertexAI/Sources/VertexLog.swift +++ b/FirebaseVertexAI/Sources/VertexLog.swift @@ -29,7 +29,7 @@ enum VertexLog { case verboseLoggingEnabled = 101 // API Enablement Errors - case firebaseMLAPIDisabled = 200 + case firebaseVertexAIAPIDisabled = 200 // Model Configuration case generativeModelInitialized = 1000 diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index c7cfe36df70..f507756c29f 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -453,6 +453,7 @@ final class GenerativeModelTests: XCTestCase { } } + // TODO(andrewheard): Remove this test case after the Firebase Vertex AI API launch. func testGenerateContent_failure_firebaseMLAPINotEnabled() async throws { let expectedStatusCode = 403 MockURLProtocol @@ -476,6 +477,30 @@ final class GenerativeModelTests: XCTestCase { } } + func testGenerateContent_failure_firebaseVertexAIAPINotEnabled() async throws { + let expectedStatusCode = 403 + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-failure-firebasevertexai-api-not-enabled", + withExtension: "json", + statusCode: expectedStatusCode + ) + + do { + _ = try await model.generateContent(testPrompt) + XCTFail("Should throw GenerateContentError.internalError; no error thrown.") + } catch let GenerateContentError.internalError(error as RPCError) { + XCTAssertEqual(error.httpResponseCode, expectedStatusCode) + XCTAssertEqual(error.status, .permissionDenied) + XCTAssertTrue(error.message + .starts(with: "Vertex AI in Firebase API has not been used in project")) + XCTAssertTrue(error.isFirebaseMLServiceDisabledError()) + return + } catch { + XCTFail("Should throw GenerateContentError.internalError(RPCError); error thrown: \(error)") + } + } + func testGenerateContent_failure_emptyContent() async throws { MockURLProtocol .requestHandler = try httpRequestHandler( From 7a80bee8c6d566105ca1c94014b39cff26cebb90 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 25 Sep 2024 18:01:37 -0400 Subject: [PATCH 2/3] Fix tests --- FirebaseVertexAI/Sources/Constants.swift | 4 +-- FirebaseVertexAI/Sources/Errors.swift | 10 +++---- .../Sources/GenerativeAIRequest.swift | 2 +- .../Sources/GenerativeAIService.swift | 8 ++--- FirebaseVertexAI/Sources/VertexLog.swift | 2 +- .../Tests/Unit/GenerativeModelTests.swift | 30 +++++++++++++++++-- 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/FirebaseVertexAI/Sources/Constants.swift b/FirebaseVertexAI/Sources/Constants.swift index da835d99d79..e3f2d45d6df 100644 --- a/FirebaseVertexAI/Sources/Constants.swift +++ b/FirebaseVertexAI/Sources/Constants.swift @@ -22,7 +22,7 @@ import Foundation enum Constants { /// The Vertex AI backend endpoint URL. /// - /// TODO(andrewheard): Update to "https://firebasevertexai.googleapis.com" after the Firebase - /// Vertex AI API launch. + /// TODO(andrewheard): Update to "https://firebasevertexai.googleapis.com" after the Vertex AI in + /// Firebase API launch. static let baseURL = "https://firebaseml.googleapis.com" } diff --git a/FirebaseVertexAI/Sources/Errors.swift b/FirebaseVertexAI/Sources/Errors.swift index ec0fd7d678a..57fbe826580 100644 --- a/FirebaseVertexAI/Sources/Errors.swift +++ b/FirebaseVertexAI/Sources/Errors.swift @@ -31,13 +31,13 @@ struct RPCError: Error { self.details = details } - // TODO(andrewheard): Remove this method after the Firebase Vertex AI API launch. + // TODO(andrewheard): Remove this method after the Vertex AI in Firebase API launch. func isFirebaseMLServiceDisabledError() -> Bool { return details.contains { $0.isFirebaseMLServiceDisabledErrorDetails() } } - func isFirebaseVertexAIServiceDisabledError() -> Bool { - return details.contains { $0.isFirebaseVertexAIServiceDisabledErrorDetails() } + func isVertexAIInFirebaseServiceDisabledError() -> Bool { + return details.contains { $0.isVertexAIInFirebaseServiceDisabledErrorDetails() } } } @@ -95,7 +95,7 @@ struct ErrorDetails { return isErrorInfo() && reason == "SERVICE_DISABLED" && domain == "googleapis.com" } - // TODO(andrewheard): Remove this method after the Firebase Vertex AI API launch. + // TODO(andrewheard): Remove this method after the Vertex AI in Firebase API launch. func isFirebaseMLServiceDisabledErrorDetails() -> Bool { guard isServiceDisabledError() else { return false @@ -106,7 +106,7 @@ struct ErrorDetails { return true } - func isFirebaseVertexAIServiceDisabledErrorDetails() -> Bool { + func isVertexAIInFirebaseServiceDisabledErrorDetails() -> Bool { guard isServiceDisabledError() else { return false } diff --git a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift index 0261d4db9bd..327b7d4f1b4 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIRequest.swift @@ -32,7 +32,7 @@ public struct RequestOptions { /// The API version to use in requests to the backend. /// - /// TODO(andrewheard): Update to "v1beta" after the Firebase Vertex AI API launch. + /// TODO(andrewheard): Update to "v1beta" after the Vertex AI in Firebase API launch. let apiVersion = "v2beta" /// Initializes a request options object. diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseVertexAI/Sources/GenerativeAIService.swift index 9637bca343d..02995926abb 100644 --- a/FirebaseVertexAI/Sources/GenerativeAIService.swift +++ b/FirebaseVertexAI/Sources/GenerativeAIService.swift @@ -266,9 +266,9 @@ struct GenerativeAIService { // Log specific RPC errors that cannot be mitigated or handled by user code. // These errors do not produce specific GenerateContentError or CountTokensError cases. private func logRPCError(_ error: RPCError) { - // TODO(andrewheard): Remove this check after the Firebase Vertex AI API launch. + // TODO(andrewheard): Remove this check after the Vertex AI in Firebase API launch. if error.isFirebaseMLServiceDisabledError() { - VertexLog.error(code: .firebaseVertexAIAPIDisabled, """ + VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ The Vertex AI for Firebase SDK requires the Firebase ML API `firebaseml.googleapis.com` to \ be enabled for your project. Get started in the Firebase Console \ (https://console.firebase.google.com/project/\(projectID)/genai/vertex) or verify that the \ @@ -278,8 +278,8 @@ struct GenerativeAIService { """) } - if error.isFirebaseVertexAIServiceDisabledError() { - VertexLog.error(code: .firebaseVertexAIAPIDisabled, """ + if error.isVertexAIInFirebaseServiceDisabledError() { + VertexLog.error(code: .vertexAIInFirebaseAPIDisabled, """ The Vertex AI for Firebase SDK requires the Firebase Vertex AI API \ `firebasevertexai.googleapis.com` to be enabled for your project. Get started by visiting \ the Firebase Console at: \ diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseVertexAI/Sources/VertexLog.swift index bea4fca18d5..bd400c200c2 100644 --- a/FirebaseVertexAI/Sources/VertexLog.swift +++ b/FirebaseVertexAI/Sources/VertexLog.swift @@ -29,7 +29,7 @@ enum VertexLog { case verboseLoggingEnabled = 101 // API Enablement Errors - case firebaseVertexAIAPIDisabled = 200 + case vertexAIInFirebaseAPIDisabled = 200 // Model Configuration case generativeModelInitialized = 1000 diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index f507756c29f..ef8d067ac70 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -453,7 +453,7 @@ final class GenerativeModelTests: XCTestCase { } } - // TODO(andrewheard): Remove this test case after the Firebase Vertex AI API launch. + // TODO(andrewheard): Remove this test case after the Vertex AI in Firebase API launch. func testGenerateContent_failure_firebaseMLAPINotEnabled() async throws { let expectedStatusCode = 403 MockURLProtocol @@ -494,7 +494,7 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(error.status, .permissionDenied) XCTAssertTrue(error.message .starts(with: "Vertex AI in Firebase API has not been used in project")) - XCTAssertTrue(error.isFirebaseMLServiceDisabledError()) + XCTAssertTrue(error.isVertexAIInFirebaseServiceDisabledError()) return } catch { XCTFail("Should throw GenerateContentError.internalError(RPCError); error thrown: \(error)") @@ -799,6 +799,7 @@ final class GenerativeModelTests: XCTestCase { XCTFail("Should have caught an error.") } + // TODO(andrewheard): Remove this test case after the Vertex AI in Firebase API launch. func testGenerateContentStream_failure_firebaseMLAPINotEnabled() async throws { let expectedStatusCode = 403 MockURLProtocol @@ -824,6 +825,31 @@ final class GenerativeModelTests: XCTestCase { XCTFail("Should have caught an error.") } + func testGenerateContentStream_failure_vertexAIInFirebaseAPINotEnabled() async throws { + let expectedStatusCode = 403 + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-failure-firebasevertexai-api-not-enabled", + withExtension: "json", + statusCode: expectedStatusCode + ) + + do { + let stream = try model.generateContentStream(testPrompt) + for try await _ in stream { + XCTFail("No content is there, this shouldn't happen.") + } + } catch let GenerateContentError.internalError(error as RPCError) { + XCTAssertEqual(error.httpResponseCode, expectedStatusCode) + XCTAssertEqual(error.status, .permissionDenied) + XCTAssertTrue(error.message.starts(with: "Vertex AI in Firebase API has not been used in project")) + XCTAssertTrue(error.isVertexAIInFirebaseServiceDisabledError()) + return + } + + XCTFail("Should have caught an error.") + } + func testGenerateContentStream_failureEmptyContent() async throws { MockURLProtocol .requestHandler = try httpRequestHandler( From 8184794253255172bde98626c3e09c41afecb8e5 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 25 Sep 2024 18:09:54 -0400 Subject: [PATCH 3/3] Fix formatting --- FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift index ef8d067ac70..e7bd91dcff1 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift @@ -842,7 +842,8 @@ final class GenerativeModelTests: XCTestCase { } catch let GenerateContentError.internalError(error as RPCError) { XCTAssertEqual(error.httpResponseCode, expectedStatusCode) XCTAssertEqual(error.status, .permissionDenied) - XCTAssertTrue(error.message.starts(with: "Vertex AI in Firebase API has not been used in project")) + XCTAssertTrue(error.message + .starts(with: "Vertex AI in Firebase API has not been used in project")) XCTAssertTrue(error.isVertexAIInFirebaseServiceDisabledError()) return }