Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,14 @@ Release Notes
- Analytics: Added `SetDefaultEventParameters()` which allows developers to
specify a list of parameters that will be set on every event logged.
- Analytics: Added a new `LogEvent()` that take in a IEnumerable of
parameters.
parameters.
- Firebase AI: Added support for using
[Server Prompt Templates](https://firebase.google.com/docs/ai-logic/server-prompt-templates/get-started).

### 13.5.0
- Changes
- Firebase AI: Add support for receiving Live API Transcripts.
- Storage: Add support for Firebase Storage emulator via `UseEmulator`.
- Storage: Add support for Firebase Storage emulator via `UseEmulator`.
The `UseEmulator` method should be called before invoking any other
methods on a new instance of Storage. Default port is 9199.

Expand Down
22 changes: 22 additions & 0 deletions firebaseai/src/FirebaseAI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,28 @@ public ImagenModel GetImagenModel(
return new ImagenModel(_firebaseApp, _backend, modelName,
generationConfig, safetySettings, requestOptions);
}

/// <summary>
/// Initializes a `TemplateGenerativeModel` with the given parameters.
/// </summary>
/// <param name="requestOptions">Configuration parameters for sending requests to the backend.</param>
/// <returns>The initialized `TemplateGenerativeModel` instance.</returns>
public TemplateGenerativeModel GetTemplateGenerativeModel(
RequestOptions? requestOptions = null)
{
return new TemplateGenerativeModel(_firebaseApp, _backend, requestOptions);
}

/// <summary>
/// Initializes a `TemplateImagenModel` with the given parameters.
/// </summary>
/// <param name="requestOptions">Configuration parameters for sending requests to the backend.</param>
/// <returns>The initialized `TemplateImagenModel` instance.</returns>
public TemplateImagenModel GetTemplateImagenModel(
RequestOptions? requestOptions = null)
{
return new TemplateImagenModel(_firebaseApp, _backend, requestOptions);
}
}

}
72 changes: 72 additions & 0 deletions firebaseai/src/Imagen/ImagenModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public class ImagenModel

private readonly HttpClient _httpClient;

/// <summary>
/// Intended for internal use only.
/// Use `FirebaseAI.GetImagenModel` instead to ensure proper initialization and configuration of the `ImagenModel`.
/// </summary>
internal ImagenModel(FirebaseApp firebaseApp,
FirebaseAI.Backend backend,
string modelName,
Expand Down Expand Up @@ -157,4 +161,72 @@ private Dictionary<string, object> MakeGenerateImagenRequestAsDictionary(
}
}

/// <summary>
/// Represents a remote Imagen model with the ability to generate images using server template prompts.
/// </summary>
public class TemplateImagenModel
{
private readonly FirebaseApp _firebaseApp;
private readonly FirebaseAI.Backend _backend;

private readonly HttpClient _httpClient;

/// <summary>
/// Intended for internal use only.
/// Use `FirebaseAI.GetTemplateImagenModel` instead to ensure proper initialization and configuration of the `TemplateImagenModel`.
/// </summary>
internal TemplateImagenModel(FirebaseApp firebaseApp,
FirebaseAI.Backend backend, RequestOptions? requestOptions = null)
{
_firebaseApp = firebaseApp;
_backend = backend;

// Create a HttpClient using the timeout requested, or the default one.
_httpClient = new HttpClient()
{
Timeout = requestOptions?.Timeout ?? RequestOptions.DefaultTimeout
};
}

/// <summary>
/// Generates images using the Template Imagen model and returns them as inline data.
/// </summary>
/// <param name="templateId">The id of the server prompt template to use.</param>
/// <param name="inputs">Any input parameters expected by the server prompt template.</param>
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
/// <returns>The generated content response from the model.</returns>
/// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
public async Task<ImagenGenerationResponse<ImagenInlineImage>> GenerateImagesAsync(
string templateId, IDictionary<string, object> inputs, CancellationToken cancellationToken = default)
{
HttpRequestMessage request = new(HttpMethod.Post,
HttpHelpers.GetTemplateURL(_firebaseApp, _backend, templateId) + ":templatePredict");

// Set the request headers
await HttpHelpers.SetRequestHeaders(request, _firebaseApp);

// Set the content
Dictionary<string, object> jsonDict = new()
{
["inputs"] = inputs
};
string bodyJson = Json.Serialize(jsonDict);
request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");

#if FIREBASE_LOG_REST_CALLS
UnityEngine.Debug.Log("Request:\n" + bodyJson);
#endif

var response = await _httpClient.SendAsync(request, cancellationToken);
await HttpHelpers.ValidateHttpResponse(response);

string result = await response.Content.ReadAsStringAsync();

#if FIREBASE_LOG_REST_CALLS
UnityEngine.Debug.Log("Response:\n" + result);
#endif

return ImagenGenerationResponse<ImagenInlineImage>.FromJson(result);
}
}
}
21 changes: 21 additions & 0 deletions firebaseai/src/Internal/HttpHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ namespace Firebase.AI.Internal
// Helper functions to help handling the Http calls.
internal static class HttpHelpers
{
internal static readonly string StreamPrefix = "data: ";

// Get the URL to use for the rest calls based on the backend.
internal static string GetURL(FirebaseApp firebaseApp,
FirebaseAI.Backend backend, string modelName)
Expand All @@ -46,6 +48,25 @@ internal static string GetURL(FirebaseApp firebaseApp,
}
}

internal static string GetTemplateURL(FirebaseApp firebaseApp,
FirebaseAI.Backend backend, string templateId)
{
var projectUrl = "https://firebasevertexai.googleapis.com/v1beta" +
$"/projects/{firebaseApp.Options.ProjectId}";
if (backend.Provider == FirebaseAI.Backend.InternalProvider.VertexAI)
{
return $"{projectUrl}/locations/{backend.Location}/templates/{templateId}";
}
else if (backend.Provider == FirebaseAI.Backend.InternalProvider.GoogleAI)
{
return $"{projectUrl}/templates/{templateId}";
}
else
{
throw new NotSupportedException($"Missing support for backend: {backend.Provider}");
}
}

internal static async Task SetRequestHeaders(HttpRequestMessage request, FirebaseApp firebaseApp)
{
request.Headers.Add("x-goog-api-key", firebaseApp.Options.ApiKey);
Expand Down
177 changes: 177 additions & 0 deletions firebaseai/src/TemplateGenerativeModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* 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.
*/

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Google.MiniJSON;
using Firebase.AI.Internal;
using System.Linq;
using System.Runtime.CompilerServices;
using System.IO;

namespace Firebase.AI
{
/// <summary>
/// A type that represents a remote multimodal model (like Gemini), with the ability to generate
/// content based on defined server prompt templates.
/// </summary>
public class TemplateGenerativeModel
{
private readonly FirebaseApp _firebaseApp;
private readonly FirebaseAI.Backend _backend;

private readonly HttpClient _httpClient;

/// <summary>
/// Intended for internal use only.
/// Use `FirebaseAI.GetTemplateGenerativeModel` instead to ensure proper
/// initialization and configuration of the `TemplateGenerativeModel`.
/// </summary>
internal TemplateGenerativeModel(FirebaseApp firebaseApp,
FirebaseAI.Backend backend,
RequestOptions? requestOptions = null)
{
_firebaseApp = firebaseApp;
_backend = backend;

// Create a HttpClient using the timeout requested, or the default one.
_httpClient = new HttpClient()
{
Timeout = requestOptions?.Timeout ?? RequestOptions.DefaultTimeout
};
}

/// <summary>
/// Generates new content by calling into a server prompt template.
/// </summary>
/// <param name="templateId">The id of the server prompt template to use.</param>
/// <param name="inputs">Any input parameters expected by the server prompt template.</param>
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
/// <returns>The generated content response from the model.</returns>
/// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
public Task<GenerateContentResponse> GenerateContentAsync(
string templateId, IDictionary<string, object> inputs,
CancellationToken cancellationToken = default)
{
return GenerateContentAsyncInternal(templateId, inputs, null, cancellationToken);
}

/// <summary>
/// Generates new content as a stream by calling into a server prompt template.
/// </summary>
/// <param name="templateId">The id of the server prompt template to use.</param>
/// <param name="inputs">Any input parameters expected by the server prompt template.</param>
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
/// <returns>A stream of generated content responses from the model.</returns>
/// <exception cref="HttpRequestException">Thrown when an error occurs during content generation.</exception>
public IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsync(
string templateId, IDictionary<string, object> inputs,
CancellationToken cancellationToken = default)
{
return GenerateContentStreamAsyncInternal(templateId, inputs, null, cancellationToken);
}

private string MakeGenerateContentRequest(IDictionary<string, object> inputs,
IEnumerable<ModelContent> chatHistory)
{
var jsonDict = new Dictionary<string, object>()
{
["inputs"] = inputs
};
if (chatHistory != null)
{
jsonDict["history"] = chatHistory.Select(t => t.ToJson()).ToList();
}
return Json.Serialize(jsonDict);
}

private async Task<GenerateContentResponse> GenerateContentAsyncInternal(
string templateId, IDictionary<string, object> inputs,
IEnumerable<ModelContent> chatHistory,
CancellationToken cancellationToken)
{
HttpRequestMessage request = new(HttpMethod.Post,
HttpHelpers.GetTemplateURL(_firebaseApp, _backend, templateId) + ":templateGenerateContent");

// Set the request headers
await HttpHelpers.SetRequestHeaders(request, _firebaseApp);

// Set the content
string bodyJson = MakeGenerateContentRequest(inputs, chatHistory);
request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");

#if FIREBASE_LOG_REST_CALLS
UnityEngine.Debug.Log("Request:\n" + bodyJson);
#endif

var response = await _httpClient.SendAsync(request, cancellationToken);
await HttpHelpers.ValidateHttpResponse(response);

string result = await response.Content.ReadAsStringAsync();

#if FIREBASE_LOG_REST_CALLS
UnityEngine.Debug.Log("Response:\n" + result);
#endif

return GenerateContentResponse.FromJson(result, _backend.Provider);
}

private async IAsyncEnumerable<GenerateContentResponse> GenerateContentStreamAsyncInternal(
string templateId, IDictionary<string, object> inputs,
IEnumerable<ModelContent> chatHistory,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
HttpRequestMessage request = new(HttpMethod.Post,
HttpHelpers.GetTemplateURL(_firebaseApp, _backend, templateId) + ":templateStreamGenerateContent?alt=sse");

// Set the request headers
await HttpHelpers.SetRequestHeaders(request, _firebaseApp);

// Set the content
string bodyJson = MakeGenerateContentRequest(inputs, chatHistory);
request.Content = new StringContent(bodyJson, Encoding.UTF8, "application/json");

#if FIREBASE_LOG_REST_CALLS
UnityEngine.Debug.Log("Request:\n" + bodyJson);
#endif

var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
await HttpHelpers.ValidateHttpResponse(response);

// We are expecting a Stream as the response, so handle that.
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);

string line;
while ((line = await reader.ReadLineAsync()) != null)
{
// Only pass along strings that begin with the expected prefix.
if (line.StartsWith(HttpHelpers.StreamPrefix))
{
#if FIREBASE_LOG_REST_CALLS
UnityEngine.Debug.Log("Streaming Response:\n" + line);
#endif

yield return GenerateContentResponse.FromJson(line[HttpHelpers.StreamPrefix.Length..], _backend.Provider);
}
}
}
}
}
11 changes: 11 additions & 0 deletions firebaseai/src/TemplateGenerativeModel.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading