From a939c49a7b0c4ed6f9bf54ec58aa0b227d73d8e1 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 16 Apr 2025 16:34:18 -0400 Subject: [PATCH 01/18] Move `FirebaseVertexAI` folder to `FirebaseAI` --- {FirebaseVertexAI => FirebaseAI}/CHANGELOG.md | 0 {FirebaseVertexAI => FirebaseAI}/README.md | 0 {FirebaseVertexAI => FirebaseAI}/Sample/README.md | 0 {FirebaseVertexAI => FirebaseAI}/Sources/Chat.swift | 0 .../Sources/Constants.swift | 0 .../Sources/Errors.swift | 0 .../Sources/FirebaseInfo.swift | 0 .../Sources/FunctionCalling.swift | 0 .../Sources/GenAIURLSession.swift | 0 .../Sources/GenerateContentError.swift | 0 .../Sources/GenerateContentRequest.swift | 0 .../Sources/GenerateContentResponse.swift | 0 .../Sources/GenerationConfig.swift | 0 .../Sources/GenerativeAIRequest.swift | 0 .../Sources/GenerativeAIService.swift | 0 .../Sources/GenerativeModel.swift | 0 .../Sources/JSONValue.swift | 0 .../Sources/ModalityTokenCount.swift | 0 .../Sources/ModelContent.swift | 0 .../Sources/PartsRepresentable+Image.swift | 0 .../Sources/PartsRepresentable.swift | 0 .../Protocols/Internal/CodableProtoEnum.swift | 0 .../Sources/Safety.swift | 0 .../Sources/Types/Internal/APIConfig.swift | 0 .../Sources/Types/Internal/DataType.swift | 0 .../Types/Internal/Errors/BackendError.swift | 0 .../Internal/Imagen/ImageGenerationInstance.swift | 0 .../Imagen/ImageGenerationOutputOptions.swift | 0 .../Internal/Imagen/ImageGenerationParameters.swift | 0 .../Types/Internal/Imagen/ImagenConstants.swift | 0 .../Types/Internal/Imagen/ImagenGCSImage.swift | 0 .../Internal/Imagen/ImagenGenerationRequest.swift | 0 .../Internal/Imagen/ImagenImageRepresentable.swift | 0 .../Types/Internal/Imagen/InternalImagenImage.swift | 0 .../Types/Internal/Imagen/RAIFilteredReason.swift | 0 .../Sources/Types/Internal/InternalPart.swift | 0 .../Sources/Types/Internal/ProtoDate.swift | 0 .../Internal/Requests/CountTokensRequest.swift | 0 .../Types/Public/Imagen/ImagenAspectRatio.swift | 0 .../Public/Imagen/ImagenGenerationConfig.swift | 0 .../Public/Imagen/ImagenGenerationResponse.swift | 0 .../Types/Public/Imagen/ImagenImageFormat.swift | 0 .../Public/Imagen/ImagenImagesBlockedError.swift | 0 .../Types/Public/Imagen/ImagenInlineImage.swift | 0 .../Sources/Types/Public/Imagen/ImagenModel.swift | 0 .../Public/Imagen/ImagenPersonFilterLevel.swift | 0 .../Public/Imagen/ImagenSafetyFilterLevel.swift | 0 .../Types/Public/Imagen/ImagenSafetySettings.swift | 0 .../Sources/Types/Public/Part.swift | 0 .../Sources/Types/Public/ResponseModality.swift | 0 .../Sources/Types/Public/Schema.swift | 0 .../Sources/VertexAI.swift | 0 .../Sources/VertexLog.swift | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../TestApp/Resources/Assets.xcassets/Contents.json | 0 .../Preview Assets.xcassets/Contents.json | 0 .../Tests/TestApp/Resources/TestApp.entitlements | 0 .../Tests/TestApp/Sources/Constants.swift | 0 .../Tests/TestApp/Sources/ContentView.swift | 0 .../Tests/TestApp/Sources/FirebaseAppUtils.swift | 0 .../Tests/TestApp/Sources/TestApp.swift | 0 .../Sources/TestAppCheckProviderFactory.swift | 0 .../Integration/CountTokensIntegrationTests.swift | 0 .../GenerateContentIntegrationTests.swift | 0 .../Tests/Integration/ImagenIntegrationTests.swift | 0 .../Tests/Integration/IntegrationTests.swift | 0 .../TestApp/Tests/Integration/SchemaTests.swift | 0 .../TestApp/Tests/Integration/TestHelpers.swift | 0 .../TestApp/Tests/Utilities/InstanceConfig.swift | 0 .../Tests/Utilities/IntegrationTestUtils.swift | 0 .../VertexAITestApp.xcodeproj/project.pbxproj | 0 .../Tests/Unit/ChatTests.swift | 0 .../Tests/Unit/Fakes/AuthInteropFake.swift | 0 .../Tests/Unit/GenerationConfigTests.swift | 0 .../Tests/Unit/GenerativeModelTests.swift | 0 .../Tests/Unit/JSONValueTests.swift | 0 .../Tests/Unit/MockURLProtocol.swift | 0 .../Tests/Unit/PartTests.swift | 0 .../Tests/Unit/PartsRepresentableTests.swift | 0 .../Tests/Unit/README.md | 0 .../Tests/Unit/RequestOptionsTest.swift | 0 .../Tests/Unit/Resources/animals.mp4 | Bin .../Tests/Unit/Resources/blue.png | Bin .../Tests/Unit/Snippets/ChatSnippets.swift | 0 .../Tests/Unit/Snippets/CloudStorageSnippets.swift | 0 .../Unit/Snippets/FirebaseAppSnippetsUtil.swift | 0 .../Unit/Snippets/FunctionCallingSnippets.swift | 0 .../Tests/Unit/Snippets/MultimodalSnippets.swift | 0 .../Tests/Unit/Snippets/README.md | 0 .../Unit/Snippets/StructuredOutputSnippets.swift | 0 .../Tests/Unit/Snippets/TextSnippets.swift | 0 .../Tests/Unit/TestUtilities/BundleTestUtil.swift | 0 .../Types/Imagen/ImageGenerationInstanceTests.swift | 0 .../Imagen/ImageGenerationOutputOptionsTests.swift | 0 .../Imagen/ImageGenerationParametersTests.swift | 0 .../Unit/Types/Imagen/ImagenGCSImageTests.swift | 0 .../Types/Imagen/ImagenGenerationRequestTests.swift | 0 .../Imagen/ImagenGenerationResponseTests.swift | 0 .../Unit/Types/Imagen/ImagenInlineImageTests.swift | 0 .../Unit/Types/Imagen/RAIFilteredReasonTests.swift | 0 .../Tests/Unit/Types/Internal/APIConfigTests.swift | 0 .../Internal/Requests/CountTokensRequestTests.swift | 0 .../Tests/Unit/Types/ProtoDateTests.swift | 0 .../Tests/Unit/Types/SchemaTests.swift | 0 .../Tests/Unit/VertexAIAPITests.swift | 0 .../Tests/Unit/VertexComponentTests.swift | 0 .../mock-responses/developerapi | 0 .../vertexai-sdk-test-data/mock-responses/vertexai | 0 Package.swift | 4 ++-- 110 files changed, 2 insertions(+), 2 deletions(-) rename {FirebaseVertexAI => FirebaseAI}/CHANGELOG.md (100%) rename {FirebaseVertexAI => FirebaseAI}/README.md (100%) rename {FirebaseVertexAI => FirebaseAI}/Sample/README.md (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Chat.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Constants.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Errors.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/FirebaseInfo.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/FunctionCalling.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenAIURLSession.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenerateContentError.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenerateContentRequest.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenerateContentResponse.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenerationConfig.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenerativeAIRequest.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenerativeAIService.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/GenerativeModel.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/JSONValue.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/ModalityTokenCount.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/ModelContent.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/PartsRepresentable+Image.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/PartsRepresentable.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Protocols/Internal/CodableProtoEnum.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Safety.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/APIConfig.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/DataType.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Errors/BackendError.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/ImageGenerationInstance.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/ImageGenerationOutputOptions.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/ImagenConstants.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/ImagenGCSImage.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/ImagenGenerationRequest.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/ImagenImageRepresentable.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/InternalImagenImage.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Imagen/RAIFilteredReason.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/InternalPart.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/ProtoDate.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Internal/Requests/CountTokensRequest.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenAspectRatio.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenGenerationConfig.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenImageFormat.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenImagesBlockedError.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenInlineImage.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenModel.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenPersonFilterLevel.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenSafetyFilterLevel.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Imagen/ImagenSafetySettings.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Part.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/ResponseModality.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/Types/Public/Schema.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/VertexAI.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Sources/VertexLog.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Resources/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Resources/Assets.xcassets/Contents.json (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Resources/Preview Content/Preview Assets.xcassets/Contents.json (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Resources/TestApp.entitlements (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Sources/Constants.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Sources/ContentView.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Sources/FirebaseAppUtils.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Sources/TestApp.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Sources/TestAppCheckProviderFactory.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Integration/IntegrationTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Integration/SchemaTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Integration/TestHelpers.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Utilities/InstanceConfig.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/Tests/Utilities/IntegrationTestUtils.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/TestApp/VertexAITestApp.xcodeproj/project.pbxproj (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/ChatTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Fakes/AuthInteropFake.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/GenerationConfigTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/GenerativeModelTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/JSONValueTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/MockURLProtocol.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/PartTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/PartsRepresentableTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/README.md (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/RequestOptionsTest.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Resources/animals.mp4 (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Resources/blue.png (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/ChatSnippets.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/CloudStorageSnippets.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/FunctionCallingSnippets.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/MultimodalSnippets.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/README.md (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/StructuredOutputSnippets.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Snippets/TextSnippets.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/TestUtilities/BundleTestUtil.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Internal/APIConfigTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/ProtoDateTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/Types/SchemaTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/VertexAIAPITests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/VertexComponentTests.swift (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi (100%) rename {FirebaseVertexAI => FirebaseAI}/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai (100%) 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/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/VertexAI.swift b/FirebaseAI/Sources/VertexAI.swift similarity index 100% rename from FirebaseVertexAI/Sources/VertexAI.swift rename to FirebaseAI/Sources/VertexAI.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 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift diff --git a/FirebaseVertexAI/Tests/TestApp/Tests/Integration/SchemaTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Integration/SchemaTests.swift rename to FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift 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 100% rename from FirebaseVertexAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift rename to FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift 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/FirebaseVertexAI/Tests/Unit/ChatTests.swift b/FirebaseAI/Tests/Unit/ChatTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/ChatTests.swift rename to FirebaseAI/Tests/Unit/ChatTests.swift 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 100% rename from FirebaseVertexAI/Tests/Unit/GenerationConfigTests.swift rename to FirebaseAI/Tests/Unit/GenerationConfigTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseAI/Tests/Unit/GenerativeModelTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/GenerativeModelTests.swift rename to FirebaseAI/Tests/Unit/GenerativeModelTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/JSONValueTests.swift b/FirebaseAI/Tests/Unit/JSONValueTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/JSONValueTests.swift rename to FirebaseAI/Tests/Unit/JSONValueTests.swift 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 100% rename from FirebaseVertexAI/Tests/Unit/PartTests.swift rename to FirebaseAI/Tests/Unit/PartTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/PartsRepresentableTests.swift rename to FirebaseAI/Tests/Unit/PartsRepresentableTests.swift 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 100% rename from FirebaseVertexAI/Tests/Unit/RequestOptionsTest.swift rename to FirebaseAI/Tests/Unit/RequestOptionsTest.swift diff --git a/FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 b/FirebaseAI/Tests/Unit/Resources/animals.mp4 similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 rename to FirebaseAI/Tests/Unit/Resources/animals.mp4 diff --git a/FirebaseVertexAI/Tests/Unit/Resources/blue.png b/FirebaseAI/Tests/Unit/Resources/blue.png similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Resources/blue.png rename to FirebaseAI/Tests/Unit/Resources/blue.png diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift rename to FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift rename to FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift b/FirebaseAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift rename to FirebaseAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift rename to FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/MultimodalSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/MultimodalSnippets.swift rename to FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/README.md b/FirebaseAI/Tests/Unit/Snippets/README.md similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/README.md rename to FirebaseAI/Tests/Unit/Snippets/README.md diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift rename to FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift rename to FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift diff --git a/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift b/FirebaseAI/Tests/Unit/TestUtilities/BundleTestUtil.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift rename to FirebaseAI/Tests/Unit/TestUtilities/BundleTestUtil.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift rename to FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Internal/APIConfigTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Internal/APIConfigTests.swift rename to FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift rename to FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift b/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift rename to FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/Types/SchemaTests.swift b/FirebaseAI/Tests/Unit/Types/SchemaTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/Types/SchemaTests.swift rename to FirebaseAI/Tests/Unit/Types/SchemaTests.swift diff --git a/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift b/FirebaseAI/Tests/Unit/VertexAIAPITests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift rename to FirebaseAI/Tests/Unit/VertexAIAPITests.swift diff --git a/FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift similarity index 100% rename from FirebaseVertexAI/Tests/Unit/VertexComponentTests.swift rename to FirebaseAI/Tests/Unit/VertexComponentTests.swift 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/Package.swift b/Package.swift index 1328706249e..da26820b079 100644 --- a/Package.swift +++ b/Package.swift @@ -1308,7 +1308,7 @@ let package = Package( "FirebaseCore", "FirebaseCoreExtension", ], - path: "FirebaseVertexAI/Sources" + path: "FirebaseAI/Sources" ), .testTarget( name: "FirebaseVertexAIUnit", @@ -1316,7 +1316,7 @@ let package = Package( "FirebaseVertexAI", "FirebaseStorage", ], - path: "FirebaseVertexAI/Tests/Unit", + path: "FirebaseAI/Tests/Unit", resources: [ .copy("vertexai-sdk-test-data/mock-responses/vertexai"), .process("Resources"), From 3fb720b9897557f22fedd71adc0e8bc21a97e6f1 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Apr 2025 15:56:43 -0400 Subject: [PATCH 02/18] Add `FirebaseAI` SPM target and fix tests --- FirebaseAI/Tests/Unit/ChatTests.swift | 4 ++-- FirebaseAI/Tests/Unit/GenerativeModelTests.swift | 2 +- FirebaseAI/Tests/Unit/PartTests.swift | 2 +- .../Tests/Unit/PartsRepresentableTests.swift | 2 +- FirebaseAI/Tests/Unit/RequestOptionsTest.swift | 2 +- .../Imagen/ImageGenerationInstanceTests.swift | 2 +- .../ImageGenerationOutputOptionsTests.swift | 2 +- .../Imagen/ImageGenerationParametersTests.swift | 2 +- .../Unit/Types/Imagen/ImagenGCSImageTests.swift | 2 +- .../Imagen/ImagenGenerationRequestTests.swift | 2 +- .../Imagen/ImagenGenerationResponseTests.swift | 2 +- .../Types/Imagen/ImagenInlineImageTests.swift | 2 +- .../Types/Imagen/RAIFilteredReasonTests.swift | 2 +- .../Unit/Types/Internal/APIConfigTests.swift | 2 +- .../Requests/CountTokensRequestTests.swift | 2 +- FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift | 2 +- FirebaseAI/Tests/Unit/VertexComponentTests.swift | 2 +- FirebaseVertexAI/Sources/VertexAI.swift | 15 +++++++++++++++ Package.swift | 16 +++++++++++++--- 19 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 FirebaseVertexAI/Sources/VertexAI.swift diff --git a/FirebaseAI/Tests/Unit/ChatTests.swift b/FirebaseAI/Tests/Unit/ChatTests.swift index acb98d1e0e1..800c98ede23 100644 --- a/FirebaseAI/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 { diff --git a/FirebaseAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseAI/Tests/Unit/GenerativeModelTests.swift index 8dd2cf07e42..f04b5fc464f 100644 --- a/FirebaseAI/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 { diff --git a/FirebaseAI/Tests/Unit/PartTests.swift b/FirebaseAI/Tests/Unit/PartTests.swift index aea3c1b5d92..544e229e08b 100644 --- a/FirebaseAI/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/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift index 879013c917e..e7531d1da9e 100644 --- a/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift +++ b/FirebaseAI/Tests/Unit/PartsRepresentableTests.swift @@ -23,7 +23,7 @@ import XCTest import CoreImage #endif // canImport(CoreImage) -@testable import 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/FirebaseAI/Tests/Unit/RequestOptionsTest.swift b/FirebaseAI/Tests/Unit/RequestOptionsTest.swift index 1525393e812..5c03a6b63f4 100644 --- a/FirebaseAI/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/Types/Imagen/ImageGenerationInstanceTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift index d179bc268a1..ce66fe94cb7 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationInstanceTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift index 9a5d93e9dc3..bd5b9f10e44 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationOutputOptionsTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift index 028356c7433..494feda9f7a 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift index badd1df5461..6bf98306cbf 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGCSImageTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift index 27e7f747453..cd5bb8f9c86 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ImagenGenerationRequestTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift index 138862e153b..97122401253 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationResponseTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift index a28bf763b1b..31effc5c0bf 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenInlineImageTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift index 0282eca58ce..90ac676f90a 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/RAIFilteredReasonTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift index 12bbd5cf2ca..8735688e76e 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/APIConfigTests.swift @@ -14,7 +14,7 @@ import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift index 338671287ab..f903f1ef2e7 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift @@ -15,7 +15,7 @@ import Foundation import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class CountTokensRequestTests: XCTestCase { diff --git a/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift b/FirebaseAI/Tests/Unit/Types/ProtoDateTests.swift index 0e6816c43df..dbe6c2e27ca 100644 --- a/FirebaseAI/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/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 3f9f6622ead..bc2582c2cea 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -17,7 +17,7 @@ internal import FirebaseCoreExtension import Foundation import XCTest -@testable import FirebaseVertexAI +@testable import FirebaseAI @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) class VertexComponentTests: XCTestCase { diff --git a/FirebaseVertexAI/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift new file mode 100644 index 00000000000..84553378073 --- /dev/null +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -0,0 +1,15 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@_exported public import FirebaseAI diff --git a/Package.swift b/Package.swift index da26820b079..5e072cb7e10 100644 --- a/Package.swift +++ b/Package.swift @@ -1298,10 +1298,10 @@ let package = Package( ] ), - // MARK: - Firebase Vertex AI + // MARK: - Firebase AI .target( - name: "FirebaseVertexAI", + name: "FirebaseAI", dependencies: [ "FirebaseAppCheckInterop", "FirebaseAuthInterop", @@ -1313,7 +1313,7 @@ let package = Package( .testTarget( name: "FirebaseVertexAIUnit", dependencies: [ - "FirebaseVertexAI", + "FirebaseAI", "FirebaseStorage", ], path: "FirebaseAI/Tests/Unit", @@ -1325,6 +1325,16 @@ let package = Package( .headerSearchPath("../../../"), ] ), + + // MARK: - Firebase Vertex AI + + .target( + name: "FirebaseVertexAI", + dependencies: [ + "FirebaseAI", + ], + path: "FirebaseVertexAI/Sources" + ), ] + firestoreTargets(), cLanguageStandard: .c99, cxxLanguageStandard: CXXLanguageStandard.gnucxx14 From a0d099ec435140a127e6cf52403daa8f82bd42f7 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Apr 2025 16:28:11 -0400 Subject: [PATCH 03/18] Add `FirebaseAI.podspec` with backwards compatibility snippets --- FirebaseAI.podspec | 70 ++++++ FirebaseAI/Tests/Unit/APITests.swift | 213 ++++++++++++++++++ .../Tests/Unit/GenerationConfigTests.swift | 2 +- FirebaseAI/Tests/Unit/JSONValueTests.swift | 2 +- FirebaseAI/Tests/Unit/Types/SchemaTests.swift | 2 +- FirebaseVertexAI.podspec | 9 +- .../Tests/Unit/Snippets/ChatSnippets.swift | 67 ++++++ .../Unit/Snippets/CloudStorageSnippets.swift | 67 ++++++ .../Snippets/FirebaseAppSnippetsUtil.swift | 57 +++++ .../Snippets/FunctionCallingSnippets.swift | 110 +++++++++ .../Unit/Snippets/MultimodalSnippets.swift | 128 +++++++++++ .../Tests/Unit/Snippets/README.md | 10 + .../Snippets/StructuredOutputSnippets.swift | 96 ++++++++ .../Tests/Unit/Snippets/TextSnippets.swift | 55 +++++ .../Tests/Unit/VertexAIAPITests.swift | 0 15 files changed, 877 insertions(+), 11 deletions(-) create mode 100644 FirebaseAI.podspec create mode 100644 FirebaseAI/Tests/Unit/APITests.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/MultimodalSnippets.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/README.md create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift create mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift rename {FirebaseAI => FirebaseVertexAI}/Tests/Unit/VertexAIAPITests.swift (100%) diff --git a/FirebaseAI.podspec b/FirebaseAI.podspec new file mode 100644 index 00000000000..4af1383b904 --- /dev/null +++ b/FirebaseAI.podspec @@ -0,0 +1,70 @@ +Pod::Spec.new do |s| + s.name = 'FirebaseAI' + s.version = '11.12.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.12.0' + s.dependency 'FirebaseCoreExtension', '~> 11.12.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/FirebaseAI/Tests/Unit/APITests.swift b/FirebaseAI/Tests/Unit/APITests.swift new file mode 100644 index 00000000000..354a69ae4e0 --- /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 = VertexAI.vertexAI() + let _ = VertexAI.vertexAI(location: "my-location") + + // Instantiate Vertex AI SDK - Custom App + let _ = VertexAI.vertexAI(app: app!) + let _ = VertexAI.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/FirebaseAI/Tests/Unit/GenerationConfigTests.swift b/FirebaseAI/Tests/Unit/GenerationConfigTests.swift index acd4d61d053..22bcd70b035 100644 --- a/FirebaseAI/Tests/Unit/GenerationConfigTests.swift +++ b/FirebaseAI/Tests/Unit/GenerationConfigTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseVertexAI +import FirebaseAI import Foundation import XCTest diff --git a/FirebaseAI/Tests/Unit/JSONValueTests.swift b/FirebaseAI/Tests/Unit/JSONValueTests.swift index 77b88b686e1..1ffe88eaf55 100644 --- a/FirebaseAI/Tests/Unit/JSONValueTests.swift +++ b/FirebaseAI/Tests/Unit/JSONValueTests.swift @@ -13,7 +13,7 @@ // limitations under the License. import XCTest -@testable import 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/FirebaseAI/Tests/Unit/Types/SchemaTests.swift b/FirebaseAI/Tests/Unit/Types/SchemaTests.swift index d046fe86d6d..e6eb839a39a 100644 --- a/FirebaseAI/Tests/Unit/Types/SchemaTests.swift +++ b/FirebaseAI/Tests/Unit/Types/SchemaTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import FirebaseVertexAI +import FirebaseAI import Foundation import XCTest diff --git a/FirebaseVertexAI.podspec b/FirebaseVertexAI.podspec index dd4d236209a..0ea566391dc 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.12.0' - s.dependency 'FirebaseCoreExtension', '~> 11.12.0' + s.dependency 'FirebaseAI', '~> 11.12.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/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/ChatSnippets.swift new file mode 100644 index 00000000000..2d96b90e2fd --- /dev/null +++ b/FirebaseVertexAI/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 FirebaseCore +import FirebaseVertexAI +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 = VertexAI.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/FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift new file mode 100644 index 00000000000..6627fb870ea --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.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. + +#if SWIFT_PACKAGE // The FirebaseStorage dependency has only been added in Package.swift. + + import FirebaseCore + import FirebaseStorage + import FirebaseVertexAI + + // These CloudStorageSnippets are not currently runnable due to the GCS upload paths but are used + // as compilation tests. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) + final class CloudStorageSnippets { + let model: GenerativeModel! = nil + + func cloudStorageFile_referenceMIMEType() async throws { + // Upload an image file using Cloud Storage for Firebase. + let storageRef = Storage.storage().reference(withPath: "images/image.jpg") + guard let imageURL = Bundle.main.url(forResource: "image", withExtension: "jpg") else { + fatalError("File 'image.jpg' not found in main bundle.") + } + let metadata = try await storageRef.putFileAsync(from: imageURL) + + // Get the MIME type and Cloud Storage for Firebase URL. + guard let mimeType = metadata.contentType else { + fatalError("The MIME type of the uploaded image is nil.") + } + // Construct a URL in the required format. + let storageURL = "gs://\(storageRef.bucket)/\(storageRef.fullPath)" + + let prompt = "What's in this picture?" + // Construct the imagePart with the MIME type and the URL. + let imagePart = FileDataPart(uri: storageURL, mimeType: mimeType) + + // To generate text output, call generateContent with the prompt and the imagePart. + let result = try await model.generateContent(prompt, imagePart) + if let text = result.text { + print(text) + } + } + + func cloudStorageFile_explicitMIMEType() async throws { + let prompt = "What's in this picture?" + // Construct an imagePart that explicitly includes the MIME type and + // Cloud Storage for Firebase URL values. + let imagePart = FileDataPart(uri: "gs://bucket-name/path/image.jpg", mimeType: "image/jpeg") + + // To generate text output, call generateContent with the prompt and imagePart. + let result = try await model.generateContent(prompt, imagePart) + if let text = result.text { + print(text) + } + } + } + +#endif // SWIFT_PACKAGE diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift new file mode 100644 index 00000000000..f463fbda188 --- /dev/null +++ b/FirebaseVertexAI/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/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift new file mode 100644 index 00000000000..492574dc11d --- /dev/null +++ b/FirebaseVertexAI/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 FirebaseCore +import FirebaseVertexAI +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 = VertexAI.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/FirebaseVertexAI/Tests/Unit/Snippets/MultimodalSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/MultimodalSnippets.swift new file mode 100644 index 00000000000..421b9897def --- /dev/null +++ b/FirebaseVertexAI/Tests/Unit/Snippets/MultimodalSnippets.swift @@ -0,0 +1,128 @@ +// 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 FirebaseVertexAI +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 = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-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 + }() + + override func setUpWithError() throws { + try FirebaseApp.configureDefaultAppForSnippets() + } + + override func tearDown() async throws { + await FirebaseApp.deleteDefaultAppForSnippets() + } + + #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) + + 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) + } + } + } +} diff --git a/FirebaseVertexAI/Tests/Unit/Snippets/README.md b/FirebaseVertexAI/Tests/Unit/Snippets/README.md new file mode 100644 index 00000000000..8d03458c456 --- /dev/null +++ b/FirebaseVertexAI/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/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift new file mode 100644 index 00000000000..1ad137188c5 --- /dev/null +++ b/FirebaseVertexAI/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 FirebaseCore +import FirebaseVertexAI +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 = VertexAI.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 = VertexAI.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/FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseVertexAI/Tests/Unit/Snippets/TextSnippets.swift new file mode 100644 index 00000000000..bd7c70fa06b --- /dev/null +++ b/FirebaseVertexAI/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 FirebaseCore +import FirebaseVertexAI +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 = VertexAI.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/VertexAIAPITests.swift b/FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift similarity index 100% rename from FirebaseAI/Tests/Unit/VertexAIAPITests.swift rename to FirebaseVertexAI/Tests/Unit/VertexAIAPITests.swift From f76ea583ec40456801d11e46a70ed9d8159222c0 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Apr 2025 16:29:48 -0400 Subject: [PATCH 04/18] Fix path in `update_vertexai_responses.sh` --- scripts/update_vertexai_responses.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From a337e9571aefef86aba9224b5c24b74cfe147a38 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Apr 2025 18:07:29 -0400 Subject: [PATCH 05/18] Add `BundleTestUtil` --- .../Unit/TestUtilities/BundleTestUtil.swift | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift diff --git a/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift b/FirebaseVertexAI/Tests/Unit/TestUtilities/BundleTestUtil.swift new file mode 100644 index 00000000000..272be41c1e4 --- /dev/null +++ b/FirebaseVertexAI/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() {} +} From cd5c1f94824346e45caf5a697d579400db7c482f Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Apr 2025 18:31:49 -0400 Subject: [PATCH 06/18] Fix SPM build --- FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift | 2 +- FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift | 2 +- FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift | 2 +- FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift | 2 +- FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift | 2 +- FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift index 2d96b90e2fd..92f142b2748 100644 --- a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseCore -import FirebaseVertexAI import XCTest // These snippet tests are intentionally skipped in CI jobs; see the README file in this directory diff --git a/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/CloudStorageSnippets.swift index 6627fb870ea..63b1be6d069 100644 --- a/FirebaseAI/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/FunctionCallingSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift index 492574dc11d..c10ff2ea22b 100644 --- a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseCore -import FirebaseVertexAI import XCTest // These snippet tests are intentionally skipped in CI jobs; see the README file in this directory diff --git a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift index 421b9897def..c2131fd6224 100644 --- a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseCore -import FirebaseVertexAI import XCTest #if canImport(UIKit) diff --git a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift index 1ad137188c5..381b392f877 100644 --- a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseCore -import FirebaseVertexAI import XCTest // These snippet tests are intentionally skipped in CI jobs; see the README file in this directory diff --git a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift index bd7c70fa06b..dba820dc8ec 100644 --- a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import FirebaseCore -import FirebaseVertexAI import XCTest // These snippet tests are intentionally skipped in CI jobs; see the README file in this directory From bddc294fec9fe3bca4d5a943c45b7e54678d3121 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Apr 2025 18:52:45 -0400 Subject: [PATCH 07/18] Add `VertexAI` wrapper for `FirebaseAI` --- .../{VertexAI.swift => FirebaseAI.swift} | 10 +- FirebaseAI/Tests/Unit/APITests.swift | 8 +- FirebaseAI/Tests/Unit/ChatTests.swift | 2 +- .../Tests/Unit/GenerativeModelTests.swift | 2 +- .../Imagen/ImagenGenerationRequestTests.swift | 2 +- .../Requests/CountTokensRequestTests.swift | 2 +- .../Tests/Unit/VertexComponentTests.swift | 44 ++++----- FirebaseVertexAI/Sources/VertexAI.swift | 95 +++++++++++++++++++ 8 files changed, 130 insertions(+), 35 deletions(-) rename FirebaseAI/Sources/{VertexAI.swift => FirebaseAI.swift} (98%) diff --git a/FirebaseAI/Sources/VertexAI.swift b/FirebaseAI/Sources/FirebaseAI.swift similarity index 98% rename from FirebaseAI/Sources/VertexAI.swift rename to FirebaseAI/Sources/FirebaseAI.swift index c3ef7d2df73..1bbd5dde554 100644 --- a/FirebaseAI/Sources/VertexAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -22,7 +22,7 @@ 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 class VertexAI { +public final class FirebaseAI: Sendable { // MARK: - Public APIs /// Creates an instance of `VertexAI`. @@ -36,7 +36,7 @@ public class VertexAI { /// 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") -> VertexAI { + 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. @@ -143,7 +143,7 @@ public class VertexAI { #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] = [:] + 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() @@ -163,7 +163,7 @@ public class VertexAI { version: .v1beta ) - static func vertexAI(app: FirebaseApp?, location: String?, apiConfig: APIConfig) -> VertexAI { + 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.") } @@ -177,7 +177,7 @@ public class VertexAI { if let instance = instances[instanceKey] { return instance } - let newInstance = VertexAI(app: app, location: location, apiConfig: apiConfig) + let newInstance = FirebaseAI(app: app, location: location, apiConfig: apiConfig) instances[instanceKey] = newInstance return newInstance } diff --git a/FirebaseAI/Tests/Unit/APITests.swift b/FirebaseAI/Tests/Unit/APITests.swift index 354a69ae4e0..32b8280623f 100644 --- a/FirebaseAI/Tests/Unit/APITests.swift +++ b/FirebaseAI/Tests/Unit/APITests.swift @@ -42,12 +42,12 @@ final class APITests: XCTestCase { let _ = RequestOptions(timeout: 30.0) // Instantiate Vertex AI SDK - Default App - let vertexAI = VertexAI.vertexAI() - let _ = VertexAI.vertexAI(location: "my-location") + let vertexAI = FirebaseAI.vertexAI() + let _ = FirebaseAI.vertexAI(location: "my-location") // Instantiate Vertex AI SDK - Custom App - let _ = VertexAI.vertexAI(app: app!) - let _ = VertexAI.vertexAI(app: app!, location: "my-location") + let _ = FirebaseAI.vertexAI(app: app!) + let _ = FirebaseAI.vertexAI(app: app!, location: "my-location") // Permutations without optional arguments. diff --git a/FirebaseAI/Tests/Unit/ChatTests.swift b/FirebaseAI/Tests/Unit/ChatTests.swift index 800c98ede23..72dd14fa448 100644 --- a/FirebaseAI/Tests/Unit/ChatTests.swift +++ b/FirebaseAI/Tests/Unit/ChatTests.swift @@ -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/FirebaseAI/Tests/Unit/GenerativeModelTests.swift b/FirebaseAI/Tests/Unit/GenerativeModelTests.swift index f04b5fc464f..89bd8a13699 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelTests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelTests.swift @@ -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/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift index cd5bb8f9c86..eb8b3df83ca 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift @@ -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/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift index f903f1ef2e7..5ca31474f1b 100644 --- a/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Internal/Requests/CountTokensRequestTests.swift @@ -23,7 +23,7 @@ final class CountTokensRequestTests: XCTestCase { let modelResourceName = "models/test-model-name" let textPart = TextPart("test-prompt") - let vertexAPIConfig = VertexAI.defaultVertexAIAPIConfig + let vertexAPIConfig = FirebaseAI.defaultVertexAIAPIConfig let developerAPIConfig = APIConfig( service: .developer(endpoint: .firebaseVertexAIProd), version: .v1beta diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index bc2582c2cea..f5e80c2a7e0 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -52,7 +52,7 @@ class VertexComponentTests: XCTestCase { /// Tests that a vertex instance can be created properly using the default Firebase app. func testVertexInstanceCreation_defaultApp() throws { - let vertex = 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/Sources/VertexAI.swift b/FirebaseVertexAI/Sources/VertexAI.swift index 84553378073..ba4ebc37df1 100644 --- a/FirebaseVertexAI/Sources/VertexAI.swift +++ b/FirebaseVertexAI/Sources/VertexAI.swift @@ -13,3 +13,98 @@ // limitations under the License. @_exported public import FirebaseAI + +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, *) +public class VertexAI { + // 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") -> VertexAI { + let firebaseAI = FirebaseAI.vertexAI(app: app, location: location) + return VertexAI(firebaseAI: firebaseAI) + } + + /// 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 { + return firebaseAI.generativeModel( + modelName: modelName, + 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 { + return firebaseAI.imagenModel( + modelName: modelName, + generationConfig: generationConfig, + safetySettings: safetySettings, + requestOptions: requestOptions + ) + } + + // MARK: - Internal APIs + + let firebaseAI: FirebaseAI + + init(firebaseAI: FirebaseAI) { + self.firebaseAI = firebaseAI + } +} From 3b59d038dfa032ad0b1dddd7a1c3d9512c695355 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 17 Apr 2025 18:58:11 -0400 Subject: [PATCH 08/18] Fix CI job --- .github/workflows/vertexai.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml index 2b08ca897df..6d2cec60244 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/vertexai.yml @@ -3,7 +3,7 @@ name: vertexai on: pull_request: paths: - - 'FirebaseVertexAI**' + - 'FirebaseAI**' - '.github/workflows/vertexai.yml' - 'Gemfile*' schedule: @@ -114,13 +114,13 @@ jobs: 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" + 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 \ - FirebaseVertexAI/Tests/TestApp/Resources/GoogleService-Info-Spark.plist "$secrets_passphrase" + 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 \ - FirebaseVertexAI/Tests/TestApp/Tests/Integration/Credentials.swift "$secrets_passphrase" + 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 @@ -155,6 +155,6 @@ jobs: - 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}}'#" FirebaseVertexAI.podspec + 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 FirebaseVertexAI.podspec --platforms=${{ matrix.target }} ${{ matrix.warnings }} + run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAI.podspec --platforms=${{ matrix.target }} ${{ matrix.warnings }} From 90859677259b7037663c2a322f4a8cb3022ac94c Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 15:25:34 -0400 Subject: [PATCH 09/18] Fix imports and update versions --- FirebaseAI.podspec | 2 +- .../Tests/Unit/Snippets/ChatSnippets.swift | 2 +- .../Snippets/FunctionCallingSnippets.swift | 2 +- .../Unit/Snippets/MultimodalSnippets.swift | 89 ++++++++++++++++++- .../Snippets/StructuredOutputSnippets.swift | 4 +- .../Tests/Unit/Snippets/TextSnippets.swift | 2 +- .../Unit/Types/ModalityTokenCountTests.swift | 2 +- FirebaseVertexAI.podspec | 2 +- 8 files changed, 96 insertions(+), 9 deletions(-) diff --git a/FirebaseAI.podspec b/FirebaseAI.podspec index 4af1383b904..987c22cf98c 100644 --- a/FirebaseAI.podspec +++ b/FirebaseAI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAI' - s.version = '11.12.0' + s.version = '11.13.0' s.summary = 'Firebase AI SDK' s.description = <<-DESC diff --git a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift index 92f142b2748..15dc6125fe0 100644 --- a/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/ChatSnippets.swift @@ -21,7 +21,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class ChatSnippets: XCTestCase { - lazy var model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") + lazy var model = FirebaseAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") override func setUpWithError() throws { try FirebaseApp.configureDefaultAppForSnippets() diff --git a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift index c10ff2ea22b..5f45eb8b7fd 100644 --- a/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift @@ -67,7 +67,7 @@ final class FunctionCallingSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports function calling, like a Gemini 1.5 model. - let model = VertexAI.vertexAI().generativeModel( + let model = FirebaseAI.vertexAI().generativeModel( modelName: "gemini-1.5-flash", // Provide the function declaration to the model. tools: [.functionDeclarations([fetchWeatherTool])] diff --git a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift index c2131fd6224..60488561c87 100644 --- a/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/MultimodalSnippets.swift @@ -26,7 +26,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class MultimodalSnippets: XCTestCase { let bundle = BundleTestUtil.bundle() - lazy var model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") + 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.") @@ -34,6 +34,20 @@ final class MultimodalSnippets: XCTestCase { 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() } @@ -42,6 +56,8 @@ final class MultimodalSnippets: XCTestCase { await FirebaseApp.deleteDefaultAppForSnippets() } + // MARK: - Image Input + #if canImport(UIKit) func testMultimodalOneImageNonStreaming() async throws { guard let image = UIImage(systemName: "bicycle") else { fatalError() } @@ -98,6 +114,8 @@ final class MultimodalSnippets: XCTestCase { } #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") @@ -125,4 +143,73 @@ final class MultimodalSnippets: XCTestCase { } } } + + // 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/StructuredOutputSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift index 381b392f877..ee2c321da8b 100644 --- a/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/StructuredOutputSnippets.swift @@ -50,7 +50,7 @@ final class StructuredOutputSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. - let model = VertexAI.vertexAI().generativeModel( + 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`. @@ -73,7 +73,7 @@ final class StructuredOutputSnippets: XCTestCase { // Initialize the Vertex AI service and the generative model. // Use a model that supports `responseSchema`, like one of the Gemini 1.5 models. - let model = VertexAI.vertexAI().generativeModel( + 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`. diff --git a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift index dba820dc8ec..075993b5bfe 100644 --- a/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift +++ b/FirebaseAI/Tests/Unit/Snippets/TextSnippets.swift @@ -21,7 +21,7 @@ import XCTest @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) final class TextSnippets: XCTestCase { - lazy var model = VertexAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") + lazy var model = FirebaseAI.vertexAI().generativeModel(modelName: "gemini-1.5-flash") override func setUpWithError() throws { try FirebaseApp.configureDefaultAppForSnippets() diff --git a/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift b/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift index ffdb36938fb..cd56a0c67d1 100644 --- a/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift +++ b/FirebaseAI/Tests/Unit/Types/ModalityTokenCountTests.swift @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 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.podspec b/FirebaseVertexAI.podspec index 7006f071ee1..4305c116ad5 100644 --- a/FirebaseVertexAI.podspec +++ b/FirebaseVertexAI.podspec @@ -44,7 +44,7 @@ Firebase SDK. s.tvos.framework = 'UIKit' s.watchos.framework = 'WatchKit' - s.dependency 'FirebaseAI', '~> 11.12.0' + s.dependency 'FirebaseAI', '~> 11.13.0' s.test_spec 'unit' do |unit_tests| unit_tests_dir = 'FirebaseVertexAI/Tests/Unit/' From 619e149eae7a28388f404cbb29f418300263ffbc Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 15:29:36 -0400 Subject: [PATCH 10/18] Fix FirebaseAI.podspec dep versions --- FirebaseAI.podspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FirebaseAI.podspec b/FirebaseAI.podspec index 987c22cf98c..0df48cf0a70 100644 --- a/FirebaseAI.podspec +++ b/FirebaseAI.podspec @@ -45,8 +45,8 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK s.dependency 'FirebaseAppCheckInterop', '~> 11.4' s.dependency 'FirebaseAuthInterop', '~> 11.4' - s.dependency 'FirebaseCore', '~> 11.12.0' - s.dependency 'FirebaseCoreExtension', '~> 11.12.0' + 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/' From a932c6c4e64d32bb7dbea8e157c095e08041d143 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 15:37:39 -0400 Subject: [PATCH 11/18] Rename `vertexai.yml` to `firebaseai.yml` --- .github/workflows/{vertexai.yml => firebaseai.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{vertexai.yml => firebaseai.yml} (98%) diff --git a/.github/workflows/vertexai.yml b/.github/workflows/firebaseai.yml similarity index 98% rename from .github/workflows/vertexai.yml rename to .github/workflows/firebaseai.yml index 6d2cec60244..e39ab132732 100644 --- a/.github/workflows/vertexai.yml +++ b/.github/workflows/firebaseai.yml @@ -1,10 +1,10 @@ -name: vertexai +name: firebaseai on: pull_request: paths: - 'FirebaseAI**' - - '.github/workflows/vertexai.yml' + - '.github/workflows/firebaseai.yml' - 'Gemfile*' schedule: # Run every day at 11pm (PST) - cron uses UTC times From 72155342e49d80cc12a0d36b00b0d24c31abc351 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 15:41:23 -0400 Subject: [PATCH 12/18] Re-add `vertexai.yml` for backwards-compatibility testing --- .github/workflows/vertexai.yml | 122 +++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 .github/workflows/vertexai.yml diff --git a/.github/workflows/vertexai.yml b/.github/workflows/vertexai.yml new file mode 100644 index 00000000000..32d25b6a2fd --- /dev/null +++ b/.github/workflows/vertexai.yml @@ -0,0 +1,122 @@ +name: vertexai + +on: + pull_request: + paths: + - 'FirebaseAI**' + - 'FirebaseVertexAI**' + - '.github/workflows/vertexai.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 + +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: 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 FirebaseVertexAIUnit ${{ matrix.target }} spm + + 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 + - 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}}'#" FirebaseVertexAI.podspec + - name: Build and test + run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseVertexAI.podspec --platforms=${{ matrix.target }} ${{ matrix.warnings }} From a5a3e4fa2c866a11bbd3709184c7c3dc3a284327 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 16:50:40 -0400 Subject: [PATCH 13/18] Fix FirebaseVertexAI test resources and add Firebase AI SPM scheme --- .github/workflows/firebaseai.yml | 4 +- .../Tests/Unit/Resources/animals.mp4 | Bin 0 -> 10881 bytes .../Tests/Unit/Resources/blue.png | Bin 0 -> 69 bytes .../Tests/Unit/Resources/gemini-report.pdf | Bin 0 -> 115898 bytes .../Tests/Unit/Resources/hello-world.mp3 | Bin 0 -> 7344 bytes .../Unit/Snippets/CloudStorageSnippets.swift | 67 --------------- Package.swift | 12 ++- scripts/build.sh | 4 +- .../spm_test_schemes/FirebaseAIUnit.xcscheme | 77 ++++++++++++++++++ 9 files changed, 92 insertions(+), 72 deletions(-) create mode 100644 FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 create mode 100644 FirebaseVertexAI/Tests/Unit/Resources/blue.png create mode 100644 FirebaseVertexAI/Tests/Unit/Resources/gemini-report.pdf create mode 100644 FirebaseVertexAI/Tests/Unit/Resources/hello-world.mp3 delete mode 100644 FirebaseVertexAI/Tests/Unit/Snippets/CloudStorageSnippets.swift create mode 100644 scripts/spm_test_schemes/FirebaseAIUnit.xcscheme diff --git a/.github/workflows/firebaseai.yml b/.github/workflows/firebaseai.yml index e39ab132732..ae7ff37a4ca 100644 --- a/.github/workflows/firebaseai.yml +++ b/.github/workflows/firebaseai.yml @@ -89,7 +89,7 @@ jobs: max_attempts: 3 retry_on: error retry_wait_seconds: 120 - command: scripts/build.sh FirebaseVertexAIUnit ${{ matrix.target }} spm + command: scripts/build.sh FirebaseAIUnit ${{ matrix.target }} spm testapp-integration: strategy: @@ -124,7 +124,7 @@ jobs: - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Run IntegrationTests - run: scripts/build.sh VertexIntegration ${{ matrix.target }} + run: scripts/build.sh FirebaseAIIntegration ${{ matrix.target }} pod-lib-lint: # Don't run on private repo unless it is a PR. diff --git a/FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 b/FirebaseVertexAI/Tests/Unit/Resources/animals.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8abcffb1ebaadd92d037f3770d65330c547c43c4 GIT binary patch literal 10881 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 z
    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

    sJBc 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 literal 0 HcmV?d00001 diff --git a/FirebaseVertexAI/Tests/Unit/Resources/hello-world.mp3 b/FirebaseVertexAI/Tests/Unit/Resources/hello-world.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..be617e65a5a34d1ae2384cf15f3574864a1316c3 GIT binary patch literal 7344 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d2f96db428598dce35ab50460ab996597f8fdb5d Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 16:53:36 -0400 Subject: [PATCH 14/18] Remove trailing comma --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 9e78eea50d7..fc30b27be75 100644 --- a/Package.swift +++ b/Package.swift @@ -1343,7 +1343,7 @@ let package = Package( path: "FirebaseVertexAI/Tests/Unit", resources: [ .process("Resources"), - ], + ] ), ] + firestoreTargets(), cLanguageStandard: .c99, From 95d90cacdbfcd13d83ee929c4efb7357d05c902a Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 17:23:15 -0400 Subject: [PATCH 15/18] Update integration test imports --- .../Integration/CountTokensIntegrationTests.swift | 12 ++++++------ .../GenerateContentIntegrationTests.swift | 14 +++++++------- .../Tests/Integration/ImagenIntegrationTests.swift | 8 ++++---- .../Tests/Integration/IntegrationTests.swift | 8 ++++---- .../TestApp/Tests/Integration/SchemaTests.swift | 12 ++++++------ .../TestApp/Tests/Utilities/InstanceConfig.swift | 12 ++++++------ 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift index a0abd8cc00d..0f43a9bbb66 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/CountTokensIntegrationTests.swift @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import 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/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index ca0d5bd7100..1cff9d92b59 100644 --- a/FirebaseAI/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/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/ImagenIntegrationTests.swift index 05317411704..1e503a7623f 100644 --- a/FirebaseAI/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/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/IntegrationTests.swift index af2c7c97d98..da34e042dc8 100644 --- a/FirebaseAI/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/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/SchemaTests.swift index 7758979050f..a4448540f85 100644 --- a/FirebaseAI/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/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index 3db2ac60371..e60cfd8cb30 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +import FirebaseAI import 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 From d7ed97a93a84e0435f22f32c7bbed4887733f8e4 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 17:46:44 -0400 Subject: [PATCH 16/18] Set `-DCMAKE_POLICY_VERSION_MINIMUM=3.5` for Firestore --- scripts/build.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/build.sh b/scripts/build.sh index 4738811f365..a0163b1984a 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -261,6 +261,7 @@ xcb_flags=("${xcb_flags[@]}" "${buildcache_xcb_flags[@]}") cmake_options=( -Wdeprecated -DCMAKE_BUILD_TYPE=Debug + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ) if [[ -n "${SANITIZERS:-}" ]]; then From 8a884f3b337ab8817bf034b719cfaa7a599eccb6 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 22 Apr 2025 18:29:26 -0400 Subject: [PATCH 17/18] Revert "Set `-DCMAKE_POLICY_VERSION_MINIMUM=3.5` for Firestore" This reverts commit d7ed97a93a84e0435f22f32c7bbed4887733f8e4. --- scripts/build.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/build.sh b/scripts/build.sh index a0163b1984a..4738811f365 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -261,7 +261,6 @@ xcb_flags=("${xcb_flags[@]}" "${buildcache_xcb_flags[@]}") cmake_options=( -Wdeprecated -DCMAKE_BUILD_TYPE=Debug - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ) if [[ -n "${SANITIZERS:-}" ]]; then From 876c9cb055acad648dd685edde6eb622b8fc8486 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 23 Apr 2025 10:09:37 -0400 Subject: [PATCH 18/18] Add `permissions:` for `firebaseai.yml` --- .github/workflows/firebaseai.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/firebaseai.yml b/.github/workflows/firebaseai.yml index ae7ff37a4ca..cdc5429137c 100644 --- a/.github/workflows/firebaseai.yml +++ b/.github/workflows/firebaseai.yml @@ -15,6 +15,10 @@ 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