From 94afbfa8076a22400e609642985218fcba2e424e Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 7 Oct 2024 16:42:24 -0400 Subject: [PATCH] [Vertex AI] Simplify `ModelContent` initializers (#13832) Co-authored-by: Ryan Wilson --- FirebaseVertexAI/CHANGELOG.md | 2 + .../ChatSample/Views/ErrorDetailsView.swift | 10 ++--- .../Sample/ChatSample/Views/ErrorView.swift | 2 +- FirebaseVertexAI/Sources/ModelContent.swift | 35 +---------------- .../Tests/Unit/VertexAIAPITests.swift | 38 +++++++------------ 5 files changed, 21 insertions(+), 66 deletions(-) diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseVertexAI/CHANGELOG.md index 13ef2aad590..1332b53f7e1 100644 --- a/FirebaseVertexAI/CHANGELOG.md +++ b/FirebaseVertexAI/CHANGELOG.md @@ -34,6 +34,8 @@ generating content the types `TextPart`; additionally the types `InlineDataPart`, `FileDataPart` and `FunctionResponsePart` may be provided as input. (#13767) +- [changed] **Breaking Change**: All initializers for `ModelContent` now require + the label `parts: `. (#13832) - [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) diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift index b8febfe0e40..4bc18345cfb 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorDetailsView.swift @@ -160,12 +160,11 @@ struct ErrorDetailsView: View { let error = GenerateContentError.responseStoppedEarly( reason: .maxTokens, response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", [ + CandidateResponse(content: ModelContent(role: "model", parts: """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. - """, - ]), + """), safetyRatings: [ SafetyRating(category: .dangerousContent, probability: .high), SafetyRating(category: .harassment, probability: .low), @@ -183,12 +182,11 @@ struct ErrorDetailsView: View { #Preview("Prompt Blocked") { let error = GenerateContentError.promptBlocked( response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", [ + CandidateResponse(content: ModelContent(role: "model", parts: """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. - """, - ]), + """), safetyRatings: [ SafetyRating(category: .dangerousContent, probability: .high), SafetyRating(category: .harassment, probability: .low), diff --git a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift index d4db2d67dc5..e43258557b4 100644 --- a/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift +++ b/FirebaseVertexAI/Sample/ChatSample/Views/ErrorView.swift @@ -37,7 +37,7 @@ struct ErrorView: View { NavigationView { let errorPromptBlocked = GenerateContentError.promptBlocked( response: GenerateContentResponse(candidates: [ - CandidateResponse(content: ModelContent(role: "model", [ + CandidateResponse(content: ModelContent(role: "model", parts: [ """ A _hypothetical_ model response. Cillum ex aliqua amet aliquip labore amet eiusmod consectetur reprehenderit sit commodo. diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseVertexAI/Sources/ModelContent.swift index d215dd4ba15..9697a97a5bf 100644 --- a/FirebaseVertexAI/Sources/ModelContent.swift +++ b/FirebaseVertexAI/Sources/ModelContent.swift @@ -71,32 +71,6 @@ public struct ModelContent: Equatable, Sendable { // TODO: Refactor this let internalParts: [InternalPart] - /// Creates a new value from any data or `Array` of data interpretable as a - /// ``Part``. See ``PartsRepresentable`` for types that can be interpreted as `Part`s. - public init(role: String? = "user", parts: some PartsRepresentable) { - self.role = role - var convertedParts = [InternalPart]() - for part in parts.partsValue { - switch part { - case let textPart as TextPart: - convertedParts.append(.text(textPart.text)) - case let inlineDataPart as InlineDataPart: - let inlineData = inlineDataPart.inlineData - convertedParts.append(.inlineData(mimetype: inlineData.mimeType, inlineData.data)) - case let fileDataPart as FileDataPart: - let fileData = fileDataPart.fileData - convertedParts.append(.fileData(mimetype: fileData.mimeType, uri: fileData.fileURI)) - case let functionCallPart as FunctionCallPart: - convertedParts.append(.functionCall(functionCallPart.functionCall)) - case let functionResponsePart as FunctionResponsePart: - convertedParts.append(.functionResponse(functionResponsePart.functionResponse)) - default: - fatalError() - } - } - internalParts = convertedParts - } - /// Creates a new value from a list of ``Part``s. public init(role: String? = "user", parts: [any Part]) { self.role = role @@ -124,14 +98,7 @@ public struct ModelContent: Equatable, Sendable { /// Creates a new value from any data interpretable as a ``Part``. /// See ``PartsRepresentable`` for types that can be interpreted as `Part`s. - public init(role: String? = "user", _ parts: any PartsRepresentable...) { - let content = parts.flatMap { $0.partsValue } - self.init(role: role, parts: content) - } - - /// Creates a new value from any data interpretable as a ``Part``. - /// See ``PartsRepresentable``for types that can be interpreted as `Part`s. - public init(role: String? = "user", _ parts: [PartsRepresentable]) { + public init(role: String? = "user", parts: any PartsRepresentable...) { let content = parts.flatMap { $0.partsValue } self.init(role: role, parts: content) } diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift index c187a997b6c..9624ee6e52c 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift +++ b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift @@ -106,8 +106,8 @@ final class VertexAIAPITests: XCTestCase { _ = try await genAI.generateContent([str, UIImage(), TextPart(str)]) _ = try await genAI.generateContent(str, UIImage(), "def", UIImage()) _ = try await genAI.generateContent([str, UIImage(), "def", UIImage()]) - _ = try await genAI.generateContent([ModelContent("def", UIImage()), - ModelContent("def", UIImage())]) + _ = try await genAI.generateContent([ModelContent(parts: "def", UIImage()), + ModelContent(parts: "def", UIImage())]) #elseif canImport(AppKit) _ = try await genAI.generateContent(NSImage()) _ = try await genAI.generateContent([NSImage()]) @@ -121,37 +121,25 @@ final class VertexAIAPITests: XCTestCase { let _ = ModelContent(parts: "Constant String") let _ = ModelContent(parts: str) let _ = ModelContent(parts: [str]) - // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot - // convert value of type 'String' to expected element type - // 'Array.ArrayLiteralElement'. Not sure if there's a way we can get it to - // work. - let _ = ModelContent( - parts: [str, InlineDataPart(data: Data(), mimeType: "foo")] as [any PartsRepresentable] - ) + let _ = ModelContent(parts: [str, InlineDataPart(data: Data(), mimeType: "foo")]) #if canImport(UIKit) _ = ModelContent(role: "user", parts: UIImage()) _ = ModelContent(role: "user", parts: [UIImage()]) - // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert - // value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a - // way we can get it to work. - _ = ModelContent(parts: [str, UIImage()] as [any PartsRepresentable]) - // Alternatively, you can explicitly declare the type in a variable and pass it in. + _ = ModelContent(parts: [str, UIImage()]) + // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile + // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`. let representable2: [any PartsRepresentable] = [str, UIImage()] _ = ModelContent(parts: representable2) - _ = - ModelContent(parts: [str, UIImage(), TextPart(str)] as [any PartsRepresentable]) + _ = ModelContent(parts: [str, UIImage(), TextPart(str)]) #elseif canImport(AppKit) _ = ModelContent(role: "user", parts: NSImage()) _ = ModelContent(role: "user", parts: [NSImage()]) - // Note: without `as [any PartsRepresentable]` this will fail to compile with "Cannot convert - // value of type `[Any]` to expected type `[any PartsRepresentable]`. Not sure if there's a - // way we can get it to work. - _ = ModelContent(parts: [str, NSImage()] as [any PartsRepresentable]) - // Alternatively, you can explicitly declare the type in a variable and pass it in. + _ = ModelContent(parts: [str, NSImage()]) + // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile + // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`. let representable2: [any PartsRepresentable] = [str, NSImage()] _ = ModelContent(parts: representable2) - _ = - ModelContent(parts: [str, NSImage(), TextPart(str)] as [any PartsRepresentable]) + _ = ModelContent(parts: [str, NSImage(), TextPart(str)]) #endif // countTokens API @@ -160,8 +148,8 @@ final class VertexAIAPITests: XCTestCase { let _: CountTokensResponse = try await genAI.countTokens("What color is the Sky?", UIImage()) let _: CountTokensResponse = try await genAI.countTokens([ - ModelContent("What color is the Sky?", UIImage()), - ModelContent(UIImage(), "What color is the Sky?", UIImage()), + ModelContent(parts: "What color is the Sky?", UIImage()), + ModelContent(parts: UIImage(), "What color is the Sky?", UIImage()), ]) #endif