Files
firebase-ios-sdk/FirebaseAI/Sources/TemplateGenerativeModel.swift

209 lines
8.9 KiB
Swift

// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
/// **[Public Preview]** A multimodal model (like Gemini) that generates content using
/// server-managed prompt templates.
///
/// > Warning: This API is a public preview and may be subject to change.
///
/// When using server prompt templates, prompts, system instructions, tools, and model
/// configurations are defined and stored server-side in the Firebase console or via the Firebase AI
/// Logic REST API. Client applications only pass a template identifier and any runtime input
/// variables required by the template.
///
/// Benefits of this approach include:
/// - Protecting prompts and system instructions from exposure in client code.
/// - Updating prompts, schemas, and configurations without releasing a new app version.
///
/// > Tip: We recommend versioning your template IDs (for example, `my-template-v1-0-0`) and using
/// [Firebase Remote Config](https://firebase.google.com/docs/ai-logic/server-prompt-templates/versioning-with-remote-config)
/// to dynamically manage template IDs in client requests.
///
/// For more details, see the
/// [Server Prompt Templates
/// guide](https://firebase.google.com/docs/ai-logic/server-prompt-templates/get-started).
public final class TemplateGenerativeModel: Sendable {
let generativeAIService: GenerativeAIService
let apiConfig: APIConfig
/// A list of tools the model may use to generate the next response.
let tools: [TemplateTool]?
/// Tool configuration for any ``TemplateTool`` specified in the request.
let toolConfig: TemplateToolConfig?
/// Configuration parameters for sending requests to the backend.
let requestOptions: RequestOptions
init(firebaseInfo: FirebaseInfo, apiConfig: APIConfig, tools: [TemplateTool]?,
toolConfig: TemplateToolConfig?, requestOptions: RequestOptions,
urlSession: URLSession = GenAIURLSession.default) {
generativeAIService = GenerativeAIService(
firebaseInfo: firebaseInfo,
urlSession: urlSession
)
self.apiConfig = apiConfig
self.tools = tools
self.toolConfig = toolConfig
self.requestOptions = requestOptions
}
/// **[Public Preview]** Generates content from a prompt template and inputs.
///
/// > Warning: This API is a public preview and may be subject to change.
///
/// - Parameters:
/// - templateID: The ID of the server prompt template to use (for example,
/// `"my-template-v1-0-0"`).
/// - inputs: A dictionary of variable names and values to substitute into the template's
/// Handlebars placeholders (such as `{{variable}}`) or `input.schema`. Supported value types
/// include `String`, `Int`, `Double`, `Float`, `Bool`, arrays, and nested dictionaries;
/// defaults to no inputs.
/// - Returns: The content generated by the model.
/// - Throws: A ``GenerateContentError`` if the request failed, or an `EncodingError` if `inputs`
/// cannot be encoded.
public func generateContent(templateID: String,
inputs: [String: Any] = [:]) async throws
-> GenerateContentResponse {
return try await generateContentWithHistory(
history: [],
template: templateID,
inputs: TemplateInput.inputs(from: inputs)
)
}
/// **[Public Preview]** Generates content from a prompt template and inputs, with streaming
/// responses.
///
/// > Warning: This API is a public preview and may be subject to change.
///
/// - Parameters:
/// - templateID: The ID of the server prompt template to use (for example,
/// `"my-template-v1-0-0"`).
/// - inputs: A dictionary of variable names and values to substitute into the template's
/// Handlebars placeholders (such as `{{variable}}`) or `input.schema`. Supported value types
/// include `String`, `Int`, `Double`, `Float`, `Bool`, arrays, and nested dictionaries;
/// defaults to no inputs.
/// - Returns: An `AsyncThrowingStream` that yields `GenerateContentResponse` objects.
/// - Throws: A ``GenerateContentError`` if the request failed, or an `EncodingError` if `inputs`
/// cannot be encoded.
@available(macOS 12.0, watchOS 8.0, *)
public func generateContentStream(templateID: String, inputs: [String: Any] = [:]) throws
-> AsyncThrowingStream<GenerateContentResponse, Error> {
return try generateContentStreamWithHistory(
history: [],
template: templateID,
inputs: TemplateInput.inputs(from: inputs)
)
}
/// **[Public Preview]** Creates a new chat conversation using this model with the provided
/// template, inputs, and history.
///
/// > Warning: This API is a public preview and may be subject to change.
///
/// The template's model, system instructions, and configurations apply to every turn
/// automatically, while the returned ``TemplateChat`` tracks conversation history.
///
/// > Important: The referenced server prompt template must include the `{{history}}` tag in its
/// content, which indicates where the conversation turns managed by the client SDK should be
/// injected.
///
/// For more details, see
/// [Multi-turn interactions with server prompt templates](https://firebase.google.com/docs/ai-logic/server-prompt-templates/multi-turn-interactions).
///
/// - Parameters:
/// - templateID: The ID of the server prompt template to use.
/// - inputs: A dictionary of variable names and values to substitute into the template; the
/// same inputs are sent with every turn of the conversation. Defaults to no inputs.
/// - history: The previous conversation history to seed the chat session with.
/// - Returns: A new ``TemplateChat`` instance.
public func startChat(templateID: String,
inputs: [String: Any] = [:],
history: [ModelContent] = []) -> TemplateChat {
return TemplateChat(
model: self,
templateID: templateID,
inputs: inputs,
history: history
)
}
// MARK: - Internal
func templateGenerateContentRequest(template: String, inputs: [String: TemplateInput],
history: [ModelContent],
stream: Bool) throws -> TemplateGenerateContentRequest {
try history.throwIfError()
// Normalize an empty tools array to `nil` so that an empty `"tools"` array is not sent.
let internalTools = try tools?.map { try $0.toInternal() }
return TemplateGenerateContentRequest(
template: template,
inputs: inputs,
history: history,
projectID: generativeAIService.firebaseInfo.projectID,
stream: stream,
apiConfig: apiConfig,
options: requestOptions,
tools: (internalTools?.isEmpty ?? true) ? nil : internalTools,
toolConfig: toolConfig
)
}
/// Generates content from a prompt template, inputs, and history.
///
/// - Parameters:
/// - history: The conversation history to use.
/// - template: The prompt template to use.
/// - inputs: The variables to substitute into the template.
/// - Returns: The content generated by the model.
/// - Throws: A ``GenerateContentError`` if the request failed.
func generateContentWithHistory(history: [ModelContent], template: String,
inputs: [String: TemplateInput]) async throws
-> GenerateContentResponse {
let request = try templateGenerateContentRequest(
template: template,
inputs: inputs,
history: history,
stream: false
)
return try await generativeAIService.loadRequest(request: request)
}
/// Generates a stream of content from a prompt template, inputs, and history.
///
/// - Parameters:
/// - history: The conversation history to use.
/// - template: The prompt template to use.
/// - inputs: The variables to substitute into the template.
/// - Returns: An `AsyncThrowingStream` that yields `GenerateContentResponse` objects.
/// - Throws: A ``GenerateContentError`` if the request failed.
@available(macOS 12.0, watchOS 8.0, *)
func generateContentStreamWithHistory(history: [ModelContent], template: String,
inputs: [String: TemplateInput]) throws
-> AsyncThrowingStream<GenerateContentResponse, Error> {
let request = try templateGenerateContentRequest(
template: template,
inputs: inputs,
history: history,
stream: true
)
return generativeAIService.loadRequestStream(request: request)
}
}