Skip to content

Commit

Permalink
[Vertex AI] Add Vertex AI in Firebase API enablement logging (#13724)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewheard authored Sep 25, 2024
1 parent 7bb68ad commit 801cbb3
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 6 deletions.
3 changes: 3 additions & 0 deletions FirebaseVertexAI/Sources/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 Vertex AI in
/// Firebase API launch.
static let baseURL = "https://firebaseml.googleapis.com"
}
22 changes: 18 additions & 4 deletions FirebaseVertexAI/Sources/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ struct RPCError: Error {
self.details = details
}

// TODO(andrewheard): Remove this method after the Vertex AI in Firebase API launch.
func isFirebaseMLServiceDisabledError() -> Bool {
return details.contains { $0.isFirebaseMLServiceDisabledErrorDetails() }
}

func isVertexAIInFirebaseServiceDisabledError() -> Bool {
return details.contains { $0.isVertexAIInFirebaseServiceDisabledErrorDetails() }
}
}

extension RPCError: Decodable {
Expand Down Expand Up @@ -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 Vertex AI in Firebase 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 isVertexAIInFirebaseServiceDisabledErrorDetails() -> 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
Expand Down
2 changes: 2 additions & 0 deletions FirebaseVertexAI/Sources/GenerativeAIRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 Vertex AI in Firebase API launch.
let apiVersion = "v2beta"

/// Initializes a request options object.
Expand Down
12 changes: 11 additions & 1 deletion FirebaseVertexAI/Sources/GenerativeAIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 Vertex AI in Firebase API launch.
if error.isFirebaseMLServiceDisabledError() {
VertexLog.error(code: .firebaseMLAPIDisabled, """
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 \
Expand All @@ -276,6 +277,15 @@ struct GenerativeAIService {
\(projectID)).
""")
}

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: \
https://console.firebase.google.com/project/\(projectID)/genai/vertex
""")
}
}

private func parseResponse<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
Expand Down
2 changes: 1 addition & 1 deletion FirebaseVertexAI/Sources/VertexLog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ enum VertexLog {
case verboseLoggingEnabled = 101

// API Enablement Errors
case firebaseMLAPIDisabled = 200
case vertexAIInFirebaseAPIDisabled = 200

// Model Configuration
case generativeModelInitialized = 1000
Expand Down
52 changes: 52 additions & 0 deletions FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ final class GenerativeModelTests: XCTestCase {
}
}

// TODO(andrewheard): Remove this test case after the Vertex AI in Firebase API launch.
func testGenerateContent_failure_firebaseMLAPINotEnabled() async throws {
let expectedStatusCode = 403
MockURLProtocol
Expand All @@ -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.isVertexAIInFirebaseServiceDisabledError())
return
} catch {
XCTFail("Should throw GenerateContentError.internalError(RPCError); error thrown: \(error)")
}
}

func testGenerateContent_failure_emptyContent() async throws {
MockURLProtocol
.requestHandler = try httpRequestHandler(
Expand Down Expand Up @@ -774,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
Expand All @@ -799,6 +825,32 @@ 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(
Expand Down

0 comments on commit 801cbb3

Please sign in to comment.