From e8b141abe99d7c219011eee8c9524ed604e2507b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 29 Aug 2025 17:54:43 -0400 Subject: [PATCH 01/12] [Firebase AI] Rename module to `FirebaseAILogic` --- FirebaseAI/Sources/Constants.swift | 4 +- FirebaseAI/Sources/FirebaseAI.swift | 23 +++++----- .../CountTokensIntegrationTests.swift | 10 ++-- .../GenerateContentIntegrationTests.swift | 20 ++++---- .../Integration/ImagenIntegrationTests.swift | 8 ++-- .../Tests/Integration/IntegrationTests.swift | 10 ++-- .../Tests/Integration/SchemaTests.swift | 12 ++--- .../Tests/Utilities/InstanceConfig.swift | 12 ++--- FirebaseAI/Tests/Unit/APITests.swift | 18 ++++---- FirebaseAI/Tests/Unit/ChatTests.swift | 10 ++-- .../Tests/Unit/GenerationConfigTests.swift | 2 +- .../Unit/GenerativeModelGoogleAITests.swift | 4 +- .../Unit/GenerativeModelVertexAITests.swift | 4 +- FirebaseAI/Tests/Unit/JSONValueTests.swift | 2 +- FirebaseAI/Tests/Unit/PartTests.swift | 2 +- .../Tests/Unit/PartsRepresentableTests.swift | 2 +- .../Tests/Unit/RequestOptionsTest.swift | 2 +- FirebaseAI/Tests/Unit/SafetyTests.swift | 2 +- .../Tests/Unit/Snippets/ChatSnippets.swift | 4 +- .../Unit/Snippets/CloudStorageSnippets.swift | 2 +- .../Unit/Snippets/CountTokensSnippets.swift | 4 +- .../Snippets/FunctionCallingSnippets.swift | 4 +- .../Unit/Snippets/MultimodalSnippets.swift | 4 +- .../Snippets/StructuredOutputSnippets.swift | 6 +-- .../Tests/Unit/Snippets/TextSnippets.swift | 4 +- .../GenerativeModelTestUtil.swift | 2 +- .../Tests/Unit/Types/BackendTests.swift | 2 +- .../Unit/Types/CitationMetadataTests.swift | 2 +- .../Tests/Unit/Types/CitationTests.swift | 2 +- .../Types/GenerateContentResponseTests.swift | 2 +- .../Unit/Types/GroundingMetadataTests.swift | 2 +- .../Imagen/ImageGenerationInstanceTests.swift | 2 +- .../ImageGenerationOutputOptionsTests.swift | 2 +- .../ImageGenerationParametersTests.swift | 2 +- .../Types/Imagen/ImagenGCSImageTests.swift | 2 +- .../Imagen/ImagenGenerationRequestTests.swift | 4 +- .../ImagenGenerationResponseTests.swift | 2 +- .../Types/Imagen/ImagenInlineImageTests.swift | 2 +- .../Types/Imagen/RAIFilteredReasonTests.swift | 2 +- .../Unit/Types/Internal/APIConfigTests.swift | 2 +- .../Requests/CountTokensRequestTests.swift | 4 +- .../Tests/Unit/Types/InternalPartTests.swift | 2 +- .../Unit/Types/ModalityTokenCountTests.swift | 2 +- .../Tests/Unit/Types/ProtoDateTests.swift | 2 +- FirebaseAI/Tests/Unit/Types/SchemaTests.swift | 2 +- FirebaseAI/Tests/Unit/Types/ToolTests.swift | 2 +- .../Tests/Unit/VertexComponentTests.swift | 46 +++++++++---------- FirebaseAI/Wrapper/FirebaseAIWrapper.swift | 46 +++++++++++++++++++ Package.swift | 18 +++++++- 49 files changed, 195 insertions(+), 136 deletions(-) create mode 100644 FirebaseAI/Wrapper/FirebaseAIWrapper.swift diff --git a/FirebaseAI/Sources/Constants.swift b/FirebaseAI/Sources/Constants.swift index 1af4c44c531..b5279a381f4 100644 --- a/FirebaseAI/Sources/Constants.swift +++ b/FirebaseAI/Sources/Constants.swift @@ -19,6 +19,6 @@ enum Constants { /// The base reverse-DNS name for `NSError` or `CustomNSError` error domains. /// /// - Important: A suffix must be appended to produce an error domain (e.g., - /// "com.google.firebase.firebaseai.ExampleError"). - static let baseErrorDomain = "com.google.firebase.firebaseai" + /// "com.google.firebase.ai-logic.ExampleError"). + static let baseErrorDomain = "com.google.firebase.ai-logic" } diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 7f05d8a0d7b..b995c86d9fc 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -22,10 +22,10 @@ internal import FirebaseCoreExtension /// The Firebase AI SDK provides access to Gemini models directly from your app. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public final class FirebaseAI: Sendable { +public final class AILogic: Sendable { // MARK: - Public APIs - /// Creates an instance of `FirebaseAI`. + /// Creates an instance of `AILogic`. /// /// - Parameters: /// - app: A custom `FirebaseApp` used for initialization; if not specified, uses the default @@ -41,17 +41,16 @@ public final class FirebaseAI: Sendable { /// _This flag is set to `false` by default._ /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay /// > protection is added. - /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. - public static func firebaseAI(app: FirebaseApp? = nil, - backend: Backend = .googleAI(), - useLimitedUseAppCheckTokens: Bool = false) -> FirebaseAI { + /// - Returns: An `AILogic` instance, configured with the custom `FirebaseApp`. + public static func aiLogic(app: FirebaseApp? = nil, backend: Backend = .googleAI(), + useLimitedUseAppCheckTokens: Bool = false) -> AILogic { let instance = createInstance( app: app, location: backend.location, apiConfig: backend.apiConfig, useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) - // Verify that the `FirebaseAI` instance is always configured with the production endpoint since + // Verify that the `AILogic` instance is always configured with the production endpoint since // this is the public API surface for creating an instance. assert(instance.apiConfig.service.endpoint == .firebaseProxyProd) assert(instance.apiConfig.version == .v1beta) @@ -152,9 +151,9 @@ public final class FirebaseAI: Sendable { let apiConfig: APIConfig - /// A map of active `FirebaseAI` instances keyed by the `FirebaseApp` name and the `location`, + /// A map of active `AILogic` instances keyed by the `FirebaseApp` name and the `location`, /// in the format `appName:location`. - private nonisolated(unsafe) static var instances: [InstanceKey: FirebaseAI] = [:] + private nonisolated(unsafe) static var instances: [InstanceKey: AILogic] = [:] /// Lock to manage access to the `instances` array to avoid race conditions. private nonisolated(unsafe) static var instancesLock: os_unfair_lock = .init() @@ -168,7 +167,7 @@ public final class FirebaseAI: Sendable { static func createInstance(app: FirebaseApp?, location: String?, apiConfig: APIConfig, - useLimitedUseAppCheckTokens: Bool) -> FirebaseAI { + useLimitedUseAppCheckTokens: Bool) -> AILogic { guard let app = app ?? FirebaseApp.app() else { fatalError("No instance of the default Firebase app was found.") } @@ -187,7 +186,7 @@ public final class FirebaseAI: Sendable { if let instance = instances[instanceKey] { return instance } - let newInstance = FirebaseAI( + let newInstance = AILogic( app: app, location: location, apiConfig: apiConfig, @@ -266,7 +265,7 @@ public final class FirebaseAI: Sendable { } } - /// Identifier for a unique instance of ``FirebaseAI``. + /// Identifier for a unique instance of ``AILogic``. /// /// This type is `Hashable` so that it can be used as a key in the `instances` dictionary. private struct InstanceKey: Sendable, Hashable { diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift index 1e68b640dfb..f59b67402bc 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseAITestApp import FirebaseAuth import FirebaseCore import FirebaseStorage import Testing -@testable import struct FirebaseAI.APIConfig +@testable import struct FirebaseAILogic.APIConfig @Suite(.serialized) struct CountTokensIntegrationTests { @@ -48,7 +48,7 @@ struct CountTokensIntegrationTests { @Test(arguments: InstanceConfig.allConfigs) func countTokens_text(_ config: InstanceConfig) async throws { let prompt = "Why is the sky blue?" - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: generationConfig, safetySettings: safetySettings @@ -65,7 +65,7 @@ struct CountTokensIntegrationTests { @Test(arguments: InstanceConfig.allConfigs) func countTokens_text_systemInstruction(_ config: InstanceConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: generationConfig, safetySettings: safetySettings, @@ -83,7 +83,7 @@ struct CountTokensIntegrationTests { @Test(arguments: InstanceConfig.allConfigs) func countTokens_jsonSchema(_ config: InstanceConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: GenerationConfig( responseMIMEType: "application/json", diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index ef0f19be217..b11cfcb97c8 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseAITestApp import FirebaseAuth import FirebaseCore @@ -23,7 +23,7 @@ import Testing import UIKit #endif // canImport(UIKit) -@testable import struct FirebaseAI.BackendError +@testable import struct FirebaseAILogic.BackendError @Suite(.serialized) struct GenerateContentIntegrationTests { @@ -64,7 +64,7 @@ struct GenerateContentIntegrationTests { // (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemma3_4B), ]) func generateContent(_ config: InstanceConfig, modelName: String) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: modelName, generationConfig: generationConfig, safetySettings: safetySettings, @@ -105,7 +105,7 @@ struct GenerateContentIntegrationTests { arguments: InstanceConfig.allConfigs ) func generateContentEnum(_ config: InstanceConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "text/x.enum", @@ -184,7 +184,7 @@ struct GenerateContentIntegrationTests { ) func generateContentThinking(_ config: InstanceConfig, modelName: String, thinkingConfig: ThinkingConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: modelName, generationConfig: GenerationConfig( temperature: 0.0, @@ -268,7 +268,7 @@ struct GenerateContentIntegrationTests { "country": .string(), ] ) - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: modelName, generationConfig: GenerationConfig( temperature: 0.0, @@ -341,7 +341,7 @@ struct GenerateContentIntegrationTests { // 'gemini-2.0-flash-preview-image-generation' model. $0.harmCategory != .civicIntegrity } - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashPreviewImageGeneration, generationConfig: generationConfig, safetySettings: safetySettings @@ -384,7 +384,7 @@ struct GenerateContentIntegrationTests { arguments: InstanceConfig.allConfigs ) func generateContent_withGoogleSearch_succeeds(_ config: InstanceConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, tools: [.googleSearch()] ) @@ -450,7 +450,7 @@ struct GenerateContentIntegrationTests { - Do NOT wrap the JSON in Markdown code blocks (e.g., ```json ... ``` or ``` ... ```). - The response must start with '[' and end with ']'. """ - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: modelName, generationConfig: generationConfig, safetySettings: safetySettings @@ -487,7 +487,7 @@ struct GenerateContentIntegrationTests { @Test(arguments: InstanceConfig.appCheckNotConfiguredConfigs) func generateContent_appCheckNotConfigured_shouldFail(_ config: InstanceConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash ) let prompt = "Where is Google headquarters located? Answer with the city name only." diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift index ade781e6176..c08ffd0b858 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseAITestApp import FirebaseAuth import FirebaseCore @@ -24,7 +24,7 @@ import Testing #endif // canImport(UIKit) // TODO(#14452): Remove `@testable import` when `generateImages(prompt:gcsURI:)` is public. -@testable import class FirebaseAI.ImagenModel +@testable import class FirebaseAILogic.ImagenModel @Suite( .enabled( @@ -34,13 +34,13 @@ import Testing .serialized ) struct ImagenIntegrationTests { - var vertex: FirebaseAI + var vertex: AILogic var storage: Storage var userID1: String init() async throws { userID1 = try await TestHelpers.getUserID() - vertex = FirebaseAI.firebaseAI(backend: .vertexAI()) + vertex = AILogic.aiLogic(backend: .vertexAI()) storage = Storage.storage() } diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift index c4c49d4b45f..c9911abc2c1 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseAITestApp import FirebaseAuth import FirebaseCore import FirebaseStorage import XCTest -@testable import struct FirebaseAI.CountTokensRequest +@testable import struct FirebaseAILogic.CountTokensRequest // TODO(#14405): Migrate to Swift Testing and parameterize tests. final class IntegrationTests: XCTestCase { @@ -44,14 +44,14 @@ final class IntegrationTests: XCTestCase { // Candidates and total token counts may differ slightly between runs due to whitespace tokens. let tokenCountAccuracy = 1 - var vertex: FirebaseAI! + var vertex: AILogic! var model: GenerativeModel! var storage: Storage! var userID1 = "" override func setUp() async throws { userID1 = try await TestHelpers.getUserID() - vertex = FirebaseAI.firebaseAI(backend: .vertexAI()) + vertex = AILogic.aiLogic(backend: .vertexAI()) model = vertex.generativeModel( modelName: "gemini-2.0-flash", generationConfig: generationConfig, @@ -196,7 +196,7 @@ final class IntegrationTests: XCTestCase { func testCountTokens_appCheckNotConfigured_shouldFail() async throws { let app = try XCTUnwrap(FirebaseApp.app(name: FirebaseAppNames.appCheckNotConfigured)) - let vertex = FirebaseAI.firebaseAI(app: app, backend: .vertexAI()) + let vertex = AILogic.aiLogic(app: app, backend: .vertexAI()) let model = vertex.generativeModel(modelName: "gemini-2.0-flash") let prompt = "Why is the sky blue?" diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift index 640b353dc2f..decc7250a83 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseAITestApp import FirebaseAuth import FirebaseCore @@ -23,7 +23,7 @@ import Testing import UIKit #endif // canImport(UIKit) -@testable import struct FirebaseAI.BackendError +@testable import struct FirebaseAILogic.BackendError /// Test the schema fields. @Suite(.serialized) @@ -50,7 +50,7 @@ struct SchemaTests { @Test(arguments: InstanceConfig.allConfigs) func generateContentSchemaItems(_ config: InstanceConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "application/json", @@ -75,7 +75,7 @@ struct SchemaTests { @Test(arguments: InstanceConfig.allConfigs) func generateContentSchemaNumberRange(_ config: InstanceConfig) async throws { - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "application/json", @@ -104,7 +104,7 @@ struct SchemaTests { let price: Double // Will correspond to .double in schema let salePrice: Float // Will correspond to .float in schema } - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "application/json", @@ -195,7 +195,7 @@ struct SchemaTests { ], description: "A U.S. mailing address" ) - let model = FirebaseAI.componentInstance(config).generativeModel( + let model = AILogic.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: GenerationConfig( temperature: 0.0, diff --git a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index df06f43c91f..f0289ad4daf 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseAITestApp import FirebaseCore import Testing -@testable import struct FirebaseAI.APIConfig +@testable import struct FirebaseAILogic.APIConfig struct InstanceConfig: Equatable, Encodable { static let vertexAI_v1beta = InstanceConfig( @@ -146,12 +146,12 @@ extension InstanceConfig: CustomTestStringConvertible { } } -extension FirebaseAI { - static func componentInstance(_ instanceConfig: InstanceConfig) -> FirebaseAI { +extension AILogic { + static func componentInstance(_ instanceConfig: InstanceConfig) -> AILogic { switch instanceConfig.apiConfig.service { case .vertexAI: let location = instanceConfig.location ?? "us-central1" - return FirebaseAI.createInstance( + return AILogic.createInstance( app: instanceConfig.app, location: location, apiConfig: instanceConfig.apiConfig, @@ -162,7 +162,7 @@ extension FirebaseAI { instanceConfig.location == nil, "The Developer API is global and does not support `location`." ) - return FirebaseAI.createInstance( + return AILogic.createInstance( app: instanceConfig.app, location: nil, apiConfig: instanceConfig.apiConfig, diff --git a/FirebaseAI/Tests/Unit/APITests.swift b/FirebaseAI/Tests/Unit/APITests.swift index 16c963b1f0c..248e3a03e8b 100644 --- a/FirebaseAI/Tests/Unit/APITests.swift +++ b/FirebaseAI/Tests/Unit/APITests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseCore import XCTest #if canImport(AppKit) @@ -42,16 +42,16 @@ final class APITests: XCTestCase { let _ = RequestOptions(timeout: 30.0) // Instantiate Firebase AI SDK - Default App - let firebaseAI = FirebaseAI.firebaseAI() - let _ = FirebaseAI.firebaseAI(backend: .googleAI()) - let _ = FirebaseAI.firebaseAI(backend: .vertexAI()) - let _ = FirebaseAI.firebaseAI(backend: .vertexAI(location: "my-location")) + let firebaseAI = AILogic.aiLogic() + let _ = AILogic.aiLogic(backend: .googleAI()) + let _ = AILogic.aiLogic(backend: .vertexAI()) + let _ = AILogic.aiLogic(backend: .vertexAI(location: "my-location")) // Instantiate Firebase AI SDK - Custom App - let _ = FirebaseAI.firebaseAI(app: app!) - let _ = FirebaseAI.firebaseAI(app: app!, backend: .googleAI()) - let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI()) - let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI(location: "my-location")) + let _ = AILogic.aiLogic(app: app!) + let _ = AILogic.aiLogic(app: app!, backend: .googleAI()) + let _ = AILogic.aiLogic(app: app!, backend: .vertexAI()) + let _ = AILogic.aiLogic(app: app!, backend: .vertexAI(location: "my-location")) // Permutations without optional arguments. diff --git a/FirebaseAI/Tests/Unit/ChatTests.swift b/FirebaseAI/Tests/Unit/ChatTests.swift index 7ecebf42e28..d46315bc5d5 100644 --- a/FirebaseAI/Tests/Unit/ChatTests.swift +++ b/FirebaseAI/Tests/Unit/ChatTests.swift @@ -16,7 +16,7 @@ import FirebaseCore import Foundation import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ChatTests: XCTestCase { @@ -71,7 +71,7 @@ final class ChatTests: XCTestCase { firebaseApp: app, useLimitedUseAppCheckTokens: false ), - apiConfig: FirebaseAI.defaultVertexAIAPIConfig, + apiConfig: AILogic.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession @@ -107,7 +107,7 @@ final class ChatTests: XCTestCase { modelName: modelName, modelResourceName: modelResourceName, firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), - apiConfig: FirebaseAI.defaultVertexAIAPIConfig, + apiConfig: AILogic.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession @@ -148,7 +148,7 @@ final class ChatTests: XCTestCase { modelName: modelName, modelResourceName: modelResourceName, firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), - apiConfig: FirebaseAI.defaultVertexAIAPIConfig, + apiConfig: AILogic.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession @@ -184,7 +184,7 @@ final class ChatTests: XCTestCase { modelName: modelName, modelResourceName: modelResourceName, firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), - apiConfig: FirebaseAI.defaultVertexAIAPIConfig, + apiConfig: AILogic.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession diff --git a/FirebaseAI/Tests/Unit/GenerationConfigTests.swift b/FirebaseAI/Tests/Unit/GenerationConfigTests.swift index 22bcd70b035..2b38d1898d4 100644 --- a/FirebaseAI/Tests/Unit/GenerationConfigTests.swift +++ b/FirebaseAI/Tests/Unit/GenerationConfigTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import Foundation import XCTest diff --git a/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift index 00e0d398855..8caf0ca4218 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift @@ -17,7 +17,7 @@ import FirebaseAuthInterop import FirebaseCore import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class GenerativeModelGoogleAITests: XCTestCase { @@ -58,7 +58,7 @@ final class GenerativeModelGoogleAITests: XCTestCase { ].sorted() let testModelName = "test-model" let testModelResourceName = "projects/test-project-id/models/test-model" - let apiConfig = FirebaseAI.defaultVertexAIAPIConfig + let apiConfig = AILogic.defaultVertexAIAPIConfig let googleAISubdirectory = "mock-responses/googleai" diff --git a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift index 0e33ba557e6..770967a77a3 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift @@ -17,7 +17,7 @@ import FirebaseAuthInterop import FirebaseCore import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class GenerativeModelVertexAITests: XCTestCase { @@ -94,7 +94,7 @@ final class GenerativeModelVertexAITests: XCTestCase { let testModelName = "test-model" let testModelResourceName = "projects/test-project-id/locations/test-location/publishers/google/models/test-model" - let apiConfig = FirebaseAI.defaultVertexAIAPIConfig + let apiConfig = AILogic.defaultVertexAIAPIConfig let vertexSubdirectory = "mock-responses/vertexai" diff --git a/FirebaseAI/Tests/Unit/JSONValueTests.swift b/FirebaseAI/Tests/Unit/JSONValueTests.swift index 54ac3520e77..1f1beafe922 100644 --- a/FirebaseAI/Tests/Unit/JSONValueTests.swift +++ b/FirebaseAI/Tests/Unit/JSONValueTests.swift @@ -13,7 +13,7 @@ // limitations under the License. import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class JSONValueTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/PartTests.swift b/FirebaseAI/Tests/Unit/PartTests.swift index 544e229e08b..ebf8ba44e33 100644 --- a/FirebaseAI/Tests/Unit/PartTests.swift +++ b/FirebaseAI/Tests/Unit/PartTests.swift @@ -15,7 +15,7 @@ import Foundation import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class PartTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift index 658db79a50e..266a64b3429 100644 --- a/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift @@ -23,7 +23,7 @@ import XCTest import CoreImage #endif // canImport(CoreImage) -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class PartsRepresentableTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/RequestOptionsTest.swift b/FirebaseAI/Tests/Unit/RequestOptionsTest.swift index 5c03a6b63f4..92ab65f1f95 100644 --- a/FirebaseAI/Tests/Unit/RequestOptionsTest.swift +++ b/FirebaseAI/Tests/Unit/RequestOptionsTest.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class RequestOptionsTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/SafetyTests.swift b/FirebaseAI/Tests/Unit/SafetyTests.swift index 4a1e07e04e3..b4422b61fb5 100644 --- a/FirebaseAI/Tests/Unit/SafetyTests.swift +++ b/FirebaseAI/Tests/Unit/SafetyTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class SafetyTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift index cfd8089b65b..06171346e34 100644 --- a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseCore import XCTest @@ -21,7 +21,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ChatSnippets: XCTestCase { - lazy var model = FirebaseAI.firebaseAI().generativeModel(modelName: "gemini-2.0-flash") + lazy var model = AILogic.aiLogic().generativeModel(modelName: "gemini-2.0-flash") override func setUpWithError() throws { try FirebaseApp.configureDefaultAppForSnippets() diff --git a/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift index 63b1be6d069..3d04e156312 100644 --- a/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift @@ -14,7 +14,7 @@ #if SWIFT_PACKAGE // The FirebaseStorage dependency has only been added in Package.swift. - import FirebaseAI + import FirebaseAILogic import FirebaseCore import FirebaseStorage diff --git a/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift index 8b8e37368f9..5770def5e80 100644 --- a/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseCore import XCTest @@ -22,7 +22,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class CountTokensSnippets: XCTestCase { let bundle = BundleTestUtil.bundle() - lazy var model = FirebaseAI.firebaseAI().generativeModel(modelName: "gemini-2.0-flash") + lazy var model = AILogic.aiLogic().generativeModel(modelName: "gemini-2.0-flash") lazy var imageURL = { guard let url = bundle.url(forResource: "blue", withExtension: "png") else { fatalError("Image file blue.png not found in Resources.") diff --git a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift index e8ef9bf512c..2922a6c5152 100644 --- a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseCore import XCTest @@ -67,7 +67,7 @@ final class FunctionCallingSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports function calling, like a Gemini 1.5 model. - let model = FirebaseAI.firebaseAI().generativeModel( + let model = AILogic.aiLogic().generativeModel( modelName: "gemini-2.0-flash", // Provide the function declaration to the model. tools: [.functionDeclarations([fetchWeatherTool])] diff --git a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift index eeda8052cc6..e0c4a14ac3f 100644 --- a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseCore import XCTest @@ -26,7 +26,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class MultimodalSnippets: XCTestCase { let bundle = BundleTestUtil.bundle() - lazy var model = FirebaseAI.firebaseAI(backend: .vertexAI()).generativeModel( + lazy var model = AILogic.aiLogic(backend: .vertexAI()).generativeModel( modelName: "gemini-2.0-flash" ) lazy var videoURL = { diff --git a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift index 8db4f803461..b1f172c8524 100644 --- a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseCore import XCTest @@ -50,7 +50,7 @@ final class StructuredOutputSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. - let model = FirebaseAI.firebaseAI().generativeModel( + let model = AILogic.aiLogic().generativeModel( modelName: "gemini-2.0-flash", // In the generation config, set the `responseMimeType` to `application/json` // and pass the JSON schema object into `responseSchema`. @@ -73,7 +73,7 @@ final class StructuredOutputSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. - let model = FirebaseAI.firebaseAI().generativeModel( + let model = AILogic.aiLogic().generativeModel( modelName: "gemini-2.0-flash", // In the generation config, set the `responseMimeType` to `text/x.enum` // and pass the enum schema object into `responseSchema`. diff --git a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift index 31c54648c5a..3590d3a4e74 100644 --- a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import FirebaseCore import XCTest @@ -21,7 +21,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class TextSnippets: XCTestCase { - lazy var model = FirebaseAI.firebaseAI().generativeModel(modelName: "gemini-2.0-flash") + lazy var model = AILogic.aiLogic().generativeModel(modelName: "gemini-2.0-flash") override func setUpWithError() throws { try FirebaseApp.configureDefaultAppForSnippets() diff --git a/FirebaseAI/Tests/Unit/TestUtilities/GenerativeModelTestUtil.swift b/FirebaseAI/Tests/Unit/TestUtilities/GenerativeModelTestUtil.swift index ee4f47bc5b0..7f9a8724363 100644 --- a/FirebaseAI/Tests/Unit/TestUtilities/GenerativeModelTestUtil.swift +++ b/FirebaseAI/Tests/Unit/TestUtilities/GenerativeModelTestUtil.swift @@ -18,7 +18,7 @@ import FirebaseCore import Foundation import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) enum GenerativeModelTestUtil { diff --git a/FirebaseAI/Tests/Unit/Types/BackendTests.swift b/FirebaseAI/Tests/Unit/Types/BackendTests.swift index e4e87784e68..74fad49e847 100644 --- a/FirebaseAI/Tests/Unit/Types/BackendTests.swift +++ b/FirebaseAI/Tests/Unit/Types/BackendTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic final class BackendTests: XCTestCase { func testVertexAI_defaultLocation() { diff --git a/FirebaseAI/Tests/Unit/Types/CitationMetadataTests.swift b/FirebaseAI/Tests/Unit/Types/CitationMetadataTests.swift index d75325f1a88..4c908813d68 100644 --- a/FirebaseAI/Tests/Unit/Types/CitationMetadataTests.swift +++ b/FirebaseAI/Tests/Unit/Types/CitationMetadataTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class CitationMetadataTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/CitationTests.swift b/FirebaseAI/Tests/Unit/Types/CitationTests.swift index ced45526721..1a372d8fd47 100644 --- a/FirebaseAI/Tests/Unit/Types/CitationTests.swift +++ b/FirebaseAI/Tests/Unit/Types/CitationTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift b/FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift index a53d215359f..0ccb370806f 100644 --- a/FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift +++ b/FirebaseAI/Tests/Unit/Types/GenerateContentResponseTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/FirebaseAI/Tests/Unit/Types/GroundingMetadataTests.swift b/FirebaseAI/Tests/Unit/Types/GroundingMetadataTests.swift index 132d47fc589..ca5e8dc3ede 100644 --- a/FirebaseAI/Tests/Unit/Types/GroundingMetadataTests.swift +++ b/FirebaseAI/Tests/Unit/Types/GroundingMetadataTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class GroundingMetadataTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift index ce66fe94cb7..4bebe401e55 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImageGenerationInstanceTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift index bd5b9f10e44..9e135fa622c 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImageGenerationOutputOptionsTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift index a96174f3b7d..0d398738111 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImageGenerationParametersTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift index 6bf98306cbf..84da2d5a300 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenGCSImageTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift index 9a48ed7c8a2..986236099e4 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenGenerationRequestTests: XCTestCase { @@ -38,7 +38,7 @@ final class ImagenGenerationRequestTests: XCTestCase { includeResponsibleAIFilterReason: includeResponsibleAIFilterReason, includeSafetyAttributes: includeSafetyAttributes ) - let apiConfig = FirebaseAI.defaultVertexAIAPIConfig + let apiConfig = AILogic.defaultVertexAIAPIConfig let instance = ImageGenerationInstance(prompt: "test-prompt") diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift index 97122401253..66e6cab4c8c 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenGenerationResponseTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift index 31effc5c0bf..0894b27fb44 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenInlineImageTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift index 90ac676f90a..4fdfa4416c7 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class RAIFilteredReasonTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift index fe4c290831a..5325b7983b8 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class APIConfigTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift index 6e2f1f790e8..939e8a30f74 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift @@ -15,7 +15,7 @@ import Foundation import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class CountTokensRequestTests: XCTestCase { @@ -23,7 +23,7 @@ final class CountTokensRequestTests: XCTestCase { let modelResourceName = "models/test-model-name" let textPart = TextPart("test-prompt") - let vertexAPIConfig = FirebaseAI.defaultVertexAIAPIConfig + let vertexAPIConfig = AILogic.defaultVertexAIAPIConfig let developerAPIConfig = APIConfig( service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta diff --git a/FirebaseAI/Tests/Unit/Types/InternalPartTests.swift b/FirebaseAI/Tests/Unit/Types/InternalPartTests.swift index 2cd5c5fee2a..00c95596cb9 100644 --- a/FirebaseAI/Tests/Unit/Types/InternalPartTests.swift +++ b/FirebaseAI/Tests/Unit/Types/InternalPartTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -@testable import FirebaseAI +@testable import FirebaseAILogic import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift b/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift index cd56a0c67d1..12a58e992bb 100644 --- a/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift +++ b/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift b/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift index dbe6c2e27ca..7f4315f1012 100644 --- a/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift +++ b/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic final class ProtoDateTests: XCTestCase { let decoder = JSONDecoder() diff --git a/FirebaseAI/Tests/Unit/Types/SchemaTests.swift b/FirebaseAI/Tests/Unit/Types/SchemaTests.swift index 4f911b31bd7..a24b4048645 100644 --- a/FirebaseAI/Tests/Unit/Types/SchemaTests.swift +++ b/FirebaseAI/Tests/Unit/Types/SchemaTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAI +import FirebaseAILogic import Foundation import XCTest diff --git a/FirebaseAI/Tests/Unit/Types/ToolTests.swift b/FirebaseAI/Tests/Unit/Types/ToolTests.swift index b163894932d..7a249322062 100644 --- a/FirebaseAI/Tests/Unit/Types/ToolTests.swift +++ b/FirebaseAI/Tests/Unit/Types/ToolTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ToolTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 702c6e50871..21207477959 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -17,7 +17,7 @@ internal import FirebaseCoreExtension import Foundation import XCTest -@testable import FirebaseAI +@testable import FirebaseAILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) class VertexComponentTests: XCTestCase { @@ -52,7 +52,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly using the default Firebase app. func testVertexInstanceCreation_defaultApp() throws { - let vertex = FirebaseAI.firebaseAI(backend: .vertexAI()) + let vertex = AILogic.aiLogic(backend: .vertexAI()) XCTAssertNotNil(vertex) XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) @@ -66,7 +66,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly using the default Firebase app and custom /// location. func testVertexInstanceCreation_defaultApp_customLocation() throws { - let vertex = FirebaseAI.firebaseAI(backend: .vertexAI(location: location)) + let vertex = AILogic.aiLogic(backend: .vertexAI(location: location)) XCTAssertNotNil(vertex) XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) @@ -79,7 +79,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly. func testVertexInstanceCreation_customApp() throws { - let vertex = FirebaseAI.firebaseAI( + let vertex = AILogic.aiLogic( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) @@ -97,19 +97,19 @@ class VertexComponentTests: XCTestCase { func testSameAppAndLocation_instanceReused() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex1 = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) - let vertex2 = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) + let vertex1 = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) + let vertex2 = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) // Ensure they're the same instance. XCTAssert(vertex1 === vertex2) } func testSameAppAndDifferentLocation_newInstanceCreated() throws { - let vertex1 = FirebaseAI.firebaseAI( + let vertex1 = AILogic.aiLogic( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) - let vertex2 = FirebaseAI.firebaseAI( + let vertex2 = AILogic.aiLogic( app: VertexComponentTests.app, backend: .vertexAI(location: "differentLocation") ) @@ -123,11 +123,11 @@ class VertexComponentTests: XCTestCase { let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) addTeardownBlock { await app2.delete() } - let vertex1 = FirebaseAI.firebaseAI( + let vertex1 = AILogic.aiLogic( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) - let vertex2 = FirebaseAI.firebaseAI(app: app2, backend: .vertexAI(location: location)) + let vertex2 = AILogic.aiLogic(app: app2, backend: .vertexAI(location: location)) XCTAssert(VertexComponentTests.app != app2) XCTAssert(vertex1 !== vertex2) // Ensure they are different instances. @@ -138,11 +138,11 @@ class VertexComponentTests: XCTestCase { let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) addTeardownBlock { await app2.delete() } - let vertex1 = FirebaseAI.firebaseAI( + let vertex1 = AILogic.aiLogic( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) - let vertex2 = FirebaseAI.firebaseAI( + let vertex2 = AILogic.aiLogic( app: app2, backend: .vertexAI(location: "differentLocation") ) @@ -152,13 +152,13 @@ class VertexComponentTests: XCTestCase { } func testSameAppAndDifferentAPI_newInstanceCreated() throws { - let vertex1 = FirebaseAI.createInstance( + let vertex1 = AILogic.createInstance( app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta), useLimitedUseAppCheckTokens: false ) - let vertex2 = FirebaseAI.createInstance( + let vertex2 = AILogic.createInstance( app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1), @@ -172,7 +172,7 @@ class VertexComponentTests: XCTestCase { /// Test that vertex instances get deallocated. func testVertexLifecycle() throws { weak var weakApp: FirebaseApp? - weak var weakVertex: FirebaseAI? + weak var weakVertex: AILogic? try autoreleasepool { let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", gcmSenderID: "00000000000000000-00000000000-000000000") @@ -180,10 +180,10 @@ class VertexComponentTests: XCTestCase { options.apiKey = VertexComponentTests.apiKey let app1 = FirebaseApp(instanceWithName: "transitory app", options: options) weakApp = try XCTUnwrap(app1) - let vertex = FirebaseAI( + let vertex = AILogic( app: app1, location: "transitory location", - apiConfig: FirebaseAI.defaultVertexAIAPIConfig, + apiConfig: AILogic.defaultVertexAIAPIConfig, useLimitedUseAppCheckTokens: false ) weakVertex = vertex @@ -195,7 +195,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_vertexAI() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) + let vertex = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -211,7 +211,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) - let vertex = FirebaseAI.createInstance( + let vertex = AILogic.createInstance( app: app, location: nil, apiConfig: apiConfig, @@ -230,7 +230,7 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance( + let vertex = AILogic.createInstance( app: app, location: nil, apiConfig: apiConfig, @@ -246,7 +246,7 @@ class VertexComponentTests: XCTestCase { func testGenerativeModel_vertexAI() async throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) + let vertex = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) @@ -257,7 +257,7 @@ class VertexComponentTests: XCTestCase { XCTAssertEqual(generativeModel.modelResourceName, modelResourceName) XCTAssertEqual(generativeModel.systemInstruction, expectedSystemInstruction) - XCTAssertEqual(generativeModel.apiConfig, FirebaseAI.defaultVertexAIAPIConfig) + XCTAssertEqual(generativeModel.apiConfig, AILogic.defaultVertexAIAPIConfig) } func testGenerativeModel_developerAPI() async throws { @@ -266,7 +266,7 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance( + let vertex = AILogic.createInstance( app: app, location: nil, apiConfig: apiConfig, diff --git a/FirebaseAI/Wrapper/FirebaseAIWrapper.swift b/FirebaseAI/Wrapper/FirebaseAIWrapper.swift new file mode 100644 index 00000000000..23fff507f4e --- /dev/null +++ b/FirebaseAI/Wrapper/FirebaseAIWrapper.swift @@ -0,0 +1,46 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore + +@_exported import FirebaseAILogic + +public typealias FirebaseAI = AILogic + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public extension AILogic { + /// Creates an instance of `AILogic`. + /// + /// - Parameters: + /// - app: A custom `FirebaseApp` used for initialization; if not specified, uses the default + /// ``FirebaseApp``. + /// - backend: The backend API for the Firebase AI SDK; if not specified, uses the default + /// ``Backend/googleAI()`` (Gemini Developer API). + /// - useLimitedUseAppCheckTokens: When sending tokens to the backend, this option enables + /// the usage of App Check's limited-use tokens instead of the standard cached tokens. Learn + /// more about [limited-use tokens](https://firebase.google.com/docs/ai-logic/app-check), + /// including their nuances, when to use them, and best practices for integrating them into + /// your app. + /// + /// _This flag is set to `false` by default._ + /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay + /// > protection is added. + /// - Returns: An `AILogic` instance, configured with the custom `FirebaseApp`. + static func firebaseAI(app: FirebaseApp? = nil, backend: Backend = .googleAI(), + useLimitedUseAppCheckTokens: Bool = false) -> AILogic { + aiLogic( + app: app, backend: backend, useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens + ) + } +} diff --git a/Package.swift b/Package.swift index 67247393177..d728a667ec0 100644 --- a/Package.swift +++ b/Package.swift @@ -26,7 +26,16 @@ let package = Package( products: [ .library( name: "FirebaseAI", - targets: ["FirebaseAI"] + targets: [ + "FirebaseAI", + "FirebaseAILogic", + ] + ), + .library( + name: "FirebaseAILogic", + targets: [ + "FirebaseAILogic", + ] ), .library( name: "FirebaseAnalytics", @@ -178,7 +187,7 @@ let package = Package( // MARK: - Firebase AI .target( - name: "FirebaseAI", + name: "FirebaseAILogic", dependencies: [ "FirebaseAppCheckInterop", "FirebaseAuthInterop", @@ -187,6 +196,11 @@ let package = Package( ], path: "FirebaseAI/Sources" ), + .target( + name: "FirebaseAI", + dependencies: ["FirebaseAILogic"], + path: "FirebaseAI/Wrapper" + ), .testTarget( name: "FirebaseAIUnit", dependencies: [ From 3e644af73204cd615901aa1c33ac7ac54cc7be60 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 2 Sep 2025 11:20:36 -0400 Subject: [PATCH 02/12] Add `FirebaseAILogic.podspec` --- FirebaseAI.podspec | 26 ++------------- FirebaseAILogic.podspec | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 24 deletions(-) create mode 100644 FirebaseAILogic.podspec diff --git a/FirebaseAI.podspec b/FirebaseAI.podspec index 25a9e71dec5..177791961b9 100644 --- a/FirebaseAI.podspec +++ b/FirebaseAI.podspec @@ -32,7 +32,7 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK s.prefix_header_file = false s.source_files = [ - 'FirebaseAI/Sources/**/*.swift', + 'FirebaseAI/Wrapper/**/*.swift', ] s.swift_version = '5.9' @@ -43,28 +43,6 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK s.tvos.framework = 'UIKit' s.watchos.framework = 'WatchKit' - s.dependency 'FirebaseAppCheckInterop', '~> 12.3.0' - s.dependency 'FirebaseAuthInterop', '~> 12.3.0' + s.dependency 'FirebaseAILogic', '12.3.0' s.dependency 'FirebaseCore', '~> 12.3.0' - s.dependency 'FirebaseCoreExtension', '~> 12.3.0' - - s.test_spec 'unit' do |unit_tests| - unit_tests_dir = 'FirebaseAI/Tests/Unit/' - unit_tests.scheme = { :code_coverage => true } - unit_tests.platforms = { - :ios => ios_deployment_target, - :osx => osx_deployment_target, - :tvos => tvos_deployment_target - } - unit_tests.source_files = [ - unit_tests_dir + '**/*.swift', - ] - unit_tests.exclude_files = [ - unit_tests_dir + 'Snippets/**/*.swift', - ] - unit_tests.resources = [ - unit_tests_dir + 'vertexai-sdk-test-data/mock-responses', - unit_tests_dir + 'Resources/**/*', - ] - end end diff --git a/FirebaseAILogic.podspec b/FirebaseAILogic.podspec new file mode 100644 index 00000000000..4644543dea9 --- /dev/null +++ b/FirebaseAILogic.podspec @@ -0,0 +1,70 @@ +Pod::Spec.new do |s| + s.name = 'FirebaseAILogic' + s.version = '12.3.0' + s.summary = 'Firebase AI Logic SDK' + + s.description = <<-DESC +Build AI-powered apps and features with the Gemini API using the Firebase AI Logic SDK. + DESC + + s.homepage = 'https://firebase.google.com' + s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } + s.authors = 'Google, Inc.' + + s.source = { + :git => 'https://github.com/firebase/firebase-ios-sdk.git', + :tag => 'CocoaPods-' + s.version.to_s + } + + s.social_media_url = 'https://twitter.com/Firebase' + + ios_deployment_target = '15.0' + osx_deployment_target = '12.0' + tvos_deployment_target = '15.0' + watchos_deployment_target = '8.0' + + s.ios.deployment_target = ios_deployment_target + s.osx.deployment_target = osx_deployment_target + s.tvos.deployment_target = tvos_deployment_target + s.watchos.deployment_target = watchos_deployment_target + + s.cocoapods_version = '>= 1.12.0' + s.prefix_header_file = false + + s.source_files = [ + 'FirebaseAI/Sources/**/*.swift', + ] + + s.swift_version = '5.9' + + s.framework = 'Foundation' + s.ios.framework = 'UIKit' + s.osx.framework = 'AppKit' + s.tvos.framework = 'UIKit' + s.watchos.framework = 'WatchKit' + + s.dependency 'FirebaseAppCheckInterop', '~> 12.3.0' + s.dependency 'FirebaseAuthInterop', '~> 12.3.0' + s.dependency 'FirebaseCore', '~> 12.3.0' + s.dependency 'FirebaseCoreExtension', '~> 12.3.0' + + s.test_spec 'unit' do |unit_tests| + unit_tests_dir = 'FirebaseAI/Tests/Unit/' + unit_tests.scheme = { :code_coverage => true } + unit_tests.platforms = { + :ios => ios_deployment_target, + :osx => osx_deployment_target, + :tvos => tvos_deployment_target + } + unit_tests.source_files = [ + unit_tests_dir + '**/*.swift', + ] + unit_tests.exclude_files = [ + unit_tests_dir + 'Snippets/**/*.swift', + ] + unit_tests.resources = [ + unit_tests_dir + 'vertexai-sdk-test-data/mock-responses', + unit_tests_dir + 'Resources/**/*', + ] + end +end From b37340a2b9d49480c11c8c1f8e84140ce9c06786 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 2 Sep 2025 11:23:46 -0400 Subject: [PATCH 03/12] Add availability annotation to `FirebaseAI` typealias --- FirebaseAI/Wrapper/FirebaseAIWrapper.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/FirebaseAI/Wrapper/FirebaseAIWrapper.swift b/FirebaseAI/Wrapper/FirebaseAIWrapper.swift index 23fff507f4e..9da83d077e6 100644 --- a/FirebaseAI/Wrapper/FirebaseAIWrapper.swift +++ b/FirebaseAI/Wrapper/FirebaseAIWrapper.swift @@ -16,6 +16,7 @@ import FirebaseCore @_exported import FirebaseAILogic +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public typealias FirebaseAI = AILogic @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) From 4f640ab8506a5a5fbd7c4c3571440e53e510f200 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 2 Sep 2025 12:31:10 -0400 Subject: [PATCH 04/12] Update `FirebaseManifest` to include `FirebaseAILogic` pod --- ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index 4b2b361b069..1a07b437436 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -38,7 +38,8 @@ public let shared = Manifest( Pod("FirebaseABTesting", zip: true), Pod("FirebaseAppCheck", zip: true), Pod("FirebaseRemoteConfig", zip: true), - Pod("FirebaseAI", zip: true), + Pod("FirebaseAILogic", zip: true), + Pod("FirebaseAI", zip: false), Pod("FirebaseAppDistribution", isBeta: true, platforms: ["ios"], zip: true), Pod("FirebaseAuth", zip: true), Pod("FirebaseCrashlytics", zip: true), From 8cc0df464e1a9ed07d9388f9c8a357446c1f953b Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 18 Sep 2025 17:09:33 -0400 Subject: [PATCH 05/12] Revert rename of `FirebaseAI` to `AILog`; keep module rename. --- FirebaseAI/Sources/Constants.swift | 4 +- FirebaseAI/Sources/FirebaseAI.swift | 23 +++++----- .../CountTokensIntegrationTests.swift | 6 +-- .../GenerateContentIntegrationTests.swift | 12 ++--- .../Integration/ImagenIntegrationTests.swift | 4 +- .../Tests/Integration/IntegrationTests.swift | 6 +-- .../Tests/Integration/SchemaTests.swift | 8 ++-- .../Tests/Utilities/InstanceConfig.swift | 8 ++-- FirebaseAI/Tests/Unit/APITests.swift | 16 +++---- FirebaseAI/Tests/Unit/ChatTests.swift | 8 ++-- .../Unit/GenerativeModelGoogleAITests.swift | 2 +- .../Unit/GenerativeModelVertexAITests.swift | 2 +- .../Tests/Unit/Snippets/ChatSnippets.swift | 2 +- .../Unit/Snippets/CountTokensSnippets.swift | 2 +- .../Snippets/FunctionCallingSnippets.swift | 2 +- .../Unit/Snippets/MultimodalSnippets.swift | 2 +- .../Snippets/StructuredOutputSnippets.swift | 4 +- .../Tests/Unit/Snippets/TextSnippets.swift | 2 +- .../Imagen/ImagenGenerationRequestTests.swift | 2 +- .../Requests/CountTokensRequestTests.swift | 2 +- .../Tests/Unit/VertexComponentTests.swift | 44 +++++++++---------- FirebaseAI/Wrapper/FirebaseAIWrapper.swift | 32 -------------- 22 files changed, 81 insertions(+), 112 deletions(-) diff --git a/FirebaseAI/Sources/Constants.swift b/FirebaseAI/Sources/Constants.swift index b5279a381f4..1af4c44c531 100644 --- a/FirebaseAI/Sources/Constants.swift +++ b/FirebaseAI/Sources/Constants.swift @@ -19,6 +19,6 @@ enum Constants { /// The base reverse-DNS name for `NSError` or `CustomNSError` error domains. /// /// - Important: A suffix must be appended to produce an error domain (e.g., - /// "com.google.firebase.ai-logic.ExampleError"). - static let baseErrorDomain = "com.google.firebase.ai-logic" + /// "com.google.firebase.firebaseai.ExampleError"). + static let baseErrorDomain = "com.google.firebase.firebaseai" } diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index b995c86d9fc..7f05d8a0d7b 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -22,10 +22,10 @@ internal import FirebaseCoreExtension /// The Firebase AI SDK provides access to Gemini models directly from your app. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public final class AILogic: Sendable { +public final class FirebaseAI: Sendable { // MARK: - Public APIs - /// Creates an instance of `AILogic`. + /// Creates an instance of `FirebaseAI`. /// /// - Parameters: /// - app: A custom `FirebaseApp` used for initialization; if not specified, uses the default @@ -41,16 +41,17 @@ public final class AILogic: Sendable { /// _This flag is set to `false` by default._ /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay /// > protection is added. - /// - Returns: An `AILogic` instance, configured with the custom `FirebaseApp`. - public static func aiLogic(app: FirebaseApp? = nil, backend: Backend = .googleAI(), - useLimitedUseAppCheckTokens: Bool = false) -> AILogic { + /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. + public static func firebaseAI(app: FirebaseApp? = nil, + backend: Backend = .googleAI(), + useLimitedUseAppCheckTokens: Bool = false) -> FirebaseAI { let instance = createInstance( app: app, location: backend.location, apiConfig: backend.apiConfig, useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) - // Verify that the `AILogic` instance is always configured with the production endpoint since + // 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 == .firebaseProxyProd) assert(instance.apiConfig.version == .v1beta) @@ -151,9 +152,9 @@ public final class AILogic: Sendable { let apiConfig: APIConfig - /// A map of active `AILogic` instances keyed by the `FirebaseApp` name and the `location`, + /// A map of active `FirebaseAI` instances keyed by the `FirebaseApp` name and the `location`, /// in the format `appName:location`. - private nonisolated(unsafe) static var instances: [InstanceKey: AILogic] = [:] + private nonisolated(unsafe) static var instances: [InstanceKey: FirebaseAI] = [:] /// Lock to manage access to the `instances` array to avoid race conditions. private nonisolated(unsafe) static var instancesLock: os_unfair_lock = .init() @@ -167,7 +168,7 @@ public final class AILogic: Sendable { static func createInstance(app: FirebaseApp?, location: String?, apiConfig: APIConfig, - useLimitedUseAppCheckTokens: Bool) -> AILogic { + useLimitedUseAppCheckTokens: Bool) -> FirebaseAI { guard let app = app ?? FirebaseApp.app() else { fatalError("No instance of the default Firebase app was found.") } @@ -186,7 +187,7 @@ public final class AILogic: Sendable { if let instance = instances[instanceKey] { return instance } - let newInstance = AILogic( + let newInstance = FirebaseAI( app: app, location: location, apiConfig: apiConfig, @@ -265,7 +266,7 @@ public final class AILogic: Sendable { } } - /// Identifier for a unique instance of ``AILogic``. + /// Identifier for a unique instance of ``FirebaseAI``. /// /// This type is `Hashable` so that it can be used as a key in the `instances` dictionary. private struct InstanceKey: Sendable, Hashable { diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift index f59b67402bc..30e8f897c58 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift @@ -48,7 +48,7 @@ struct CountTokensIntegrationTests { @Test(arguments: InstanceConfig.allConfigs) func countTokens_text(_ config: InstanceConfig) async throws { let prompt = "Why is the sky blue?" - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: generationConfig, safetySettings: safetySettings @@ -65,7 +65,7 @@ struct CountTokensIntegrationTests { @Test(arguments: InstanceConfig.allConfigs) func countTokens_text_systemInstruction(_ config: InstanceConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: generationConfig, safetySettings: safetySettings, @@ -83,7 +83,7 @@ struct CountTokensIntegrationTests { @Test(arguments: InstanceConfig.allConfigs) func countTokens_jsonSchema(_ config: InstanceConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: GenerationConfig( responseMIMEType: "application/json", diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index c40cc017a5d..232e3c889f2 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -105,7 +105,7 @@ struct GenerateContentIntegrationTests { arguments: InstanceConfig.allConfigs ) func generateContentEnum(_ config: InstanceConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "text/x.enum", @@ -184,7 +184,7 @@ struct GenerateContentIntegrationTests { ) func generateContentThinking(_ config: InstanceConfig, modelName: String, thinkingConfig: ThinkingConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: modelName, generationConfig: GenerationConfig( temperature: 0.0, @@ -268,7 +268,7 @@ struct GenerateContentIntegrationTests { "country": .string(), ] ) - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: modelName, generationConfig: GenerationConfig( temperature: 0.0, @@ -390,7 +390,7 @@ struct GenerateContentIntegrationTests { arguments: InstanceConfig.allConfigs ) func generateContent_withGoogleSearch_succeeds(_ config: InstanceConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, tools: [.googleSearch()] ) @@ -485,7 +485,7 @@ struct GenerateContentIntegrationTests { - Do NOT wrap the JSON in Markdown code blocks (e.g., ```json ... ``` or ``` ... ```). - The response must start with '[' and end with ']'. """ - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: modelName, generationConfig: generationConfig, safetySettings: safetySettings @@ -589,7 +589,7 @@ struct GenerateContentIntegrationTests { @Test(arguments: InstanceConfig.appCheckNotConfiguredConfigs) func generateContent_appCheckNotConfigured_shouldFail(_ config: InstanceConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash ) let prompt = "Where is Google headquarters located? Answer with the city name only." diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift index c08ffd0b858..95a4f04ff2b 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift @@ -34,13 +34,13 @@ import Testing .serialized ) struct ImagenIntegrationTests { - var vertex: AILogic + var vertex: FirebaseAI var storage: Storage var userID1: String init() async throws { userID1 = try await TestHelpers.getUserID() - vertex = AILogic.aiLogic(backend: .vertexAI()) + vertex = FirebaseAI.firebaseAI(backend: .vertexAI()) storage = Storage.storage() } diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift index c9911abc2c1..37353eba51a 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift @@ -44,14 +44,14 @@ final class IntegrationTests: XCTestCase { // Candidates and total token counts may differ slightly between runs due to whitespace tokens. let tokenCountAccuracy = 1 - var vertex: AILogic! + var vertex: FirebaseAI! var model: GenerativeModel! var storage: Storage! var userID1 = "" override func setUp() async throws { userID1 = try await TestHelpers.getUserID() - vertex = AILogic.aiLogic(backend: .vertexAI()) + vertex = FirebaseAI.firebaseAI(backend: .vertexAI()) model = vertex.generativeModel( modelName: "gemini-2.0-flash", generationConfig: generationConfig, @@ -196,7 +196,7 @@ final class IntegrationTests: XCTestCase { func testCountTokens_appCheckNotConfigured_shouldFail() async throws { let app = try XCTUnwrap(FirebaseApp.app(name: FirebaseAppNames.appCheckNotConfigured)) - let vertex = AILogic.aiLogic(app: app, backend: .vertexAI()) + let vertex = FirebaseAI.firebaseAI(app: app, backend: .vertexAI()) let model = vertex.generativeModel(modelName: "gemini-2.0-flash") let prompt = "Why is the sky blue?" diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift index decc7250a83..4f4dd1e3dc8 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift @@ -50,7 +50,7 @@ struct SchemaTests { @Test(arguments: InstanceConfig.allConfigs) func generateContentSchemaItems(_ config: InstanceConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "application/json", @@ -75,7 +75,7 @@ struct SchemaTests { @Test(arguments: InstanceConfig.allConfigs) func generateContentSchemaNumberRange(_ config: InstanceConfig) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "application/json", @@ -104,7 +104,7 @@ struct SchemaTests { let price: Double // Will correspond to .double in schema let salePrice: Float // Will correspond to .float in schema } - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "application/json", @@ -195,7 +195,7 @@ struct SchemaTests { ], description: "A U.S. mailing address" ) - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: GenerationConfig( temperature: 0.0, diff --git a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index f0289ad4daf..f86132084b7 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -146,12 +146,12 @@ extension InstanceConfig: CustomTestStringConvertible { } } -extension AILogic { - static func componentInstance(_ instanceConfig: InstanceConfig) -> AILogic { +extension FirebaseAI { + static func componentInstance(_ instanceConfig: InstanceConfig) -> FirebaseAI { switch instanceConfig.apiConfig.service { case .vertexAI: let location = instanceConfig.location ?? "us-central1" - return AILogic.createInstance( + return FirebaseAI.createInstance( app: instanceConfig.app, location: location, apiConfig: instanceConfig.apiConfig, @@ -162,7 +162,7 @@ extension AILogic { instanceConfig.location == nil, "The Developer API is global and does not support `location`." ) - return AILogic.createInstance( + return FirebaseAI.createInstance( app: instanceConfig.app, location: nil, apiConfig: instanceConfig.apiConfig, diff --git a/FirebaseAI/Tests/Unit/APITests.swift b/FirebaseAI/Tests/Unit/APITests.swift index 248e3a03e8b..fbfd647533d 100644 --- a/FirebaseAI/Tests/Unit/APITests.swift +++ b/FirebaseAI/Tests/Unit/APITests.swift @@ -42,16 +42,16 @@ final class APITests: XCTestCase { let _ = RequestOptions(timeout: 30.0) // Instantiate Firebase AI SDK - Default App - let firebaseAI = AILogic.aiLogic() - let _ = AILogic.aiLogic(backend: .googleAI()) - let _ = AILogic.aiLogic(backend: .vertexAI()) - let _ = AILogic.aiLogic(backend: .vertexAI(location: "my-location")) + let firebaseAI = FirebaseAI.firebaseAI() + let _ = FirebaseAI.firebaseAI(backend: .googleAI()) + let _ = FirebaseAI.firebaseAI(backend: .vertexAI()) + let _ = FirebaseAI.firebaseAI(backend: .vertexAI(location: "my-location")) // Instantiate Firebase AI SDK - Custom App - let _ = AILogic.aiLogic(app: app!) - let _ = AILogic.aiLogic(app: app!, backend: .googleAI()) - let _ = AILogic.aiLogic(app: app!, backend: .vertexAI()) - let _ = AILogic.aiLogic(app: app!, backend: .vertexAI(location: "my-location")) + let _ = FirebaseAI.firebaseAI(app: app!) + let _ = FirebaseAI.firebaseAI(app: app!, backend: .googleAI()) + let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI()) + let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI(location: "my-location")) // Permutations without optional arguments. diff --git a/FirebaseAI/Tests/Unit/ChatTests.swift b/FirebaseAI/Tests/Unit/ChatTests.swift index d46315bc5d5..b2e43ba610c 100644 --- a/FirebaseAI/Tests/Unit/ChatTests.swift +++ b/FirebaseAI/Tests/Unit/ChatTests.swift @@ -71,7 +71,7 @@ final class ChatTests: XCTestCase { firebaseApp: app, useLimitedUseAppCheckTokens: false ), - apiConfig: AILogic.defaultVertexAIAPIConfig, + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession @@ -107,7 +107,7 @@ final class ChatTests: XCTestCase { modelName: modelName, modelResourceName: modelResourceName, firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), - apiConfig: AILogic.defaultVertexAIAPIConfig, + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession @@ -148,7 +148,7 @@ final class ChatTests: XCTestCase { modelName: modelName, modelResourceName: modelResourceName, firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), - apiConfig: AILogic.defaultVertexAIAPIConfig, + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession @@ -184,7 +184,7 @@ final class ChatTests: XCTestCase { modelName: modelName, modelResourceName: modelResourceName, firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), - apiConfig: AILogic.defaultVertexAIAPIConfig, + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession diff --git a/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift index 030dde1c20e..5a856db16db 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift @@ -58,7 +58,7 @@ final class GenerativeModelGoogleAITests: XCTestCase { ].sorted() let testModelName = "test-model" let testModelResourceName = "projects/test-project-id/models/test-model" - let apiConfig = AILogic.defaultVertexAIAPIConfig + let apiConfig = FirebaseAI.defaultVertexAIAPIConfig let googleAISubdirectory = "mock-responses/googleai" diff --git a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift index ac9035a9036..f205324a99c 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift @@ -94,7 +94,7 @@ final class GenerativeModelVertexAITests: XCTestCase { let testModelName = "test-model" let testModelResourceName = "projects/test-project-id/locations/test-location/publishers/google/models/test-model" - let apiConfig = AILogic.defaultVertexAIAPIConfig + let apiConfig = FirebaseAI.defaultVertexAIAPIConfig let vertexSubdirectory = "mock-responses/vertexai" diff --git a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift index 06171346e34..67bd5cb9a4a 100644 --- a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift @@ -21,7 +21,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ChatSnippets: XCTestCase { - lazy var model = AILogic.aiLogic().generativeModel(modelName: "gemini-2.0-flash") + lazy var model = FirebaseAI.firebaseAI().generativeModel(modelName: "gemini-2.0-flash") override func setUpWithError() throws { try FirebaseApp.configureDefaultAppForSnippets() diff --git a/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift index 5770def5e80..37c4f7e3c04 100644 --- a/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/CountTokensSnippets.swift @@ -22,7 +22,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class CountTokensSnippets: XCTestCase { let bundle = BundleTestUtil.bundle() - lazy var model = AILogic.aiLogic().generativeModel(modelName: "gemini-2.0-flash") + lazy var model = FirebaseAI.firebaseAI().generativeModel(modelName: "gemini-2.0-flash") lazy var imageURL = { guard let url = bundle.url(forResource: "blue", withExtension: "png") else { fatalError("Image file blue.png not found in Resources.") diff --git a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift index 2922a6c5152..dac1dea76ba 100644 --- a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -67,7 +67,7 @@ final class FunctionCallingSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports function calling, like a Gemini 1.5 model. - let model = AILogic.aiLogic().generativeModel( + let model = FirebaseAI.firebaseAI().generativeModel( modelName: "gemini-2.0-flash", // Provide the function declaration to the model. tools: [.functionDeclarations([fetchWeatherTool])] diff --git a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift index e0c4a14ac3f..7e8af1e3882 100644 --- a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift @@ -26,7 +26,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class MultimodalSnippets: XCTestCase { let bundle = BundleTestUtil.bundle() - lazy var model = AILogic.aiLogic(backend: .vertexAI()).generativeModel( + lazy var model = FirebaseAI.firebaseAI(backend: .vertexAI()).generativeModel( modelName: "gemini-2.0-flash" ) lazy var videoURL = { diff --git a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift index b1f172c8524..17c426c3651 100644 --- a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -50,7 +50,7 @@ final class StructuredOutputSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. - let model = AILogic.aiLogic().generativeModel( + let model = FirebaseAI.firebaseAI().generativeModel( modelName: "gemini-2.0-flash", // In the generation config, set the `responseMimeType` to `application/json` // and pass the JSON schema object into `responseSchema`. @@ -73,7 +73,7 @@ final class StructuredOutputSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. - let model = AILogic.aiLogic().generativeModel( + let model = FirebaseAI.firebaseAI().generativeModel( modelName: "gemini-2.0-flash", // In the generation config, set the `responseMimeType` to `text/x.enum` // and pass the enum schema object into `responseSchema`. diff --git a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift index 3590d3a4e74..47ee865585d 100644 --- a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift @@ -21,7 +21,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class TextSnippets: XCTestCase { - lazy var model = AILogic.aiLogic().generativeModel(modelName: "gemini-2.0-flash") + lazy var model = FirebaseAI.firebaseAI().generativeModel(modelName: "gemini-2.0-flash") override func setUpWithError() throws { try FirebaseApp.configureDefaultAppForSnippets() diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift index 986236099e4..f36376061d7 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift @@ -38,7 +38,7 @@ final class ImagenGenerationRequestTests: XCTestCase { includeResponsibleAIFilterReason: includeResponsibleAIFilterReason, includeSafetyAttributes: includeSafetyAttributes ) - let apiConfig = AILogic.defaultVertexAIAPIConfig + let apiConfig = FirebaseAI.defaultVertexAIAPIConfig let instance = ImageGenerationInstance(prompt: "test-prompt") diff --git a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift index 939e8a30f74..7c43833ed45 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift @@ -23,7 +23,7 @@ final class CountTokensRequestTests: XCTestCase { let modelResourceName = "models/test-model-name" let textPart = TextPart("test-prompt") - let vertexAPIConfig = AILogic.defaultVertexAIAPIConfig + let vertexAPIConfig = FirebaseAI.defaultVertexAIAPIConfig let developerAPIConfig = APIConfig( service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 21207477959..1893980dba2 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -52,7 +52,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly using the default Firebase app. func testVertexInstanceCreation_defaultApp() throws { - let vertex = AILogic.aiLogic(backend: .vertexAI()) + let vertex = FirebaseAI.firebaseAI(backend: .vertexAI()) XCTAssertNotNil(vertex) XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) @@ -66,7 +66,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly using the default Firebase app and custom /// location. func testVertexInstanceCreation_defaultApp_customLocation() throws { - let vertex = AILogic.aiLogic(backend: .vertexAI(location: location)) + let vertex = FirebaseAI.firebaseAI(backend: .vertexAI(location: location)) XCTAssertNotNil(vertex) XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) @@ -79,7 +79,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly. func testVertexInstanceCreation_customApp() throws { - let vertex = AILogic.aiLogic( + let vertex = FirebaseAI.firebaseAI( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) @@ -97,19 +97,19 @@ class VertexComponentTests: XCTestCase { func testSameAppAndLocation_instanceReused() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex1 = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) - let vertex2 = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) + let vertex1 = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) + let vertex2 = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) // Ensure they're the same instance. XCTAssert(vertex1 === vertex2) } func testSameAppAndDifferentLocation_newInstanceCreated() throws { - let vertex1 = AILogic.aiLogic( + let vertex1 = FirebaseAI.firebaseAI( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) - let vertex2 = AILogic.aiLogic( + let vertex2 = FirebaseAI.firebaseAI( app: VertexComponentTests.app, backend: .vertexAI(location: "differentLocation") ) @@ -123,11 +123,11 @@ class VertexComponentTests: XCTestCase { let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) addTeardownBlock { await app2.delete() } - let vertex1 = AILogic.aiLogic( + let vertex1 = FirebaseAI.firebaseAI( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) - let vertex2 = AILogic.aiLogic(app: app2, backend: .vertexAI(location: location)) + let vertex2 = FirebaseAI.firebaseAI(app: app2, backend: .vertexAI(location: location)) XCTAssert(VertexComponentTests.app != app2) XCTAssert(vertex1 !== vertex2) // Ensure they are different instances. @@ -138,11 +138,11 @@ class VertexComponentTests: XCTestCase { let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) addTeardownBlock { await app2.delete() } - let vertex1 = AILogic.aiLogic( + let vertex1 = FirebaseAI.firebaseAI( app: VertexComponentTests.app, backend: .vertexAI(location: location) ) - let vertex2 = AILogic.aiLogic( + let vertex2 = FirebaseAI.firebaseAI( app: app2, backend: .vertexAI(location: "differentLocation") ) @@ -152,13 +152,13 @@ class VertexComponentTests: XCTestCase { } func testSameAppAndDifferentAPI_newInstanceCreated() throws { - let vertex1 = AILogic.createInstance( + let vertex1 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta), useLimitedUseAppCheckTokens: false ) - let vertex2 = AILogic.createInstance( + let vertex2 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1), @@ -172,7 +172,7 @@ class VertexComponentTests: XCTestCase { /// Test that vertex instances get deallocated. func testVertexLifecycle() throws { weak var weakApp: FirebaseApp? - weak var weakVertex: AILogic? + weak var weakVertex: FirebaseAI? try autoreleasepool { let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", gcmSenderID: "00000000000000000-00000000000-000000000") @@ -180,10 +180,10 @@ class VertexComponentTests: XCTestCase { options.apiKey = VertexComponentTests.apiKey let app1 = FirebaseApp(instanceWithName: "transitory app", options: options) weakApp = try XCTUnwrap(app1) - let vertex = AILogic( + let vertex = FirebaseAI( app: app1, location: "transitory location", - apiConfig: AILogic.defaultVertexAIAPIConfig, + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, useLimitedUseAppCheckTokens: false ) weakVertex = vertex @@ -195,7 +195,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_vertexAI() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) + let vertex = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -211,7 +211,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) - let vertex = AILogic.createInstance( + let vertex = FirebaseAI.createInstance( app: app, location: nil, apiConfig: apiConfig, @@ -230,7 +230,7 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = AILogic.createInstance( + let vertex = FirebaseAI.createInstance( app: app, location: nil, apiConfig: apiConfig, @@ -246,7 +246,7 @@ class VertexComponentTests: XCTestCase { func testGenerativeModel_vertexAI() async throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex = AILogic.aiLogic(app: app, backend: .vertexAI(location: location)) + let vertex = FirebaseAI.firebaseAI(app: app, backend: .vertexAI(location: location)) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) @@ -257,7 +257,7 @@ class VertexComponentTests: XCTestCase { XCTAssertEqual(generativeModel.modelResourceName, modelResourceName) XCTAssertEqual(generativeModel.systemInstruction, expectedSystemInstruction) - XCTAssertEqual(generativeModel.apiConfig, AILogic.defaultVertexAIAPIConfig) + XCTAssertEqual(generativeModel.apiConfig, FirebaseAI.defaultVertexAIAPIConfig) } func testGenerativeModel_developerAPI() async throws { @@ -266,7 +266,7 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = AILogic.createInstance( + let vertex = FirebaseAI.createInstance( app: app, location: nil, apiConfig: apiConfig, diff --git a/FirebaseAI/Wrapper/FirebaseAIWrapper.swift b/FirebaseAI/Wrapper/FirebaseAIWrapper.swift index 9da83d077e6..a4ff8613b44 100644 --- a/FirebaseAI/Wrapper/FirebaseAIWrapper.swift +++ b/FirebaseAI/Wrapper/FirebaseAIWrapper.swift @@ -12,36 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseCore - @_exported import FirebaseAILogic - -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public typealias FirebaseAI = AILogic - -@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public extension AILogic { - /// Creates an instance of `AILogic`. - /// - /// - Parameters: - /// - app: A custom `FirebaseApp` used for initialization; if not specified, uses the default - /// ``FirebaseApp``. - /// - backend: The backend API for the Firebase AI SDK; if not specified, uses the default - /// ``Backend/googleAI()`` (Gemini Developer API). - /// - useLimitedUseAppCheckTokens: When sending tokens to the backend, this option enables - /// the usage of App Check's limited-use tokens instead of the standard cached tokens. Learn - /// more about [limited-use tokens](https://firebase.google.com/docs/ai-logic/app-check), - /// including their nuances, when to use them, and best practices for integrating them into - /// your app. - /// - /// _This flag is set to `false` by default._ - /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay - /// > protection is added. - /// - Returns: An `AILogic` instance, configured with the custom `FirebaseApp`. - static func firebaseAI(app: FirebaseApp? = nil, backend: Backend = .googleAI(), - useLimitedUseAppCheckTokens: Bool = false) -> AILogic { - aiLogic( - app: app, backend: backend, useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens - ) - } -} From fadb9d305266052c4be04c0fde93cd2d3d809790 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 18 Sep 2025 17:24:16 -0400 Subject: [PATCH 06/12] Updates for 12.4.0 --- FirebaseAILogic.podspec | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/FirebaseAILogic.podspec b/FirebaseAILogic.podspec index 4644543dea9..a677193f4ab 100644 --- a/FirebaseAILogic.podspec +++ b/FirebaseAILogic.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAILogic' - s.version = '12.3.0' + s.version = '12.4.0' s.summary = 'Firebase AI Logic SDK' s.description = <<-DESC @@ -43,10 +43,10 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI Log s.tvos.framework = 'UIKit' s.watchos.framework = 'WatchKit' - s.dependency 'FirebaseAppCheckInterop', '~> 12.3.0' - s.dependency 'FirebaseAuthInterop', '~> 12.3.0' - s.dependency 'FirebaseCore', '~> 12.3.0' - s.dependency 'FirebaseCoreExtension', '~> 12.3.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.4.0' + s.dependency 'FirebaseAuthInterop', '~> 12.4.0' + s.dependency 'FirebaseCore', '~> 12.4.0' + s.dependency 'FirebaseCoreExtension', '~> 12.4.0' s.test_spec 'unit' do |unit_tests| unit_tests_dir = 'FirebaseAI/Tests/Unit/' From c8a4360d5af3b11cfb5bb88dfa02497aa284646d Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 18 Sep 2025 17:27:13 -0400 Subject: [PATCH 07/12] Revert rename in GenerateContentIntegrationTests.swift --- .../Tests/Integration/GenerateContentIntegrationTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index 232e3c889f2..d9062115781 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -64,7 +64,7 @@ struct GenerateContentIntegrationTests { // (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemma3_4B), ]) func generateContent(_ config: InstanceConfig, modelName: String) async throws { - let model = AILogic.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: modelName, generationConfig: generationConfig, safetySettings: safetySettings, From ba145bee2d8c8f96a2dde6694de169c40748505d Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 24 Sep 2025 18:34:42 -0400 Subject: [PATCH 08/12] Add initial `FirebaseAILogicBinary.json` file for Carthage --- ReleaseTooling/CarthageJSON/FirebaseAILogicBinary.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 ReleaseTooling/CarthageJSON/FirebaseAILogicBinary.json diff --git a/ReleaseTooling/CarthageJSON/FirebaseAILogicBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAILogicBinary.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/ReleaseTooling/CarthageJSON/FirebaseAILogicBinary.json @@ -0,0 +1 @@ +{} From d83dc64114ded1e914aa9569c521a70b5f0607ca Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 26 Sep 2025 18:50:35 -0400 Subject: [PATCH 09/12] Move `FirebaseAIWrapper` into subdir and add API tests --- .github/workflows/firebaseai.yml | 10 +- FirebaseAI.podspec | 15 +- .../{ => Sources}/FirebaseAIWrapper.swift | 0 FirebaseAI/Wrapper/Tests/APITests.swift | 186 ++++++++++++++++++ Package.swift | 19 +- .../FirebaseAILogicUnit.xcscheme | 77 ++++++++ 6 files changed, 297 insertions(+), 10 deletions(-) rename FirebaseAI/Wrapper/{ => Sources}/FirebaseAIWrapper.swift (100%) create mode 100644 FirebaseAI/Wrapper/Tests/APITests.swift create mode 100644 scripts/spm_test_schemes/FirebaseAILogicUnit.xcscheme diff --git a/.github/workflows/firebaseai.yml b/.github/workflows/firebaseai.yml index 21cdfb83540..3d9a593ad7d 100644 --- a/.github/workflows/firebaseai.yml +++ b/.github/workflows/firebaseai.yml @@ -27,9 +27,12 @@ permissions: jobs: spm: + strategy: + matrix: + target: [FirebaseAILogicUnit, FirebaseAIUnit] uses: ./.github/workflows/common.yml with: - target: FirebaseAIUnit + target: ${{ matrix.target }} setup_command: scripts/update_vertexai_responses.sh testapp-integration: @@ -77,9 +80,12 @@ jobs: retention-days: 2 pod_lib_lint: + strategy: + matrix: + product: [FirebaseAILogic, FirebaseAI] uses: ./.github/workflows/common_cocoapods.yml with: - product: FirebaseAI + product: ${{ matrix.product }} supports_swift6: true setup_command: scripts/update_vertexai_responses.sh diff --git a/FirebaseAI.podspec b/FirebaseAI.podspec index a0d546a46ca..fa6a1b39345 100644 --- a/FirebaseAI.podspec +++ b/FirebaseAI.podspec @@ -32,7 +32,7 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK s.prefix_header_file = false s.source_files = [ - 'FirebaseAI/Wrapper/**/*.swift', + 'FirebaseAI/Wrapper/Sources/**/*.swift', ] s.swift_version = '5.9' @@ -45,4 +45,17 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK s.dependency 'FirebaseAILogic', '12.4.0' s.dependency 'FirebaseCore', '~> 12.4.0' + + s.test_spec 'unit' do |unit_tests| + unit_tests_dir = 'FirebaseAI/Wrapper/Tests/' + unit_tests.scheme = { :code_coverage => true } + unit_tests.platforms = { + :ios => ios_deployment_target, + :osx => osx_deployment_target, + :tvos => tvos_deployment_target + } + unit_tests.source_files = [ + unit_tests_dir + '**/*.swift', + ] + end end diff --git a/FirebaseAI/Wrapper/FirebaseAIWrapper.swift b/FirebaseAI/Wrapper/Sources/FirebaseAIWrapper.swift similarity index 100% rename from FirebaseAI/Wrapper/FirebaseAIWrapper.swift rename to FirebaseAI/Wrapper/Sources/FirebaseAIWrapper.swift diff --git a/FirebaseAI/Wrapper/Tests/APITests.swift b/FirebaseAI/Wrapper/Tests/APITests.swift new file mode 100644 index 00000000000..16c963b1f0c --- /dev/null +++ b/FirebaseAI/Wrapper/Tests/APITests.swift @@ -0,0 +1,186 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseAI +import FirebaseCore +import XCTest +#if canImport(AppKit) + import AppKit // For NSImage extensions. +#elseif canImport(UIKit) + import UIKit // For UIImage extensions. +#endif + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class APITests: XCTestCase { + func codeSamples() async throws { + let app = FirebaseApp.app() + let config = GenerationConfig(temperature: 0.2, + topP: 0.1, + topK: 16, + candidateCount: 4, + maxOutputTokens: 256, + stopSequences: ["..."], + responseMIMEType: "text/plain") + let filters = [SafetySetting(harmCategory: .dangerousContent, threshold: .blockOnlyHigh)] + let systemInstruction = ModelContent( + role: "system", + parts: TextPart("Talk like a pirate.") + ) + + let requestOptions = RequestOptions() + let _ = RequestOptions(timeout: 30.0) + + // Instantiate Firebase AI SDK - Default App + let firebaseAI = FirebaseAI.firebaseAI() + let _ = FirebaseAI.firebaseAI(backend: .googleAI()) + let _ = FirebaseAI.firebaseAI(backend: .vertexAI()) + let _ = FirebaseAI.firebaseAI(backend: .vertexAI(location: "my-location")) + + // Instantiate Firebase AI SDK - Custom App + let _ = FirebaseAI.firebaseAI(app: app!) + let _ = FirebaseAI.firebaseAI(app: app!, backend: .googleAI()) + let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI()) + let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI(location: "my-location")) + + // Permutations without optional arguments. + + let _ = firebaseAI.generativeModel(modelName: "gemini-2.0-flash") + + let _ = firebaseAI.generativeModel( + modelName: "gemini-2.0-flash", + safetySettings: filters + ) + + let _ = firebaseAI.generativeModel( + modelName: "gemini-2.0-flash", + generationConfig: config + ) + + let _ = firebaseAI.generativeModel( + modelName: "gemini-2.0-flash", + systemInstruction: systemInstruction + ) + + // All arguments passed. + let model = firebaseAI.generativeModel( + modelName: "gemini-2.0-flash", + generationConfig: config, // Optional + safetySettings: filters, // Optional + systemInstruction: systemInstruction, // Optional + requestOptions: requestOptions // Optional + ) + + // Full Typed Usage + let pngData = Data() // .... + let contents = [ModelContent( + role: "user", + parts: [ + TextPart("Is it a cat?"), + InlineDataPart(data: pngData, mimeType: "image/png"), + ] + )] + + do { + let response = try await model.generateContent(contents) + print(response.text ?? "Couldn't get text... check status") + } catch { + print("Error generating content: \(error)") + } + + // Content input combinations. + let _ = try await model.generateContent("Constant String") + let str = "String Variable" + let _ = try await model.generateContent(str) + let _ = try await model.generateContent([str]) + let _ = try await model.generateContent(str, "abc", "def") + let _ = try await model.generateContent( + str, + FileDataPart(uri: "gs://test-bucket/image.jpg", mimeType: "image/jpeg") + ) + #if canImport(UIKit) + _ = try await model.generateContent(UIImage()) + _ = try await model.generateContent([UIImage()]) + _ = try await model.generateContent([str, UIImage(), TextPart(str)]) + _ = try await model.generateContent(str, UIImage(), "def", UIImage()) + _ = try await model.generateContent([str, UIImage(), "def", UIImage()]) + _ = try await model.generateContent([ModelContent(parts: "def", UIImage()), + ModelContent(parts: "def", UIImage())]) + #elseif canImport(AppKit) + _ = try await model.generateContent(NSImage()) + _ = try await model.generateContent([NSImage()]) + _ = try await model.generateContent(str, NSImage(), "def", NSImage()) + _ = try await model.generateContent([str, NSImage(), "def", NSImage()]) + #endif + + // PartsRepresentable combinations. + let _ = ModelContent(parts: [TextPart(str)]) + let _ = ModelContent(role: "model", parts: [TextPart(str)]) + let _ = ModelContent(parts: "Constant String") + let _ = ModelContent(parts: str) + let _ = ModelContent(parts: [str]) + let _ = ModelContent(parts: [str, InlineDataPart(data: Data(), mimeType: "foo")]) + #if canImport(UIKit) + _ = ModelContent(role: "user", parts: UIImage()) + _ = ModelContent(role: "user", parts: [UIImage()]) + _ = 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)]) + #elseif canImport(AppKit) + _ = ModelContent(role: "user", parts: NSImage()) + _ = ModelContent(role: "user", parts: [NSImage()]) + _ = 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)]) + #endif + + // countTokens API + let _: CountTokensResponse = try await model.countTokens("What color is the Sky?") + #if canImport(UIKit) + let _: CountTokensResponse = try await model.countTokens("What color is the Sky?", + UIImage()) + let _: CountTokensResponse = try await model.countTokens([ + ModelContent(parts: "What color is the Sky?", UIImage()), + ModelContent(parts: UIImage(), "What color is the Sky?", UIImage()), + ]) + #endif + + // Chat + _ = model.startChat() + _ = model.startChat(history: [ModelContent(parts: "abc")]) + } + + // Public API tests for GenerateContentResponse. + func generateContentResponseAPI() { + let response = GenerateContentResponse(candidates: []) + + let _: [Candidate] = response.candidates + let _: PromptFeedback? = response.promptFeedback + + // Usage Metadata + guard let usageMetadata = response.usageMetadata else { fatalError() } + let _: Int = usageMetadata.promptTokenCount + let _: Int = usageMetadata.candidatesTokenCount + let _: Int = usageMetadata.totalTokenCount + + // Computed Properties + let _: String? = response.text + let _: [FunctionCallPart] = response.functionCalls + } +} diff --git a/Package.swift b/Package.swift index 7efcfa69191..36dc8527f30 100644 --- a/Package.swift +++ b/Package.swift @@ -196,15 +196,10 @@ let package = Package( ], path: "FirebaseAI/Sources" ), - .target( - name: "FirebaseAI", - dependencies: ["FirebaseAILogic"], - path: "FirebaseAI/Wrapper" - ), .testTarget( - name: "FirebaseAIUnit", + name: "FirebaseAILogicUnit", dependencies: [ - "FirebaseAI", + "FirebaseAILogic", "FirebaseStorage", ], path: "FirebaseAI/Tests/Unit", @@ -216,6 +211,16 @@ let package = Package( .headerSearchPath("../../../"), ] ), + .target( + name: "FirebaseAI", + dependencies: ["FirebaseAILogic"], + path: "FirebaseAI/Wrapper/Sources" + ), + .testTarget( + name: "FirebaseAIUnit", + dependencies: ["FirebaseAI"], + path: "FirebaseAI/Wrapper/Tests", + ), // MARK: - Firebase Core diff --git a/scripts/spm_test_schemes/FirebaseAILogicUnit.xcscheme b/scripts/spm_test_schemes/FirebaseAILogicUnit.xcscheme new file mode 100644 index 00000000000..22c999d990a --- /dev/null +++ b/scripts/spm_test_schemes/FirebaseAILogicUnit.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5a8c1a97fdfef031ab496e178504d785bbb2efb1 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Fri, 26 Sep 2025 18:53:08 -0400 Subject: [PATCH 10/12] Remove trailing comma --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 36dc8527f30..ee75f8f9b16 100644 --- a/Package.swift +++ b/Package.swift @@ -219,7 +219,7 @@ let package = Package( .testTarget( name: "FirebaseAIUnit", dependencies: ["FirebaseAI"], - path: "FirebaseAI/Wrapper/Tests", + path: "FirebaseAI/Wrapper/Tests" ), // MARK: - Firebase Core From d20f1f5b4475bc98c850b34c19e93d1ada340abf Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 29 Sep 2025 17:21:30 -0400 Subject: [PATCH 11/12] Add `quickstart_framework_firebaseai` job --- .github/workflows/zip.yml | 49 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index eba3c5b61b5..c56bee4b549 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -398,6 +398,55 @@ jobs: name: quickstart_artifacts database path: quickstart-ios/ + quickstart_framework_firebaseai: + needs: package-head + if: ${{ !cancelled() && (success() || github.event.inputs.zip_run_id != '') }} + env: + plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} + SDK: "FirebaseAI" + strategy: + matrix: + artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] + build-env: + - os: macos-15 + xcode: Xcode_16.4 + runs-on: ${{ matrix.build-env.os }} + steps: + - uses: actions/checkout@v4 + - name: Get framework dir + uses: actions/download-artifact@v4.1.7 + with: + name: ${{ matrix.artifact }} + run-id: ${{ github.event.inputs.zip_run_id || github.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer + - name: Setup Bundler + run: ./scripts/setup_bundler.sh + - name: Move frameworks + run: | + mkdir -p "${HOME}"/ios_frameworks/ + find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + + - uses: actions/checkout@v4 + - name: Setup quickstart + run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ + "${HOME}"/ios_frameworks/Firebase/FirebaseAILogic/* \ + "${HOME}"/ios_frameworks/Firebase/FirebaseAnalytics/* + - name: Install Secret GoogleService-Info.plist + run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info.plist.gpg \ + quickstart-ios/firebaseai/GoogleService-Info.plist "$plist_secret" + - name: Test Quickstart + run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart_framework.sh "${SDK}") + - name: Remove data before upload + if: ${{ failure() }} + run: scripts/remove_data.sh firebaseai + - uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: quickstart_artifacts_firebaseai + path: quickstart-ios/ + quickstart_framework_firestore: needs: package-head if: ${{ !cancelled() && (success() || github.event.inputs.zip_run_id != '') }} From 209d26f6ef199dfa0988a2e4b8fdfe6f038936f2 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Mon, 29 Sep 2025 18:11:59 -0400 Subject: [PATCH 12/12] Add workaround to use the FirebaseAIExampleZip scheme --- .github/workflows/zip.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index c56bee4b549..122ee3ed1a9 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -404,6 +404,8 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} SDK: "FirebaseAI" + # This is a workaround to use the FirebaseAIExampleZip scheme that does not have the SPM dependency. + SWIFT_SUFFIX: "Zip" strategy: matrix: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] @@ -430,7 +432,7 @@ jobs: find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - name: Setup quickstart - run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ + run: SAMPLE="$SDK" TARGET="${SDK}ExampleZip" scripts/setup_quickstart_framework.sh \ "${HOME}"/ios_frameworks/Firebase/FirebaseAILogic/* \ "${HOME}"/ios_frameworks/Firebase/FirebaseAnalytics/* - name: Install Secret GoogleService-Info.plist