Skip to content

Commit

Permalink
[Vertex AI] Add code snippets for use in docs (#13653)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewheard authored Oct 16, 2024
1 parent 78a181d commit 1f1f308
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
46 changes: 46 additions & 0 deletions FirebaseVertexAI/Tests/Unit/Snippets/FirebaseAppSnippetsUtil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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 {
static let projectIDEnvVar = "PROJECT_ID"
static let appIDEnvVar = "APP_ID"
static let apiKeyEnvVar = "API_KEY"

static func configureForSnippets() throws {
let environment = ProcessInfo.processInfo.environment
guard let projectID = environment[projectIDEnvVar] else {
throw XCTSkip("No Firebase Project ID specified in environment variable \(projectIDEnvVar).")
}
guard let appID = environment[appIDEnvVar] else {
throw XCTSkip("No Google App ID specified in environment variable \(appIDEnvVar).")
}
guard let apiKey = environment[apiKeyEnvVar] else {
throw XCTSkip("No API key specified in environment variable \(apiKeyEnvVar).")
}

let options = FirebaseOptions(googleAppID: appID, gcmSenderID: "")
options.projectID = projectID
options.apiKey = apiKey

FirebaseApp.configure(options: options)
guard FirebaseApp.isDefaultAppConfigured() else {
XCTFail("Default Firebase app not configured.")
return
}
}
}
109 changes: 109 additions & 0 deletions FirebaseVertexAI/Tests/Unit/Snippets/FunctionCallingSnippets.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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

@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.configureForSnippets()
}

override func tearDown() async throws {
if let app = FirebaseApp.app() {
await app.delete()
}
}

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.")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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

@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.configureForSnippets()
}

override func tearDown() async throws {
if let app = FirebaseApp.app() {
await app.delete()
}
}

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.")
}
}

0 comments on commit 1f1f308

Please sign in to comment.