Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Vertex AI] Prepare for 11.4 release #13707

Merged
merged 13 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions FirebaseVertexAI/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,37 @@
# 11.4.0
- [changed] **Breaking Change**: The `HarmCategory` enum is no longer nested
inside the `SafetySetting` struct and the `unspecified` case has been
removed. (#13686)
- [changed] **Breaking Change**: The `BlockThreshold` enum in `SafetySetting`
has been renamed to `HarmBlockThreshold`. (#13696)
- [changed] **Breaking Change**: The `unspecified` case has been removed from
the `FinishReason`, `BlockReason` and `HarmProbability` enums; this scenario
is now handled by the existing `unknown` case. (#13699)
- [changed] **Breaking Change**: The `data` case in the `Part` enum has been
renamed to `inlineData`; no functionality changes. (#13700)
- [changed] **Breaking Change**: The property `citationSources` of
`CitationMetadata` has been renamed to `citations`. (#13702)
- [changed] **Breaking Change**: The constructor for `Schema` is now deprecated;
use the new static methods `Schema.string(...)`, `Schema.object(...)`, etc.,
instead. (#13616)
- [changed] **Breaking Change**: The constructor for `FunctionDeclaration` now
accepts an array of *optional* parameters instead of a list of *required*
parameters; if a parameter is not listed as optional it is assumed to be
required. (#13616)
- [changed] **Breaking Change**: `CountTokensResponse.totalBillableCharacters`
is now optional (`Int?`); it may be `null` in cases such as when a
`GenerateContentRequest` contains only images or other non-text content.
(#13721)
- [changed] **Breaking Change**: The `ImageConversionError` enum is no longer
public; image conversion errors are still reported as
`GenerateContentError.promptImageContentError`. (#13735)
- [changed] **Breaking Change**: The `CountTokensError` enum has been removed;
errors occurring in `GenerativeModel.countTokens(...)` are now thrown directly
instead of being wrapped in a `CountTokensError.internalError`. (#13736)
- [changed] The default request timeout is now 180 seconds instead of the
platform-default value of 60 seconds for a `URLRequest`; this timeout may
still be customized in `RequestOptions`. (#13722)

# 11.3.0
- [added] Added `Decodable` conformance for `FunctionResponse`. (#13606)
- [changed] **Breaking Change**: Reverted refactor of `GenerativeModel` and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@ import FirebaseVertexAI
import MarkdownUI
import SwiftUI

extension SafetySetting.HarmCategory: CustomStringConvertible {
extension HarmCategory: CustomStringConvertible {
public var description: String {
switch self {
case .dangerousContent: "Dangerous content"
case .harassment: "Harassment"
case .hateSpeech: "Hate speech"
case .sexuallyExplicit: "Sexually explicit"
case .unknown: "Unknown"
case .unspecified: "Unspecified"
}
}
}
Expand All @@ -37,7 +36,6 @@ extension SafetyRating.HarmProbability: CustomStringConvertible {
case .medium: "Medium"
case .negligible: "Negligible"
case .unknown: "Unknown"
case .unspecified: "Unspecified"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,15 @@ class FunctionCallingViewModel: ObservableObject {
name: "get_exchange_rate",
description: "Get the exchange rate for currencies between countries",
parameters: [
"currency_from": Schema(
type: .string,
format: "enum",
description: "The currency to convert from in ISO 4217 format",
enumValues: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"]
"currency_from": .enumeration(
values: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"],
description: "The currency to convert from in ISO 4217 format"
),
"currency_to": Schema(
type: .string,
format: "enum",
description: "The currency to convert to in ISO 4217 format",
enumValues: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"]
"currency_to": .enumeration(
values: ["USD", "EUR", "JPY", "GBP", "AUD", "CAD"],
description: "The currency to convert to in ISO 4217 format"
),
],
requiredParameters: ["currency_from", "currency_to"]
]
),
])]
)
Expand Down Expand Up @@ -156,7 +151,7 @@ class FunctionCallingViewModel: ObservableObject {
case let .functionCall(functionCall):
messages.insert(functionCall.chatMessage(), at: messages.count - 1)
functionCalls.append(functionCall)
case .data, .fileData, .functionResponse:
case .inlineData, .fileData, .functionResponse:
fatalError("Unsupported response content.")
}
}
Expand Down
2 changes: 1 addition & 1 deletion FirebaseVertexAI/Sources/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ public class Chat {
case let .text(str):
combinedText += str

case .data, .fileData, .functionCall, .functionResponse:
case .inlineData, .fileData, .functionCall, .functionResponse:
// Don't combine it, just add to the content. If there's any text pending, add that as
// a part.
if !combinedText.isEmpty {
Expand Down
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"
}
18 changes: 2 additions & 16 deletions FirebaseVertexAI/Sources/CountTokensRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public struct CountTokensResponse {
///
/// > Important: This does not include billable image, video or other non-text input. See
/// [Vertex AI pricing](https://cloud.google.com/vertex-ai/generative-ai/pricing) for details.
public let totalBillableCharacters: Int
public let totalBillableCharacters: Int?
}

// MARK: - Codable Conformances
Expand All @@ -53,18 +53,4 @@ extension CountTokensRequest: Encodable {
}

@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension CountTokensResponse: Decodable {
enum CodingKeys: CodingKey {
case totalTokens
case totalBillableCharacters
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
totalTokens = try container.decode(Int.self, forKey: .totalTokens)
totalBillableCharacters = try container.decodeIfPresent(
Int.self,
forKey: .totalBillableCharacters
) ?? 0
}
}
extension CountTokensResponse: Decodable {}
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
14 changes: 6 additions & 8 deletions FirebaseVertexAI/Sources/FunctionCalling.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,15 @@ public struct FunctionDeclaration {
/// - name: The name of the function; must be a-z, A-Z, 0-9, or contain underscores and dashes,
/// with a maximum length of 63.
/// - description: A brief description of the function.
/// - parameters: Describes the parameters to this function; the keys are parameter names and
/// the values are ``Schema`` objects describing them.
/// - requiredParameters: A list of required parameters by name.
public init(name: String, description: String, parameters: [String: Schema]?,
requiredParameters: [String]? = nil) {
/// - parameters: Describes the parameters to this function.
public init(name: String, description: String, parameters: [String: Schema],
optionalParameters: [String] = []) {
self.name = name
self.description = description
self.parameters = Schema(
type: .object,
self.parameters = Schema.object(
properties: parameters,
requiredProperties: requiredParameters
optionalProperties: optionalParameters,
nullable: false
)
}
}
Expand Down
6 changes: 3 additions & 3 deletions FirebaseVertexAI/Sources/GenerateContentError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import Foundation
/// Errors that occur when generating content from a model.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public enum GenerateContentError: Error {
/// An error occurred when constructing the prompt. Examine the related error for details.
case promptImageContentError(underlying: ImageConversionError)

/// An internal error occurred. See the underlying error for more context.
case internalError(underlying: Error)

/// An error occurred when constructing the prompt. Examine the related error for details.
case promptImageContentError(underlying: Error)

/// A prompt was blocked. See the response's `promptFeedback.blockReason` for more information.
case promptBlocked(response: GenerateContentResponse)

Expand Down
38 changes: 19 additions & 19 deletions FirebaseVertexAI/Sources/GenerateContentResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ public struct GenerateContentResponse: Sendable {
/// The response's content as text, if it exists.
public var text: String? {
guard let candidate = candidates.first else {
Logging.default
.error("[FirebaseVertexAI] Could not get text from a response that had no candidates.")
VertexLog.error(
code: .generateContentResponseNoCandidates,
"Could not get text from a response that had no candidates."
)
return nil
}
let textValues: [String] = candidate.content.parts.compactMap { part in
Expand All @@ -53,8 +55,10 @@ public struct GenerateContentResponse: Sendable {
return text
}
guard textValues.count > 0 else {
Logging.default
.error("[FirebaseVertexAI] Could not get a text part from the first candidate.")
VertexLog.error(
code: .generateContentResponseNoText,
"Could not get a text part from the first candidate."
)
return nil
}
return textValues.joined(separator: " ")
Expand Down Expand Up @@ -113,7 +117,7 @@ public struct CandidateResponse: Sendable {
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public struct CitationMetadata: Sendable {
/// A list of individual cited sources and the parts of the content to which they apply.
public let citationSources: [Citation]
public let citations: [Citation]
}

/// A struct describing a source attribution.
Expand All @@ -138,10 +142,9 @@ public struct Citation: Sendable {
/// A value enumerating possible reasons for a model to terminate a content generation request.
@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
public enum FinishReason: String, Sendable {
/// The finish reason is unknown.
case unknown = "FINISH_REASON_UNKNOWN"

case unspecified = "FINISH_REASON_UNSPECIFIED"

/// Natural stop point of the model or provided stop sequence.
case stop = "STOP"

Expand All @@ -168,9 +171,6 @@ public struct PromptFeedback: Sendable {
/// The block reason is unknown.
case unknown = "UNKNOWN"

/// The block reason was not specified in the server response.
case unspecified = "BLOCK_REASON_UNSPECIFIED"

/// The prompt was blocked because it was deemed unsafe.
case safety = "SAFETY"

Expand Down Expand Up @@ -294,11 +294,7 @@ extension CandidateResponse: Decodable {
}

@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension CitationMetadata: Decodable {
enum CodingKeys: String, CodingKey {
case citationSources = "citations"
}
}
extension CitationMetadata: Decodable {}

@available(iOS 15.0, macOS 11.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
extension Citation: Decodable {
Expand Down Expand Up @@ -338,8 +334,10 @@ extension FinishReason: Decodable {
public init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(String.self)
guard let decodedFinishReason = FinishReason(rawValue: value) else {
Logging.default
.error("[FirebaseVertexAI] Unrecognized FinishReason with value \"\(value)\".")
VertexLog.error(
code: .generateContentResponseUnrecognizedFinishReason,
"Unrecognized FinishReason with value \"\(value)\"."
)
self = .unknown
return
}
Expand All @@ -353,8 +351,10 @@ extension PromptFeedback.BlockReason: Decodable {
public init(from decoder: Decoder) throws {
let value = try decoder.singleValueContainer().decode(String.self)
guard let decodedBlockReason = PromptFeedback.BlockReason(rawValue: value) else {
Logging.default
.error("[FirebaseVertexAI] Unrecognized BlockReason with value \"\(value)\".")
VertexLog.error(
code: .generateContentResponseUnrecognizedBlockReason,
"Unrecognized BlockReason with value \"\(value)\"."
)
self = .unknown
return
}
Expand Down
9 changes: 5 additions & 4 deletions FirebaseVertexAI/Sources/GenerativeAIRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,18 @@ protocol GenerativeAIRequest: Encodable {
public struct RequestOptions {
/// The request’s timeout interval in seconds; if not specified uses the default value for a
/// `URLRequest`.
let timeout: TimeInterval?
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.
///
/// - Parameters:
/// - timeout The request’s timeout interval in seconds; if not specified uses the default value
/// for a `URLRequest`.
public init(timeout: TimeInterval? = nil) {
/// - timeout The request’s timeout interval in seconds; defaults to 180 seconds.
public init(timeout: TimeInterval = 180.0) {
self.timeout = timeout
}
}
Loading
Loading