diff --git a/.github/workflows/firebaseai.yml b/.github/workflows/firebaseai.yml new file mode 100644 index 00000000000..cdc5429137c --- /dev/null +++ b/.github/workflows/firebaseai.yml @@ -0,0 +1,164 @@ +name: firebaseai + +on: + pull_request: + paths: + - 'FirebaseAI**' + - '.github/workflows/firebaseai.yml' + - 'Gemfile*' + schedule: + # Run every day at 11pm (PST) - cron uses UTC times + - cron: '0 7 * * *' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +permissions: + contents: read # Needed for actions/checkout + actions: write # Needed for actions/cache (save and restore) + +jobs: + spm-package-resolved: + runs-on: macos-14 + outputs: + cache_key: ${{ steps.generate_cache_key.outputs.cache_key }} + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + steps: + - uses: actions/checkout@v4 + - name: Generate Swift Package.resolved + id: swift_package_resolve + run: | + swift package resolve + - name: Generate cache key + id: generate_cache_key + run: | + cache_key="${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}" + echo "cache_key=${cache_key}" >> "$GITHUB_OUTPUT" + - uses: actions/cache/save@v4 + id: cache + with: + path: .build + key: ${{ steps.generate_cache_key.outputs.cache_key }} + + spm-unit: + strategy: + matrix: + include: + - os: macos-14 + xcode: Xcode_16.2 + target: iOS + - os: macos-15 + xcode: Xcode_16.3 + target: iOS + - os: macos-15 + xcode: Xcode_16.3 + target: tvOS + - os: macos-15 + xcode: Xcode_16.3 + target: macOS + - os: macos-15 + xcode: Xcode_16.3 + target: watchOS + - os: macos-15 + xcode: Xcode_16.3 + target: catalyst + - os: macos-15 + xcode: Xcode_16.3 + target: visionOS + runs-on: ${{ matrix.os }} + needs: spm-package-resolved + env: + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + steps: + - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} + - name: Clone mock responses + run: scripts/update_vertexai_responses.sh + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Install visionOS, if needed. + if: matrix.target == 'visionOS' + run: xcodebuild -downloadPlatform visionOS + - name: Initialize xcodebuild + run: scripts/setup_spm_tests.sh + - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/build.sh FirebaseAIUnit ${{ matrix.target }} spm + + testapp-integration: + strategy: + matrix: + target: [iOS] + os: [macos-15] + include: + - os: macos-15 + xcode: Xcode_16.3 + runs-on: ${{ matrix.os }} + needs: spm-package-resolved + env: + TEST_RUNNER_FIRAAppCheckDebugToken: ${{ secrets.VERTEXAI_INTEGRATION_FAC_DEBUG_TOKEN }} + TEST_RUNNER_VTXIntegrationImagen: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} + FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 + secrets_passphrase: ${{ secrets.GHASecretsGPGPassphrase1 }} + steps: + - uses: actions/checkout@v4 + - uses: actions/cache/restore@v4 + with: + path: .build + key: ${{needs.spm-package-resolved.outputs.cache_key}} + - name: Install Secret GoogleService-Info.plist + run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info.plist.gpg \ + FirebaseAI/Tests/TestApp/Resources/GoogleService-Info.plist "$secrets_passphrase" + - name: Install Secret GoogleService-Info-Spark.plist + run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info-Spark.plist.gpg \ + FirebaseAI/Tests/TestApp/Resources/GoogleService-Info-Spark.plist "$secrets_passphrase" + - name: Install Secret Credentials.swift + run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg \ + FirebaseAI/Tests/TestApp/Tests/Integration/Credentials.swift "$secrets_passphrase" + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Run IntegrationTests + run: scripts/build.sh FirebaseAIIntegration ${{ matrix.target }} + + pod-lib-lint: + # Don't run on private repo unless it is a PR. + if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' + strategy: + matrix: + include: + - os: macos-14 + xcode: Xcode_16.2 + swift_version: 5.9 + warnings: + - os: macos-15 + xcode: Xcode_16.3 + swift_version: 5.9 + warnings: + - os: macos-15 + xcode: Xcode_16.3 + swift_version: 6.0 + warnings: + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Clone mock responses + run: scripts/update_vertexai_responses.sh + - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Setup Bundler + run: scripts/setup_bundler.sh + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Set Swift swift_version + run: sed -i "" "s#s.swift_version = '5.9'#s.swift_version = '${{ matrix.swift_version}}'#" FirebaseAI.podspec + - name: Build and test + run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAI.podspec --platforms=${{ matrix.target }} ${{ matrix.warnings }} diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 2b08ca897df..32d25b6a2fd 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -3,6 +3,7 @@ name: vertexai on: pull_request: paths: + - 'FirebaseAI**' - 'FirebaseVertexAI**' - '.github/workflows/vertexai.yml' - 'Gemfile*' @@ -74,8 +75,6 @@ jobs: with: path: .build key: ${{needs.spm-package-resolved.outputs.cache_key}} - - name: Clone mock responses - run: scripts/update_vertexai_responses.sh - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Install visionOS, if needed. @@ -91,41 +90,6 @@ jobs: retry_wait_seconds: 120 command: scripts/build.sh FirebaseVertexAIUnit ${{ matrix.target }} spm - testapp-integration: - strategy: - matrix: - target: [iOS] - os: [macos-15] - include: - - os: macos-15 - xcode: Xcode_16.3 - runs-on: ${{ matrix.os }} - needs: spm-package-resolved - env: - TEST_RUNNER_FIRAAppCheckDebugToken: ${{ secrets.VERTEXAI_INTEGRATION_FAC_DEBUG_TOKEN }} - TEST_RUNNER_VTXIntegrationImagen: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} - FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 - secrets_passphrase: ${{ secrets.GHASecretsGPGPassphrase1 }} - steps: - - uses: actions/checkout@v4 - - uses: actions/cache/restore@v4 - with: - path: .build - key: ${{needs.spm-package-resolved.outputs.cache_key}} - - name: Install Secret GoogleService-Info.plist - run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info.plist.gpg \ - FirebaseVertexAI/Tests/TestApp/Resources/GoogleService-Info.plist "$secrets_passphrase" - - name: Install Secret GoogleService-Info-Spark.plist - run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-GoogleService-Info-Spark.plist.gpg \ - FirebaseVertexAI/Tests/TestApp/Resources/GoogleService-Info-Spark.plist "$secrets_passphrase" - - name: Install Secret Credentials.swift - run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/VertexAI/TestApp-Credentials.swift.gpg \ - FirebaseVertexAI/Tests/TestApp/Tests/Integration/Credentials.swift "$secrets_passphrase" - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Run IntegrationTests - run: scripts/build.sh VertexIntegration ${{ matrix.target }} - pod-lib-lint: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' @@ -147,8 +111,6 @@ jobs: runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - name: Clone mock responses - run: scripts/update_vertexai_responses.sh - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup Bundler run: scripts/setup_bundler.sh diff --git a/FirebaseAI.podspec b/FirebaseAI.podspec new file mode 100644 index 00000000000..0df48cf0a70 --- /dev/null +++ b/FirebaseAI.podspec @@ -0,0 +1,70 @@ +Pod::Spec.new do |s| + s.name = 'FirebaseAI' + s.version = '11.13.0' + s.summary = 'Firebase AI SDK' + + s.description = <<-DESC +Build AI-powered apps and features with the Gemini API using the Firebase AI 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', '~> 11.4' + s.dependency 'FirebaseAuthInterop', '~> 11.4' + s.dependency 'FirebaseCore', '~> 11.13.0' + s.dependency 'FirebaseCoreExtension', '~> 11.13.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/vertexai', + unit_tests_dir + 'Resources/**/*', + ] + end +end diff --git a/FirebaseVertexAI/CHANGELOG.md b/FirebaseAI/CHANGELOG.md similarity index 100% rename from FirebaseVertexAI/CHANGELOG.md rename to FirebaseAI/CHANGELOG.md diff --git a/FirebaseVertexAI/README.md b/FirebaseAI/README.md similarity index 100% rename from FirebaseVertexAI/README.md rename to FirebaseAI/README.md diff --git a/FirebaseVertexAI/Sample/README.md b/FirebaseAI/Sample/README.md similarity index 100% rename from FirebaseVertexAI/Sample/README.md rename to FirebaseAI/Sample/README.md diff --git a/FirebaseVertexAI/Sources/Chat.swift b/FirebaseAI/Sources/Chat.swift similarity index 100% rename from FirebaseVertexAI/Sources/Chat.swift rename to FirebaseAI/Sources/Chat.swift diff --git a/FirebaseVertexAI/Sources/Constants.swift b/FirebaseAI/Sources/Constants.swift similarity index 100% rename from FirebaseVertexAI/Sources/Constants.swift rename to FirebaseAI/Sources/Constants.swift diff --git a/FirebaseVertexAI/Sources/Errors.swift b/FirebaseAI/Sources/Errors.swift similarity index 100% rename from FirebaseVertexAI/Sources/Errors.swift rename to FirebaseAI/Sources/Errors.swift diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift new file mode 100644 index 00000000000..1bbd5dde554 --- /dev/null +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -0,0 +1,260 @@ +// Copyright 2024 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 FirebaseAppCheckInterop +import FirebaseAuthInterop +import FirebaseCore +import Foundation + +// Avoids exposing internal FirebaseCore APIs to Swift users. +internal import FirebaseCoreExtension + +/// The Vertex AI for Firebase 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 { + // MARK: - Public APIs + + /// Creates an instance of `VertexAI`. + /// + /// - Parameters: + /// - app: A custom `FirebaseApp` used for initialization; if not specified, uses the default + /// ``FirebaseApp``. + /// - location: The region identifier, defaulting to `us-central1`; see + /// [Vertex AI locations] + /// (https://firebase.google.com/docs/vertex-ai/locations?platform=ios#available-locations) + /// for a list of supported locations. + /// - Returns: A `VertexAI` instance, configured with the custom `FirebaseApp`. + public static func vertexAI(app: FirebaseApp? = nil, + location: String = "us-central1") -> FirebaseAI { + let vertexInstance = vertexAI(app: app, location: location, apiConfig: defaultVertexAIAPIConfig) + // Verify that the `VertexAI` instance is always configured with the production endpoint since + // this is the public API surface for creating an instance. + assert(vertexInstance.apiConfig.service == .vertexAI(endpoint: .firebaseVertexAIProd)) + assert(vertexInstance.apiConfig.service.endpoint == .firebaseVertexAIProd) + assert(vertexInstance.apiConfig.version == .v1beta) + + return vertexInstance + } + + /// Initializes a generative model with the given parameters. + /// + /// - Note: Refer to [Gemini models](https://firebase.google.com/docs/vertex-ai/gemini-models) for + /// guidance on choosing an appropriate model for your use case. + /// + /// - Parameters: + /// - modelName: The name of the model to use, for example `"gemini-1.5-flash"`; see + /// [available model names + /// ](https://firebase.google.com/docs/vertex-ai/gemini-models#available-model-names) for a + /// list of supported model names. + /// - generationConfig: The content generation parameters your model should use. + /// - safetySettings: A value describing what types of harmful content your model should allow. + /// - tools: A list of ``Tool`` objects that the model may use to generate the next response. + /// - toolConfig: Tool configuration for any `Tool` specified in the request. + /// - systemInstruction: Instructions that direct the model to behave a certain way; currently + /// only text content is supported. + /// - requestOptions: Configuration parameters for sending requests to the backend. + public func generativeModel(modelName: String, + generationConfig: GenerationConfig? = nil, + safetySettings: [SafetySetting]? = nil, + tools: [Tool]? = nil, + toolConfig: ToolConfig? = nil, + systemInstruction: ModelContent? = nil, + requestOptions: RequestOptions = RequestOptions()) + -> GenerativeModel { + if !modelName.starts(with: GenerativeModel.geminiModelNamePrefix) { + VertexLog.warning(code: .unsupportedGeminiModel, """ + Unsupported Gemini model "\(modelName)"; see \ + https://firebase.google.com/docs/vertex-ai/models for a list supported Gemini model names. + """) + } + + return GenerativeModel( + modelName: modelName, + modelResourceName: modelResourceName(modelName: modelName), + firebaseInfo: firebaseInfo, + apiConfig: apiConfig, + generationConfig: generationConfig, + safetySettings: safetySettings, + tools: tools, + toolConfig: toolConfig, + systemInstruction: systemInstruction, + requestOptions: requestOptions + ) + } + + /// **[Public Preview]** Initializes an ``ImagenModel`` with the given parameters. + /// + /// > Warning: For Vertex AI in Firebase, image generation using Imagen 3 models is in Public + /// Preview, which means that the feature is not subject to any SLA or deprecation policy and + /// could change in backwards-incompatible ways. + /// + /// > Important: Only Imagen 3 models (named `imagen-3.0-*`) are supported. + /// + /// - Parameters: + /// - modelName: The name of the Imagen 3 model to use, for example `"imagen-3.0-generate-002"`; + /// see [model versions](https://firebase.google.com/docs/vertex-ai/models) for a list of + /// supported Imagen 3 models. + /// - generationConfig: Configuration options for generating images with Imagen. + /// - safetySettings: Settings describing what types of potentially harmful content your model + /// should allow. + /// - requestOptions: Configuration parameters for sending requests to the backend. + public func imagenModel(modelName: String, generationConfig: ImagenGenerationConfig? = nil, + safetySettings: ImagenSafetySettings? = nil, + requestOptions: RequestOptions = RequestOptions()) -> ImagenModel { + if !modelName.starts(with: ImagenModel.imagenModelNamePrefix) { + VertexLog.warning(code: .unsupportedImagenModel, """ + Unsupported Imagen model "\(modelName)"; see \ + https://firebase.google.com/docs/vertex-ai/models for a list supported Imagen model names. + """) + } + + return ImagenModel( + modelResourceName: modelResourceName(modelName: modelName), + firebaseInfo: firebaseInfo, + apiConfig: apiConfig, + generationConfig: generationConfig, + safetySettings: safetySettings, + requestOptions: requestOptions + ) + } + + /// Class to enable VertexAI to register via the Objective-C based Firebase component system + /// to include VertexAI in the userAgent. + @objc(FIRVertexAIComponent) class FirebaseVertexAIComponent: NSObject {} + + // MARK: - Private + + /// Firebase data relevant to Vertex AI. + let firebaseInfo: FirebaseInfo + + let apiConfig: APIConfig + + #if compiler(>=6) + /// A map of active `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in + /// the format `appName:location`. + 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() + #else + /// A map of active `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in + /// the format `appName:location`. + private static var instances: [InstanceKey: VertexAI] = [:] + + /// Lock to manage access to the `instances` array to avoid race conditions. + private static var instancesLock: os_unfair_lock = .init() + #endif + + let location: String? + + static let defaultVertexAIAPIConfig = APIConfig( + service: .vertexAI(endpoint: .firebaseVertexAIProd), + version: .v1beta + ) + + static func vertexAI(app: FirebaseApp?, location: String?, apiConfig: APIConfig) -> FirebaseAI { + guard let app = app ?? FirebaseApp.app() else { + fatalError("No instance of the default Firebase app was found.") + } + + os_unfair_lock_lock(&instancesLock) + + // Unlock before the function returns. + defer { os_unfair_lock_unlock(&instancesLock) } + + let instanceKey = InstanceKey(appName: app.name, location: location, apiConfig: apiConfig) + if let instance = instances[instanceKey] { + return instance + } + let newInstance = FirebaseAI(app: app, location: location, apiConfig: apiConfig) + instances[instanceKey] = newInstance + return newInstance + } + + init(app: FirebaseApp, location: String?, apiConfig: APIConfig) { + guard let projectID = app.options.projectID else { + fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.") + } + guard let apiKey = app.options.apiKey else { + fatalError("The Firebase app named \"\(app.name)\" has no API key in its configuration.") + } + firebaseInfo = FirebaseInfo( + appCheck: ComponentType.instance( + for: AppCheckInterop.self, + in: app.container + ), + auth: ComponentType.instance(for: AuthInterop.self, in: app.container), + projectID: projectID, + apiKey: apiKey, + firebaseAppID: app.options.googleAppID, + firebaseApp: app + ) + self.apiConfig = apiConfig + self.location = location + } + + func modelResourceName(modelName: String) -> String { + guard !modelName.isEmpty && modelName + .allSatisfy({ !$0.isWhitespace && !$0.isNewline && $0 != "/" }) else { + fatalError(""" + Invalid model name "\(modelName)" specified; see \ + https://firebase.google.com/docs/vertex-ai/gemini-model#available-models for a list of \ + available models. + """) + } + + switch apiConfig.service { + case .vertexAI: + return vertexAIModelResourceName(modelName: modelName) + case .developer: + return developerModelResourceName(modelName: modelName) + } + } + + private func vertexAIModelResourceName(modelName: String) -> String { + guard let location else { + fatalError("Location must be specified for the Vertex AI service.") + } + guard !location.isEmpty && location + .allSatisfy({ !$0.isWhitespace && !$0.isNewline && $0 != "/" }) else { + fatalError(""" + Invalid location "\(location)" specified; see \ + https://firebase.google.com/docs/vertex-ai/locations?platform=ios#available-locations \ + for a list of available locations. + """) + } + + let projectID = firebaseInfo.projectID + return "projects/\(projectID)/locations/\(location)/publishers/google/models/\(modelName)" + } + + private func developerModelResourceName(modelName: String) -> String { + switch apiConfig.service.endpoint { + case .firebaseVertexAIStaging, .firebaseVertexAIProd: + let projectID = firebaseInfo.projectID + return "projects/\(projectID)/models/\(modelName)" + case .generativeLanguage: + return "models/\(modelName)" + } + } + + /// Identifier for a unique instance of ``VertexAI``. + /// + /// This type is `Hashable` so that it can be used as a key in the `instances` dictionary. + private struct InstanceKey: Sendable, Hashable { + let appName: String + let location: String? + let apiConfig: APIConfig + } +} diff --git a/FirebaseVertexAI/Sources/FirebaseInfo.swift b/FirebaseAI/Sources/FirebaseInfo.swift similarity index 100% rename from FirebaseVertexAI/Sources/FirebaseInfo.swift rename to FirebaseAI/Sources/FirebaseInfo.swift diff --git a/FirebaseVertexAI/Sources/FunctionCalling.swift b/FirebaseAI/Sources/FunctionCalling.swift similarity index 100% rename from FirebaseVertexAI/Sources/FunctionCalling.swift rename to FirebaseAI/Sources/FunctionCalling.swift diff --git a/FirebaseVertexAI/Sources/GenAIURLSession.swift b/FirebaseAI/Sources/GenAIURLSession.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenAIURLSession.swift rename to FirebaseAI/Sources/GenAIURLSession.swift diff --git a/FirebaseVertexAI/Sources/GenerateContentError.swift b/FirebaseAI/Sources/GenerateContentError.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenerateContentError.swift rename to FirebaseAI/Sources/GenerateContentError.swift diff --git a/FirebaseVertexAI/Sources/GenerateContentRequest.swift b/FirebaseAI/Sources/GenerateContentRequest.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenerateContentRequest.swift rename to FirebaseAI/Sources/GenerateContentRequest.swift diff --git a/FirebaseVertexAI/Sources/GenerateContentResponse.swift b/FirebaseAI/Sources/GenerateContentResponse.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenerateContentResponse.swift rename to FirebaseAI/Sources/GenerateContentResponse.swift diff --git a/FirebaseVertexAI/Sources/GenerationConfig.swift b/FirebaseAI/Sources/GenerationConfig.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenerationConfig.swift rename to FirebaseAI/Sources/GenerationConfig.swift diff --git a/FirebaseVertexAI/Sources/GenerativeAIRequest.swift b/FirebaseAI/Sources/GenerativeAIRequest.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenerativeAIRequest.swift rename to FirebaseAI/Sources/GenerativeAIRequest.swift diff --git a/FirebaseVertexAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenerativeAIService.swift rename to FirebaseAI/Sources/GenerativeAIService.swift diff --git a/FirebaseVertexAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift similarity index 100% rename from FirebaseVertexAI/Sources/GenerativeModel.swift rename to FirebaseAI/Sources/GenerativeModel.swift diff --git a/FirebaseVertexAI/Sources/JSONValue.swift b/FirebaseAI/Sources/JSONValue.swift similarity index 100% rename from FirebaseVertexAI/Sources/JSONValue.swift rename to FirebaseAI/Sources/JSONValue.swift diff --git a/FirebaseVertexAI/Sources/ModalityTokenCount.swift b/FirebaseAI/Sources/ModalityTokenCount.swift similarity index 100% rename from FirebaseVertexAI/Sources/ModalityTokenCount.swift rename to FirebaseAI/Sources/ModalityTokenCount.swift diff --git a/FirebaseVertexAI/Sources/ModelContent.swift b/FirebaseAI/Sources/ModelContent.swift similarity index 100% rename from FirebaseVertexAI/Sources/ModelContent.swift rename to FirebaseAI/Sources/ModelContent.swift diff --git a/FirebaseVertexAI/Sources/PartsRepresentable+Image.swift b/FirebaseAI/Sources/PartsRepresentable+Image.swift similarity index 100% rename from FirebaseVertexAI/Sources/PartsRepresentable+Image.swift rename to FirebaseAI/Sources/PartsRepresentable+Image.swift diff --git a/FirebaseVertexAI/Sources/PartsRepresentable.swift b/FirebaseAI/Sources/PartsRepresentable.swift similarity index 100% rename from FirebaseVertexAI/Sources/PartsRepresentable.swift rename to FirebaseAI/Sources/PartsRepresentable.swift diff --git a/FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift b/FirebaseAI/Sources/Protocols/Internal/CodableProtoEnum.swift similarity index 100% rename from FirebaseVertexAI/Sources/Protocols/Internal/CodableProtoEnum.swift rename to FirebaseAI/Sources/Protocols/Internal/CodableProtoEnum.swift diff --git a/FirebaseVertexAI/Sources/Safety.swift b/FirebaseAI/Sources/Safety.swift similarity index 100% rename from FirebaseVertexAI/Sources/Safety.swift rename to FirebaseAI/Sources/Safety.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/APIConfig.swift b/FirebaseAI/Sources/Types/Internal/APIConfig.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/APIConfig.swift rename to FirebaseAI/Sources/Types/Internal/APIConfig.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/DataType.swift b/FirebaseAI/Sources/Types/Internal/DataType.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/DataType.swift rename to FirebaseAI/Sources/Types/Internal/DataType.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Errors/BackendError.swift b/FirebaseAI/Sources/Types/Internal/Errors/BackendError.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Errors/BackendError.swift rename to FirebaseAI/Sources/Types/Internal/Errors/BackendError.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenConstants.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImagenConstants.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenConstants.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/ImagenConstants.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenGCSImage.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImagenGCSImage.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenGCSImage.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/ImagenGCSImage.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/InternalImagenImage.swift b/FirebaseAI/Sources/Types/Internal/Imagen/InternalImagenImage.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/InternalImagenImage.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/InternalImagenImage.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Imagen/RAIFilteredReason.swift b/FirebaseAI/Sources/Types/Internal/Imagen/RAIFilteredReason.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Imagen/RAIFilteredReason.swift rename to FirebaseAI/Sources/Types/Internal/Imagen/RAIFilteredReason.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift b/FirebaseAI/Sources/Types/Internal/InternalPart.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/InternalPart.swift rename to FirebaseAI/Sources/Types/Internal/InternalPart.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift b/FirebaseAI/Sources/Types/Internal/ProtoDate.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift rename to FirebaseAI/Sources/Types/Internal/ProtoDate.swift diff --git a/FirebaseVertexAI/Sources/Types/Internal/Requests/CountTokensRequest.swift b/FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Internal/Requests/CountTokensRequest.swift rename to FirebaseAI/Sources/Types/Internal/Requests/CountTokensRequest.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenAspectRatio.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenAspectRatio.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenAspectRatio.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenAspectRatio.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenImageFormat.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenImageFormat.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenImageFormat.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenImageFormat.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenInlineImage.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenInlineImage.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenInlineImage.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenInlineImage.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenModel.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenSafetySettings.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetySettings.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Imagen/ImagenSafetySettings.swift rename to FirebaseAI/Sources/Types/Public/Imagen/ImagenSafetySettings.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Part.swift b/FirebaseAI/Sources/Types/Public/Part.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Part.swift rename to FirebaseAI/Sources/Types/Public/Part.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/ResponseModality.swift b/FirebaseAI/Sources/Types/Public/ResponseModality.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/ResponseModality.swift rename to FirebaseAI/Sources/Types/Public/ResponseModality.swift diff --git a/FirebaseVertexAI/Sources/Types/Public/Schema.swift b/FirebaseAI/Sources/Types/Public/Schema.swift similarity index 100% rename from FirebaseVertexAI/Sources/Types/Public/Schema.swift rename to FirebaseAI/Sources/Types/Public/Schema.swift diff --git a/FirebaseVertexAI/Sources/VertexLog.swift b/FirebaseAI/Sources/VertexLog.swift similarity index 100% rename from FirebaseVertexAI/Sources/VertexLog.swift rename to FirebaseAI/Sources/VertexLog.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json rename to FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json b/FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json rename to FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json b/FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json rename to FirebaseAI/Tests/TestApp/Resources/Assets.xcassets/Contents.json diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json b/FirebaseAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json rename to FirebaseAI/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/FirebaseVertexAI/Tests/TestApp/Resources/TestApp.entitlements b/FirebaseAI/Tests/TestApp/Resources/TestApp.entitlements similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Resources/TestApp.entitlements rename to FirebaseAI/Tests/TestApp/Resources/TestApp.entitlements diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/Constants.swift b/FirebaseAI/Tests/TestApp/Sources/Constants.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Sources/Constants.swift rename to FirebaseAI/Tests/TestApp/Sources/Constants.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/ContentView.swift b/FirebaseAI/Tests/TestApp/Sources/ContentView.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Sources/ContentView.swift rename to FirebaseAI/Tests/TestApp/Sources/ContentView.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/FirebaseAppUtils.swift b/FirebaseAI/Tests/TestApp/Sources/FirebaseAppUtils.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Sources/FirebaseAppUtils.swift rename to FirebaseAI/Tests/TestApp/Sources/FirebaseAppUtils.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift b/FirebaseAI/Tests/TestApp/Sources/TestApp.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Sources/TestApp.swift rename to FirebaseAI/Tests/TestApp/Sources/TestApp.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift b/FirebaseAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift rename to FirebaseAI/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift similarity index 94% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift index a0abd8cc00d..0f43a9bbb66 100644 --- a/FirebaseVertexAI/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 FirebaseAuth import FirebaseCore import FirebaseStorage -import FirebaseVertexAI import Testing import VertexAITestApp -@testable import struct FirebaseVertexAI.APIConfig +@testable import struct FirebaseAI.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 = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: generationConfig, safetySettings: safetySettings @@ -74,7 +74,7 @@ struct CountTokensIntegrationTests { arguments: InstanceConfig.allConfigsExceptDeveloperV1 ) func countTokens_text_systemInstruction(_ config: InstanceConfig) async throws { - let model = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: generationConfig, safetySettings: safetySettings, @@ -101,7 +101,7 @@ struct CountTokensIntegrationTests { InstanceConfig.developerV1Spark, ]) func countTokens_text_systemInstruction_unsupported(_ config: InstanceConfig) async throws { - let model = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, systemInstruction: systemInstruction // Not supported on the v1 Developer API ) @@ -123,7 +123,7 @@ struct CountTokensIntegrationTests { arguments: InstanceConfig.allConfigsExceptDeveloperV1 ) func countTokens_jsonSchema(_ config: InstanceConfig) async throws { - let model = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: GenerationConfig( responseMIMEType: "application/json", diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift similarity index 95% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index ca0d5bd7100..1cff9d92b59 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseAuth import FirebaseCore import FirebaseStorage -import FirebaseVertexAI import Testing import VertexAITestApp @@ -23,7 +23,7 @@ import VertexAITestApp import UIKit #endif // canImport(UIKit) -@testable import struct FirebaseVertexAI.BackendError +@testable import struct FirebaseAI.BackendError @Suite(.serialized) struct GenerateContentIntegrationTests { @@ -49,7 +49,7 @@ struct GenerateContentIntegrationTests { @Test(arguments: InstanceConfig.allConfigs) func generateContent(_ config: InstanceConfig) async throws { - let model = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: generationConfig, safetySettings: safetySettings @@ -81,7 +81,7 @@ struct GenerateContentIntegrationTests { arguments: InstanceConfig.allConfigsExceptDeveloperV1 ) func generateContentEnum(_ config: InstanceConfig) async throws { - let model = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: GenerationConfig( responseMIMEType: "text/x.enum", // Not supported on the v1 Developer API @@ -128,7 +128,7 @@ struct GenerateContentIntegrationTests { topK: 1, responseModalities: [.text, .image] ) - let model = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashExperimental, generationConfig: generationConfig, safetySettings: safetySettings @@ -180,7 +180,7 @@ struct GenerateContentIntegrationTests { What are the names of the planets in the solar system, ordered from closest to furthest from the sun? Answer with a Markdown numbered list of the names and no other text. """ - let model = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2FlashLite, generationConfig: generationConfig, safetySettings: safetySettings @@ -218,7 +218,7 @@ struct GenerateContentIntegrationTests { // bypasses the Vertex AI in Firebase proxy. ]) func generateContent_appCheckNotConfigured_shouldFail(_ config: InstanceConfig) async throws { - let model = VertexAI.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/FirebaseVertexAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift similarity index 97% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift index 05317411704..1e503a7623f 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseAuth import FirebaseCore import FirebaseStorage -import FirebaseVertexAI import Testing import VertexAITestApp @@ -24,7 +24,7 @@ import VertexAITestApp #endif // canImport(UIKit) // TODO(#14452): Remove `@testable import` when `generateImages(prompt:gcsURI:)` is public. -@testable import class FirebaseVertexAI.ImagenModel +@testable import class FirebaseAI.ImagenModel @Suite( .enabled( @@ -34,13 +34,13 @@ import VertexAITestApp .serialized ) struct ImagenIntegrationTests { - var vertex: VertexAI + var vertex: FirebaseAI var storage: Storage var userID1: String init() async throws { userID1 = try await TestHelpers.getUserID() - vertex = VertexAI.vertexAI() + vertex = FirebaseAI.vertexAI() storage = Storage.storage() } diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift similarity index 98% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift index af2c7c97d98..da34e042dc8 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseAuth import FirebaseCore import FirebaseStorage -import FirebaseVertexAI import VertexAITestApp import XCTest @@ -42,14 +42,14 @@ final class IntegrationTests: XCTestCase { // Candidates and total token counts may differ slightly between runs due to whitespace tokens. let tokenCountAccuracy = 1 - var vertex: VertexAI! + var vertex: FirebaseAI! var model: GenerativeModel! var storage: Storage! var userID1 = "" override func setUp() async throws { userID1 = try await TestHelpers.getUserID() - vertex = VertexAI.vertexAI() + vertex = FirebaseAI.vertexAI() model = vertex.generativeModel( modelName: "gemini-2.0-flash", generationConfig: generationConfig, @@ -200,7 +200,7 @@ final class IntegrationTests: XCTestCase { func testCountTokens_appCheckNotConfigured_shouldFail() async throws { let app = try XCTUnwrap(FirebaseApp.app(name: FirebaseAppNames.appCheckNotConfigured)) - let vertex = VertexAI.vertexAI(app: app) + let vertex = FirebaseAI.vertexAI(app: app) let model = vertex.generativeModel(modelName: "gemini-2.0-flash") let prompt = "Why is the sky blue?" diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/SchemaTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift similarity index 96% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/SchemaTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift index 7758979050f..a4448540f85 100644 --- a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/SchemaTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseAuth import FirebaseCore import FirebaseStorage -import FirebaseVertexAI import Testing import VertexAITestApp @@ -23,7 +23,7 @@ import VertexAITestApp import UIKit #endif // canImport(UIKit) -@testable import struct FirebaseVertexAI.BackendError +@testable import struct FirebaseAI.BackendError @Suite(.serialized) /// Test the schema fields. @@ -50,7 +50,7 @@ struct SchemaTests { @Test(arguments: InstanceConfig.allConfigsExceptDeveloperV1) func generateContentSchemaItems(_ config: InstanceConfig) async throws { - let model = VertexAI.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.allConfigsExceptDeveloperV1) func generateContentSchemaNumberRange(_ config: InstanceConfig) async throws { - let model = VertexAI.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 = VertexAI.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 = VertexAI.componentInstance(config).generativeModel( + let model = FirebaseAI.componentInstance(config).generativeModel( modelName: ModelNames.gemini2Flash, generationConfig: GenerationConfig( temperature: 0.0, diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/TestHelpers.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/TestHelpers.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/TestHelpers.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/TestHelpers.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift similarity index 95% rename from FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift rename to FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index 3db2ac60371..e60cfd8cb30 100644 --- a/FirebaseVertexAI/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 FirebaseCore import Testing import VertexAITestApp -@testable import struct FirebaseVertexAI.APIConfig -@testable import class FirebaseVertexAI.VertexAI +@testable import struct FirebaseAI.APIConfig struct InstanceConfig { static let vertexV1 = InstanceConfig( @@ -122,12 +122,12 @@ extension InstanceConfig: CustomTestStringConvertible { } } -extension VertexAI { - static func componentInstance(_ instanceConfig: InstanceConfig) -> VertexAI { +extension FirebaseAI { + static func componentInstance(_ instanceConfig: InstanceConfig) -> FirebaseAI { switch instanceConfig.apiConfig.service { case .vertexAI: let location = instanceConfig.location ?? "us-central1" - return VertexAI.vertexAI( + return FirebaseAI.vertexAI( app: instanceConfig.app, location: location, apiConfig: instanceConfig.apiConfig @@ -137,7 +137,7 @@ extension VertexAI { instanceConfig.location == nil, "The Developer API is global and does not support `location`." ) - return VertexAI.vertexAI( + return FirebaseAI.vertexAI( app: instanceConfig.app, location: nil, apiConfig: instanceConfig.apiConfig diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift rename to FirebaseAI/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift diff --git a/FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj b/FirebaseAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj rename to FirebaseAI/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj diff --git a/FirebaseAI/Tests/Unit/APITests.swift b/FirebaseAI/Tests/Unit/APITests.swift new file mode 100644 index 00000000000..32b8280623f --- /dev/null +++ b/FirebaseAI/Tests/Unit/APITests.swift @@ -0,0 +1,213 @@ +// 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 Vertex AI SDK - Default App + let vertexAI = FirebaseAI.vertexAI() + let _ = FirebaseAI.vertexAI(location: "my-location") + + // Instantiate Vertex AI SDK - Custom App + let _ = FirebaseAI.vertexAI(app: app!) + let _ = FirebaseAI.vertexAI(app: app!, location: "my-location") + + // Permutations without optional arguments. + + let _ = vertexAI.generativeModel(modelName: "gemini-1.0-pro") + + let _ = vertexAI.generativeModel( + modelName: "gemini-1.0-pro", + safetySettings: filters + ) + + let _ = vertexAI.generativeModel( + modelName: "gemini-1.0-pro", + generationConfig: config + ) + + let _ = vertexAI.generativeModel( + modelName: "gemini-1.0-pro", + systemInstruction: systemInstruction + ) + + // All arguments passed. + let genAI = vertexAI.generativeModel( + modelName: "gemini-1.0-pro", + 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 genAI.generateContent(contents) + print(response.text ?? "Couldn't get text... check status") + } catch { + print("Error generating content: \(error)") + } + + // Content input combinations. + let _ = try await genAI.generateContent("Constant String") + let str = "String Variable" + let _ = try await genAI.generateContent(str) + let _ = try await genAI.generateContent([str]) + let _ = try await genAI.generateContent(str, "abc", "def") + let _ = try await genAI.generateContent( + str, + FileDataPart(uri: "gs://test-bucket/image.jpg", mimeType: "image/jpeg") + ) + #if canImport(UIKit) + _ = try await genAI.generateContent(UIImage()) + _ = try await genAI.generateContent([UIImage()]) + _ = try await genAI.generateContent([str, UIImage(), TextPart(str)]) + _ = try await genAI.generateContent(str, UIImage(), "def", UIImage()) + _ = try await genAI.generateContent([str, UIImage(), "def", UIImage()]) + _ = try await genAI.generateContent([ModelContent(parts: "def", UIImage()), + ModelContent(parts: "def", UIImage())]) + #elseif canImport(AppKit) + _ = try await genAI.generateContent(NSImage()) + _ = try await genAI.generateContent([NSImage()]) + _ = try await genAI.generateContent(str, NSImage(), "def", NSImage()) + _ = try await genAI.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 genAI.countTokens("What color is the Sky?") + #if canImport(UIKit) + let _: CountTokensResponse = try await genAI.countTokens("What color is the Sky?", + UIImage()) + let _: CountTokensResponse = try await genAI.countTokens([ + ModelContent(parts: "What color is the Sky?", UIImage()), + ModelContent(parts: UIImage(), "What color is the Sky?", UIImage()), + ]) + #endif + + // Chat + _ = genAI.startChat() + _ = genAI.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 + } + + // Result builder alternative + + /* + let pngData = Data() // .... + let contents = [GenAIContent(role: "user", + parts: [ + .text("Is it a cat?"), + .png(pngData) + ])] + + // Turns into... + + let contents = GenAIContent { + Role("user") { + Text("Is this a cat?") + Image(png: pngData) + } + } + + GenAIContent { + ForEach(myInput) { input in + Role(input.role) { + input.contents + } + } + } + + // Thoughts: this looks great from a code demo, but since I assume most content will be + // user generated, the result builder may not be the best API. + */ +} diff --git a/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseAI/Tests/Unit/ChatTests.swift similarity index 97% rename from FirebaseVertexAI/Tests/Unit/ChatTests.swift rename to FirebaseAI/Tests/Unit/ChatTests.swift index acb98d1e0e1..72dd14fa448 100644 --- a/FirebaseVertexAI/Tests/Unit/ChatTests.swift +++ b/FirebaseAI/Tests/Unit/ChatTests.swift @@ -12,11 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseCore import Foundation import XCTest -import FirebaseCore -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ChatTests: XCTestCase { @@ -70,7 +70,7 @@ final class ChatTests: XCTestCase { firebaseAppID: "My app ID", firebaseApp: app ), - apiConfig: VertexAI.defaultVertexAIAPIConfig, + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, tools: nil, requestOptions: RequestOptions(), urlSession: urlSession diff --git a/FirebaseVertexAI/Tests/Unit/Fakes/AuthInteropFake.swift b/FirebaseAI/Tests/Unit/Fakes/AuthInteropFake.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Fakes/AuthInteropFake.swift rename to FirebaseAI/Tests/Unit/Fakes/AuthInteropFake.swift diff --git a/FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift b/FirebaseAI/Tests/Unit/GenerationConfigTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift rename to FirebaseAI/Tests/Unit/GenerationConfigTests.swift index acd4d61d053..22bcd70b035 100644 --- a/FirebaseVertexAI/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 FirebaseVertexAI +import FirebaseAI import Foundation import XCTest diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseAI/Tests/Unit/GenerativeModelTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift rename to FirebaseAI/Tests/Unit/GenerativeModelTests.swift index 8dd2cf07e42..89bd8a13699 100644 --- a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelTests.swift @@ -17,7 +17,7 @@ import FirebaseAuthInterop import FirebaseCore import XCTest -@testable public import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class GenerativeModelTests: XCTestCase { @@ -59,7 +59,7 @@ final class GenerativeModelTests: XCTestCase { let testModelName = "test-model" let testModelResourceName = "projects/test-project-id/locations/test-location/publishers/google/models/test-model" - let apiConfig = VertexAI.defaultVertexAIAPIConfig + let apiConfig = FirebaseAI.defaultVertexAIAPIConfig let vertexSubdirectory = "vertexai" diff --git a/FirebaseVertexAI/Tests/Unit/JSONValueTests.swift b/FirebaseAI/Tests/Unit/JSONValueTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/JSONValueTests.swift rename to FirebaseAI/Tests/Unit/JSONValueTests.swift index 77b88b686e1..1ffe88eaf55 100644 --- a/FirebaseVertexAI/Tests/Unit/JSONValueTests.swift +++ b/FirebaseAI/Tests/Unit/JSONValueTests.swift @@ -13,7 +13,7 @@ // limitations under the License. import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class JSONValueTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/MockURLProtocol.swift b/FirebaseAI/Tests/Unit/MockURLProtocol.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/MockURLProtocol.swift rename to FirebaseAI/Tests/Unit/MockURLProtocol.swift diff --git a/FirebaseVertexAI/Tests/Unit/PartTests.swift b/FirebaseAI/Tests/Unit/PartTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/PartTests.swift rename to FirebaseAI/Tests/Unit/PartTests.swift index aea3c1b5d92..544e229e08b 100644 --- a/FirebaseVertexAI/Tests/Unit/PartTests.swift +++ b/FirebaseAI/Tests/Unit/PartTests.swift @@ -15,7 +15,7 @@ import Foundation import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class PartTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift rename to FirebaseAI/Tests/Unit/PartsRepresentableTests.swift index 879013c917e..e7531d1da9e 100644 --- a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift @@ -23,7 +23,7 @@ import XCTest import CoreImage #endif // canImport(CoreImage) -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class PartsRepresentableTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/README.md b/FirebaseAI/Tests/Unit/README.md similarity index 100% rename from FirebaseVertexAI/Tests/Unit/README.md rename to FirebaseAI/Tests/Unit/README.md diff --git a/FirebaseVertexAI/Tests/Unit/RequestOptionsTest.swift b/FirebaseAI/Tests/Unit/RequestOptionsTest.swift similarity index 97% rename from FirebaseVertexAI/Tests/Unit/RequestOptionsTest.swift rename to FirebaseAI/Tests/Unit/RequestOptionsTest.swift index 1525393e812..5c03a6b63f4 100644 --- a/FirebaseVertexAI/Tests/Unit/RequestOptionsTest.swift +++ b/FirebaseAI/Tests/Unit/RequestOptionsTest.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @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/Resources/animals.mp4 b/FirebaseAI/Tests/Unit/Resources/animals.mp4 new file mode 100644 index 00000000000..8abcffb1eba Binary files /dev/null and b/FirebaseAI/Tests/Unit/Resources/animals.mp4 differ diff --git a/FirebaseAI/Tests/Unit/Resources/blue.png b/FirebaseAI/Tests/Unit/Resources/blue.png new file mode 100644 index 00000000000..a0cf28c6edb Binary files /dev/null and b/FirebaseAI/Tests/Unit/Resources/blue.png differ diff --git a/FirebaseAI/Tests/Unit/Resources/gemini-report.pdf b/FirebaseAI/Tests/Unit/Resources/gemini-report.pdf new file mode 100644 index 00000000000..2a2f00cb77b Binary files /dev/null and b/FirebaseAI/Tests/Unit/Resources/gemini-report.pdf differ diff --git a/FirebaseAI/Tests/Unit/Resources/hello-world.mp3 b/FirebaseAI/Tests/Unit/Resources/hello-world.mp3 new file mode 100644 index 00000000000..be617e65a5a Binary files /dev/null and b/FirebaseAI/Tests/Unit/Resources/hello-world.mp3 differ diff --git a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift new file mode 100644 index 00000000000..15dc6125fe0 --- /dev/null +++ b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift @@ -0,0 +1,67 @@ +// Copyright 2024 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 + +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class ChatSnippets: XCTestCase { + lazy var model = FirebaseAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") + + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + func testChatNonStreaming() async throws { + // Optionally specify existing chat history + let history = [ + ModelContent(role: "user", parts: "Hello, I have 2 dogs in my house."), + ModelContent(role: "model", parts: "Great to meet you. What would you like to know?"), + ] + + // Initialize the chat with optional chat history + let chat = model.startChat(history: history) + + // To generate text output, call sendMessage and pass in the message + let response = try await chat.sendMessage("How many paws are in my house?") + print(response.text ?? "No text in response.") + } + + func testChatStreaming() async throws { + // Optionally specify existing chat history + let history = [ + ModelContent(role: "user", parts: "Hello, I have 2 dogs in my house."), + ModelContent(role: "model", parts: "Great to meet you. What would you like to know?"), + ] + + // Initialize the chat with optional chat history + let chat = model.startChat(history: history) + + // To stream generated text output, call sendMessageStream and pass in the message + let contentStream = try chat.sendMessageStream("How many paws are in my house?") + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } +} diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift rename to FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift index 6627fb870ea..63b1be6d069 100644 --- a/FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift @@ -14,9 +14,9 @@ #if SWIFT_PACKAGE // The FirebaseStorage dependency has only been added in Package.swift. + import FirebaseAI import FirebaseCore import FirebaseStorage - import FirebaseVertexAI // These CloudStorageSnippets are not currently runnable due to the GCS upload paths but are used // as compilation tests. diff --git a/FirebaseAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift b/FirebaseAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift new file mode 100644 index 00000000000..f463fbda188 --- /dev/null +++ b/FirebaseAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift @@ -0,0 +1,57 @@ +// Copyright 2024 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 +import Foundation +import XCTest + +extension FirebaseApp { + /// Configures the default `FirebaseApp` for use in snippets tests. + /// + /// Uses a `GoogleService-Info.plist` file from the + /// [`Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources) + /// directory. + /// + /// > Note: This is typically called in a snippet test's set up; overriding + /// > `setUpWithError() throws` works well since it supports throwing errors. + static func configureDefaultAppForSnippets() throws { + guard let plistPath = BundleTestUtil.bundle().path( + forResource: "GoogleService-Info", + ofType: "plist" + ) else { + throw XCTSkip("No GoogleService-Info.plist found in FirebaseVertexAI/Tests/Unit/Resources.") + } + + let options = try XCTUnwrap(FirebaseOptions(contentsOfFile: plistPath)) + FirebaseApp.configure(options: options) + + guard FirebaseApp.isDefaultAppConfigured() else { + XCTFail("Default Firebase app not configured.") + return + } + } + + /// Deletes the default `FirebaseApp` if configured. + /// + /// > Note: This is typically called in a snippet test's tear down; overriding + /// > `tearDown() async throws` works well since deletion is asynchronous. + static func deleteDefaultAppForSnippets() async { + // Checking if `isDefaultAppConfigured()` before calling `FirebaseApp.app()` suppresses a log + // message that "The default Firebase app has not yet been configured." during `tearDown` when + // the tests are skipped. This reduces extraneous noise in the test logs. + if FirebaseApp.isDefaultAppConfigured(), let app = FirebaseApp.app() { + await app.delete() + } + } +} diff --git a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift new file mode 100644 index 00000000000..5f45eb8b7fd --- /dev/null +++ b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -0,0 +1,110 @@ +// Copyright 2024 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 + +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class FunctionCallingSnippets: XCTestCase { + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + func testFunctionCalling() async throws { + // This function calls a hypothetical external API that returns + // a collection of weather information for a given location on a given date. + func fetchWeather(city: String, state: String, date: String) -> JSONObject { + // TODO(developer): Write a standard function that would call an external weather API. + + // For demo purposes, this hypothetical response is hardcoded here in the expected format. + return [ + "temperature": .number(38), + "chancePrecipitation": .string("56%"), + "cloudConditions": .string("partlyCloudy"), + ] + } + + let fetchWeatherTool = FunctionDeclaration( + name: "fetchWeather", + description: "Get the weather conditions for a specific city on a specific date.", + parameters: [ + "location": .object( + properties: [ + "city": .string(description: "The city of the location."), + "state": .string(description: "The US state of the location."), + ], + description: """ + The name of the city and its state for which to get the weather. Only cities in the + USA are supported. + """ + ), + "date": .string( + description: """ + The date for which to get the weather. Date must be in the format: YYYY-MM-DD. + """ + ), + ] + ) + + // 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.vertexAI().generativeModel( + modelName: "gemini-1.5-flash", + // Provide the function declaration to the model. + tools: [.functionDeclarations([fetchWeatherTool])] + ) + + let chat = model.startChat() + let prompt = "What was the weather in Boston on October 17, 2024?" + + // Send the user's question (the prompt) to the model using multi-turn chat. + let response = try await chat.sendMessage(prompt) + + var functionResponses = [FunctionResponsePart]() + + // When the model responds with one or more function calls, invoke the function(s). + for functionCall in response.functionCalls { + if functionCall.name == "fetchWeather" { + // TODO(developer): Handle invalid arguments. + guard case let .object(location) = functionCall.args["location"] else { fatalError() } + guard case let .string(city) = location["city"] else { fatalError() } + guard case let .string(state) = location["state"] else { fatalError() } + guard case let .string(date) = functionCall.args["date"] else { fatalError() } + + functionResponses.append(FunctionResponsePart( + name: functionCall.name, + response: fetchWeather(city: city, state: state, date: date) + )) + } + // TODO(developer): Handle other potential function calls, if any. + } + + // Send the response(s) from the function back to the model so that the model can use it + // to generate its final response. + let finalResponse = try await chat.sendMessage( + [ModelContent(role: "function", parts: functionResponses)] + ) + + // Log the text response. + print(finalResponse.text ?? "No text in response.") + } +} diff --git a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift new file mode 100644 index 00000000000..60488561c87 --- /dev/null +++ b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift @@ -0,0 +1,215 @@ +// Copyright 2024 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(UIKit) + import UIKit +#endif // canImport(UIKit) + +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + +@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.vertexAI().generativeModel(modelName: "gemini-2.0-flash") + lazy var videoURL = { + guard let url = bundle.url(forResource: "animals", withExtension: "mp4") else { + fatalError("Video file animals.mp4 not found in Resources.") + } + return url + }() + + lazy var audioURL = { + guard let url = bundle.url(forResource: "hello-world", withExtension: "mp3") else { + fatalError("Audio file hello-world.mp3 not found in Resources.") + } + return url + }() + + lazy var pdfURL = { + guard let url = bundle.url(forResource: "gemini-report", withExtension: "pdf") else { + fatalError("PDF file gemini-report.pdf not found in Resources.") + } + return url + }() + + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + // MARK: - Image Input + + #if canImport(UIKit) + func testMultimodalOneImageNonStreaming() async throws { + guard let image = UIImage(systemName: "bicycle") else { fatalError() } + + // Provide a text prompt to include with the image + let prompt = "What's in this picture?" + + // To generate text output, call generateContent and pass in the prompt + let response = try await model.generateContent(image, prompt) + print(response.text ?? "No text in response.") + } + + func testMultimodalOneImageStreaming() async throws { + guard let image = UIImage(systemName: "bicycle") else { fatalError() } + + // Provide a text prompt to include with the image + let prompt = "What's in this picture?" + + // To stream generated text output, call generateContentStream and pass in the prompt + let contentStream = try model.generateContentStream(image, prompt) + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } + + func testMultimodalMultiImagesNonStreaming() async throws { + guard let image1 = UIImage(systemName: "car") else { fatalError() } + guard let image2 = UIImage(systemName: "car.2") else { fatalError() } + + // Provide a text prompt to include with the images + let prompt = "What's different between these pictures?" + + // To generate text output, call generateContent and pass in the prompt + let response = try await model.generateContent(image1, image2, prompt) + print(response.text ?? "No text in response.") + } + + func testMultimodalMultiImagesStreaming() async throws { + guard let image1 = UIImage(systemName: "car") else { fatalError() } + guard let image2 = UIImage(systemName: "car.2") else { fatalError() } + + // Provide a text prompt to include with the images + let prompt = "What's different between these pictures?" + + // To stream generated text output, call generateContentStream and pass in the prompt + let contentStream = try model.generateContentStream(image1, image2, prompt) + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } + #endif // canImport(UIKit) + + // MARK: - Video Input + + func testMultimodalVideoNonStreaming() async throws { + // Provide the video as `Data` with the appropriate MIME type + let video = try InlineDataPart(data: Data(contentsOf: videoURL), mimeType: "video/mp4") + + // Provide a text prompt to include with the video + let prompt = "What is in the video?" + + // To generate text output, call generateContent with the text and video + let response = try await model.generateContent(video, prompt) + print(response.text ?? "No text in response.") + } + + func testMultimodalVideoStreaming() async throws { + // Provide the video as `Data` with the appropriate MIME type + let video = try InlineDataPart(data: Data(contentsOf: videoURL), mimeType: "video/mp4") + + // Provide a text prompt to include with the video + let prompt = "What is in the video?" + + // To stream generated text output, call generateContentStream with the text and video + let contentStream = try model.generateContentStream(video, prompt) + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } + + // MARK: - Audio Input + + func testMultiModalAudioNonStreaming() async throws { + // Provide the audio as `Data` with the appropriate MIME type + let audio = try InlineDataPart(data: Data(contentsOf: audioURL), mimeType: "audio/mpeg") + + // Provide a text prompt to include with the audio + let prompt = "Transcribe what's said in this audio recording." + + // To generate text output, call `generateContent` with the audio and text prompt + let response = try await model.generateContent(audio, prompt) + + // Print the generated text, handling the case where it might be nil + print(response.text ?? "No text in response.") + } + + func testMultiModalAudioStreaming() async throws { + // Provide the audio as `Data` with the appropriate MIME type + let audio = try InlineDataPart(data: Data(contentsOf: audioURL), mimeType: "audio/mpeg") + + // Provide a text prompt to include with the audio + let prompt = "Transcribe what's said in this audio recording." + + // To stream generated text output, call `generateContentStream` with the audio and text prompt + let contentStream = try model.generateContentStream(audio, prompt) + + // Print the generated text, handling the case where it might be nil + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } + + // MARK: - Document Input + + func testMultiModalPDFStreaming() async throws { + // Provide the PDF as `Data` with the appropriate MIME type + let pdf = try InlineDataPart(data: Data(contentsOf: pdfURL), mimeType: "application/pdf") + + // Provide a text prompt to include with the PDF file + let prompt = "Summarize the important results in this report." + + // To stream generated text output, call `generateContentStream` with the PDF file and text + // prompt + let contentStream = try model.generateContentStream(pdf, prompt) + + // Print the generated text, handling the case where it might be nil + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } + + func testMultiModalPDFNonStreaming() async throws { + // Provide the PDF as `Data` with the appropriate MIME type + let pdf = try InlineDataPart(data: Data(contentsOf: pdfURL), mimeType: "application/pdf") + + // Provide a text prompt to include with the PDF file + let prompt = "Summarize the important results in this report." + + // To generate text output, call `generateContent` with the PDF file and text prompt + let response = try await model.generateContent(pdf, prompt) + + // Print the generated text, handling the case where it might be nil + print(response.text ?? "No text in response.") + } +} diff --git a/FirebaseAI/Tests/Unit/Snippets/README.md b/FirebaseAI/Tests/Unit/Snippets/README.md new file mode 100644 index 00000000000..8d03458c456 --- /dev/null +++ b/FirebaseAI/Tests/Unit/Snippets/README.md @@ -0,0 +1,10 @@ +# Vertex AI in Firebase Code Snippet Tests + +These "tests" are for verifying that the code snippets provided in our +documentation continue to compile. They are intentionally skipped in CI but can +be manually run to verify expected behavior / outputs. + +To run the tests, place a valid `GoogleService-Info.plist` file in the +[`FirebaseVertexAI/Tests/Unit/Resources`](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI/Tests/Unit/Resources) +folder. They may then be invoked individually or alongside the rest of the unit +tests in Xcode. diff --git a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift new file mode 100644 index 00000000000..ee2c321da8b --- /dev/null +++ b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -0,0 +1,96 @@ +// Copyright 2024 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 + +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class StructuredOutputSnippets: XCTestCase { + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + func testStructuredOutputJSONBasic() async throws { + // Provide a JSON schema object using a standard format. + // Later, pass this schema object into `responseSchema` in the generation config. + let jsonSchema = Schema.object( + properties: [ + "characters": Schema.array( + items: .object( + properties: [ + "name": .string(), + "age": .integer(), + "species": .string(), + "accessory": .enumeration(values: ["hat", "belt", "shoes"]), + ], + optionalProperties: ["accessory"] + ) + ), + ] + ) + + // 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.vertexAI().generativeModel( + modelName: "gemini-1.5-flash", + // In the generation config, set the `responseMimeType` to `application/json` + // and pass the JSON schema object into `responseSchema`. + generationConfig: GenerationConfig( + responseMIMEType: "application/json", + responseSchema: jsonSchema + ) + ) + + let prompt = "For use in a children's card game, generate 10 animal-based characters." + + let response = try await model.generateContent(prompt) + print(response.text ?? "No text in response.") + } + + func testStructuredOutputEnumBasic() async throws { + // Provide an enum schema object using a standard format. + // Later, pass this schema object into `responseSchema` in the generation config. + let enumSchema = Schema.enumeration(values: ["drama", "comedy", "documentary"]) + + // 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.vertexAI().generativeModel( + modelName: "gemini-1.5-flash", + // In the generation config, set the `responseMimeType` to `text/x.enum` + // and pass the enum schema object into `responseSchema`. + generationConfig: GenerationConfig( + responseMIMEType: "text/x.enum", + responseSchema: enumSchema + ) + ) + + let prompt = """ + The film aims to educate and inform viewers about real-life subjects, events, or people. + It offers a factual record of a particular topic by combining interviews, historical footage, + and narration. The primary purpose of a film is to present information and provide insights + into various aspects of reality. + """ + + let response = try await model.generateContent(prompt) + print(response.text ?? "No text in response.") + } +} diff --git a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift new file mode 100644 index 00000000000..075993b5bfe --- /dev/null +++ b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift @@ -0,0 +1,55 @@ +// Copyright 2024 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 + +// These snippet tests are intentionally skipped in CI jobs; see the README file in this directory +// for instructions on running them manually. + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class TextSnippets: XCTestCase { + lazy var model = FirebaseAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") + + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + func testTextOnlyNonStreaming() async throws { + // Provide a prompt that contains text + let prompt = "Write a story about a magic backpack." + + // To generate text output, call generateContent with the text input + let response = try await model.generateContent(prompt) + print(response.text ?? "No text in response.") + } + + func testTextOnlyStreaming() async throws { + // Provide a prompt that contains text + let prompt = "Write a story about a magic backpack." + + // To stream generated text output, call generateContentStream with the text input + let contentStream = try model.generateContentStream(prompt) + for try await chunk in contentStream { + if let text = chunk.text { + print(text) + } + } + } +} diff --git a/FirebaseAI/Tests/Unit/TestUtilities/BundleTestUtil.swift b/FirebaseAI/Tests/Unit/TestUtilities/BundleTestUtil.swift new file mode 100644 index 00000000000..272be41c1e4 --- /dev/null +++ b/FirebaseAI/Tests/Unit/TestUtilities/BundleTestUtil.swift @@ -0,0 +1,31 @@ +// Copyright 2024 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 Foundation + +/// `Bundle` test utilities. +final class BundleTestUtil { + /// Returns the `Bundle` for the test module or target containing the file. + /// + /// This abstracts away the `Bundle` differences between SPM and CocoaPods tests. + static func bundle() -> Bundle { + #if SWIFT_PACKAGE + return Bundle.module + #else // SWIFT_PACKAGE + return Bundle(for: Self.self) + #endif // SWIFT_PACKAGE + } + + private init() {} +} diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift similarity index 97% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift index d179bc268a1..ce66fe94cb7 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImageGenerationInstanceTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift similarity index 98% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift index 9a5d93e9dc3..bd5b9f10e44 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImageGenerationOutputOptionsTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift index 028356c7433..494feda9f7a 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImageGenerationParametersTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift similarity index 98% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift index badd1df5461..6bf98306cbf 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenGCSImageTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift similarity index 98% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift index 27e7f747453..eb8b3df83ca 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenGenerationRequestTests: XCTestCase { @@ -36,7 +36,7 @@ final class ImagenGenerationRequestTests: XCTestCase { addWatermark: nil, includeResponsibleAIFilterReason: includeResponsibleAIFilterReason ) - let apiConfig = VertexAI.defaultVertexAIAPIConfig + let apiConfig = FirebaseAI.defaultVertexAIAPIConfig let instance = ImageGenerationInstance(prompt: "test-prompt") diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift index 138862e153b..97122401253 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenGenerationResponseTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift index a28bf763b1b..31effc5c0bf 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenInlineImageTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift similarity index 98% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift index 0282eca58ce..90ac676f90a 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class RAIFilteredReasonTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Internal/APIConfigTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift similarity index 98% rename from FirebaseVertexAI/Tests/Unit/Types/Internal/APIConfigTests.swift rename to FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift index 12bbd5cf2ca..8735688e76e 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/Internal/APIConfigTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class APIConfigTests: XCTestCase { diff --git a/FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift similarity index 97% rename from FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift rename to FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift index 338671287ab..5ca31474f1b 100644 --- a/FirebaseVertexAI/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 FirebaseVertexAI +@testable import FirebaseAI @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 = VertexAI.defaultVertexAIAPIConfig + let vertexAPIConfig = FirebaseAI.defaultVertexAIAPIConfig let developerAPIConfig = APIConfig( service: .developer(endpoint: .firebaseVertexAIProd), version: .v1beta diff --git a/FirebaseVertexAI/Tests/Unit/Types/ModalityTokenCountTests.swift b/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/Types/ModalityTokenCountTests.swift rename to FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift index ffdb36938fb..cd56a0c67d1 100644 --- a/FirebaseVertexAI/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 FirebaseVertexAI +import FirebaseAI import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) diff --git a/FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift b/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift rename to FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift index 0e6816c43df..dbe6c2e27ca 100644 --- a/FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift +++ b/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI final class ProtoDateTests: XCTestCase { let decoder = JSONDecoder() diff --git a/FirebaseVertexAI/Tests/Unit/Types/SchemaTests.swift b/FirebaseAI/Tests/Unit/Types/SchemaTests.swift similarity index 99% rename from FirebaseVertexAI/Tests/Unit/Types/SchemaTests.swift rename to FirebaseAI/Tests/Unit/Types/SchemaTests.swift index d046fe86d6d..e6eb839a39a 100644 --- a/FirebaseVertexAI/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 FirebaseVertexAI +import FirebaseAI import Foundation import XCTest diff --git a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift similarity index 85% rename from FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift rename to FirebaseAI/Tests/Unit/VertexComponentTests.swift index 3f9f6622ead..f5e80c2a7e0 100644 --- a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -17,7 +17,7 @@ internal import FirebaseCoreExtension import Foundation import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @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 = VertexAI.vertexAI() + let vertex = FirebaseAI.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 = VertexAI.vertexAI(location: location) + let vertex = FirebaseAI.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 = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) + let vertex = FirebaseAI.vertexAI(app: VertexComponentTests.app, location: location) XCTAssertNotNil(vertex) XCTAssertEqual(vertex.firebaseInfo.projectID, VertexComponentTests.projectID) @@ -94,16 +94,16 @@ class VertexComponentTests: XCTestCase { func testSameAppAndLocation_instanceReused() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex1 = VertexAI.vertexAI(app: app, location: location) - let vertex2 = VertexAI.vertexAI(app: app, location: location) + let vertex1 = FirebaseAI.vertexAI(app: app, location: location) + let vertex2 = FirebaseAI.vertexAI(app: app, location: location) // Ensure they're the same instance. XCTAssert(vertex1 === vertex2) } func testSameAppAndDifferentLocation_newInstanceCreated() throws { - let vertex1 = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) - let vertex2 = VertexAI.vertexAI(app: VertexComponentTests.app, location: "differentLocation") + let vertex1 = FirebaseAI.vertexAI(app: VertexComponentTests.app, location: location) + let vertex2 = FirebaseAI.vertexAI(app: VertexComponentTests.app, location: "differentLocation") // Ensure they are different instances. XCTAssert(vertex1 !== vertex2) @@ -114,8 +114,8 @@ class VertexComponentTests: XCTestCase { let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) addTeardownBlock { await app2.delete() } - let vertex1 = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) - let vertex2 = VertexAI.vertexAI(app: app2, location: location) + let vertex1 = FirebaseAI.vertexAI(app: VertexComponentTests.app, location: location) + let vertex2 = FirebaseAI.vertexAI(app: app2, location: location) XCTAssert(VertexComponentTests.app != app2) XCTAssert(vertex1 !== vertex2) // Ensure they are different instances. @@ -126,20 +126,20 @@ class VertexComponentTests: XCTestCase { let app2 = FirebaseApp(instanceWithName: "test-2", options: VertexComponentTests.options) addTeardownBlock { await app2.delete() } - let vertex1 = VertexAI.vertexAI(app: VertexComponentTests.app, location: location) - let vertex2 = VertexAI.vertexAI(app: app2, location: "differentLocation") + let vertex1 = FirebaseAI.vertexAI(app: VertexComponentTests.app, location: location) + let vertex2 = FirebaseAI.vertexAI(app: app2, location: "differentLocation") XCTAssert(VertexComponentTests.app != app2) XCTAssert(vertex1 !== vertex2) // Ensure they are different instances. } func testSameAppAndDifferentAPI_newInstanceCreated() throws { - let vertex1 = VertexAI.vertexAI( + let vertex1 = FirebaseAI.vertexAI( app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1beta) ) - let vertex2 = VertexAI.vertexAI( + let vertex2 = FirebaseAI.vertexAI( app: VertexComponentTests.app, location: location, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseVertexAIProd), version: .v1) @@ -152,7 +152,7 @@ class VertexComponentTests: XCTestCase { /// Test that vertex instances get deallocated. func testVertexLifecycle() throws { weak var weakApp: FirebaseApp? - weak var weakVertex: VertexAI? + weak var weakVertex: FirebaseAI? try autoreleasepool { let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", gcmSenderID: "00000000000000000-00000000000-000000000") @@ -160,10 +160,10 @@ class VertexComponentTests: XCTestCase { options.apiKey = VertexComponentTests.apiKey let app1 = FirebaseApp(instanceWithName: "transitory app", options: options) weakApp = try XCTUnwrap(app1) - let vertex = VertexAI( + let vertex = FirebaseAI( app: app1, location: "transitory location", - apiConfig: VertexAI.defaultVertexAIAPIConfig + apiConfig: FirebaseAI.defaultVertexAIAPIConfig ) weakVertex = vertex XCTAssertNotNil(weakVertex) @@ -174,7 +174,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_vertexAI() throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex = VertexAI.vertexAI(app: app, location: location) + let vertex = FirebaseAI.vertexAI(app: app, location: location) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -190,7 +190,7 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig(service: .developer(endpoint: .generativeLanguage), version: .v1beta) - let vertex = VertexAI.vertexAI(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.vertexAI(app: app, location: nil, apiConfig: apiConfig) let model = "test-model-name" let modelResourceName = vertex.modelResourceName(modelName: model) @@ -204,7 +204,7 @@ class VertexComponentTests: XCTestCase { service: .developer(endpoint: .firebaseVertexAIStaging), version: .v1beta ) - let vertex = VertexAI.vertexAI(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.vertexAI(app: app, location: nil, apiConfig: apiConfig) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -215,7 +215,7 @@ class VertexComponentTests: XCTestCase { func testGenerativeModel_vertexAI() async throws { let app = try XCTUnwrap(VertexComponentTests.app) - let vertex = VertexAI.vertexAI(app: app, location: location) + let vertex = FirebaseAI.vertexAI(app: app, location: location) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) @@ -226,7 +226,7 @@ class VertexComponentTests: XCTestCase { XCTAssertEqual(generativeModel.modelResourceName, modelResourceName) XCTAssertEqual(generativeModel.systemInstruction, expectedSystemInstruction) - XCTAssertEqual(generativeModel.apiConfig, VertexAI.defaultVertexAIAPIConfig) + XCTAssertEqual(generativeModel.apiConfig, FirebaseAI.defaultVertexAIAPIConfig) } func testGenerativeModel_developerAPI() async throws { @@ -235,7 +235,7 @@ class VertexComponentTests: XCTestCase { service: .developer(endpoint: .firebaseVertexAIStaging), version: .v1beta ) - let vertex = VertexAI.vertexAI(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.vertexAI(app: app, location: nil, apiConfig: apiConfig) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) diff --git a/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi similarity index 100% rename from FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi rename to FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi diff --git a/FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai similarity index 100% rename from FirebaseVertexAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai rename to FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index a1bf7e1c696..4305c116ad5 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -44,10 +44,7 @@ Firebase SDK. s.tvos.framework = 'UIKit' s.watchos.framework = 'WatchKit' - s.dependency 'FirebaseAppCheckInterop', '~> 11.4' - s.dependency 'FirebaseAuthInterop', '~> 11.4' - s.dependency 'FirebaseCore', '~> 11.13.0' - s.dependency 'FirebaseCoreExtension', '~> 11.13.0' + s.dependency 'FirebaseAI', '~> 11.13.0' s.test_spec 'unit' do |unit_tests| unit_tests_dir = 'FirebaseVertexAI/Tests/Unit/' @@ -60,9 +57,5 @@ Firebase SDK. unit_tests.source_files = [ unit_tests_dir + '**/*.swift', ] - unit_tests.resources = [ - unit_tests_dir + 'vertexai-sdk-test-data/mock-responses/vertexai', - unit_tests_dir + 'Resources/**/*', - ] end end diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index c3ef7d2df73..ba4ebc37df1 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// 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. @@ -12,13 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseAppCheckInterop -import FirebaseAuthInterop -import FirebaseCore -import Foundation +@_exported public import FirebaseAI -// Avoids exposing internal FirebaseCore APIs to Swift users. -internal import FirebaseCoreExtension +import FirebaseCore /// The Vertex AI for Firebase 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, *) @@ -37,14 +33,8 @@ public class VertexAI { /// - Returns: A `VertexAI` instance, configured with the custom `FirebaseApp`. public static func vertexAI(app: FirebaseApp? = nil, location: String = "us-central1") -> VertexAI { - let vertexInstance = vertexAI(app: app, location: location, apiConfig: defaultVertexAIAPIConfig) - // Verify that the `VertexAI` instance is always configured with the production endpoint since - // this is the public API surface for creating an instance. - assert(vertexInstance.apiConfig.service == .vertexAI(endpoint: .firebaseVertexAIProd)) - assert(vertexInstance.apiConfig.service.endpoint == .firebaseVertexAIProd) - assert(vertexInstance.apiConfig.version == .v1beta) - - return vertexInstance + let firebaseAI = FirebaseAI.vertexAI(app: app, location: location) + return VertexAI(firebaseAI: firebaseAI) } /// Initializes a generative model with the given parameters. @@ -72,18 +62,8 @@ public class VertexAI { systemInstruction: ModelContent? = nil, requestOptions: RequestOptions = RequestOptions()) -> GenerativeModel { - if !modelName.starts(with: GenerativeModel.geminiModelNamePrefix) { - VertexLog.warning(code: .unsupportedGeminiModel, """ - Unsupported Gemini model "\(modelName)"; see \ - https://firebase.google.com/docs/vertex-ai/models for a list supported Gemini model names. - """) - } - - return GenerativeModel( + return firebaseAI.generativeModel( modelName: modelName, - modelResourceName: modelResourceName(modelName: modelName), - firebaseInfo: firebaseInfo, - apiConfig: apiConfig, generationConfig: generationConfig, safetySettings: safetySettings, tools: tools, @@ -112,149 +92,19 @@ public class VertexAI { public func imagenModel(modelName: String, generationConfig: ImagenGenerationConfig? = nil, safetySettings: ImagenSafetySettings? = nil, requestOptions: RequestOptions = RequestOptions()) -> ImagenModel { - if !modelName.starts(with: ImagenModel.imagenModelNamePrefix) { - VertexLog.warning(code: .unsupportedImagenModel, """ - Unsupported Imagen model "\(modelName)"; see \ - https://firebase.google.com/docs/vertex-ai/models for a list supported Imagen model names. - """) - } - - return ImagenModel( - modelResourceName: modelResourceName(modelName: modelName), - firebaseInfo: firebaseInfo, - apiConfig: apiConfig, + return firebaseAI.imagenModel( + modelName: modelName, generationConfig: generationConfig, safetySettings: safetySettings, requestOptions: requestOptions ) } - /// Class to enable VertexAI to register via the Objective-C based Firebase component system - /// to include VertexAI in the userAgent. - @objc(FIRVertexAIComponent) class FirebaseVertexAIComponent: NSObject {} - - // MARK: - Private - - /// Firebase data relevant to Vertex AI. - let firebaseInfo: FirebaseInfo - - let apiConfig: APIConfig - - #if compiler(>=6) - /// A map of active `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in - /// the format `appName:location`. - private nonisolated(unsafe) static var instances: [InstanceKey: VertexAI] = [:] - - /// Lock to manage access to the `instances` array to avoid race conditions. - private nonisolated(unsafe) static var instancesLock: os_unfair_lock = .init() - #else - /// A map of active `VertexAI` instances keyed by the `FirebaseApp` name and the `location`, in - /// the format `appName:location`. - private static var instances: [InstanceKey: VertexAI] = [:] - - /// Lock to manage access to the `instances` array to avoid race conditions. - private static var instancesLock: os_unfair_lock = .init() - #endif - - let location: String? - - static let defaultVertexAIAPIConfig = APIConfig( - service: .vertexAI(endpoint: .firebaseVertexAIProd), - version: .v1beta - ) - - static func vertexAI(app: FirebaseApp?, location: String?, apiConfig: APIConfig) -> VertexAI { - guard let app = app ?? FirebaseApp.app() else { - fatalError("No instance of the default Firebase app was found.") - } - - os_unfair_lock_lock(&instancesLock) - - // Unlock before the function returns. - defer { os_unfair_lock_unlock(&instancesLock) } - - let instanceKey = InstanceKey(appName: app.name, location: location, apiConfig: apiConfig) - if let instance = instances[instanceKey] { - return instance - } - let newInstance = VertexAI(app: app, location: location, apiConfig: apiConfig) - instances[instanceKey] = newInstance - return newInstance - } - - init(app: FirebaseApp, location: String?, apiConfig: APIConfig) { - guard let projectID = app.options.projectID else { - fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.") - } - guard let apiKey = app.options.apiKey else { - fatalError("The Firebase app named \"\(app.name)\" has no API key in its configuration.") - } - firebaseInfo = FirebaseInfo( - appCheck: ComponentType.instance( - for: AppCheckInterop.self, - in: app.container - ), - auth: ComponentType.instance(for: AuthInterop.self, in: app.container), - projectID: projectID, - apiKey: apiKey, - firebaseAppID: app.options.googleAppID, - firebaseApp: app - ) - self.apiConfig = apiConfig - self.location = location - } - - func modelResourceName(modelName: String) -> String { - guard !modelName.isEmpty && modelName - .allSatisfy({ !$0.isWhitespace && !$0.isNewline && $0 != "/" }) else { - fatalError(""" - Invalid model name "\(modelName)" specified; see \ - https://firebase.google.com/docs/vertex-ai/gemini-model#available-models for a list of \ - available models. - """) - } + // MARK: - Internal APIs - switch apiConfig.service { - case .vertexAI: - return vertexAIModelResourceName(modelName: modelName) - case .developer: - return developerModelResourceName(modelName: modelName) - } - } - - private func vertexAIModelResourceName(modelName: String) -> String { - guard let location else { - fatalError("Location must be specified for the Vertex AI service.") - } - guard !location.isEmpty && location - .allSatisfy({ !$0.isWhitespace && !$0.isNewline && $0 != "/" }) else { - fatalError(""" - Invalid location "\(location)" specified; see \ - https://firebase.google.com/docs/vertex-ai/locations?platform=ios#available-locations \ - for a list of available locations. - """) - } - - let projectID = firebaseInfo.projectID - return "projects/\(projectID)/locations/\(location)/publishers/google/models/\(modelName)" - } + let firebaseAI: FirebaseAI - private func developerModelResourceName(modelName: String) -> String { - switch apiConfig.service.endpoint { - case .firebaseVertexAIStaging, .firebaseVertexAIProd: - let projectID = firebaseInfo.projectID - return "projects/\(projectID)/models/\(modelName)" - case .generativeLanguage: - return "models/\(modelName)" - } - } - - /// Identifier for a unique instance of ``VertexAI``. - /// - /// This type is `Hashable` so that it can be used as a key in the `instances` dictionary. - private struct InstanceKey: Sendable, Hashable { - let appName: String - let location: String? - let apiConfig: APIConfig + init(firebaseAI: FirebaseAI) { + self.firebaseAI = firebaseAI } } diff --git a/Package.swift b/Package.swift index de2a3f7f435..fc30b27be75 100644 --- a/Package.swift +++ b/Package.swift @@ -1298,25 +1298,25 @@ let package = Package( ] ), - // MARK: - Firebase Vertex AI + // MARK: - Firebase AI .target( - name: "FirebaseVertexAI", + name: "FirebaseAI", dependencies: [ "FirebaseAppCheckInterop", "FirebaseAuthInterop", "FirebaseCore", "FirebaseCoreExtension", ], - path: "FirebaseVertexAI/Sources" + path: "FirebaseAI/Sources" ), .testTarget( - name: "FirebaseVertexAIUnit", + name: "FirebaseAIUnit", dependencies: [ - "FirebaseVertexAI", + "FirebaseAI", "FirebaseStorage", ], - path: "FirebaseVertexAI/Tests/Unit", + path: "FirebaseAI/Tests/Unit", resources: [ .copy("vertexai-sdk-test-data/mock-responses/vertexai"), .process("Resources"), @@ -1325,6 +1325,26 @@ let package = Package( .headerSearchPath("../../../"), ] ), + + // MARK: - Firebase Vertex AI + + .target( + name: "FirebaseVertexAI", + dependencies: [ + "FirebaseAI", + ], + path: "FirebaseVertexAI/Sources" + ), + .testTarget( + name: "FirebaseVertexAIUnit", + dependencies: [ + "FirebaseVertexAI", + ], + path: "FirebaseVertexAI/Tests/Unit", + resources: [ + .process("Resources"), + ] + ), ] + firestoreTargets(), cLanguageStandard: .c99, cxxLanguageStandard: CXXLanguageStandard.gnucxx14 diff --git a/scripts/build.sh b/scripts/build.sh index 9ad85b209a1..4738811f365 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -504,9 +504,9 @@ case "$product-$platform-$method" in build ;; - VertexIntegration-*-*) + FirebaseAIIntegration-*-*) RunXcodebuild \ - -project 'FirebaseVertexAI/Tests/TestApp/VertexAITestApp.xcodeproj' \ + -project 'FirebaseAI/Tests/TestApp/VertexAITestApp.xcodeproj' \ -scheme "VertexAITestApp-SPM" \ "${xcb_flags[@]}" \ -parallel-testing-enabled NO \ diff --git a/scripts/spm_test_schemes/FirebaseAIUnit.xcscheme b/scripts/spm_test_schemes/FirebaseAIUnit.xcscheme new file mode 100644 index 00000000000..1d5f4cd2713 --- /dev/null +++ b/scripts/spm_test_schemes/FirebaseAIUnit.xcscheme @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/update_vertexai_responses.sh b/scripts/update_vertexai_responses.sh index 8ac02970251..c61a50488e6 100755 --- a/scripts/update_vertexai_responses.sh +++ b/scripts/update_vertexai_responses.sh @@ -17,6 +17,6 @@ # This script replaces mock response files for Vertex AI unit tests with a fresh # clone of the shared repository of Vertex AI test data. -cd "$(dirname "$0")/../FirebaseVertexAI/Tests/Unit" || exit +cd "$(dirname "$0")/../FirebaseAI/Tests/Unit" || exit rm -rf vertexai-sdk-test-data || exit git clone --depth 1 https://github.com/FirebaseExtended/vertexai-sdk-test-data.git