|
|
@@ -0,0 +1,186 @@
|
|
|
+// Copyright 2023 Google LLC
|
|
|
+//
|
|
|
+// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
+// you may not use this file except in compliance with the License.
|
|
|
+// You may obtain a copy of the License at
|
|
|
+//
|
|
|
+// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+//
|
|
|
+// Unless required by applicable law or agreed to in writing, software
|
|
|
+// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+// See the License for the specific language governing permissions and
|
|
|
+// limitations under the License.
|
|
|
+
|
|
|
+import FirebaseAI
|
|
|
+import FirebaseCore
|
|
|
+import XCTest
|
|
|
+#if canImport(AppKit)
|
|
|
+ import AppKit // For NSImage extensions.
|
|
|
+#elseif canImport(UIKit)
|
|
|
+ import UIKit // For UIImage extensions.
|
|
|
+#endif
|
|
|
+
|
|
|
+@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
|
|
|
+final class APITests: XCTestCase {
|
|
|
+ func codeSamples() async throws {
|
|
|
+ let app = FirebaseApp.app()
|
|
|
+ let config = GenerationConfig(temperature: 0.2,
|
|
|
+ topP: 0.1,
|
|
|
+ topK: 16,
|
|
|
+ candidateCount: 4,
|
|
|
+ maxOutputTokens: 256,
|
|
|
+ stopSequences: ["..."],
|
|
|
+ responseMIMEType: "text/plain")
|
|
|
+ let filters = [SafetySetting(harmCategory: .dangerousContent, threshold: .blockOnlyHigh)]
|
|
|
+ let systemInstruction = ModelContent(
|
|
|
+ role: "system",
|
|
|
+ parts: TextPart("Talk like a pirate.")
|
|
|
+ )
|
|
|
+
|
|
|
+ let requestOptions = RequestOptions()
|
|
|
+ let _ = RequestOptions(timeout: 30.0)
|
|
|
+
|
|
|
+ // Instantiate Firebase AI SDK - Default App
|
|
|
+ let firebaseAI = FirebaseAI.firebaseAI()
|
|
|
+ let _ = FirebaseAI.firebaseAI(backend: .googleAI())
|
|
|
+ let _ = FirebaseAI.firebaseAI(backend: .vertexAI())
|
|
|
+ let _ = FirebaseAI.firebaseAI(backend: .vertexAI(location: "my-location"))
|
|
|
+
|
|
|
+ // Instantiate Firebase AI SDK - Custom App
|
|
|
+ let _ = FirebaseAI.firebaseAI(app: app!)
|
|
|
+ let _ = FirebaseAI.firebaseAI(app: app!, backend: .googleAI())
|
|
|
+ let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI())
|
|
|
+ let _ = FirebaseAI.firebaseAI(app: app!, backend: .vertexAI(location: "my-location"))
|
|
|
+
|
|
|
+ // Permutations without optional arguments.
|
|
|
+
|
|
|
+ let _ = firebaseAI.generativeModel(modelName: "gemini-2.0-flash")
|
|
|
+
|
|
|
+ let _ = firebaseAI.generativeModel(
|
|
|
+ modelName: "gemini-2.0-flash",
|
|
|
+ safetySettings: filters
|
|
|
+ )
|
|
|
+
|
|
|
+ let _ = firebaseAI.generativeModel(
|
|
|
+ modelName: "gemini-2.0-flash",
|
|
|
+ generationConfig: config
|
|
|
+ )
|
|
|
+
|
|
|
+ let _ = firebaseAI.generativeModel(
|
|
|
+ modelName: "gemini-2.0-flash",
|
|
|
+ systemInstruction: systemInstruction
|
|
|
+ )
|
|
|
+
|
|
|
+ // All arguments passed.
|
|
|
+ let model = firebaseAI.generativeModel(
|
|
|
+ modelName: "gemini-2.0-flash",
|
|
|
+ generationConfig: config, // Optional
|
|
|
+ safetySettings: filters, // Optional
|
|
|
+ systemInstruction: systemInstruction, // Optional
|
|
|
+ requestOptions: requestOptions // Optional
|
|
|
+ )
|
|
|
+
|
|
|
+ // Full Typed Usage
|
|
|
+ let pngData = Data() // ....
|
|
|
+ let contents = [ModelContent(
|
|
|
+ role: "user",
|
|
|
+ parts: [
|
|
|
+ TextPart("Is it a cat?"),
|
|
|
+ InlineDataPart(data: pngData, mimeType: "image/png"),
|
|
|
+ ]
|
|
|
+ )]
|
|
|
+
|
|
|
+ do {
|
|
|
+ let response = try await model.generateContent(contents)
|
|
|
+ print(response.text ?? "Couldn't get text... check status")
|
|
|
+ } catch {
|
|
|
+ print("Error generating content: \(error)")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Content input combinations.
|
|
|
+ let _ = try await model.generateContent("Constant String")
|
|
|
+ let str = "String Variable"
|
|
|
+ let _ = try await model.generateContent(str)
|
|
|
+ let _ = try await model.generateContent([str])
|
|
|
+ let _ = try await model.generateContent(str, "abc", "def")
|
|
|
+ let _ = try await model.generateContent(
|
|
|
+ str,
|
|
|
+ FileDataPart(uri: "gs://test-bucket/image.jpg", mimeType: "image/jpeg")
|
|
|
+ )
|
|
|
+ #if canImport(UIKit)
|
|
|
+ _ = try await model.generateContent(UIImage())
|
|
|
+ _ = try await model.generateContent([UIImage()])
|
|
|
+ _ = try await model.generateContent([str, UIImage(), TextPart(str)])
|
|
|
+ _ = try await model.generateContent(str, UIImage(), "def", UIImage())
|
|
|
+ _ = try await model.generateContent([str, UIImage(), "def", UIImage()])
|
|
|
+ _ = try await model.generateContent([ModelContent(parts: "def", UIImage()),
|
|
|
+ ModelContent(parts: "def", UIImage())])
|
|
|
+ #elseif canImport(AppKit)
|
|
|
+ _ = try await model.generateContent(NSImage())
|
|
|
+ _ = try await model.generateContent([NSImage()])
|
|
|
+ _ = try await model.generateContent(str, NSImage(), "def", NSImage())
|
|
|
+ _ = try await model.generateContent([str, NSImage(), "def", NSImage()])
|
|
|
+ #endif
|
|
|
+
|
|
|
+ // PartsRepresentable combinations.
|
|
|
+ let _ = ModelContent(parts: [TextPart(str)])
|
|
|
+ let _ = ModelContent(role: "model", parts: [TextPart(str)])
|
|
|
+ let _ = ModelContent(parts: "Constant String")
|
|
|
+ let _ = ModelContent(parts: str)
|
|
|
+ let _ = ModelContent(parts: [str])
|
|
|
+ let _ = ModelContent(parts: [str, InlineDataPart(data: Data(), mimeType: "foo")])
|
|
|
+ #if canImport(UIKit)
|
|
|
+ _ = ModelContent(role: "user", parts: UIImage())
|
|
|
+ _ = ModelContent(role: "user", parts: [UIImage()])
|
|
|
+ _ = ModelContent(parts: [str, UIImage()])
|
|
|
+ // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile
|
|
|
+ // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`.
|
|
|
+ let representable2: [any PartsRepresentable] = [str, UIImage()]
|
|
|
+ _ = ModelContent(parts: representable2)
|
|
|
+ _ = ModelContent(parts: [str, UIImage(), TextPart(str)])
|
|
|
+ #elseif canImport(AppKit)
|
|
|
+ _ = ModelContent(role: "user", parts: NSImage())
|
|
|
+ _ = ModelContent(role: "user", parts: [NSImage()])
|
|
|
+ _ = ModelContent(parts: [str, NSImage()])
|
|
|
+ // Note: without explicitly specifying`: [any PartsRepresentable]` this will fail to compile
|
|
|
+ // below with "Cannot convert value of type `[Any]` to expected type `[any Part]`.
|
|
|
+ let representable2: [any PartsRepresentable] = [str, NSImage()]
|
|
|
+ _ = ModelContent(parts: representable2)
|
|
|
+ _ = ModelContent(parts: [str, NSImage(), TextPart(str)])
|
|
|
+ #endif
|
|
|
+
|
|
|
+ // countTokens API
|
|
|
+ let _: CountTokensResponse = try await model.countTokens("What color is the Sky?")
|
|
|
+ #if canImport(UIKit)
|
|
|
+ let _: CountTokensResponse = try await model.countTokens("What color is the Sky?",
|
|
|
+ UIImage())
|
|
|
+ let _: CountTokensResponse = try await model.countTokens([
|
|
|
+ ModelContent(parts: "What color is the Sky?", UIImage()),
|
|
|
+ ModelContent(parts: UIImage(), "What color is the Sky?", UIImage()),
|
|
|
+ ])
|
|
|
+ #endif
|
|
|
+
|
|
|
+ // Chat
|
|
|
+ _ = model.startChat()
|
|
|
+ _ = model.startChat(history: [ModelContent(parts: "abc")])
|
|
|
+ }
|
|
|
+
|
|
|
+ // Public API tests for GenerateContentResponse.
|
|
|
+ func generateContentResponseAPI() {
|
|
|
+ let response = GenerateContentResponse(candidates: [])
|
|
|
+
|
|
|
+ let _: [Candidate] = response.candidates
|
|
|
+ let _: PromptFeedback? = response.promptFeedback
|
|
|
+
|
|
|
+ // Usage Metadata
|
|
|
+ guard let usageMetadata = response.usageMetadata else { fatalError() }
|
|
|
+ let _: Int = usageMetadata.promptTokenCount
|
|
|
+ let _: Int = usageMetadata.candidatesTokenCount
|
|
|
+ let _: Int = usageMetadata.totalTokenCount
|
|
|
+
|
|
|
+ // Computed Properties
|
|
|
+ let _: String? = response.text
|
|
|
+ let _: [FunctionCallPart] = response.functionCalls
|
|
|
+ }
|
|
|
+}
|