// 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 { 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 { let request = try templateGenerateContentRequest( template: template, inputs: inputs, history: history, stream: true ) return generativeAIService.loadRequestStream(request: request) } }